From b5ae4e48d30124e356aaf680e19622dc540b3836 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 17 Oct 2014 21:14:18 -0500 Subject: [PATCH] win32_apply.c: Try to remove/rename conflicting short names --- NEWS | 4 ++ src/win32_apply.c | 131 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 125 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index 2bf8b667..8329afd1 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,10 @@ Version 1.7.3-BETA: The system registry files are now automatically treated as being in [PrepopulateList]. + Added a workaround that tries to prevent short filename conflicts. It + can have an effect primarily on versions of Windows that do not support + removing short names from files (earlier than Windows 7). + Version 1.7.2: Made more improvements to the XPRESS, LZX, and LZMS compressors. diff --git a/src/win32_apply.c b/src/win32_apply.c index 30da6b98..507bde86 100644 --- a/src/win32_apply.c +++ b/src/win32_apply.c @@ -126,7 +126,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? */ @@ -785,9 +788,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 +996,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 +1098,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 +1129,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; } @@ -2344,17 +2444,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.", -- 2.43.0