]> wimlib.net Git - wimlib/blobdiff - src/inode.c
Clean up inode alias tracking
[wimlib] / src / inode.c
index 539d5667b5949952a3ea65b9b78444f719fef44e..6384b11c408d4ccb9212cccbf3c10053ea475d30 100644 (file)
@@ -1,40 +1,41 @@
 /*
  * inode.c
+ *
+ * Functions that operate on WIM inodes.
+ *
+ * See dentry.c for a description of the relationship between WIM dentries and
+ * WIM inodes.
  */
 
 /*
- * Copyright (C) 2012, 2013 Eric Biggers
- *
- * This file is part of wimlib, a library for working with WIM files.
+ * Copyright (C) 2012, 2013, 2014 Eric Biggers
  *
- * wimlib is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free
- * Software Foundation; either version 3 of the License, or (at your option)
- * any later version.
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option) any
+ * later version.
  *
- * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  * details.
  *
- * You should have received a copy of the GNU General Public License
- * along with wimlib; if not, see http://www.gnu.org/licenses/.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this file; if not, see http://www.gnu.org/licenses/.
  */
 
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
 #endif
 
+#include <errno.h>
+
 #include "wimlib/assert.h"
-#include "wimlib/case.h"
-#include "wimlib/dentry.h" /* Only for dentry_full_path().  Otherwise the code
-                             in this file doesn't care about file names/paths.
-                           */
+#include "wimlib/dentry.h"
 #include "wimlib/encoding.h"
 #include "wimlib/endianness.h"
 #include "wimlib/error.h"
 #include "wimlib/inode.h"
-#include "wimlib/inode_table.h"
 #include "wimlib/lookup_table.h"
 #include "wimlib/security.h"
 #include "wimlib/timestamp.h"
