+static const tchar *
+dentry_get_file_type_string(const struct wim_dentry *dentry)
+{
+ const struct wim_inode *inode = dentry->d_inode;
+ if (inode_is_directory(inode))
+ return T("directory");
+ else if (inode_is_symlink(inode))
+ return T("symbolic link");
+ else
+ return T("file");
+}
+
+static bool
+dentry_is_dot_or_dotdot(const struct wim_dentry *dentry)
+{
+ if (dentry->file_name_nbytes <= 4) {
+ if (dentry->file_name_nbytes == 4) {
+ if (dentry->file_name[0] == cpu_to_le16('.') &&
+ dentry->file_name[1] == cpu_to_le16('.'))
+ return true;
+ } else if (dentry->file_name_nbytes == 2) {
+ if (dentry->file_name[0] == cpu_to_le16('.'))
+ return true;
+ }
+ }
+ return false;
+}
+
+static int
+read_dentry_tree_recursive(const u8 * restrict buf, size_t buf_len,
+ struct wim_dentry * restrict dir)
+{
+ u64 cur_offset = dir->subdir_offset;
+
+ /* Check for cyclic directory structure, which would cause infinite
+ * recursion if not handled. */
+ for (struct wim_dentry *d = dir->parent;
+ !dentry_is_root(d); d = d->parent)
+ {
+ if (unlikely(d->subdir_offset == cur_offset)) {
+ ERROR("Cyclic directory structure detected: children "
+ "of \"%"TS"\" coincide with children of \"%"TS"\"",
+ dentry_full_path(dir), dentry_full_path(d));
+ return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
+ }
+ }
+
+ for (;;) {
+ struct wim_dentry *child;
+ struct wim_dentry *duplicate;
+ int ret;
+
+ /* Read next child of @dir. */
+ ret = read_dentry(buf, buf_len, cur_offset, &child);
+ if (ret)
+ return ret;
+
+ /* Check for end of directory. */
+ if (child == NULL)
+ return 0;
+
+ /* Advance to the offset of the next child. Note: We need to
+ * advance by the TOTAL length of the dentry, not by the length
+ * child->length, which although it does take into account the
+ * padding, it DOES NOT take into account alternate stream
+ * entries. */
+ cur_offset += dentry_in_total_length(child);
+
+ /* All dentries except the root should be named. */
+ if (unlikely(!dentry_has_long_name(child))) {
+ WARNING("Ignoring unnamed dentry in "
+ "directory \"%"TS"\"", dentry_full_path(dir));
+ free_dentry(child);
+ continue;
+ }
+
+ /* Don't allow files named "." or "..". */
+ if (unlikely(dentry_is_dot_or_dotdot(child))) {
+ WARNING("Ignoring file named \".\" or \"..\"; "
+ "potentially malicious archive!!!");
+ free_dentry(child);
+ continue;
+ }
+
+ /* Link the child into the directory. */
+ duplicate = dentry_add_child(dir, child);
+ if (unlikely(duplicate)) {
+ /* We already found a dentry with this same
+ * case-sensitive long name. Only keep the first one.
+ */
+ const tchar *child_type, *duplicate_type;
+ child_type = dentry_get_file_type_string(child);
+ duplicate_type = dentry_get_file_type_string(duplicate);
+ WARNING("Ignoring duplicate %"TS" \"%"TS"\" "
+ "(the WIM image already contains a %"TS" "
+ "at that path with the exact same name)",
+ child_type, dentry_full_path(duplicate),
+ duplicate_type);
+ free_dentry(child);
+ continue;
+ }
+
+ /* If this child is a directory that itself has children, call
+ * this procedure recursively. */
+ if (child->subdir_offset != 0) {
+ if (likely(dentry_is_directory(child))) {
+ ret = read_dentry_tree_recursive(buf,
+ buf_len,
+ child);
+ if (ret)
+ return ret;
+ } else {
+ WARNING("Ignoring children of "
+ "non-directory file \"%"TS"\"",
+ dentry_full_path(child));
+ }
+ }
+ }
+}
+
+/*
+ * Read a tree of dentries (directory entries) from a WIM metadata resource.