+ return cmp_u64(NTFS_INODE(node1)->ino, NTFS_INODE(node2)->ino);
+}
+
+/* Adds an NTFS inode to the map. */
+static void
+ntfs_inode_map_add_inode(struct ntfs_inode_map *map, struct ntfs_inode *ni)
+{
+ if (avl_tree_insert(&map->root, &ni->index_node, _avl_cmp_ntfs_inodes)) {
+ WARNING("Inode 0x%016"PRIx64" is a duplicate!", ni->ino);
+ FREE(ni);
+ }
+}
+
+/* Find an ntfs_inode in the map by inode number. Returns NULL if not found. */
+static struct ntfs_inode *
+ntfs_inode_map_lookup(struct ntfs_inode_map *map, u64 ino)
+{
+ struct ntfs_inode tmp;
+ struct avl_tree_node *res;
+
+ tmp.ino = ino;
+ res = avl_tree_lookup_node(map->root, &tmp.index_node, _avl_cmp_ntfs_inodes);
+ if (!res)
+ return NULL;
+ return NTFS_INODE(res);
+}
+
+/* Remove an ntfs_inode from the map and free it. */
+static void
+ntfs_inode_map_remove(struct ntfs_inode_map *map, struct ntfs_inode *ni)
+{
+ avl_tree_remove(&map->root, &ni->index_node);
+ FREE(ni);
+}
+
+/* Free all ntfs_inodes in the map. */
+static void
+ntfs_inode_map_destroy(struct ntfs_inode_map *map)
+{
+ struct ntfs_inode *ni;
+
+ avl_tree_for_each_in_postorder(ni, map->root, struct ntfs_inode, index_node)
+ FREE(ni);
+}
+
+static bool
+file_has_streams(const FILE_LAYOUT_ENTRY *file)
+{
+ return (file->FirstStreamOffset != 0) &&
+ !(file->FileAttributes & FILE_ATTRIBUTE_ENCRYPTED);
+}
+
+static bool
+is_valid_name_entry(const FILE_LAYOUT_NAME_ENTRY *name)
+{
+ return name->FileNameLength > 0 &&
+ name->FileNameLength % 2 == 0 &&
+ !wmemchr(name->FileName, L'\0', name->FileNameLength / 2) &&
+ (!(name->Flags & FILE_LAYOUT_NAME_ENTRY_DOS) ||
+ name->FileNameLength <= 24);
+}
+
+/* Validate the FILE_LAYOUT_NAME_ENTRYs of the specified file and compute the
+ * total length in bytes of the ntfs_dentry structures needed to hold the name
+ * information. */
+static int
+validate_names_and_compute_total_length(const FILE_LAYOUT_ENTRY *file,
+ size_t *total_length_ret)
+{
+ const FILE_LAYOUT_NAME_ENTRY *name =
+ (const void *)file + file->FirstNameOffset;
+ size_t total = 0;
+ size_t num_long_names = 0;
+
+ for (;;) {
+ if (unlikely(!is_valid_name_entry(name))) {
+ ERROR("Invalid FILE_LAYOUT_NAME_ENTRY! "
+ "FileReferenceNumber=0x%016"PRIx64", "
+ "FileNameLength=%"PRIu32", "
+ "FileName=%.*ls, Flags=0x%08"PRIx32,
+ file->FileReferenceNumber,
+ name->FileNameLength,
+ (int)(name->FileNameLength / 2),
+ name->FileName, name->Flags);
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+ if (name->Flags != FILE_LAYOUT_NAME_ENTRY_DOS) {
+ num_long_names++;
+ total += ALIGN(sizeof(struct ntfs_dentry) +
+ name->FileNameLength + sizeof(wchar_t),
+ 8);
+ }
+ if (name->NextNameOffset == 0)
+ break;
+ name = (const void *)name + name->NextNameOffset;
+ }
+
+ if (unlikely(num_long_names == 0)) {
+ ERROR("Inode 0x%016"PRIx64" has no long names!",
+ file->FileReferenceNumber);
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+
+ *total_length_ret = total;
+ return 0;
+}
+
+static bool
+is_valid_stream_entry(const STREAM_LAYOUT_ENTRY *stream)
+{
+ return stream->StreamIdentifierLength % 2 == 0 &&
+ !wmemchr(stream->StreamIdentifier , L'\0',
+ stream->StreamIdentifierLength / 2);
+}
+
+static bool
+is_object_id_stream(const STREAM_LAYOUT_ENTRY *stream)
+{
+ return stream->StreamIdentifierLength == 24 &&
+ !wmemcmp(stream->StreamIdentifier, L"::$OBJECT_ID", 12);
+}
+
+/*
+ * If the specified STREAM_LAYOUT_ENTRY represents a DATA stream as opposed to
+ * some other type of NTFS stream such as a STANDARD_INFORMATION stream, return
+ * true and set *stream_name_ret and *stream_name_nchars_ret to specify just the
+ * stream name. For example, ":foo:$DATA" would become "foo" with length 3
+ * characters. Otherwise return false.
+ */
+static bool
+use_stream(const FILE_LAYOUT_ENTRY *file, const STREAM_LAYOUT_ENTRY *stream,
+ const wchar_t **stream_name_ret, size_t *stream_name_nchars_ret)
+{
+ const wchar_t *stream_name;
+ size_t stream_name_nchars;
+
+ if (stream->StreamIdentifierLength == 0) {
+ /* The unnamed data stream may be given as an empty string
+ * rather than as "::$DATA". Handle it both ways. */
+ stream_name = L"";
+ stream_name_nchars = 0;
+ } else if (!get_data_stream_name(stream->StreamIdentifier,
+ stream->StreamIdentifierLength / 2,
+ &stream_name, &stream_name_nchars))
+ return false;
+
+ /* Skip the unnamed data stream for directories. */
+ if (stream_name_nchars == 0 &&
+ (file->FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ return false;
+
+ *stream_name_ret = stream_name;
+ *stream_name_nchars_ret = stream_name_nchars;
+ return true;
+}
+
+/* Validate the STREAM_LAYOUT_ENTRYs of the specified file and compute the total
+ * length in bytes of the ntfs_stream structures needed to hold the stream
+ * information. In addition, set *have_object_id_ret=true if the file has an
+ * object ID stream. */
+static int
+validate_streams_and_compute_total_length(const FILE_LAYOUT_ENTRY *file,
+ size_t *total_length_ret,
+ bool *have_object_id_ret)
+{
+ const STREAM_LAYOUT_ENTRY *stream =
+ (const void *)file + file->FirstStreamOffset;
+ size_t total = 0;
+ for (;;) {
+ const wchar_t *name;
+ size_t name_nchars;
+
+ if (unlikely(!is_valid_stream_entry(stream))) {
+ WARNING("Invalid STREAM_LAYOUT_ENTRY! "
+ "FileReferenceNumber=0x%016"PRIx64", "
+ "StreamIdentifierLength=%"PRIu32", "
+ "StreamIdentifier=%.*ls",
+ file->FileReferenceNumber,
+ stream->StreamIdentifierLength,
+ (int)(stream->StreamIdentifierLength / 2),
+ stream->StreamIdentifier);
+ return WIMLIB_ERR_UNSUPPORTED;
+ }
+
+ if (use_stream(file, stream, &name, &name_nchars)) {
+ total += ALIGN(sizeof(struct ntfs_stream) +
+ (name_nchars + 1) * sizeof(wchar_t), 8);
+ } else if (is_object_id_stream(stream)) {
+ *have_object_id_ret = true;
+ }
+ if (stream->NextStreamOffset == 0)
+ break;
+ stream = (const void *)stream + stream->NextStreamOffset;
+ }
+
+ *total_length_ret = total;
+ return 0;
+}
+
+static void *
+load_name_information(const FILE_LAYOUT_ENTRY *file, struct ntfs_inode *ni,
+ void *p)
+{
+ const FILE_LAYOUT_NAME_ENTRY *name =
+ (const void *)file + file->FirstNameOffset;
+ for (;;) {
+ struct ntfs_dentry *nd = p;
+ /* Note that a name may be just a short (DOS) name, just a long
+ * name, or both a short name and a long name. If there is a
+ * short name, one name should also be marked as "primary" to
+ * indicate which long name the short name is associated with.
+ * Also, there should be at most one short name per inode. */
+ if (name->Flags & FILE_LAYOUT_NAME_ENTRY_DOS) {
+ memcpy(ni->short_name,
+ name->FileName, name->FileNameLength);
+ ni->short_name[name->FileNameLength / 2] = L'\0';
+ }
+ if (name->Flags != FILE_LAYOUT_NAME_ENTRY_DOS) {
+ ni->num_aliases++;
+ nd->offset_from_inode = (u8 *)nd - (u8 *)ni;
+ nd->is_primary = ((name->Flags &
+ FILE_LAYOUT_NAME_ENTRY_PRIMARY) != 0);
+ nd->parent_ino = name->ParentFileReferenceNumber;
+ memcpy(nd->name, name->FileName, name->FileNameLength);
+ nd->name[name->FileNameLength / 2] = L'\0';
+ p += ALIGN(sizeof(struct ntfs_dentry) +
+ name->FileNameLength + sizeof(wchar_t), 8);
+ }
+ if (name->NextNameOffset == 0)
+ break;
+ name = (const void *)name + name->NextNameOffset;
+ }
+ return p;
+}
+
+static u64
+load_starting_lcn(const STREAM_LAYOUT_ENTRY *stream)
+{
+ const STREAM_EXTENT_ENTRY *entry;
+
+ if (stream->ExtentInformationOffset == 0)
+ return 0;
+
+ entry = (const void *)stream + stream->ExtentInformationOffset;
+
+ if (!(entry->Flags & STREAM_EXTENT_ENTRY_AS_RETRIEVAL_POINTERS))
+ return 0;
+
+ return extract_starting_lcn(&entry->ExtentInformation.RetrievalPointers);
+}
+
+static void *
+load_stream_information(const FILE_LAYOUT_ENTRY *file, struct ntfs_inode *ni,
+ void *p)
+{
+ const STREAM_LAYOUT_ENTRY *stream =
+ (const void *)file + file->FirstStreamOffset;
+ const u32 first_stream_offset = (const u8 *)p - (const u8 *)ni;
+ for (;;) {
+ struct ntfs_stream *ns = p;
+ const wchar_t *name;
+ size_t name_nchars;
+
+ if (use_stream(file, stream, &name, &name_nchars)) {
+ ni->first_stream_offset = first_stream_offset;
+ ni->num_streams++;
+ if (name_nchars == 0)
+ ni->starting_lcn = load_starting_lcn(stream);
+ ns->size = stream->EndOfFile;
+ wmemcpy(ns->name, name, name_nchars);
+ ns->name[name_nchars] = L'\0';
+ p += ALIGN(sizeof(struct ntfs_stream) +
+ (name_nchars + 1) * sizeof(wchar_t), 8);
+ }
+ if (stream->NextStreamOffset == 0)
+ break;
+ stream = (const void *)stream + stream->NextStreamOffset;
+ }
+ return p;
+}
+
+/* Process the information for a file given by FSCTL_QUERY_FILE_LAYOUT. */
+static int
+load_one_file(const FILE_LAYOUT_ENTRY *file, struct ntfs_inode_map *inode_map)
+{
+ const FILE_LAYOUT_INFO_ENTRY *info =
+ (const void *)file + file->ExtraInfoOffset;
+ size_t inode_size;
+ struct ntfs_inode *ni;
+ size_t n;