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?
*/
}
}
+static const wchar_t *
+current_path(struct win32_apply_ctx *ctx);
+
+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)
return true;
}
-static const wchar_t *
-current_path(struct win32_apply_ctx *ctx);
-
-static void
-build_extraction_path(const struct wim_dentry *dentry,
- struct win32_apply_ctx *ctx);
-
#define WIM_BACKING_NOT_ENABLED -1
#define WIM_BACKING_NOT_POSSIBLE -2
#define WIM_BACKING_EXCLUDED -3
-/*
- * Determines if the unnamed data stream of a file will be created as an
- * external backing, as opposed to a standard extraction.
- */
static int
-win32_will_externally_back(struct wim_dentry *dentry, struct apply_ctx *_ctx)
+will_externally_back_inode(struct wim_inode *inode, struct win32_apply_ctx *ctx,
+ const struct wim_dentry **excluded_dentry_ret)
{
- struct win32_apply_ctx *ctx = (struct win32_apply_ctx *)_ctx;
+ struct list_head *next;
+ struct wim_dentry *dentry;
struct wim_lookup_table_entry *stream;
int ret;
- if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT))
- return WIM_BACKING_NOT_ENABLED;
+ if (inode->i_can_externally_back)
+ return 0;
- if (!ctx->wimboot.tried_to_load_prepopulate_list) {
- ret = load_prepopulate_pats(ctx);
- if (ret == WIMLIB_ERR_NOMEM)
- return ret;
- }
+ /* This may do redundant checks because the cached value
+ * i_can_externally_back is 2-state (as opposed to 3-state:
+ * unknown/no/yes). But most files can be externally backed, so this
+ * way is fine. */
- if (dentry->d_inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY |
- FILE_ATTRIBUTE_REPARSE_POINT |
- FILE_ATTRIBUTE_ENCRYPTED))
+ if (inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_REPARSE_POINT |
+ FILE_ATTRIBUTE_ENCRYPTED))
return WIM_BACKING_NOT_POSSIBLE;
- stream = inode_unnamed_lte_resolved(dentry->d_inode);
+ stream = inode_unnamed_lte_resolved(inode);
if (!stream ||
stream->resource_location != RESOURCE_IN_WIM ||
stream->size != stream->rspec->uncompressed_size)
return WIM_BACKING_NOT_POSSIBLE;
- ret = calculate_dentry_full_path(dentry);
- if (ret)
- return ret;
+ /*
+ * We need to check the patterns in [PrepopulateList] against every name
+ * of the inode, in case any of them match.
+ */
+ next = inode->i_extraction_aliases.next;
+ do {
+ dentry = list_entry(next, struct wim_dentry,
+ d_extraction_alias_node);
+
+ ret = calculate_dentry_full_path(dentry);
+ if (ret)
+ return ret;
- if (!can_externally_back_path(dentry->_full_path,
- wcslen(dentry->_full_path), ctx))
- return WIM_BACKING_EXCLUDED;
+ if (!can_externally_back_path(dentry->_full_path,
+ wcslen(dentry->_full_path), ctx))
+ {
+ if (excluded_dentry_ret)
+ *excluded_dentry_ret = dentry;
+ return WIM_BACKING_EXCLUDED;
+ }
+ next = next->next;
+ } while (next != &inode->i_extraction_aliases);
+ inode->i_can_externally_back = 1;
return 0;
}
+/*
+ * Determines if the unnamed data stream of a file will be created as an
+ * external backing, as opposed to a standard extraction.
+ */
static int
-set_external_backing(HANDLE h, struct wim_dentry *dentry, struct win32_apply_ctx *ctx)
+win32_will_externally_back(struct wim_dentry *dentry, struct apply_ctx *_ctx)
+{
+ struct win32_apply_ctx *ctx = (struct win32_apply_ctx *)_ctx;
+
+ if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT))
+ return WIM_BACKING_NOT_ENABLED;
+
+ if (!ctx->wimboot.tried_to_load_prepopulate_list)
+ if (load_prepopulate_pats(ctx) == WIMLIB_ERR_NOMEM)
+ return WIMLIB_ERR_NOMEM;
+
+ return will_externally_back_inode(dentry->d_inode, ctx, NULL);
+}
+
+static int
+set_external_backing(HANDLE h, struct wim_inode *inode, struct win32_apply_ctx *ctx)
{
int ret;
+ const struct wim_dentry *excluded_dentry;
- ret = win32_will_externally_back(dentry, &ctx->common);
+ ret = will_externally_back_inode(inode, ctx, &excluded_dentry);
if (ret > 0) /* Error. */
return ret;
if (ret < 0 && ret != WIM_BACKING_EXCLUDED)
return 0; /* Not externally backing, other than due to exclusion. */
- build_extraction_path(dentry, ctx);
-
- if (ret == WIM_BACKING_EXCLUDED) {
+ if (unlikely(ret == WIM_BACKING_EXCLUDED)) {
/* Not externally backing due to exclusion. */
union wimlib_progress_info info;
- info.wimboot_exclude.path_in_wim = dentry->_full_path;
+ build_extraction_path(excluded_dentry, ctx);
+
+ info.wimboot_exclude.path_in_wim = excluded_dentry->_full_path;
info.wimboot_exclude.extraction_path = current_path(ctx);
return call_progress(ctx->common.progfunc,
&info, ctx->common.progctx);
} else {
/* Externally backing. */
- return wimboot_set_pointer(h,
- current_path(ctx),
- inode_unnamed_lte_resolved(dentry->d_inode),
- ctx->wimboot.data_source_id,
- ctx->wimboot.wim_lookup_table_hash,
- ctx->wimboot.wof_running);
+ if (unlikely(!wimboot_set_pointer(h,
+ inode_unnamed_lte_resolved(inode),
+ ctx->wimboot.data_source_id,
+ ctx->wimboot.wim_lookup_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);
+ return WIMLIB_ERR_WIMBOOT;
+ }
+ return 0;
}
}
int ret;
WIMStruct *wim = ctx->common.wim;
- if (!ctx->wimboot.tried_to_load_prepopulate_list) {
- ret = load_prepopulate_pats(ctx);
- if (ret == WIMLIB_ERR_NOMEM)
- return ret;
- }
+ if (!ctx->wimboot.tried_to_load_prepopulate_list)
+ if (load_prepopulate_pats(ctx) == WIMLIB_ERR_NOMEM)
+ return WIMLIB_ERR_NOMEM;
if (!wim_info_get_wimboot(wim->wim_info, wim->current_image))
WARNING("Image is not marked as WIMBoot compatible!");
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);
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.
*
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);
}
}
+ /*
+ * 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;
}
* 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;
}
/* Create a nondirectory file, including all links. */
static int
-create_nondirectory(const struct wim_inode *inode, struct win32_apply_ctx *ctx)
+create_nondirectory(struct wim_inode *inode, struct win32_apply_ctx *ctx)
{
struct wim_dentry *first_dentry;
HANDLE h;
/* "WIMBoot" extraction: set external backing by the WIM file if needed. */
if (!ret && unlikely(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT))
- ret = set_external_backing(h, first_dentry, ctx);
+ ret = set_external_backing(h, inode, ctx);
(*func_NtClose)(h);
return ret;
static int
create_nondirectories(struct list_head *dentry_list, struct win32_apply_ctx *ctx)
{
- const struct wim_dentry *dentry;
- const struct wim_inode *inode;
+ struct wim_dentry *dentry;
+ struct wim_inode *inode;
int ret;
list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
/* 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;
}
/* 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;
}
* 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
* 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? */
* 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) {
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 {
dentry,
stream_name,
ctx);
+ ret = check_apply_error(dentry, ctx, ret);
if (ret)
goto fail;
next = next->next;
"%"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. */
ret = set_reparse_data(dentry, &ctx->rpbuf,
stream->size + REPARSE_DATA_OFFSET,
ctx);
+ ret = check_apply_error(dentry, ctx, ret);
if (ret)
return ret;
}
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;
}
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);
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.",