+/* Tally features necessary to extract a dentry and the corresponding inode. */
+static int
+dentry_tally_features(struct wim_dentry *dentry, void *_features)
+{
+ struct wim_features *features = _features;
+ struct wim_inode *inode = dentry->d_inode;
+
+ if (dentry->extraction_skipped)
+ return 0;
+
+ if (inode->i_attributes & FILE_ATTRIBUTE_ARCHIVE)
+ features->archive_files++;
+ if (inode->i_attributes & FILE_ATTRIBUTE_HIDDEN)
+ features->hidden_files++;
+ if (inode->i_attributes & FILE_ATTRIBUTE_SYSTEM)
+ features->system_files++;
+ if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED)
+ features->compressed_files++;
+ if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) {
+ if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)
+ features->encrypted_directories++;
+ else
+ features->encrypted_files++;
+ }
+ if (inode->i_attributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
+ features->not_context_indexed_files++;
+ if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE)
+ features->sparse_files++;
+ if (inode_has_named_stream(inode))
+ features->named_data_streams++;
+ if (inode->i_visited)
+ features->hard_links++;
+ if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+ features->reparse_points++;
+ if (inode_is_symlink(inode))
+ features->symlink_reparse_points++;
+ else
+ features->other_reparse_points++;
+ }
+ if (inode->i_security_id != -1)
+ features->security_descriptors++;
+ if (dentry->short_name_nbytes)
+ features->short_names++;
+ if (inode_has_unix_data(inode))
+ features->unix_data++;
+ inode->i_visited = 1;
+ return 0;
+}
+
+/* Tally the features necessary to extract a dentry tree. */
+static void
+dentry_tree_get_features(struct wim_dentry *root, struct wim_features *features)
+{
+ memset(features, 0, sizeof(struct wim_features));
+ for_dentry_in_tree(root, dentry_tally_features, features);
+ dentry_tree_clear_inode_visited(root);
+}
+
+static u32
+compute_supported_attributes_mask(const struct wim_features *supported_features)
+{
+ u32 mask = ~(u32)0;
+
+ if (!supported_features->archive_files)
+ mask &= ~FILE_ATTRIBUTE_ARCHIVE;
+
+ if (!supported_features->hidden_files)
+ mask &= ~FILE_ATTRIBUTE_HIDDEN;
+
+ if (!supported_features->system_files)
+ mask &= ~FILE_ATTRIBUTE_SYSTEM;
+
+ if (!supported_features->not_context_indexed_files)
+ mask &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
+
+ if (!supported_features->compressed_files)
+ mask &= ~FILE_ATTRIBUTE_COMPRESSED;
+
+ if (!supported_features->sparse_files)
+ mask &= ~FILE_ATTRIBUTE_SPARSE_FILE;
+
+ if (!supported_features->reparse_points)
+ mask &= ~FILE_ATTRIBUTE_REPARSE_POINT;
+
+ return mask;
+}
+
+static int
+do_feature_check(const struct wim_features *required_features,
+ const struct wim_features *supported_features,
+ int extract_flags,
+ const struct apply_operations *ops,
+ const tchar *wim_source_path,
+ bool warn)
+{
+ const tchar *loc;
+ const tchar *mode = T("this extraction mode");
+
+ if (wim_source_path[0] == '\0')
+ loc = T("the WIM image");
+ else
+ loc = wim_source_path;
+
+ if (warn) {
+ /* We're an archive program, so theoretically we can do what we want
+ * with FILE_ATTRIBUTE_ARCHIVE (which is a dumb flag anyway). Don't
+ * bother the user about it. */
+#if 0
+ if (required_features->archive_files && !supported_features->archive_files)
+ {
+ WARNING(
+ "%lu files in %"TS" are marked as archived, but this attribute\n"
+ " is not supported in %"TS".",
+ required_features->archive_files, loc, mode);
+ }
+#endif
+
+ if (required_features->hidden_files && !supported_features->hidden_files)
+ {
+ WARNING(
+ "%lu files in %"TS" are marked as hidden, but this\n"
+ " attribute is not supported in %"TS".",
+ required_features->hidden_files, loc, mode);
+ }
+
+ if (required_features->system_files && !supported_features->system_files)
+ {
+ WARNING(
+ "%lu files in %"TS" are marked as system files,\n"
+ " but this attribute is not supported in %"TS".",
+ required_features->system_files, loc, mode);
+ }
+
+ if (required_features->compressed_files && !supported_features->compressed_files)
+ {
+ WARNING(
+ "%lu files in %"TS" are marked as being transparently\n"
+ " compressed, but transparent compression is not supported in\n"
+ " %"TS". These files will be extracted as uncompressed.",
+ required_features->compressed_files, loc, mode);
+ }
+
+ if (required_features->encrypted_files && !supported_features->encrypted_files)
+ {
+ WARNING(
+ "%lu files in %"TS" are marked as being encrypted,\n"
+ " but encryption is not supported in %"TS". These files\n"
+ " will not be extracted.",
+ required_features->encrypted_files, loc, mode);
+ }
+
+ if (required_features->encrypted_directories &&
+ !supported_features->encrypted_directories)
+ {
+ WARNING(
+ "%lu directories in %"TS" are marked as being encrypted,\n"
+ " but encryption is not supported in %"TS".\n"
+ " These directories will be extracted as unencrypted.",
+ required_features->encrypted_directories, loc, mode);
+ }
+
+ if (required_features->not_context_indexed_files &&
+ !supported_features->not_context_indexed_files)
+ {
+ WARNING(
+ "%lu files in %"TS" are marked as not content indexed,\n"
+ " but this attribute is not supported in %"TS".",
+ required_features->not_context_indexed_files, loc, mode);
+ }
+
+ if (required_features->sparse_files && !supported_features->sparse_files)
+ {
+ WARNING(
+ "%lu files in %"TS" are marked as sparse, but creating\n"
+ " sparse files is not supported in %"TS". These files\n"
+ " will be extracted as non-sparse.",
+ required_features->sparse_files, loc, mode);
+ }
+
+ if (required_features->named_data_streams &&
+ !supported_features->named_data_streams)
+ {
+ WARNING(
+ "%lu files in %"TS" contain one or more alternate (named)\n"
+ " data streams, which are not supported in %"TS".\n"
+ " Alternate data streams will NOT be extracted.",
+ required_features->named_data_streams, loc, mode);
+ }
+
+ if (unlikely(extract_flags & (WIMLIB_EXTRACT_FLAG_HARDLINK |
+ WIMLIB_EXTRACT_FLAG_SYMLINK)) &&
+ required_features->named_data_streams &&
+ supported_features->named_data_streams)
+ {
+ WARNING(
+ "%lu files in %"TS" contain one or more alternate (named)\n"
+ " data streams, which are not supported in linked extraction mode.\n"
+ " Alternate data streams will NOT be extracted.",
+ required_features->named_data_streams, loc);
+ }
+
+ if (required_features->hard_links && !supported_features->hard_links)
+ {
+ WARNING(
+ "%lu files in %"TS" are hard links, but hard links are\n"
+ " not supported in %"TS". Hard links will be extracted as\n"
+ " duplicate copies of the linked files.",
+ required_features->hard_links, loc, mode);
+ }
+
+ if (required_features->reparse_points && !supported_features->reparse_points)
+ {
+ if (supported_features->symlink_reparse_points) {
+ if (required_features->other_reparse_points) {
+ WARNING(
+ "%lu files in %"TS" are reparse points that are neither\n"
+ " symbolic links nor junction points and are not supported in\n"
+ " %"TS". These reparse points will not be extracted.",
+ required_features->other_reparse_points, loc,
+ mode);
+ }
+ } else {
+ WARNING(
+ "%lu files in %"TS" are reparse points, which are\n"
+ " not supported in %"TS" and will not be extracted.",
+ required_features->reparse_points, loc, mode);
+ }
+ }
+
+ if (required_features->security_descriptors &&
+ !supported_features->security_descriptors)
+ {
+ WARNING(
+ "%lu files in %"TS" have Windows NT security descriptors,\n"
+ " but extracting security descriptors is not supported in\n"
+ " %"TS". No security descriptors will be extracted.",
+ required_features->security_descriptors, loc, mode);
+ }
+
+ if (required_features->short_names && !supported_features->short_names)
+ {
+ WARNING(
+ "%lu files in %"TS" have short (DOS) names, but\n"
+ " extracting short names is not supported in %"TS".\n"
+ " Short names will not be extracted.\n",
+ required_features->short_names, loc, mode);
+ }
+ }
+
+ if ((extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) &&
+ required_features->unix_data && !supported_features->unix_data)
+ {
+ ERROR("Extracting UNIX data is not supported in %"TS, mode);
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+ if ((extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES) &&
+ required_features->short_names && !supported_features->short_names)
+ {
+ ERROR("Extracting short names is not supported in %"TS"", mode);
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+ if ((extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS) &&
+ !ops->set_timestamps)
+ {
+ ERROR("Extracting timestamps is not supported in %"TS"", mode);
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+ if (((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_ACLS |
+ WIMLIB_EXTRACT_FLAG_UNIX_DATA))
+ == WIMLIB_EXTRACT_FLAG_STRICT_ACLS) &&
+ required_features->security_descriptors &&
+ !supported_features->security_descriptors)
+ {
+ ERROR("Extracting security descriptors is not supported in %"TS, mode);
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+
+ if ((extract_flags & WIMLIB_EXTRACT_FLAG_HARDLINK) &&
+ !supported_features->hard_links)
+ {
+ ERROR("Hard link extraction mode requested, but "
+ "%"TS" does not support hard links!", mode);
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+
+ if ((extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS) &&
+ required_features->symlink_reparse_points &&
+ !(supported_features->symlink_reparse_points ||
+ supported_features->reparse_points))
+ {
+ ERROR("Extracting symbolic links is not supported in %"TS, mode);
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+
+ if ((extract_flags & WIMLIB_EXTRACT_FLAG_SYMLINK) &&
+ !supported_features->symlink_reparse_points)
+ {
+ ERROR("Symbolic link extraction mode requested, but "
+ "%"TS" does not support symbolic "
+ "links!", mode);
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+ return 0;
+}
+
+static void
+do_extract_warnings(struct apply_ctx *ctx)
+{
+ if (ctx->partial_security_descriptors == 0 &&
+ ctx->no_security_descriptors == 0)
+ return;
+
+ WARNING("Extraction to \"%"TS"\" complete, but with one or more warnings:",
+ ctx->target);
+ if (ctx->partial_security_descriptors != 0) {
+ WARNING("- Could only partially set the security descriptor\n"
+ " on %lu files or directories.",
+ ctx->partial_security_descriptors);
+ }
+ if (ctx->no_security_descriptors != 0) {
+ WARNING("- Could not set security descriptor at all\n"
+ " on %lu files or directories.",
+ ctx->no_security_descriptors);
+ }
+#ifdef __WIN32__
+ WARNING("To fully restore all security descriptors, run the program\n"
+ " with Administrator rights.");
+#endif
+}
+
+static int
+dentry_set_skipped(struct wim_dentry *dentry, void *_ignore)
+{
+ dentry->in_extraction_tree = 1;
+ dentry->extraction_skipped = 1;
+ return 0;
+}
+
+static int
+dentry_set_not_skipped(struct wim_dentry *dentry, void *_ignore)
+{
+ dentry->in_extraction_tree = 1;
+ dentry->extraction_skipped = 0;
+ return 0;
+}
+
+static int
+extract_trees(WIMStruct *wim, struct wim_dentry **trees, size_t num_trees,
+ const tchar *wim_source_path, const tchar *target,
+ int extract_flags, wimlib_progress_func_t progress_func)
+{
+ struct wim_features required_features;
+ struct apply_ctx ctx;
+ int ret;
+ struct wim_lookup_table_entry *lte;
+
+ wimlib_assert(num_trees != 0);
+ wimlib_assert(target != NULL);
+
+ if (num_trees > 1)
+ wimlib_assert((extract_flags & WIMLIB_EXTRACT_FLAG_PATHMODE));
+
+ /* Start initializing the apply_ctx. */
+ memset(&ctx, 0, sizeof(struct apply_ctx));
+ ctx.wim = wim;
+ ctx.extract_flags = extract_flags;
+ ctx.target = target;
+ ctx.target_nchars = tstrlen(target);
+ ctx.progress_func = progress_func;
+ if (progress_func) {
+ ctx.progress.extract.wimfile_name = wim->filename;
+ ctx.progress.extract.image = wim->current_image;
+ ctx.progress.extract.extract_flags = (extract_flags &
+ WIMLIB_EXTRACT_MASK_PUBLIC);
+ ctx.progress.extract.image_name = wimlib_get_image_name(wim,
+ wim->current_image);
+ ctx.progress.extract.extract_root_wim_source_path = wim_source_path;
+ ctx.progress.extract.target = target;
+ }
+ INIT_LIST_HEAD(&ctx.stream_list);
+
+ if (extract_flags & WIMLIB_EXTRACT_FLAG_PATHMODE) {
+ /* Path mode --- there may be multiple trees, and targets are
+ * set relative to the root of the image.
+ *
+ * Consider all dentries to be in the extraction tree, but
+ * assume all to be skipped unless in one of the subtrees being
+ * extracted or one of the ancestors of the subtrees up to the
+ * image root. */
+ ctx.extract_root = wim_root_dentry(wim);
+ for_dentry_in_tree(ctx.extract_root, dentry_set_skipped, NULL);
+
+ for (size_t i = 0; i < num_trees; i++) {
+ struct wim_dentry *d;
+
+ for_dentry_in_tree(trees[i], dentry_set_not_skipped, NULL);
+ d = trees[i];
+ while (d != ctx.extract_root) {
+ d = d->parent;
+ dentry_set_not_skipped(d, NULL);
+ }
+ }
+ } else {
+ ctx.extract_root = trees[0];
+ for_dentry_in_tree(ctx.extract_root, dentry_set_not_skipped, NULL);
+ }
+
+ /* Select the appropriate apply_operations based on the
+ * platform and extract_flags. */
+#ifdef __WIN32__
+ ctx.ops = &win32_apply_ops;
+#else
+ ctx.ops = &unix_apply_ops;
+#endif
+
+#ifdef WITH_NTFS_3G
+ if (extract_flags & WIMLIB_EXTRACT_FLAG_NTFS)
+ ctx.ops = &ntfs_3g_apply_ops;
+#endif
+
+ /* Call the start_extract() callback. This gives the apply_operations
+ * implementation a chance to do any setup needed to access the volume.
+ * Furthermore, it's expected to set the supported features of this
+ * extraction mode (ctx.supported_features), which are determined at
+ * runtime as they may vary depending on the actual volume. These
+ * features are then compared with the actual features extracting this
+ * dentry tree requires. Some mismatches will merely produce warnings
+ * and the unsupported data will be ignored; others will produce errors.
+ */
+ ret = ctx.ops->start_extract(target, &ctx);
+ if (ret)
+ goto out_dentry_reset_needs_extraction;
+
+ /* Get and check the features required to extract the dentry tree. */
+ dentry_tree_get_features(ctx.extract_root, &required_features);
+ ret = do_feature_check(&required_features, &ctx.supported_features,
+ extract_flags, ctx.ops,
+ wim_source_path,
+ !(extract_flags & WIMLIB_EXTRACT_FLAG_PATHMODE));
+ if (ret)
+ goto out_finish_or_abort_extract;
+
+ ctx.supported_attributes_mask =
+ compute_supported_attributes_mask(&ctx.supported_features);
+
+ /* Figure out whether the root dentry is being extracted to the root of
+ * a volume and therefore needs to be treated "specially", for example
+ * not being explicitly created and not having attributes set. */
+ if (ctx.ops->target_is_root && ctx.ops->root_directory_is_special)
+ ctx.root_dentry_is_special = ctx.ops->target_is_root(target);
+
+ /* Calculate the actual filename component of each extracted dentry. In
+ * the process, set the dentry->extraction_skipped flag on dentries that
+ * are being skipped because of filename or supported features problems. */
+ ret = for_dentry_in_tree(ctx.extract_root,
+ dentry_calculate_extraction_path, &ctx);
+ if (ret)
+ goto out_dentry_reset_needs_extraction;
+
+ /* Build the list of the streams that need to be extracted and
+ * initialize ctx.progress.extract with stream information. */
+ ret = for_dentry_in_tree(ctx.extract_root,
+ dentry_resolve_and_zero_lte_refcnt, &ctx);
+ if (ret)
+ goto out_dentry_reset_needs_extraction;
+
+ ret = for_dentry_in_tree(ctx.extract_root,
+ dentry_add_streams_to_extract, &ctx);
+ if (ret)
+ goto out_teardown_stream_list;
+
+ if (extract_flags & WIMLIB_EXTRACT_FLAG_FROM_PIPE) {
+ /* When extracting from a pipe, the number of bytes of data to
+ * extract can't be determined in the normal way (examining the
+ * lookup table), since at this point all we have is a set of
+ * SHA1 message digests of streams that need to be extracted.
+ * However, we can get a reasonably accurate estimate by taking
+ * <TOTALBYTES> from the corresponding <IMAGE> in the WIM XML
+ * data. This does assume that a full image is being extracted,
+ * but currently there is no API for doing otherwise. (Also,
+ * subtract <HARDLINKBYTES> from this if hard links are
+ * supported by the extraction mode.) */
+ ctx.progress.extract.total_bytes =
+ wim_info_get_image_total_bytes(wim->wim_info,
+ wim->current_image);
+ if (ctx.supported_features.hard_links) {
+ ctx.progress.extract.total_bytes -=
+ wim_info_get_image_hard_link_bytes(wim->wim_info,
+ wim->current_image);
+ }
+ }
+
+ /* Handle the special case of extracting a file to standard
+ * output. In that case, "root" should be a single file, not a
+ * directory tree. (If not, extract_dentry_to_stdout() will
+ * return an error.) */
+ if (extract_flags & WIMLIB_EXTRACT_FLAG_TO_STDOUT) {
+ ret = extract_dentry_to_stdout(ctx.extract_root);
+ goto out_teardown_stream_list;
+ }
+
+ if (ctx.ops->realpath_works_on_nonexisting_files &&
+ ((extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) ||
+ ctx.ops->requires_realtarget_in_paths))
+ {
+ ctx.realtarget = realpath(target, NULL);
+ if (!ctx.realtarget) {
+ ret = WIMLIB_ERR_NOMEM;
+ goto out_teardown_stream_list;
+ }
+ ctx.realtarget_nchars = tstrlen(ctx.realtarget);
+ }
+
+ if (progress_func) {
+ int msg;
+ if (*wim_source_path || (extract_flags & WIMLIB_EXTRACT_FLAG_PATHMODE))
+ msg = WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN;
+ else
+ msg = WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN;
+ progress_func(msg, &ctx.progress);
+ }
+
+ if (!ctx.root_dentry_is_special)
+ {
+ tchar path[ctx.ops->path_max];
+ if (build_extraction_path(path, ctx.extract_root, &ctx))
+ {
+ ret = extract_inode(path, &ctx, ctx.extract_root->d_inode);
+ if (ret)
+ goto out_free_realtarget;
+ }
+ }
+
+ /* If we need to fix up the targets of absolute symbolic links
+ * (WIMLIB_EXTRACT_FLAG_RPFIX) or the extraction mode requires paths to
+ * be absolute, use realpath() (or its replacement on Windows) to get
+ * the absolute path to the extraction target. Note that this requires
+ * the target directory to exist, unless
+ * realpath_works_on_nonexisting_files is set in the apply_operations.
+ * */
+ if (!ctx.realtarget &&
+ (((extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) &&
+ required_features.symlink_reparse_points) ||
+ ctx.ops->requires_realtarget_in_paths))
+ {
+ ctx.realtarget = realpath(target, NULL);
+ if (!ctx.realtarget) {
+ ret = WIMLIB_ERR_NOMEM;
+ goto out_free_realtarget;
+ }
+ ctx.realtarget_nchars = tstrlen(ctx.realtarget);
+ }
+
+ if (ctx.ops->requires_short_name_reordering) {
+ ret = for_dentry_in_tree(ctx.extract_root, dentry_extract_dir_skeleton,
+ &ctx);
+ if (ret)
+ goto out_free_realtarget;
+ }
+
+ /* Finally, the important part: extract the tree of files. */
+ if (!(extract_flags & WIMLIB_EXTRACT_FLAG_FILE_ORDER)) {
+ /* Sequential extraction requested, so two passes are needed
+ * (one for directory structure, one for streams.) */
+ if (progress_func)
+ progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN,
+ &ctx.progress);
+
+ if (!(extract_flags & WIMLIB_EXTRACT_FLAG_RESUME)) {
+ ret = for_dentry_in_tree(ctx.extract_root, dentry_extract_skeleton, &ctx);
+ if (ret)
+ goto out_free_realtarget;
+ }
+ if (progress_func)
+ progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_END,
+ &ctx.progress);
+ if (extract_flags & WIMLIB_EXTRACT_FLAG_FROM_PIPE)
+ ret = extract_streams_from_pipe(&ctx);
+ else
+ ret = extract_stream_list(&ctx);
+ if (ret)
+ goto out_free_realtarget;
+ } else {
+ /* Sequential extraction was not requested, so we can make do
+ * with one pass where we both create the files and extract
+ * streams. */
+ if (progress_func)
+ progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN,
+ &ctx.progress);
+ ret = for_dentry_in_tree(ctx.extract_root, dentry_extract, &ctx);
+ if (ret)
+ goto out_free_realtarget;
+ if (progress_func)
+ progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_END,
+ &ctx.progress);
+ }
+
+ /* If the total number of bytes to extract was miscalculated, just jump
+ * to the calculated number in order to avoid confusing the progress
+ * function. This should only occur when extracting from a pipe. */
+ if (ctx.progress.extract.completed_bytes != ctx.progress.extract.total_bytes)
+ {
+ DEBUG("Calculated %"PRIu64" bytes to extract, but actually "
+ "extracted %"PRIu64,
+ ctx.progress.extract.total_bytes,
+ ctx.progress.extract.completed_bytes);
+ }
+ if (progress_func &&
+ ctx.progress.extract.completed_bytes < ctx.progress.extract.total_bytes)
+ {
+ ctx.progress.extract.completed_bytes = ctx.progress.extract.total_bytes;
+ progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS, &ctx.progress);
+ }
+
+ /* Apply security descriptors and timestamps. This is done at the end,
+ * and in a depth-first manner, to prevent timestamps from getting
+ * changed by subsequent extract operations and to minimize the chance
+ * of the restored security descriptors getting in our way. */
+ if (progress_func)
+ progress_func(WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS,
+ &ctx.progress);
+ ret = for_dentry_in_tree_depth(ctx.extract_root, dentry_extract_final, &ctx);
+ if (ret)
+ goto out_free_realtarget;
+
+ if (progress_func) {
+ progress_func(*wim_source_path ? WIMLIB_PROGRESS_MSG_EXTRACT_TREE_END :
+ WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END,
+ &ctx.progress);
+ }
+
+ do_extract_warnings(&ctx);
+
+ ret = 0;
+out_free_realtarget:
+ FREE(ctx.realtarget);
+out_teardown_stream_list:
+ /* Free memory allocated as part of the mapping from each
+ * wim_lookup_table_entry to the dentries that reference it. */
+ if (!(ctx.extract_flags & WIMLIB_EXTRACT_FLAG_FILE_ORDER))
+ list_for_each_entry(lte, &ctx.stream_list, extraction_list)
+ if (lte->out_refcnt > ARRAY_LEN(lte->inline_lte_dentries))
+ FREE(lte->lte_dentries);
+out_finish_or_abort_extract:
+ if (ret) {
+ if (ctx.ops->abort_extract)
+ ctx.ops->abort_extract(&ctx);
+ } else {
+ if (ctx.ops->finish_extract)
+ ret = ctx.ops->finish_extract(&ctx);
+ }
+out_dentry_reset_needs_extraction:
+ for_dentry_in_tree(ctx.extract_root, dentry_reset_needs_extraction, NULL);
+ return ret;
+}
+