+/*
+ * This file contains logic to deal with WIM directory entries, or "dentries":
+ *
+ * - Reading a dentry tree from a metadata resource in a WIM file
+ * - Writing a dentry tree to a metadata resource in a WIM file
+ * - Iterating through a tree of WIM dentries
+ * - Path lookup: translating a path into a WIM dentry or inode
+ * - Creating, modifying, and deleting WIM dentries
+ *
+ * Notes:
+ *
+ * - A WIM file can contain multiple images, each of which has an independent
+ * tree of dentries. "On disk", the dentry tree for an image is stored in
+ * the "metadata resource" for that image.
+ *
+ * - Multiple dentries in an image may correspond to the same inode, or "file".
+ * When this occurs, it means that the file has multiple names, or "hard
+ * links". A dentry is not a file, but rather the name of a file!
+ *
+ * - Inodes are not represented explicitly in the WIM file format. Instead,
+ * the metadata resource provides a "hard link group ID" for each dentry.
+ * wimlib handles pulling out actual inodes from this information, but this
+ * occurs in inode_fixup.c and not in this file.
+ *
+ * - wimlib does not allow *directory* hard links, so a WIM image really does
+ * have a *tree* of dentries (and not an arbitrary graph of dentries).
+ *
+ * - wimlib indexes dentries both case-insensitively and case-sensitively,
+ * allowing either behavior to be used for path lookup.
+ *
+ * - Multiple dentries in a directory might have the same case-insensitive
+ * name. But wimlib enforces that at most one dentry in a directory can have
+ * a given case-sensitive name.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <errno.h>
+
+#include "wimlib/assert.h"
+#include "wimlib/dentry.h"
+#include "wimlib/inode.h"
+#include "wimlib/encoding.h"
+#include "wimlib/endianness.h"
+#include "wimlib/metadata.h"
+#include "wimlib/paths.h"
+
+/* On-disk format of a WIM dentry (directory entry), located in the metadata
+ * resource for a WIM image. */
+struct wim_dentry_on_disk {
+
+ /* Length of this directory entry in bytes, not including any extra
+ * stream entries. Should be a multiple of 8 so that the following
+ * dentry or extra stream entry is aligned on an 8-byte boundary. (If
+ * not, wimlib will round it up.) It must be at least as long as the
+ * fixed-length fields of the dentry (WIM_DENTRY_DISK_SIZE), plus the
+ * lengths of the file name and/or short name if present, plus the size
+ * of any "extra" data.
+ *
+ * It is also possible for this field to be 0. This case indicates the
+ * end of a list of sibling entries in a directory. It also means the
+ * real length is 8, because the dentry included only the length field,
+ * but that takes up 8 bytes. */
+ le64 length;
+
+ /* File attributes for the file or directory. This is a bitwise OR of
+ * the FILE_ATTRIBUTE_* constants and should correspond to the value
+ * retrieved by GetFileAttributes() on Windows. */
+ le32 attributes;
+
+ /* A value that specifies the security descriptor for this file or
+ * directory. If -1, the file or directory has no security descriptor.
+ * Otherwise, it is a 0-based index into the WIM image's table of
+ * security descriptors (see: `struct wim_security_data') */
+ sle32 security_id;
+
+ /* Offset, in bytes, from the start of the uncompressed metadata
+ * resource of this directory's child directory entries, or 0 if this
+ * directory entry does not correspond to a directory or otherwise does
+ * not have any children. */
+ le64 subdir_offset;
+
+ /* Reserved fields */
+ le64 unused_1;
+ le64 unused_2;
+
+ /* Creation time, last access time, and last write time, in
+ * 100-nanosecond intervals since 12:00 a.m UTC January 1, 1601. They
+ * should correspond to the times gotten by calling GetFileTime() on
+ * Windows. */
+ le64 creation_time;
+ le64 last_access_time;
+ le64 last_write_time;
+
+ /*
+ * Usually this is the SHA-1 message digest of the file's "contents"
+ * (the unnamed data stream).
+ *
+ * If the file has FILE_ATTRIBUTE_REPARSE_POINT set, then this is
+ * instead usually the SHA-1 message digest of the uncompressed reparse
+ * point data.
+ *
+ * However, there are some special rules that need to be applied to
+ * interpret this field correctly when extra stream entries are present.
+ * See the code for details.
+ */
+ u8 default_hash[SHA1_HASH_SIZE];
+
+ /* Unknown field (maybe accidental padding) */
+ le32 unknown_0x54;
+
+ /*
+ * The following 8-byte union contains either information about the
+ * reparse point (for files with FILE_ATTRIBUTE_REPARSE_POINT set), or
+ * the "hard link group ID" (for other files).
+ *
+ * The reparse point information contains ReparseTag and ReparseReserved
+ * from the header of the reparse point buffer. It also contains a flag
+ * that indicates whether a reparse point fixup (for the target of an
+ * absolute symbolic link or junction) was done or not.
+ *
+ * The "hard link group ID" is like an inode number; all dentries for
+ * the same inode share the same value. See inode_fixup.c for more
+ * information.
+ *
+ * Note that this union creates the limitation that reparse point files
+ * cannot have multiple names (hard links).
+ */
+ union {
+ struct {
+ le32 reparse_tag;
+ le16 rp_reserved;
+ le16 rp_flags;
+ } _packed_attribute reparse;
+ struct {
+ le64 hard_link_group_id;
+ } _packed_attribute nonreparse;
+ };
+
+ /* Number of extra stream entries that directly follow this dentry
+ * on-disk. */
+ le16 num_extra_streams;
+
+ /* If nonzero, this is the length, in bytes, of this dentry's UTF-16LE
+ * encoded short name (8.3 DOS-compatible name), excluding the null
+ * terminator. If zero, then the long name of this dentry does not have
+ * a corresponding short name (but this does not exclude the possibility
+ * that another dentry for the same file has a short name). */
+ le16 short_name_nbytes;
+
+ /* If nonzero, this is the length, in bytes, of this dentry's UTF-16LE
+ * encoded "long" name, excluding the null terminator. If zero, then
+ * this file has no long name. The root dentry should not have a long
+ * name, but all other dentries in the image should have long names. */
+ le16 name_nbytes;
+
+ /* Beginning of optional, variable-length fields */
+
+ /* If name_nbytes != 0, the next field will be the UTF-16LE encoded long
+ * name. This will be null-terminated, so the size of this field will
+ * really be name_nbytes + 2. */
+ /*utf16lechar name[];*/
+
+ /* If short_name_nbytes != 0, the next field will be the UTF-16LE
+ * encoded short name. This will be null-terminated, so the size of
+ * this field will really be short_name_nbytes + 2. */
+ /*utf16lechar short_name[];*/
+
+ /* If there is still space in the dentry (according to the 'length'
+ * field) after 8-byte alignment, then the remaining space will be a
+ * variable-length list of tagged metadata items. See tagged_items.c
+ * for more information. */
+ /* u8 tagged_items[] _aligned_attribute(8); */
+
+} _packed_attribute;
+ /* If num_extra_streams != 0, then there are that many extra stream
+ * entries following the dentry, starting on the next 8-byte aligned
+ * boundary. They are not counted in the 'length' field of the dentry.
+ */
+
+/* On-disk format of an extra stream entry. This represents an extra NTFS-style
+ * "stream" associated with the file, such as a named data stream. */
+struct wim_extra_stream_entry_on_disk {
+
+ /* Length of this extra stream entry, in bytes. This includes all
+ * fixed-length fields, plus the name and null terminator if present,
+ * and any needed padding such that the length is a multiple of 8. */
+ le64 length;
+
+ /* Reserved field */
+ le64 reserved;
+
+ /* SHA-1 message digest of this stream's uncompressed data, or all
+ * zeroes if this stream's data is of zero length. */
+ u8 hash[SHA1_HASH_SIZE];
+
+ /* Length of this stream's name, in bytes and excluding the null
+ * terminator; or 0 if this stream is unnamed. */
+ le16 name_nbytes;
+
+ /* Stream name in UTF-16LE. It is @name_nbytes bytes long, excluding
+ * the null terminator. There is a null terminator character if
+ * @name_nbytes != 0; i.e., if this stream is named. */
+ utf16lechar name[];
+} _packed_attribute;
+
+static void
+do_dentry_set_name(struct wim_dentry *dentry, utf16lechar *name,
+ size_t name_nbytes)