@@ -45,7 +46,7 @@ new_inode(void)
 {
        struct wim_inode *inode = new_timeless_inode();
        if (inode) {
-               u64 now = get_wim_timestamp();
+               u64 now = now_as_wim_timestamp();
                inode->i_creation_time = now;
                inode->i_last_access_time = now;
                inode->i_last_write_time = now;
@@ -53,7 +54,6 @@ new_inode(void)
        return inode;
 }
 
-
 /* Allocate a new inode.  Leave the timestamps zeroed out.  */
 struct wim_inode *
 new_timeless_inode(void)
@@ -61,7 +61,7 @@ new_timeless_inode(void)
        struct wim_inode *inode = CALLOC(1, sizeof(struct wim_inode));
        if (inode) {
                inode->i_security_id = -1;
-               inode->i_nlink = 1;
+               /*inode->i_nlink = 0;*/
                inode->i_next_stream_id = 1;
                inode->i_not_rpfixed = 1;
                inode->i_canonical_streams = 1;
@@ -71,43 +71,23 @@ new_timeless_inode(void)
        return inode;
 }
 
-/* Decrement link count on an inode.  */
-void
-put_inode(struct wim_inode *inode)
-{
-       wimlib_assert(inode->i_nlink != 0);
-       if (--inode->i_nlink == 0) {
-               /* If FUSE mounts are enabled, we must keep a unlinked inode
-                * around until all file descriptors to it have been closed.
-                * inode_put_fd() in mount_image.c handles dropping a file
-                * descriptor.  */
-       #ifdef WITH_FUSE
-               if (inode->i_num_opened_fds == 0)
-       #endif
-                       free_inode(inode);
-       }
-}
-
-/* De-allocate memory for an alternate data stream entry.  */
+/* Free memory allocated within an alternate data stream entry.  */
 static void
 destroy_ads_entry(struct wim_ads_entry *ads_entry)
 {
        FREE(ads_entry->stream_name);
 }
 
-/* Free an inode.  Only use this if there can't be other links to the inode or
- * if it doesn't matter if there are.  */
-void
+static void
 free_inode(struct wim_inode *inode)
 {
-       if (inode == NULL)
-               return;
-
-       if (inode->i_ads_entries) {
-               for (u16 i = 0; i < inode->i_num_ads; i++)
+       if (unlikely(inode->i_ads_entries)) {
+               for (unsigned i = 0; i < inode->i_num_ads; i++)
                        destroy_ads_entry(&inode->i_ads_entries[i]);
                FREE(inode->i_ads_entries);
        }
+       if (unlikely(inode->i_extra))
+               FREE(inode->i_extra);
        /* HACK: This may instead delete the inode from i_list, but hlist_del()
         * behaves the same as list_del(). */
        if (!hlist_unhashed(&inode->i_hlist))
@@ -115,20 +95,65 @@ free_inode(struct wim_inode *inode)
        FREE(inode);
 }
 
-/* Return %true iff the alternate data stream entry @entry has the UTF-16LE
- * stream name @name that has length @name_nbytes bytes.  */
-static inline bool
-ads_entry_has_name(const struct wim_ads_entry *entry,
-                  const utf16lechar *name, size_t name_nbytes,
-                  bool ignore_case)
+static inline void
+free_inode_if_unneeded(struct wim_inode *inode)
 {
-       return 0 == cmp_utf16le_strings(name,
-                                       name_nbytes / 2,
-                                       entry->stream_name,
-                                       entry->stream_name_nbytes / 2,
-                                       ignore_case);
+       if (inode->i_nlink)
+               return;
+#ifdef WITH_FUSE
+       if (inode->i_num_opened_fds)
+               return;
+#endif
+       free_inode(inode);
 }
 
+/* Associate a dentry with the specified inode.  */
+void
+d_associate(struct wim_dentry *dentry, struct wim_inode *inode)
+{
+       wimlib_assert(!dentry->d_inode);
+
+       list_add_tail(&dentry->d_alias, &inode->i_dentry);
+       dentry->d_inode = inode;
+       inode->i_nlink++;
+}
+
+/* Disassociate a dentry from its inode, if any.  Following this, free the inode
+ * if it is no longer in use.  */
+void
+d_disassociate(struct wim_dentry *dentry)
+{
+       struct wim_inode *inode = dentry->d_inode;
+
+       if (unlikely(!inode))
+               return;
+
+       wimlib_assert(inode->i_nlink > 0);
+
+       list_del(&dentry->d_alias);
+       dentry->d_inode = NULL;
+       inode->i_nlink--;
+
+       free_inode_if_unneeded(inode);
+}
+
+#ifdef WITH_FUSE
+void
+inode_dec_num_opened_fds(struct wim_inode *inode)
+{
+       wimlib_assert(inode->i_num_opened_fds > 0);
+
+       if (--inode->i_num_opened_fds == 0) {
+               /* The last file descriptor to this inode was closed.  */
+               FREE(inode->i_fds);
+               inode->i_fds = NULL;
+               inode->i_num_allocated_fds = 0;
+
+               free_inode_if_unneeded(inode);
+       }
+}
+#endif
+
 /*
  * Returns the alternate data stream entry belonging to @inode that has the
  * stream name @stream_name, or NULL if the inode has no alternate data stream
@@ -136,204 +161,185 @@ ads_entry_has_name(const struct wim_ads_entry *entry,
  *
  * If @p stream_name is the empty string, NULL is returned --- that is, this
  * function will not return "unnamed" alternate data stream entries.
+ *
+ * If NULL is returned, errno is set.
  */
 struct wim_ads_entry *
-inode_get_ads_entry(struct wim_inode *inode, const tchar *stream_name,
-                   u16 *idx_ret)
+inode_get_ads_entry(struct wim_inode *inode, const tchar *stream_name)
 {
+       int ret;
+       const utf16lechar *stream_name_utf16le;
+       size_t stream_name_utf16le_nbytes;
+       unsigned i;
+       struct wim_ads_entry *result;
+
        if (inode->i_num_ads == 0) {
+               errno = ENOENT;
                return NULL;
-       } else {
-               size_t stream_name_utf16le_nbytes;
-               u16 i;
-               struct wim_ads_entry *result;
-
-               if (stream_name[0] == T('\0'))
-                       return NULL;
+       }
 
-       #if TCHAR_IS_UTF16LE
-               const utf16lechar *stream_name_utf16le;
+       if (stream_name[0] == T('\0')) {
+               errno = ENOENT;
+               return NULL;
+       }
 
-               stream_name_utf16le = stream_name;
-               stream_name_utf16le_nbytes = tstrlen(stream_name) * sizeof(tchar);
-       #else
-               utf16lechar *stream_name_utf16le;
+       ret = tstr_get_utf16le_and_len(stream_name, &stream_name_utf16le,
+                                      &stream_name_utf16le_nbytes);
+       if (ret)
+               return NULL;
 
+       i = 0;
+       result = NULL;
+       do {
+               if (!cmp_utf16le_strings(inode->i_ads_entries[i].stream_name,
+                                        inode->i_ads_entries[i].stream_name_nbytes /
+                                               sizeof(utf16lechar),
+                                        stream_name_utf16le,
+                                        stream_name_utf16le_nbytes /
+                                               sizeof(utf16lechar),
+                                        default_ignore_case))
                {
-                       int ret = tstr_to_utf16le(stream_name,
-                                                 tstrlen(stream_name) *
-                                                     sizeof(tchar),
-                                                 &stream_name_utf16le,
-                                                 &stream_name_utf16le_nbytes);
-                       if (ret)
-                               return NULL;
+                       result = &inode->i_ads_entries[i];
+                       break;
                }
-       #endif
-               i = 0;
-               result = NULL;
-               do {
-                       if (ads_entry_has_name(&inode->i_ads_entries[i],
-                                              stream_name_utf16le,
-                                              stream_name_utf16le_nbytes,
-                                              default_ignore_case))
-                       {
-                               if (idx_ret)
-                                       *idx_ret = i;
-                               result = &inode->i_ads_entries[i];
-                               break;
-                       }
-               } while (++i != inode->i_num_ads);
-       #if !TCHAR_IS_UTF16LE
-               FREE(stream_name_utf16le);
-       #endif
-               return result;
-       }
-}
+       } while (++i != inode->i_num_ads);
 
-static int
-init_ads_entry(struct wim_ads_entry *ads_entry, const void *name,
-              size_t name_nbytes, bool is_utf16le)
-{
-       int ret = 0;
-       memset(ads_entry, 0, sizeof(*ads_entry));
-
-       if (is_utf16le) {
-               utf16lechar *p = MALLOC(name_nbytes + sizeof(utf16lechar));
-               if (p == NULL)
-                       return WIMLIB_ERR_NOMEM;
-               memcpy(p, name, name_nbytes);
-               p[name_nbytes / 2] = cpu_to_le16(0);
-               ads_entry->stream_name = p;
-               ads_entry->stream_name_nbytes = name_nbytes;
-       } else {
-               if (name && *(const tchar*)name != T('\0')) {
-                       ret = get_utf16le_string(name, &ads_entry->stream_name,
-                                                &ads_entry->stream_name_nbytes);
-               }
-       }
-       return ret;
+       tstr_put_utf16le(stream_name_utf16le);
+
+       if (!result)
+               errno = ENOENT;
+       return result;
 }
 
 static struct wim_ads_entry *
-do_inode_add_ads(struct wim_inode *inode, const void *stream_name,
-                size_t stream_name_nbytes, bool is_utf16le)
+do_inode_add_ads(struct wim_inode *inode,
+                utf16lechar *stream_name, size_t stream_name_nbytes)
 {
-       u16 num_ads;
+       unsigned num_ads;
        struct wim_ads_entry *ads_entries;
        struct wim_ads_entry *new_entry;
 
-       wimlib_assert(stream_name_nbytes != 0);
-
-       if (inode->i_num_ads >= 0xfffe) {
-               ERROR("Too many alternate data streams in one inode!");
+       if (unlikely(inode->i_num_ads >= 0xfffe)) {
+               ERROR("File \"%"TS"\" has too many alternate data streams!",
+                     inode_first_full_path(inode));
+               errno = EFBIG;
                return NULL;
        }
        num_ads = inode->i_num_ads + 1;
        ads_entries = REALLOC(inode->i_ads_entries,
                              num_ads * sizeof(inode->i_ads_entries[0]));
-       if (ads_entries == NULL) {
-               ERROR("Failed to allocate memory for new alternate data stream");
+       if (!ads_entries)
                return NULL;
-       }
+
        inode->i_ads_entries = ads_entries;
 
        new_entry = &inode->i_ads_entries[num_ads - 1];
-       if (init_ads_entry(new_entry, stream_name, stream_name_nbytes, is_utf16le))
-               return NULL;
+
+       memset(new_entry, 0, sizeof(struct wim_ads_entry));
+       new_entry->stream_name = stream_name;
+       new_entry->stream_name_nbytes = stream_name_nbytes;
        new_entry->stream_id = inode->i_next_stream_id++;
        inode->i_num_ads = num_ads;
        return new_entry;
 }
 
+/*
+ * Add an alternate data stream entry to a WIM inode (UTF-16LE version).  On
+ * success, returns a pointer to the new entry.  Note that this pointer might
+ * become invalid if another ADS entry is added to the inode.  On failure,
+ * returns NULL and sets errno.
+ */
 struct wim_ads_entry *
 inode_add_ads_utf16le(struct wim_inode *inode,
-                     const utf16lechar *stream_name,
-                     size_t stream_name_nbytes)
+                     const utf16lechar *stream_name, size_t stream_name_nbytes)
 {
-       DEBUG("Add alternate data stream \"%"WS"\"", stream_name);
-       return do_inode_add_ads(inode, stream_name, stream_name_nbytes, true);
+       utf16lechar *dup = NULL;
+       struct wim_ads_entry *result;
+
+       if (stream_name_nbytes) {
+               dup = utf16le_dupz(stream_name, stream_name_nbytes);
+               if (!dup)
+                       return NULL;
+       }
+
+       result = do_inode_add_ads(inode, dup, stream_name_nbytes);
+       if (!result)
+               FREE(dup);
+       return result;
 }
 
 /*
- * Add an alternate stream entry to a WIM inode.  On success, returns a pointer
- * to the new entry; on failure, returns NULL.
- *
- * @stream_name must be a nonempty string.
+ * Add an alternate data stream entry to a WIM inode (tchar version).  On
+ * success, returns a pointer to the new entry.  Note that this pointer might
+ * become invalid if another ADS entry is added to the inode.  On failure,
+ * returns NULL and sets errno.
  */
 struct wim_ads_entry *
 inode_add_ads(struct wim_inode *inode, const tchar *stream_name)
 {
-       DEBUG("Add alternate data stream \"%"TS"\"", stream_name);
-       return do_inode_add_ads(inode, stream_name,
-                               tstrlen(stream_name) * sizeof(tchar),
-                               TCHAR_IS_UTF16LE);
+       utf16lechar *stream_name_utf16le = NULL;
+       size_t stream_name_utf16le_nbytes = 0;
+       struct wim_ads_entry *result;
+
+       if (stream_name && *stream_name)
+               if (tstr_to_utf16le(stream_name,
+                                   tstrlen(stream_name) * sizeof(tchar),
+                                   &stream_name_utf16le,
+                                   &stream_name_utf16le_nbytes))
+                       return NULL;
+
+       result = do_inode_add_ads(inode, stream_name_utf16le,
+                                 stream_name_utf16le_nbytes);
+       if (!result)
+               FREE(stream_name_utf16le);
+       return result;
 }
 
-int
+/*
+ * Add an data alternate stream entry to a WIM inode, where the contents of the
+ * new stream are specified in a data buffer.  The inode must be resolved.
+ *
+ * On success, returns a pointer to the new alternate data stream entry.  Note
+ * that this pointer might become invalid if another ADS entry is added to the
+ * inode.  On failure, returns NULL and sets errno.
+ */
+struct wim_ads_entry *
 inode_add_ads_with_data(struct wim_inode *inode, const tchar *name,
                        const void *value, size_t size,
                        struct wim_lookup_table *lookup_table)
 {
-       struct wim_ads_entry *new_ads_entry;
+       struct wim_ads_entry *new_entry;
 
        wimlib_assert(inode->i_resolved);
 
-       new_ads_entry = inode_add_ads(inode, name);
-       if (new_ads_entry == NULL)
-               return WIMLIB_ERR_NOMEM;
+       new_entry = inode_add_ads(inode, name);
+       if (unlikely(!new_entry))
+               return NULL;
 
-       new_ads_entry->lte = new_stream_from_data_buffer(value, size,
-                                                        lookup_table);
-       if (new_ads_entry->lte == NULL) {
-               inode_remove_ads(inode, new_ads_entry - inode->i_ads_entries,
-                                lookup_table);
-               return WIMLIB_ERR_NOMEM;
+       new_entry->lte = new_stream_from_data_buffer(value, size, lookup_table);
+       if (unlikely(!new_entry->lte)) {
+               inode_remove_ads(inode, new_entry, NULL);
+               return NULL;
        }
-       return 0;
-}
-
-bool
-inode_has_named_stream(const struct wim_inode *inode)
-{
-       for (u16 i = 0; i < inode->i_num_ads; i++)
-               if (ads_entry_is_named_stream(&inode->i_ads_entries[i]))
-                       return true;
-       return false;
-}
-
-/* Set the unnamed stream of a WIM inode, given a data buffer containing the
- * stream contents. */
-int
-inode_set_unnamed_stream(struct wim_inode *inode, const void *data, size_t len,
-                        struct wim_lookup_table *lookup_table)
-{
-       inode->i_lte = new_stream_from_data_buffer(data, len, lookup_table);
-       if (inode->i_lte == NULL)
-               return WIMLIB_ERR_NOMEM;
-       inode->i_resolved = 1;
-       return 0;
+       return new_entry;
 }
 
-/* Remove an alternate data stream from a WIM inode  */
+/* Remove an alternate data stream from a WIM inode.  */
 void
-inode_remove_ads(struct wim_inode *inode, u16 idx,
+inode_remove_ads(struct wim_inode *inode, struct wim_ads_entry *entry,
                 struct wim_lookup_table *lookup_table)
 {
-       struct wim_ads_entry *ads_entry;
        struct wim_lookup_table_entry *lte;
+       unsigned idx = entry - inode->i_ads_entries;
 
        wimlib_assert(idx < inode->i_num_ads);
        wimlib_assert(inode->i_resolved);
 
-       ads_entry = &inode->i_ads_entries[idx];
-
-       DEBUG("Remove alternate data stream \"%"WS"\"", ads_entry->stream_name);
-
-       lte = ads_entry->lte;
+       lte = entry->lte;
        if (lte)
                lte_decrement_refcnt(lte, lookup_table);
 
-       destroy_ads_entry(ads_entry);
+       destroy_ads_entry(entry);
 
        memmove(&inode->i_ads_entries[idx],
                &inode->i_ads_entries[idx + 1],
@@ -341,189 +347,166 @@ inode_remove_ads(struct wim_inode *inode, u16 idx,
        inode->i_num_ads--;
 }
 
+/* Return true iff the specified inode has at least one named data stream.  */
 bool
-inode_has_unix_data(const struct wim_inode *inode)
+inode_has_named_stream(const struct wim_inode *inode)
 {
-       for (u16 i = 0; i < inode->i_num_ads; i++)
-               if (ads_entry_is_unix_data(&inode->i_ads_entries[i]))
+       for (unsigned i = 0; i < inode->i_num_ads; i++)
+               if (inode->i_ads_entries[i].stream_name_nbytes)
                        return true;
        return false;
 }
 
-#ifndef __WIN32__
+/* Set the unnamed stream of a WIM inode, given a data buffer containing the
+ * stream contents.  The inode must be resolved and cannot already have an
+ * unnamed stream.  */
 int
-inode_get_unix_data(const struct wim_inode *inode,
-                   struct wimlib_unix_data *unix_data,
-                   u16 *stream_idx_ret)
+inode_set_unnamed_stream(struct wim_inode *inode, const void *data, size_t len,
+                        struct wim_lookup_table *lookup_table)
 {
-       const struct wim_ads_entry *ads_entry;
-       const struct wim_lookup_table_entry *lte;
-       size_t size;
-       int ret;
-
        wimlib_assert(inode->i_resolved);
+       wimlib_assert(!inode->i_lte);
 
-       ads_entry = inode_get_ads_entry((struct wim_inode*)inode,
-                                       WIMLIB_UNIX_DATA_TAG, NULL);
-       if (ads_entry == NULL)
-               return NO_UNIX_DATA;
-
-       if (stream_idx_ret)
-               *stream_idx_ret = ads_entry - inode->i_ads_entries;
-
-       lte = ads_entry->lte;
-       if (lte == NULL)
-               return NO_UNIX_DATA;
-
-       size = lte->size;
-       if (size != sizeof(struct wimlib_unix_data))
-               return BAD_UNIX_DATA;
-
-       ret = read_full_stream_into_buf(lte, unix_data);
-       if (ret)
-               return ret;
-
-       if (unix_data->version != 0)
-               return BAD_UNIX_DATA;
+       inode->i_lte = new_stream_from_data_buffer(data, len, lookup_table);
+       if (!inode->i_lte)
+               return WIMLIB_ERR_NOMEM;
        return 0;
 }
 
-int
-inode_set_unix_data(struct wim_inode *inode, u16 uid, u16 gid, u16 mode,
-                   struct wim_lookup_table *lookup_table, int which)
-{
-       struct wimlib_unix_data unix_data;
-       int ret;
-       bool have_good_unix_data = false;
-       bool have_unix_data = false;
-       u16 stream_idx;
-
-       if (!(which & UNIX_DATA_CREATE)) {
-               ret = inode_get_unix_data(inode, &unix_data, &stream_idx);
-               if (ret == 0 || ret == BAD_UNIX_DATA || ret > 0)
-                       have_unix_data = true;
-               if (ret == 0)
-                       have_good_unix_data = true;
-       }
-       unix_data.version = 0;
-       if (which & UNIX_DATA_UID || !have_good_unix_data)
-               unix_data.uid = uid;
-       if (which & UNIX_DATA_GID || !have_good_unix_data)
-               unix_data.gid = gid;
-       if (which & UNIX_DATA_MODE || !have_good_unix_data)
-               unix_data.mode = mode;
-       ret = inode_add_ads_with_data(inode, WIMLIB_UNIX_DATA_TAG,
-                                     &unix_data,
-                                     sizeof(struct wimlib_unix_data),
-                                     lookup_table);
-       if (ret == 0 && have_unix_data)
-               inode_remove_ads(inode, stream_idx, lookup_table);
-       return ret;
-}
-#endif /* __WIN32__  */
-
 /*
- * Resolve an inode's lookup table entries.
+ * Resolve an inode's single-instance streams.
  *
- * This replaces the SHA1 hash fields (which are used to lookup an entry in the
- * lookup table) with pointers directly to the lookup table entries.
+ * This takes each SHA-1 message digest stored in the inode or one of its ADS
+ * entries and replaces it with a pointer directly to the appropriate 'struct
+ * wim_lookup_table_entry' currently inserted into @table to represent the
+ * single-instance stream having that SHA-1 message digest.
  *
  * If @force is %false:
- *     If any needed SHA1 message digests are not found in the lookup table,
- *     WIMLIB_ERR_RESOURCE_NOT_FOUND is returned and the inode is left
- *     unmodified.
+ *     If any of the needed single-instance streams do not exist in @table,
+ *     return WIMLIB_ERR_RESOURCE_NOT_FOUND and leave the inode unmodified.
  * If @force is %true:
- *     If any needed SHA1 message digests are not found in the lookup table,
- *     new entries are allocated and inserted into the lookup table.
+ *     If any of the needed single-instance streams do not exist in @table,
+ *     allocate new entries for them and insert them into @table.  This does
+ *     not, of course, cause these streams to magically exist, but this is
+ *     needed by the code for extraction from a pipe.
+ *
+ * If the inode is already resolved, this function does nothing.
+ *
+ * Returns 0 on success; WIMLIB_ERR_NOMEM if out of memory; or
+ * WIMLIB_ERR_RESOURCE_NOT_FOUND if @force is %false and at least one
+ * single-instance stream referenced by the inode was missing.
  */
 int
 inode_resolve_streams(struct wim_inode *inode, struct wim_lookup_table *table,
                      bool force)
 {
        const u8 *hash;
+       struct wim_lookup_table_entry *lte, *ads_lte;
 
-       if (!inode->i_resolved) {
-               struct wim_lookup_table_entry *lte, *ads_lte;
+       if (inode->i_resolved)
+               return 0;
+
+       struct wim_lookup_table_entry *ads_ltes[inode->i_num_ads];
+
+       /* Resolve the default data stream */
+       lte = NULL;
+       hash = inode->i_hash;
+       if (!is_zero_hash(hash)) {
+               lte = lookup_stream(table, hash);
+               if (!lte) {
+                       if (force) {
+                               lte = new_lookup_table_entry();
+                               if (!lte)
+                                       return WIMLIB_ERR_NOMEM;
+                               copy_hash(lte->hash, hash);
+                               lookup_table_insert(table, lte);
+                       } else {
+                               goto stream_not_found;
+                       }
+               }
+       }
 
-               /* Resolve the default file stream */
-               lte = NULL;
-               hash = inode->i_hash;
+       /* Resolve the alternate data streams */
+       for (unsigned i = 0; i < inode->i_num_ads; i++) {
+               struct wim_ads_entry *cur_entry;
+
+               ads_lte = NULL;
+               cur_entry = &inode->i_ads_entries[i];
+               hash = cur_entry->hash;
                if (!is_zero_hash(hash)) {
-                       lte = lookup_stream(table, hash);
-                       if (!lte) {
+                       ads_lte = lookup_stream(table, hash);
+                       if (!ads_lte) {
                                if (force) {
-                                       lte = new_lookup_table_entry();
-                                       if (!lte)
+                                       ads_lte = new_lookup_table_entry();
+                                       if (!ads_lte)
                                                return WIMLIB_ERR_NOMEM;
-                                       copy_hash(lte->hash, hash);
-                                       lookup_table_insert(table, lte);
+                                       copy_hash(ads_lte->hash, hash);
+                                       lookup_table_insert(table, ads_lte);
                                } else {
                                        goto stream_not_found;
                                }
                        }
                }
-
-               /* Resolve the alternate data streams */
-               struct wim_lookup_table_entry *ads_ltes[inode->i_num_ads];
-               for (u16 i = 0; i < inode->i_num_ads; i++) {
-                       struct wim_ads_entry *cur_entry;
-
-                       ads_lte = NULL;
-                       cur_entry = &inode->i_ads_entries[i];
-                       hash = cur_entry->hash;
-                       if (!is_zero_hash(hash)) {
-                               ads_lte = lookup_stream(table, hash);
-                               if (!ads_lte) {
-                                       if (force) {
-                                               ads_lte = new_lookup_table_entry();
-                                               if (!ads_lte)
-                                                       return WIMLIB_ERR_NOMEM;
-                                               copy_hash(ads_lte->hash, hash);
-                                               lookup_table_insert(table, ads_lte);
-                                       } else {
-                                               goto stream_not_found;
-                                       }
-                               }
-                       }
-                       ads_ltes[i] = ads_lte;
-               }
-               inode->i_lte = lte;
-               for (u16 i = 0; i < inode->i_num_ads; i++)
-                       inode->i_ads_entries[i].lte = ads_ltes[i];
-               inode->i_resolved = 1;
+               ads_ltes[i] = ads_lte;
        }
+       inode->i_lte = lte;
+       for (unsigned i = 0; i < inode->i_num_ads; i++)
+               inode->i_ads_entries[i].lte = ads_ltes[i];
+       inode->i_resolved = 1;
        return 0;
 
 stream_not_found:
        return stream_not_found_error(inode, hash);
 }
 
+/*
+ * Undo the effects of inode_resolve_streams().
+ *
+ * If the inode is not resolved, this function does nothing.
+ */
 void
 inode_unresolve_streams(struct wim_inode *inode)
 {
-       if (inode->i_resolved) {
-               if (inode->i_lte)
-                       copy_hash(inode->i_hash, inode->i_lte->hash);
+       if (!inode->i_resolved)
+               return;
+
+       if (inode->i_lte)
+               copy_hash(inode->i_hash, inode->i_lte->hash);
+       else
+               zero_out_hash(inode->i_hash);
+
+       for (unsigned i = 0; i < inode->i_num_ads; i++) {
+               if (inode->i_ads_entries[i].lte)
+                       copy_hash(inode->i_ads_entries[i].hash,
+                                 inode->i_ads_entries[i].lte->hash);
                else
-                       zero_out_hash(inode->i_hash);
-
-               for (u16 i = 0; i < inode->i_num_ads; i++) {
-                       if (inode->i_ads_entries[i].lte)
-                               copy_hash(inode->i_ads_entries[i].hash,
-                                         inode->i_ads_entries[i].lte->hash);
-                       else
-                               zero_out_hash(inode->i_ads_entries[i].hash);
-               }
-               inode->i_resolved = 0;
+                       zero_out_hash(inode->i_ads_entries[i].hash);
        }
+       inode->i_resolved = 0;
+}
+
+int
+stream_not_found_error(const struct wim_inode *inode, const u8 *hash)
+{
+       if (wimlib_print_errors) {
+               tchar hashstr[SHA1_HASH_SIZE * 2 + 1];
+
+               sprint_hash(hash, hashstr);
+
+               ERROR("\"%"TS"\": stream not found\n"
+                     "        SHA-1 message digest of missing stream:\n"
+                     "        %"TS"",
+                     inode_first_full_path(inode), hashstr);
+       }
+       return WIMLIB_ERR_RESOURCE_NOT_FOUND;
 }
 
 /*
- * Returns the lookup table entry for stream @stream_idx of the inode, where
- * stream_idx = 0 means the default un-named file stream, and stream_idx >= 1
- * corresponds to an alternate data stream.
+ * Return the lookup table entry for the specified stream of the inode, or NULL
+ * if the specified stream is empty or not available.
  *
- * This works for both resolved and un-resolved inodes.
+ * stream_idx = 0: default data stream
+ * stream_idx > 0: alternate data stream
  */
 struct wim_lookup_table_entry *
 inode_stream_lte(const struct wim_inode *inode, unsigned stream_idx,
@@ -531,77 +514,87 @@ inode_stream_lte(const struct wim_inode *inode, unsigned stream_idx,
 {
        if (inode->i_resolved)
                return inode_stream_lte_resolved(inode, stream_idx);
-       else
-               return inode_stream_lte_unresolved(inode, stream_idx, table);
+       if (stream_idx == 0)
+               return lookup_stream(table, inode->i_hash);
+       return lookup_stream(table, inode->i_ads_entries[stream_idx - 1].hash);
 }
 
+/*
+ * Return the lookup table entry for the unnamed data stream of a *resolved*
+ * inode, or NULL if the inode's unnamed data stream is empty.  Also return the
+ * 0-based index of the unnamed data stream in *stream_idx_ret.
+ */
 struct wim_lookup_table_entry *
-inode_unnamed_stream_resolved(const struct wim_inode *inode, u16 *stream_idx_ret)
+inode_unnamed_stream_resolved(const struct wim_inode *inode,
+                             unsigned *stream_idx_ret)
 {
        wimlib_assert(inode->i_resolved);
-       for (unsigned i = 0; i <= inode->i_num_ads; i++) {
-               if (inode_stream_name_nbytes(inode, i) == 0 &&
-                   !is_zero_hash(inode_stream_hash_resolved(inode, i)))
-               {
-                       *stream_idx_ret = i;
-                       return inode_stream_lte_resolved(inode, i);
-               }
-       }
-       *stream_idx_ret = 0;
-       return NULL;
-}
 
-struct wim_lookup_table_entry *
-inode_unnamed_lte_resolved(const struct wim_inode *inode)
-{
-       u16 stream_idx;
-       return inode_unnamed_stream_resolved(inode, &stream_idx);
-}
+       *stream_idx_ret = 0;
+       if (likely(inode->i_lte))
+               return inode->i_lte;
 
-struct wim_lookup_table_entry *
-inode_unnamed_lte_unresolved(const struct wim_inode *inode,
-                            const struct wim_lookup_table *table)
-{
-       wimlib_assert(!inode->i_resolved);
-       for (unsigned i = 0; i <= inode->i_num_ads; i++) {
-               if (inode_stream_name_nbytes(inode, i) == 0 &&
-                   !is_zero_hash(inode_stream_hash_unresolved(inode, i)))
+       for (unsigned i = 0; i < inode->i_num_ads; i++) {
+               if (inode->i_ads_entries[i].stream_name_nbytes == 0 &&
+                   inode->i_ads_entries[i].lte)
                {
-                       return inode_stream_lte_unresolved(inode, i, table);
+                       *stream_idx_ret = i + 1;
+                       return inode->i_ads_entries[i].lte;
                }
        }
        return NULL;
 }
 
-/* Return the lookup table entry for the unnamed data stream of an inode, or
- * NULL if there is none.
- *
- * You'd think this would be easier than it actually is, since the unnamed data
- * stream should be the one referenced from the inode itself.  Alas, if there
- * are named data streams, Microsoft's "imagex.exe" program will put the unnamed
- * data stream in one of the alternate data streams instead of inside the WIM
- * dentry itself.  So we need to check the alternate data streams too.
+/*
+ * Return the lookup table entry for the unnamed data stream of an inode, or
+ * NULL if the inode's unnamed data stream is empty or not available.
  *
- * Also, note that a dentry may appear to have more than one unnamed stream, but
- * if the SHA1 message digest is all 0's then the corresponding stream does not
- * really "count" (this is the case for the inode's own file stream when the
- * file stream that should be there is actually in one of the alternate stream
- * entries.).  This is despite the fact that we may need to extract such a
- * missing entry as an empty file or empty named data stream.
+ * Note: this is complicated by the fact that WIMGAPI may put the unnamed data
+ * stream in an alternate data stream entry rather than in the dentry itself.
  */
 struct wim_lookup_table_entry *
 inode_unnamed_lte(const struct wim_inode *inode,
                  const struct wim_lookup_table *table)
 {
+       struct wim_lookup_table_entry *lte;
+
        if (inode->i_resolved)
                return inode_unnamed_lte_resolved(inode);
-       else
-               return inode_unnamed_lte_unresolved(inode, table);
+
+       lte = lookup_stream(table, inode->i_hash);
+       if (likely(lte))
+               return lte;
+
+       for (unsigned i = 0; i < inode->i_num_ads; i++) {
+               if (inode->i_ads_entries[i].stream_name_nbytes)
+                       continue;
+               lte = lookup_stream(table, inode->i_ads_entries[i].hash);
+               if (lte)
+                       return lte;
+       }
+       return NULL;
+}
+
+/* Return the SHA-1 message digest of the specified stream of the inode, or a
+ * void SHA-1 of all zeroes if the specified stream is empty.   */
+const u8 *
+inode_stream_hash(const struct wim_inode *inode, unsigned stream_idx)
+{
+       if (inode->i_resolved) {
+               struct wim_lookup_table_entry *lte;
+
+               lte = inode_stream_lte_resolved(inode, stream_idx);
+               if (lte)
+                       return lte->hash;
+               return zero_hash;
+       }
+       if (stream_idx == 0)
+               return inode->i_hash;
+       return inode->i_ads_entries[stream_idx - 1].hash;
 }
 
-/* Returns the SHA1 message digest of the unnamed data stream of a WIM inode, or
- * 'zero_hash' if the unnamed data stream is missing has all zeroes in its SHA1
- * message digest field.  */
+/* Return the SHA-1 message digest of the unnamed data stream of the inode, or a
+ * void SHA-1 of all zeroes if the inode's unnamed data stream is empty.   */
 const u8 *
 inode_unnamed_stream_hash(const struct wim_inode *inode)
 {
@@ -617,38 +610,39 @@ inode_unnamed_stream_hash(const struct wim_inode *inode)
        return zero_hash;
 }
 
-/* Given an unhashed stream, get the pointer to it in an inode.
- * As this is only for unhashed streams, there can only be one such pointer.  */
-struct wim_lookup_table_entry **
-retrieve_lte_pointer(struct wim_lookup_table_entry *lte)
+/* Acquire another reference to each single-instance stream referenced by this
+ * inode.  This is necessary when creating a hard link to this inode.
+ *
+ * The inode must be resolved.  */
+void
+inode_ref_streams(struct wim_inode *inode)
 {
-       wimlib_assert(lte->unhashed);
-       struct wim_inode *inode = lte->back_inode;
-       u32 stream_id = lte->back_stream_id;
-       if (stream_id == 0)
-               return &inode->i_lte;
-       else
-               for (u16 i = 0; i < inode->i_num_ads; i++)
-                       if (inode->i_ads_entries[i].stream_id == stream_id)
-                               return &inode->i_ads_entries[i].lte;
-       wimlib_assert(0);
-       return NULL;
+       wimlib_assert(inode->i_resolved);
+
+       if (inode->i_lte)
+               inode->i_lte->refcnt++;
+       for (unsigned i = 0; i < inode->i_num_ads; i++)
+               if (inode->i_ads_entries[i].lte)
+                       inode->i_ads_entries[i].lte->refcnt++;
 }
 
-int
-stream_not_found_error(const struct wim_inode *inode, const u8 *hash)
+/* Drop a reference to each single-instance stream referenced by this inode.
+ * This is necessary when deleting a hard link to this inode.  */
+void
+inode_unref_streams(struct wim_inode *inode,
+                   struct wim_lookup_table *lookup_table)
 {
-       if (wimlib_print_errors) {
-               ERROR("\"%"TS"\": stream not found", inode_first_full_path(inode));
-               tfprintf(stderr, T("        SHA-1 message digest of missing stream:\n        "));
-               print_hash(hash, stderr);
-               tputc(T('\n'), stderr);
+       for (unsigned i = 0; i <= inode->i_num_ads; i++) {
+               struct wim_lookup_table_entry *lte;
+
+               lte = inode_stream_lte(inode, i, lookup_table);
+               if (lte)
+                       lte_decrement_refcnt(lte, lookup_table);
        }
-       return WIMLIB_ERR_RESOURCE_NOT_FOUND;
 }
 
 /*
- * Reads the alternate data stream entries of a WIM dentry.
+ * Read the alternate data stream entries of a WIM dentry.
  *
  * @p:
  *     Pointer to buffer that starts with the first alternate stream entry.
@@ -657,8 +651,9 @@ stream_not_found_error(const struct wim_inode *inode, const u8 *hash)
  *     Inode to load the alternate data streams into.  @inode->i_num_ads must
  *     have been set to the number of alternate data streams that are expected.
  *
- * @remaining_size:
+ * @nbytes_remaining_p:
  *     Number of bytes of data remaining in the buffer pointed to by @p.
+ *     On success this will be updated to point just past the ADS entries.
  *
  * On success, inode->i_ads_entries is set to an array of `struct
  * wim_ads_entry's of length inode->i_num_ads.  On failure, @inode is not
@@ -671,9 +666,10 @@ stream_not_found_error(const struct wim_inode *inode, const u8 *hash)
  */
 int
 read_ads_entries(const u8 * restrict p, struct wim_inode * restrict inode,
-                size_t nbytes_remaining)
+                size_t *nbytes_remaining_p)
 {
-       u16 num_ads;
+       size_t nbytes_remaining = *nbytes_remaining_p;
+       unsigned num_ads;
        struct wim_ads_entry *ads_entries;
        int ret;
 
@@ -683,11 +679,11 @@ read_ads_entries(const u8 * restrict p, struct wim_inode * restrict inode,
         * data stream entries. */
        num_ads = inode->i_num_ads;
        ads_entries = CALLOC(num_ads, sizeof(inode->i_ads_entries[0]));
-       if (ads_entries == NULL)
+       if (!ads_entries)
                goto out_of_memory;
 
        /* Read the entries into our newly allocated buffer. */
-       for (u16 i = 0; i < num_ads; i++) {
+       for (unsigned i = 0; i < num_ads; i++) {
                u64 length;
                struct wim_ads_entry *cur_entry;
                const struct wim_ads_entry_on_disk *disk_entry =
@@ -737,14 +733,10 @@ read_ads_entries(const u8 * restrict p, struct wim_inode * restrict inode,
                            cur_entry->stream_name_nbytes > length)
                                goto out_invalid;
 
-                       cur_entry->stream_name = MALLOC(cur_entry->stream_name_nbytes + 2);
-                       if (cur_entry->stream_name == NULL)
+                       cur_entry->stream_name = utf16le_dupz(disk_entry->stream_name,
+                                                             cur_entry->stream_name_nbytes);
+                       if (!cur_entry->stream_name)
                                goto out_of_memory;
-
-                       memcpy(cur_entry->stream_name,
-                              disk_entry->stream_name,
-                              cur_entry->stream_name_nbytes);
-                       cur_entry->stream_name[cur_entry->stream_name_nbytes / 2] = cpu_to_le16(0);
                } else {
                        /* Mark inode as having weird stream entries.  */
                        inode->i_canonical_streams = 0;
@@ -753,12 +745,12 @@ read_ads_entries(const u8 * restrict p, struct wim_inode * restrict inode,
                /* It's expected that the size of every ADS entry is a multiple
                 * of 8.  However, to be safe, I'm allowing the possibility of
                 * an ADS entry at the very end of the metadata resource ending
-                * un-aligned.  So although we still need to increment the input
+                * unaligned.  So although we still need to increment the input
                 * pointer by @length to reach the next ADS entry, it's possible
                 * that less than @length is actually remaining in the metadata
                 * resource. We should set the remaining bytes to 0 if this
                 * happens. */
-               length = (length + 7) & ~(u64)7;
+               length = (length + 7) & ~7;
                p += length;
                if (nbytes_remaining < length)
                        nbytes_remaining = 0;
@@ -767,6 +759,7 @@ read_ads_entries(const u8 * restrict p, struct wim_inode * restrict inode,
        }
        inode->i_ads_entries = ads_entries;
        inode->i_next_stream_id = inode->i_num_ads + 1;
+       *nbytes_remaining_p = nbytes_remaining;
        ret = 0;
        goto out;
 out_of_memory:
@@ -777,7 +770,7 @@ out_invalid:
        ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE;
 out_free_ads_entries:
        if (ads_entries) {
-               for (u16 i = 0; i < num_ads; i++)
+               for (unsigned i = 0; i < num_ads; i++)
                        destroy_ads_entry(&ads_entries[i]);
                FREE(ads_entries);
        }
@@ -785,21 +778,10 @@ out:
        return ret;
 }
 
-/*
- * Verify a WIM inode:
- *
- * - Check to make sure the security ID is valid
- * - Check to make sure there is at most one unnamed stream
- * - Check to make sure there is at most one DOS name.
- *
- * Return values:
- *     WIMLIB_ERR_SUCCESS (0)
- */
-int
-verify_inode(struct wim_inode *inode, const struct wim_security_data *sd)
+/* Check a WIM inode for unusual field values.  */
+void
+check_inode(struct wim_inode *inode, const struct wim_security_data *sd)
 {
-       struct wim_dentry *dentry;
-
        /* Check the security ID.  -1 is valid and means "no security
         * descriptor".  Anything else has to be a valid index into the WIM
         * image's security descriptors table. */
@@ -821,175 +803,33 @@ verify_inode(struct wim_inode *inode, const struct wim_security_data *sd)
                        num_unnamed_streams++;
        }
        if (num_unnamed_streams > 1) {
-               WARNING("\"%"TS"\" has multiple (%u) un-named streams",
+               WARNING("\"%"TS"\" has multiple (%u) unnamed streams",
                        inode_first_full_path(inode), num_unnamed_streams);
+               /* We currently don't treat this as an error and will just end
+                * up using the first unnamed data stream in the inode.  */
        }
-
-       /* Files cannot have multiple DOS names, even if they have multiple
-        * names in multiple directories (i.e. hard links).
-        * Source: NTFS-3g authors. */
-       struct wim_dentry *dentry_with_dos_name = NULL;
-       inode_for_each_dentry(dentry, inode) {
-               if (dentry_has_short_name(dentry)) {
-                       if (dentry_with_dos_name) {
-                               /* This was previously an error, but if we
-                                * capture a WIM from UDF on Windows, hard links
-                                * are supported but DOS names are automatically
-                                * generated for all names for an inode.  */
-                       #if 0
-                               ERROR("Hard-linked file has a DOS name at "
-                                     "both `%"TS"' and `%"TS"'",
-                                     dentry_full_path(dentry_with_dos_name),
-                                     dentry_full_path(dentry));
-                               return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
-                       #else
-                               dentry->dos_name_invalid = 1;
-                       #endif
-                       }
-                       dentry_with_dos_name = dentry;
-               }
-       }
-       return 0;
-}
-
-void
-inode_ref_streams(struct wim_inode *inode)
-{
-       for (unsigned i = 0; i <= inode->i_num_ads; i++) {
-               struct wim_lookup_table_entry *lte;
-               lte = inode_stream_lte_resolved(inode, i);
-               if (lte)
-                       lte->refcnt++;
-       }
-}
-
-int
-init_inode_table(struct wim_inode_table *table, size_t capacity)
-{
-       table->array = CALLOC(capacity, sizeof(table->array[0]));
-       if (table->array == NULL) {
-               ERROR("Cannot initalize inode table: out of memory");
-               return WIMLIB_ERR_NOMEM;
-       }
-       table->num_entries  = 0;
-       table->capacity  = capacity;
-       INIT_LIST_HEAD(&table->extra_inodes);
-       return 0;
-}
-
-void
-destroy_inode_table(struct wim_inode_table *table)
-{
-       FREE(table->array);
-}
-
-static struct wim_inode *
-inode_table_get_inode(struct wim_inode_table *table, u64 ino, u64 devno)
-{
-       u64 hash = hash_u64(hash_u64(ino) + hash_u64(devno));
-       size_t pos = hash % table->capacity;
-       struct wim_inode *inode;
-       struct hlist_node *cur;
-
-       hlist_for_each_entry(inode, cur, &table->array[pos], i_hlist) {
-               if (inode->i_ino == ino && inode->i_devno == devno) {
-                       DEBUG("Using existing inode {devno=%"PRIu64", ino=%"PRIu64"}",
-                             devno, ino);
-                       inode->i_nlink++;
-                       return inode;
-               }
-       }
-       inode = new_timeless_inode();
-       if (inode) {
-               inode->i_ino = ino;
-               inode->i_devno = devno;
-               hlist_add_head(&inode->i_hlist, &table->array[pos]);
-               table->num_entries++;
-       }
-       return inode;
 }
 
-
-/* Given a directory entry with the name @name for the file with the inode
- * number @ino and device number @devno, create a new WIM dentry with an
- * associated inode, where the inode is shared if an inode with the same @ino
- * and @devno has already been created.  On success, the new WIM dentry is
- * written to *dentry_ret, and its inode has i_nlink > 1 if a previously
- * existing inode was used.
+/*
+ * Translate a single-instance stream entry into the pointer contained in the
+ * inode (or ads entry of an inode) that references it.
+ *
+ * This is only possible for "unhashed" streams, which are guaranteed to have
+ * only one reference, and that reference is guaranteed to be in a resolved
+ * inode.  (It can't be in an unresolved inode, since that would imply the hash
+ * is known!)
  */
-int
-inode_table_new_dentry(struct wim_inode_table *table, const tchar *name,
-                      u64 ino, u64 devno, bool noshare,
-                      struct wim_dentry **dentry_ret)
-{
-       struct wim_dentry *dentry;
-       struct wim_inode *inode;
-       int ret;
-
-       if (noshare) {
-               /* File that cannot be hardlinked--- Return a new inode with its
-                * inode and device numbers left at 0. */
-               ret = new_dentry_with_timeless_inode(name, &dentry);
-               if (ret)
-                       return ret;
-               list_add_tail(&dentry->d_inode->i_list, &table->extra_inodes);
-       } else {
-               /* File that can be hardlinked--- search the table for an
-                * existing inode matching the inode number and device;
-                * otherwise create a new inode. */
-               ret = new_dentry(name, &dentry);
-               if (ret)
-                       return ret;
-               inode = inode_table_get_inode(table, ino, devno);
-               if (!inode) {
-                       free_dentry(dentry);
-                       return WIMLIB_ERR_NOMEM;
-               }
-               /* If using an existing inode, we need to gain a reference to
-                * each of its streams. */
-               if (inode->i_nlink > 1)
-                       inode_ref_streams(inode);
-               dentry->d_inode = inode;
-               inode_add_dentry(dentry, inode);
-       }
-       *dentry_ret = dentry;
-       return 0;
-}
-
-
-
-/* Assign consecutive inode numbers to a new set of inodes from the inode table,
- * and append the inodes to a single list @head that contains the inodes already
- * existing in the WIM image.  */
-void
-inode_table_prepare_inode_list(struct wim_inode_table *table,
-                              struct list_head *head)
+struct wim_lookup_table_entry **
+retrieve_lte_pointer(struct wim_lookup_table_entry *lte)
 {
-       struct wim_inode *inode, *tmp_inode;
-       struct hlist_node *cur, *tmp;
-       u64 cur_ino = 1;
-
-       /* Re-assign inode numbers in the existing list to avoid duplicates. */
-       list_for_each_entry(inode, head, i_list)
-               inode->i_ino = cur_ino++;
-
-       /* Assign inode numbers to the new inodes and move them to the image's
-        * inode list. */
-       for (size_t i = 0; i < table->capacity; i++) {
-               hlist_for_each_entry_safe(inode, cur, tmp, &table->array[i], i_hlist)
-               {
-                       inode->i_ino = cur_ino++;
-                       inode->i_devno = 0;
-                       list_add_tail(&inode->i_list, head);
-               }
-               INIT_HLIST_HEAD(&table->array[i]);
-       }
-       list_for_each_entry_safe(inode, tmp_inode, &table->extra_inodes, i_list)
-       {
-               inode->i_ino = cur_ino++;
-               inode->i_devno = 0;
-               list_add_tail(&inode->i_list, head);
-       }
-       INIT_LIST_HEAD(&table->extra_inodes);
-       table->num_entries = 0;
+       wimlib_assert(lte->unhashed);
+       struct wim_inode *inode = lte->back_inode;
+       u32 stream_id = lte->back_stream_id;
+       if (stream_id == 0)
+               return &inode->i_lte;
+       for (unsigned i = 0; i < inode->i_num_ads; i++)
+               if (inode->i_ads_entries[i].stream_id == stream_id)
+                       return &inode->i_ads_entries[i].lte;
+       wimlib_assert(0);
+       return NULL;
 }