+ return 0;
+
+skip_dentry:
+ for_dentry_in_tree(dentry, dentry_mark_skipped, NULL);
+ return 0;
+}
+
+/* Clean up dentry and inode structure after extraction. */
+static int
+dentry_reset_needs_extraction(struct wim_dentry *dentry, void *_ignore)
+{
+ struct wim_inode *inode = dentry->d_inode;
+
+ dentry->extraction_skipped = 0;
+ dentry->was_hardlinked = 0;
+ inode->i_visited = 0;
+ FREE(inode->i_extracted_file);
+ inode->i_extracted_file = NULL;
+ if ((void*)dentry->extraction_name != (void*)dentry->file_name)
+ FREE(dentry->extraction_name);
+ dentry->extraction_name = NULL;
+ return 0;
+}
+
+/* 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 (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)
+ 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;
+}
+
+static int
+dentry_clear_inode_visited(struct wim_dentry *dentry, void *_ignore)
+{
+ dentry->d_inode->i_visited = 0;
+ 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);
+ for_dentry_in_tree(root, dentry_clear_inode_visited, NULL);
+}
+
+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)
+{
+ if (required_features->archive_files && !supported_features->archive_files)
+ {
+ WARNING(
+ "%lu files are marked as archived, but this attribute\n"
+" is not supported in this extraction mode or volume.",
+ required_features->archive_files);
+ }
+
+ if (required_features->hidden_files && !supported_features->hidden_files)
+ {
+ WARNING(
+ "%lu files are marked as hidden, but this attribute\n"
+" is not supported in this extraction mode or volume.",
+ required_features->hidden_files);
+ }
+
+ if (required_features->system_files && !supported_features->system_files)
+ {
+ WARNING(
+ "%lu files are marked as system files, but this attribute\n"
+" is not supported in this extraction mode or volume.",
+ required_features->system_files);
+ }
+
+ if (required_features->compressed_files && !supported_features->compressed_files)
+ {
+ WARNING(
+ "%lu files are marked as being transparently compressed, but\n"
+" transparent compression is not supported in this extraction\n"
+" mode or volume. These files will be extracted as uncompressed.",
+ required_features->compressed_files);
+ }
+
+ if (required_features->encrypted_files && !supported_features->encrypted_files)
+ {
+ WARNING(
+ "%lu files are marked as being encrypted, but encryption is not\n"
+" supported in this extraction mode or volume. These files will be\n"
+" extracted as raw encrypted data instead.",
+ required_features->encrypted_files);
+ }
+
+ if (required_features->not_context_indexed_files &&
+ !supported_features->not_context_indexed_files)
+ {
+ WARNING(
+ "%lu files are marked as not content indexed, but this attribute\n"
+" is not supported in this extraction mode or volume.",
+ required_features->not_context_indexed_files);
+ }
+
+ if (required_features->sparse_files && !supported_features->sparse_files)
+ {
+ WARNING(
+ "%lu files are marked as sparse, but creating sparse files is not\n"
+" supported in this extraction mode or volume. These files will be\n"
+" extracted as non-sparse.",
+ required_features->not_context_indexed_files);
+ }
+
+ if (required_features->named_data_streams &&
+ !supported_features->named_data_streams)
+ {
+ WARNING(
+ "%lu files contain one or more alternate (named) data streams,\n"
+" which are not supported in this extraction mode or volume.\n"
+" Alternate data streams will NOT be extracted.",
+ required_features->named_data_streams);
+ }
+
+ 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 contain one or more alternate (named) data streams,\n"
+" which are not supported in linked extraction mode.\n"
+" Alternate data streams will NOT be extracted.",
+ required_features->named_data_streams);
+ }
+
+ if (required_features->hard_links && !supported_features->hard_links)
+ {
+ WARNING(
+ "%lu files are hard links, but hard links are not supported in\n"
+" this extraction mode or volume. Hard links will be extracted as\n"
+" duplicate copies of the linked files.",
+ required_features->hard_links);
+ }
+
+ if (required_features->reparse_points && !supported_features->reparse_points)
+ {
+ if (supported_features->symlink_reparse_points) {
+ if (required_features->other_reparse_points) {
+ WARNING(
+ "%lu files are reparse points that are neither symbolic links\n"
+" nor junction points and are not supported in this extraction mode\n"
+" or volume. These reparse points will not be extracted.",
+ required_features->other_reparse_points);
+ }
+ } else {
+ WARNING(
+ "%lu files are reparse points, which are not supported in this\n"
+" extraction mode or volume and will not be extracted.",
+ required_features->reparse_points);
+ }
+ }
+
+ if (required_features->security_descriptors &&
+ !supported_features->security_descriptors)
+ {
+ WARNING(
+ "%lu files have Windows NT security descriptors, but extracting\n"
+" security descriptors is not supported in this extraction mode\n"
+" or volume. No security descriptors will be extracted.",
+ required_features->security_descriptors);
+ }
+
+ if (required_features->short_names && !supported_features->short_names)
+ {
+ WARNING(
+ "%lu files have short (DOS) names, but extracting short names\n"
+" is not supported in this extraction mode or volume. Short names\n"
+" will not be extracted.\n",
+ required_features->short_names);
+ }
+
+ if ((extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) &&
+ required_features->unix_data && !supported_features->unix_data)
+ {
+ ERROR("UNIX data not supported in this extraction mode "
+ "or volume", ops->name);
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+ if ((extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES) &&
+ required_features->short_names && !supported_features->short_names)
+ {
+ ERROR("Short names are not supported in this extraction "
+ "mode or volume", ops->name);
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+ if ((extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS) &&
+ !ops->set_timestamps)
+ {
+ ERROR("Timestamps are not supported in this extraction "
+ "mode or volume", ops->name);
+ 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("Security descriptors not supported in this extraction "
+ "mode or volume.");
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+
+ if ((extract_flags & WIMLIB_EXTRACT_FLAG_HARDLINK) &&
+ !supported_features->hard_links)
+ {
+ ERROR("Hard link extraction mode requested, but "
+ "extraction mode or volume does not support hard links!");
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+
+ if ((extract_flags & WIMLIB_EXTRACT_FLAG_SYMLINK) &&
+ !supported_features->symlink_reparse_points)
+ {
+ ERROR("Symbolic link extraction mode requested, but "
+ "extraction mode or volume does not support symbolic "
+ "links!");
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+ return 0;
+}
+
+/*
+ * extract_tree - Extract a file or directory tree from the currently selected
+ * WIM image.
+ *
+ * @wim: WIMStruct for the WIM file, with the desired image selected
+ * (as wim->current_image).
+ *
+ * @wim_source_path:
+ * "Canonical" (i.e. no leading or trailing slashes, path
+ * separators WIM_PATH_SEPARATOR) path inside the WIM image to
+ * extract. An empty string means the full image.
+ *
+ * @target:
+ * Filesystem path to extract the file or directory tree to.
+ * (Or, with WIMLIB_EXTRACT_FLAG_NTFS: the name of a NTFS volume.)
+ *
+ * @extract_flags:
+ * WIMLIB_EXTRACT_FLAG_*. Also, the private flag
+ * WIMLIB_EXTRACT_FLAG_MULTI_IMAGE will be set if this is being
+ * called through wimlib_extract_image() with WIMLIB_ALL_IMAGES as
+ * the image.
+ *
+ * @progress_func:
+ * If non-NULL, progress function for the extraction. The messages
+ * that may be sent in this function are:
+ *
+ * WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN or
+ * WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN;
+ * WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN;
+ * WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_END;
+ * WIMLIB_PROGRESS_MSG_EXTRACT_DENTRY;
+ * WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS;
+ * WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS;
+ * WIMLIB_PROGRESS_MSG_EXTRACT_TREE_END or
+ * WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END.
+ *
+ * Returns 0 on success; a positive WIMLIB_ERR_* code on failure.
+ */
+static int
+extract_tree(WIMStruct *wim, const tchar *wim_source_path, const tchar *target,
+ int extract_flags, wimlib_progress_func_t progress_func)
+{
+ struct wim_dentry *root;
+ struct wim_features required_features;
+ struct apply_ctx ctx;
+ int ret;
+ struct wim_lookup_table_entry *lte;
+
+ /* 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);
+
+ /* Translate the path to extract into the corresponding
+ * `struct wim_dentry', which will be the root of the
+ * "dentry tree" to extract. */
+ root = get_dentry(wim, wim_source_path);
+ if (!root) {
+ ERROR("Path \"%"TS"\" does not exist in WIM image %d",
+ wim_source_path, wim->current_image);
+ ret = WIMLIB_ERR_PATH_DOES_NOT_EXIST;
+ goto out;
+ }
+
+ ctx.extract_root = root;
+
+ /* 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_tree_get_features(root, &required_features);
+ ret = do_feature_check(&required_features, &ctx.supported_features,
+ extract_flags, ctx.ops);
+ if (ret)
+ goto out_finish_or_abort_extract;
+
+ /* 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 for some reason (e.g. invalid filename). */
+ ret = for_dentry_in_tree(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. */
+ ctx.progress.extract.total_bytes =
+ wim_info_get_image_total_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(root);
+ goto out_teardown_stream_list;
+ }
+
+ /* If a sequential extraction was specified, sort the streams to be
+ * extracted by their position in the WIM file so that the WIM file can
+ * be read sequentially. */
+ if ((extract_flags & (WIMLIB_EXTRACT_FLAG_SEQUENTIAL |
+ WIMLIB_EXTRACT_FLAG_FROM_PIPE))
+ == WIMLIB_EXTRACT_FLAG_SEQUENTIAL)
+ {
+ ret = sort_stream_list_by_sequential_order(
+ &ctx.stream_list,
+ offsetof(struct wim_lookup_table_entry,
+ extraction_list));
+ if (ret)
+ 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) {
+ progress_func(*wim_source_path ? WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN :
+ WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN,
+ &ctx.progress);
+ }
+
+ if (!ctx.root_dentry_is_special)
+ {
+ tchar path[ctx.ops->path_max];
+ if (build_extraction_path(path, root, &ctx))
+ {
+ ret = extract_inode(path, &ctx, 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);
+ }
+
+ /* Finally, the important part: extract the tree of files. */
+ if (extract_flags & (WIMLIB_EXTRACT_FLAG_SEQUENTIAL |
+ WIMLIB_EXTRACT_FLAG_FROM_PIPE)) {
+ /* 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);
+ ret = for_dentry_in_tree(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(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(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);
+ }
+
+ 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_SEQUENTIAL)
+ 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_dentry_reset_needs_extraction:
+ for_dentry_in_tree(root, dentry_reset_needs_extraction, NULL);
+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: