+static void
+free_wimlib_dentry(struct wimlib_dir_entry *wdentry)
+{
+#if !TCHAR_IS_UTF16LE
+ FREE((tchar*)wdentry->filename);
+ FREE((tchar*)wdentry->dos_name);
+ for (unsigned i = 1; i <= wdentry->num_named_streams; i++)
+ FREE((tchar*)wdentry->streams[i].stream_name);
+#endif
+ FREE(wdentry);
+}
+
+struct iterate_dir_tree_ctx {
+ WIMStruct *wim;
+ int flags;
+ wimlib_iterate_dir_tree_callback_t cb;
+ void *user_ctx;
+};
+
+static int
+do_iterate_dir_tree(WIMStruct *wim,
+ struct wim_dentry *dentry, int flags,
+ wimlib_iterate_dir_tree_callback_t cb,
+ void *user_ctx);
+
+static int
+call_do_iterate_dir_tree(struct wim_dentry *dentry, void *_ctx)
+{
+ struct iterate_dir_tree_ctx *ctx = _ctx;
+ return do_iterate_dir_tree(ctx->wim, dentry, ctx->flags,
+ ctx->cb, ctx->user_ctx);
+}
+
+static int
+do_iterate_dir_tree(WIMStruct *wim,
+ struct wim_dentry *dentry, int flags,
+ wimlib_iterate_dir_tree_callback_t cb,
+ void *user_ctx)
+{
+ struct wimlib_dir_entry *wdentry;
+ int ret = WIMLIB_ERR_NOMEM;
+
+
+ wdentry = CALLOC(1, sizeof(struct wimlib_dir_entry) +
+ (1 + dentry->d_inode->i_num_ads) *
+ sizeof(struct wimlib_stream_entry));
+ if (wdentry == NULL)
+ goto out;
+
+ ret = init_wimlib_dentry(wdentry, dentry, wim, flags);
+ if (ret)
+ goto out_free_wimlib_dentry;
+
+ if (!(flags & WIMLIB_ITERATE_DIR_TREE_FLAG_CHILDREN)) {
+ ret = (*cb)(wdentry, user_ctx);
+ if (ret)
+ goto out_free_wimlib_dentry;
+ }
+
+ if (flags & (WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE |
+ WIMLIB_ITERATE_DIR_TREE_FLAG_CHILDREN))
+ {
+ struct iterate_dir_tree_ctx ctx = {
+ .wim = wim,
+ .flags = flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_CHILDREN,
+ .cb = cb,
+ .user_ctx = user_ctx,
+ };
+ ret = for_dentry_child(dentry, call_do_iterate_dir_tree, &ctx);
+ }
+out_free_wimlib_dentry:
+ free_wimlib_dentry(wdentry);
+out:
+ return ret;
+}
+
+struct image_iterate_dir_tree_ctx {
+ const tchar *path;
+ int flags;
+ wimlib_iterate_dir_tree_callback_t cb;
+ void *user_ctx;
+};
+
+
+static int
+image_do_iterate_dir_tree(WIMStruct *wim)
+{
+ struct image_iterate_dir_tree_ctx *ctx = wim->private;
+ struct wim_dentry *dentry;
+
+ dentry = get_dentry(wim, ctx->path, WIMLIB_CASE_PLATFORM_DEFAULT);
+ if (dentry == NULL)
+ return WIMLIB_ERR_PATH_DOES_NOT_EXIST;
+ return do_iterate_dir_tree(wim, dentry, ctx->flags, ctx->cb, ctx->user_ctx);
+}
+
+/* API function documented in wimlib.h */
+WIMLIBAPI int
+wimlib_iterate_dir_tree(WIMStruct *wim, int image, const tchar *path,
+ int flags,
+ wimlib_iterate_dir_tree_callback_t cb, void *user_ctx)
+{
+ struct image_iterate_dir_tree_ctx ctx = {
+ .path = path,
+ .flags = flags,
+ .cb = cb,
+ .user_ctx = user_ctx,
+ };
+ wim->private = &ctx;
+ return for_image(wim, image, image_do_iterate_dir_tree);
+}
+
+/* Returns %true iff the metadata of @inode and @template_inode are reasonably
+ * consistent with them being the same, unmodified file. */
+static bool
+inode_metadata_consistent(const struct wim_inode *inode,
+ const struct wim_inode *template_inode,
+ const struct wim_lookup_table *template_lookup_table)
+{
+ /* Must have exact same creation time and last write time. */
+ if (inode->i_creation_time != template_inode->i_creation_time ||
+ inode->i_last_write_time != template_inode->i_last_write_time)
+ return false;
+
+ /* Last access time may have stayed the same or increased, but certainly
+ * shouldn't have decreased. */
+ if (inode->i_last_access_time < template_inode->i_last_access_time)
+ return false;
+
+ /* Must have same number of alternate data stream entries. */
+ if (inode->i_num_ads != template_inode->i_num_ads)
+ return false;
+
+ /* If the stream entries for the inode are for some reason not resolved,
+ * then the hashes are already available and the point of this function
+ * is defeated. */
+ if (!inode->i_resolved)
+ return false;
+
+ /* Iterate through each stream and do some more checks. */
+ for (unsigned i = 0; i <= inode->i_num_ads; i++) {
+ const struct wim_lookup_table_entry *lte, *template_lte;
+
+ lte = inode_stream_lte_resolved(inode, i);
+ template_lte = inode_stream_lte(template_inode, i,
+ template_lookup_table);
+
+ /* Compare stream sizes. */
+ if (lte && template_lte) {
+ if (lte->size != template_lte->size)
+ return false;
+
+ /* If hash happens to be available, compare with template. */
+ if (!lte->unhashed && !template_lte->unhashed &&
+ !hashes_equal(lte->hash, template_lte->hash))
+ return false;
+
+ } else if (lte && lte->size) {
+ return false;
+ } else if (template_lte && template_lte->size) {
+ return false;