X-Git-Url: https://wimlib.net/git/?a=blobdiff_plain;f=src%2Fwin32_apply.c;h=7580a71875e156c5a1dd2dab7f4ef6b35292e647;hb=f1c07e953597e3f6a809d35d7d5160af1ff67ed3;hp=30da6b98c23bb7b56474ee117afc0d167888255b;hpb=29ac4319aa9c75811cd5629cd3471a681fbeb552;p=wimlib diff --git a/src/win32_apply.c b/src/win32_apply.c index 30da6b98..7580a718 100644 --- a/src/win32_apply.c +++ b/src/win32_apply.c @@ -33,6 +33,7 @@ #include "wimlib/error.h" #include "wimlib/lookup_table.h" #include "wimlib/metadata.h" +#include "wimlib/paths.h" #include "wimlib/reparse.h" #include "wimlib/textfile.h" #include "wimlib/xml.h" @@ -126,7 +127,10 @@ struct win32_apply_ctx { unsigned long no_security_descriptors; /* Number of files for which we couldn't set the short name. */ - unsigned long num_short_name_failures; + unsigned long num_set_short_name_failures; + + /* Number of files for which we couldn't remove the short name. */ + unsigned long num_remove_short_name_failures; /* Have we tried to enable short name support on the target volume yet? */ @@ -199,6 +203,23 @@ static void build_extraction_path(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx); +static int +report_dentry_apply_error(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx, int ret) +{ + build_extraction_path(dentry, ctx); + return report_apply_error(&ctx->common, ret, current_path(ctx)); +} + +static inline int +check_apply_error(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx, int ret) +{ + if (unlikely(ret)) + ret = report_dentry_apply_error(dentry, ctx, ret); + return ret; +} + static int win32_get_supported_features(const wchar_t *target, struct wim_features *supported_features) @@ -785,9 +806,11 @@ prepare_target(struct list_head *dentry_list, struct win32_apply_ctx *ctx) path_max = compute_path_max(dentry_list); - /* Add some extra for building Win32 paths for the file encryption APIs - * ... */ - path_max += 2 + (ctx->target_ntpath.Length / sizeof(wchar_t)); + /* Add some extra for building Win32 paths for the file encryption APIs, + * and ensure we have at least enough to potentially use a 8.3 name for + * the last component. */ + path_max += max(2 + (ctx->target_ntpath.Length / sizeof(wchar_t)), + 8 + 1 + 3); ctx->pathbuf.MaximumLength = path_max * sizeof(wchar_t); ctx->pathbuf.Buffer = MALLOC(ctx->pathbuf.MaximumLength); @@ -991,6 +1014,72 @@ fail: return false; } +static NTSTATUS +remove_conflicting_short_name(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx) +{ + wchar_t *name; + wchar_t *end; + NTSTATUS status; + HANDLE h; + size_t bufsize = offsetof(FILE_NAME_INFORMATION, FileName) + + (13 * sizeof(wchar_t)); + u8 buf[bufsize] _aligned_attribute(8); + bool retried = false; + FILE_NAME_INFORMATION *info = (FILE_NAME_INFORMATION *)buf; + + memset(buf, 0, bufsize); + + /* Build the path with the short name. */ + name = &ctx->pathbuf.Buffer[ctx->pathbuf.Length / sizeof(wchar_t)]; + while (name != ctx->pathbuf.Buffer && *(name - 1) != L'\\') + name--; + end = mempcpy(name, dentry->short_name, dentry->short_name_nbytes); + ctx->pathbuf.Length = ((u8 *)end - (u8 *)ctx->pathbuf.Buffer); + + /* Open the conflicting file (by short name). */ + status = (*func_NtOpenFile)(&h, GENERIC_WRITE | DELETE, + &ctx->attr, &ctx->iosb, + FILE_SHARE_VALID_FLAGS, + FILE_OPEN_REPARSE_POINT | FILE_OPEN_FOR_BACKUP_INTENT); + if (!NT_SUCCESS(status)) { + WARNING("Can't open \"%ls\" (status=0x%08"PRIx32")", + current_path(ctx), (u32)status); + goto out; + } + +#if 0 + WARNING("Overriding conflicting short name; path=\"%ls\"", + current_path(ctx)); +#endif + + /* Try to remove the short name on the conflicting file. */ + +retry: + status = (*func_NtSetInformationFile)(h, &ctx->iosb, info, bufsize, + FileShortNameInformation); + + if (status == STATUS_INVALID_PARAMETER && !retried) { + + /* Microsoft forgot to make it possible to remove short names + * until Windows 7. Oops. Use a random short name instead. */ + + info->FileNameLength = 12 * sizeof(wchar_t); + for (int i = 0; i < 8; i++) + info->FileName[i] = 'A' + (rand() % 26); + info->FileName[8] = L'.'; + info->FileName[9] = L'W'; + info->FileName[10] = L'L'; + info->FileName[11] = L'B'; + info->FileName[12] = L'\0'; + retried = true; + goto retry; + } + (*func_NtClose)(h); +out: + build_extraction_path(dentry, ctx); + return status; +} + /* Set the short name on the open file @h which has been created at the location * indicated by @dentry. * @@ -1027,6 +1116,7 @@ set_short_name(HANDLE h, const struct wim_dentry *dentry, u8 buf[bufsize] _aligned_attribute(8); FILE_NAME_INFORMATION *info = (FILE_NAME_INFORMATION *)buf; NTSTATUS status; + bool tried_to_remove_existing = false; memset(buf, 0, bufsize); @@ -1057,10 +1147,38 @@ retry: } } + /* + * Short names can conflict in several cases: + * + * - a file being extracted has a short name conflicting with an + * existing file + * + * - a file being extracted has a short name conflicting with another + * file being extracted (possible, but shouldn't happen) + * + * - a file being extracted has a short name that conflicts with the + * automatically generated short name of a file we previously + * extracted, but failed to set the short name for. Sounds unlikely, + * but this actually does happen fairly often on versions of Windows + * prior to Windows 7 because they do not support removing short names + * from files. + */ + if (unlikely(status == STATUS_OBJECT_NAME_COLLISION) && + dentry->short_name_nbytes && !tried_to_remove_existing) + { + tried_to_remove_existing = true; + status = remove_conflicting_short_name(dentry, ctx); + if (NT_SUCCESS(status)) + goto retry; + } + /* By default, failure to set short names is not an error (since short * names aren't too important anymore...). */ if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES)) { - ctx->num_short_name_failures++; + if (dentry->short_name_nbytes) + ctx->num_set_short_name_failures++; + else + ctx->num_remove_short_name_failures++; return 0; } @@ -1148,6 +1266,8 @@ create_any_empty_ads(const struct wim_dentry *dentry, const struct wim_ads_entry *entry; NTSTATUS status; HANDLE h; + bool retried; + DWORD disposition; entry = &inode->i_ads_entries[i]; @@ -1168,9 +1288,23 @@ create_any_empty_ads(const struct wim_dentry *dentry, entry->stream_name_nbytes / sizeof(wchar_t)); path_modified = true; + + retried = false; + disposition = FILE_SUPERSEDE; + retry: status = do_create_file(&h, FILE_WRITE_DATA, &allocation_size, - 0, FILE_SUPERSEDE, 0, ctx); - if (!NT_SUCCESS(status)) { + 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")", @@ -1266,10 +1400,12 @@ create_directories(struct list_head *dentry_list, * in prepare_target(). */ if (!dentry_is_root(dentry)) { ret = create_directory(dentry, ctx); + ret = check_apply_error(dentry, ctx, ret); if (ret) return ret; ret = create_any_empty_ads(dentry, ctx); + ret = check_apply_error(dentry, ctx, ret); if (ret) return ret; } @@ -1296,6 +1432,7 @@ create_nondirectory_inode(HANDLE *h_ret, const struct wim_dentry *dentry, ULONG attrib; NTSTATUS status; bool retried = false; + DWORD disposition; inode = dentry->d_inode; @@ -1322,11 +1459,12 @@ create_nondirectory_inode(HANDLE *h_ret, const struct wim_dentry *dentry, 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, FILE_SUPERSEDE, + NULL, attrib, disposition, FILE_NON_DIRECTORY_FILE, ctx); - if (NT_SUCCESS(status)) { + if (likely(NT_SUCCESS(status))) { int ret; ret = adjust_compression_attribute(*h_ret, dentry, ctx); @@ -1364,6 +1502,16 @@ retry: 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() @@ -1525,6 +1673,7 @@ create_nondirectories(struct list_head *dentry_list, struct win32_apply_ctx *ctx /* Call create_nondirectory() only once per inode */ if (dentry == inode_first_extraction_dentry(inode)) { ret = create_nondirectory(inode, ctx); + ret = check_apply_error(dentry, ctx, ret); if (ret) return ret; } @@ -1544,17 +1693,17 @@ close_handles(struct win32_apply_ctx *ctx) /* Prepare to read the next stream, which has size @stream_size, into an * in-memory buffer. */ -static int +static bool prepare_data_buffer(struct win32_apply_ctx *ctx, u64 stream_size) { if (stream_size > ctx->data_buffer_size) { /* Larger buffer needed. */ void *new_buffer; if ((size_t)stream_size != stream_size) - return WIMLIB_ERR_NOMEM; + return false; new_buffer = REALLOC(ctx->data_buffer, stream_size); if (!new_buffer) - return WIMLIB_ERR_NOMEM; + return false; ctx->data_buffer = new_buffer; ctx->data_buffer_size = stream_size; } @@ -1562,7 +1711,7 @@ prepare_data_buffer(struct win32_apply_ctx *ctx, u64 stream_size) * extract_chunk() that the data buffer needs to be filled while reading * the stream data. */ ctx->data_buffer_ptr = ctx->data_buffer; - return 0; + return true; } static int @@ -1598,8 +1747,10 @@ begin_extract_stream_instance(const struct wim_lookup_table_entry *stream, * 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 prepare_data_buffer(ctx, stream->size); + return 0; } /* Encrypted file? */ @@ -1620,8 +1771,10 @@ begin_extract_stream_instance(const struct wim_lookup_table_entry *stream, * TODO: This isn't sufficient for extremely large encrypted * files. Perhaps we should create an extra thread to write * such files... */ + if (!prepare_data_buffer(ctx, stream->size)) + return WIMLIB_ERR_NOMEM; list_add_tail(&dentry->tmp_list, &ctx->encrypted_dentries); - return prepare_data_buffer(ctx, stream->size); + return 0; } if (ctx->num_open_handles == MAX_OPEN_STREAMS) { @@ -1941,6 +2094,7 @@ begin_extract_stream(struct wim_lookup_table_entry *stream, void *_ctx) dentry = inode_first_extraction_dentry(inode); ret = begin_extract_stream_instance(stream, dentry, stream_name, ctx); + ret = check_apply_error(dentry, ctx, ret); if (ret) goto fail; } else { @@ -1956,6 +2110,7 @@ begin_extract_stream(struct wim_lookup_table_entry *stream, void *_ctx) dentry, stream_name, ctx); + ret = check_apply_error(dentry, ctx, ret); if (ret) goto fail; next = next->next; @@ -2033,7 +2188,8 @@ end_extract_stream(struct wim_lookup_table_entry *stream, int status, void *_ctx "%"PRIu64" bytes (exceeds %u bytes)", current_path(ctx), stream->size, REPARSE_DATA_MAX_SIZE); - return WIMLIB_ERR_INVALID_REPARSE_DATA; + 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. */ @@ -2045,6 +2201,7 @@ end_extract_stream(struct wim_lookup_table_entry *stream, int status, void *_ctx ret = set_reparse_data(dentry, &ctx->rpbuf, stream->size + REPARSE_DATA_OFFSET, ctx); + ret = check_apply_error(dentry, ctx, ret); if (ret) return ret; } @@ -2054,6 +2211,7 @@ end_extract_stream(struct wim_lookup_table_entry *stream, int status, void *_ctx ctx->encrypted_size = stream->size; list_for_each_entry(dentry, &ctx->encrypted_dentries, tmp_list) { ret = extract_encrypted_file(dentry, ctx); + ret = check_apply_error(dentry, ctx, ret); if (ret) return ret; } @@ -2329,6 +2487,7 @@ apply_metadata(struct list_head *dentry_list, struct win32_apply_ctx *ctx) list_for_each_entry_reverse(dentry, dentry_list, d_extraction_list_node) { ret = apply_metadata_to_file(dentry, ctx); + ret = check_apply_error(dentry, ctx, ret); if (ret) return ret; ret = report_file_metadata_applied(&ctx->common); @@ -2344,17 +2503,28 @@ apply_metadata(struct list_head *dentry_list, struct win32_apply_ctx *ctx) static void do_warnings(const struct win32_apply_ctx *ctx) { - if (ctx->partial_security_descriptors == 0 && - ctx->no_security_descriptors == 0 && - ctx->num_short_name_failures == 0) + if (ctx->partial_security_descriptors == 0 + && ctx->no_security_descriptors == 0 + && ctx->num_set_short_name_failures == 0 + #if 0 + && ctx->num_remove_short_name_failures == 0 + #endif + ) return; WARNING("Extraction to \"%ls\" complete, but with one or more warnings:", ctx->common.target); - if (ctx->num_short_name_failures) { + if (ctx->num_set_short_name_failures) { WARNING("- Could not set short names on %lu files or directories", - ctx->num_short_name_failures); + ctx->num_set_short_name_failures); } +#if 0 + if (ctx->num_remove_short_name_failures) { + WARNING("- Could not remove short names on %lu files or directories" + " (This is expected on Vista and earlier)", + ctx->num_remove_short_name_failures); + } +#endif if (ctx->partial_security_descriptors) { WARNING("- Could only partially set the security descriptor\n" " on %lu files or directories.",