+ wimlib_assert(ref_inode != inode);
+
+ if (ref_inode->i_num_ads != inode->i_num_ads &&
+ inode->i_num_ads != 0)
+ return false;
+ if (ref_inode->i_security_id != inode->i_security_id
+ || ref_inode->i_attributes != inode->i_attributes)
+ return false;
+ for (unsigned i = 0; i <= min(ref_inode->i_num_ads, inode->i_num_ads); i++) {
+ const u8 *ref_hash, *hash;
+ ref_hash = inode_stream_hash(ref_inode, i);
+ hash = inode_stream_hash(inode, i);
+ if (!hashes_equal(ref_hash, hash) && !is_zero_hash(hash))
+ return false;
+ if (i && !ads_entries_have_same_name(&ref_inode->i_ads_entries[i - 1],
+ &inode->i_ads_entries[i - 1]))
+ return false;
+ }
+ return true;
+}
+
+/* Fix up a "true" inode and check for inconsistencies */
+static int
+fix_true_inode(struct wim_inode *inode, struct list_head *inode_list)
+{
+ struct wim_dentry *dentry;
+ struct wim_dentry *ref_dentry = NULL;
+ struct wim_inode *ref_inode;
+ u64 last_ctime = 0;
+ u64 last_mtime = 0;
+ u64 last_atime = 0;
+
+ inode_for_each_dentry(dentry, inode) {
+ if (!ref_dentry || dentry->d_inode->i_num_ads > ref_dentry->d_inode->i_num_ads)
+ ref_dentry = dentry;
+ if (dentry->d_inode->i_creation_time > last_ctime)
+ last_ctime = dentry->d_inode->i_creation_time;
+ if (dentry->d_inode->i_last_write_time > last_mtime)
+ last_mtime = dentry->d_inode->i_last_write_time;
+ if (dentry->d_inode->i_last_access_time > last_atime)
+ last_atime = dentry->d_inode->i_last_access_time;
+ }
+
+ ref_inode = ref_dentry->d_inode;
+ wimlib_assert(ref_inode->i_nlink == 1);
+ list_add_tail(&ref_inode->i_list, inode_list);
+
+ list_del(&inode->i_dentry);
+ list_add(&ref_inode->i_dentry, &ref_dentry->d_alias);
+
+ inode_for_each_dentry(dentry, ref_inode) {
+ if (dentry != ref_dentry) {
+ if (!inodes_consistent(ref_inode, dentry->d_inode)) {
+ inconsistent_inode(ref_inode);
+ return WIMLIB_ERR_INVALID_DENTRY;
+ }
+ /* Free the unneeded `struct wim_inode'. */
+ wimlib_assert(dentry->d_inode->i_nlink == 1);
+ free_inode(dentry->d_inode);
+ dentry->d_inode = ref_inode;
+ ref_inode->i_nlink++;
+ }
+ }
+ ref_inode->i_creation_time = last_ctime;
+ ref_inode->i_last_write_time = last_mtime;
+ ref_inode->i_last_access_time = last_atime;
+ wimlib_assert(inode_link_count(ref_inode) == ref_inode->i_nlink);
+ return 0;
+}
+
+/*
+ * Fixes up a nominal inode.
+ *
+ * By a nominal inode we mean a group of two or more dentries that share the
+ * same hard link group ID.
+ *
+ * If dentries in the inode are found to be inconsistent, we may split the inode
+ * into several "true" inodes.
+ *
+ * After splitting up each nominal inode into the "true" inodes we will
+ * canonicalize the link group by getting rid of all the unnecessary `struct
+ * wim_inode's. There will be just one `struct wim_inode' for each hard link
+ * group remaining.
+ */
+static int
+fix_nominal_inode(struct wim_inode *inode, struct list_head *inode_list,
+ bool *ino_changes_needed)
+{
+ struct wim_dentry *dentry;
+ struct hlist_node *cur, *tmp;
+ int ret;
+ size_t num_true_inodes;
+ unsigned nominal_group_size = inode_link_count(inode);
+
+ LIST_HEAD(dentries_with_data_streams);
+ LIST_HEAD(dentries_with_no_data_streams);
+ HLIST_HEAD(true_inodes);
+
+ /* Create a list of dentries in the nominal inode that have at
+ * least one data stream with a non-zero hash, and another list that
+ * contains the dentries that have a zero hash for all data streams. */
+ inode_for_each_dentry(dentry, inode) {
+ for (unsigned i = 0; i <= dentry->d_inode->i_num_ads; i++) {
+ const u8 *hash;
+ hash = inode_stream_hash(dentry->d_inode, i);
+ if (!is_zero_hash(hash)) {
+ list_add(&dentry->tmp_list,
+ &dentries_with_data_streams);
+ goto next_dentry;
+ }
+ }
+ list_add(&dentry->tmp_list,
+ &dentries_with_no_data_streams);
+ next_dentry:
+ ;
+ }
+
+ /* If there are no dentries with data streams, we require the nominal
+ * inode to be a true inode */
+ if (list_empty(&dentries_with_data_streams)) {
+ #ifdef ENABLE_DEBUG
+ if (nominal_group_size > 1) {
+ DEBUG("Found link group of size %u without "
+ "any data streams:", nominal_group_size);
+ print_inode_dentries(inode);
+ DEBUG("We are going to interpret it as true "
+ "link group, provided that the dentries "
+ "are consistent.");
+ }
+ #endif
+ return fix_true_inode(inode, inode_list);
+ }
+
+ /* One or more dentries had data streams specified. We check each of
+ * these dentries for consistency with the others to form a set of true
+ * inodes. */
+ num_true_inodes = 0;
+ list_for_each_entry(dentry, &dentries_with_data_streams, tmp_list) {
+ /* Look for a true inode that is consistent with this dentry and
+ * add this dentry to it. Or, if none of the true inodes are
+ * consistent with this dentry, add a new one (if that happens,
+ * we have split the hard link group). */
+ hlist_for_each_entry(inode, cur, &true_inodes, i_hlist) {
+ if (ref_inodes_consistent(inode, dentry->d_inode)) {
+ inode_add_dentry(dentry, inode);
+ goto next_dentry_2;
+ }
+ }
+ num_true_inodes++;
+ INIT_LIST_HEAD(&dentry->d_inode->i_dentry);
+ inode_add_dentry(dentry, dentry->d_inode);
+ hlist_add_head(&dentry->d_inode->i_hlist, &true_inodes);
+next_dentry_2:
+ ;
+ }
+
+ wimlib_assert(num_true_inodes != 0);
+
+ /* If there were dentries with no data streams, we require there to only
+ * be one true inode so that we know which inode to assign the
+ * streamless dentries to. */
+ if (!list_empty(&dentries_with_no_data_streams)) {
+ if (num_true_inodes != 1) {
+ ERROR("Hard inode ambiguity detected!");
+ ERROR("We split up inode 0x%"PRIx64" due to "
+ "inconsistencies,", inode->i_ino);
+ ERROR("but dentries with no stream information remained. "
+ "We don't know which inode");
+ ERROR("to assign them to.");
+ ret = WIMLIB_ERR_INVALID_DENTRY;
+ goto out_cleanup_true_inode_list;
+ }
+ inode = container_of(true_inodes.first, struct wim_inode, i_hlist);
+ /* Assign the streamless dentries to the one and only true
+ * inode. */
+ list_for_each_entry(dentry, &dentries_with_no_data_streams, tmp_list)
+ inode_add_dentry(dentry, inode);
+ }
+ if (num_true_inodes != 1) {
+ #ifdef ENABLE_DEBUG
+ inode = container_of(true_inodes.first, struct wim_inode, i_hlist);
+
+ tprintf(T("Split nominal inode 0x%"PRIx64" into %zu "
+ "inodes:\n"), inode->i_ino, num_true_inodes);
+ tputs(T("----------------------------------------------------"
+ "--------------------------"));
+ size_t i = 1;
+ hlist_for_each_entry(inode, cur, &true_inodes, i_hlist) {
+ tprintf(T("[Split inode %zu]\n"), i++);
+ print_inode_dentries(inode);
+ tputchar(T('\n'));
+ }
+ tputs(T("----------------------------------------------------"
+ "--------------------------"));
+ #endif
+ *ino_changes_needed = true;
+ }
+
+ hlist_for_each_entry_safe(inode, cur, tmp, &true_inodes, i_hlist) {
+ hlist_del_init(&inode->i_hlist);
+ ret = fix_true_inode(inode, inode_list);
+ if (ret)
+ goto out_cleanup_true_inode_list;
+ }
+ ret = 0;
+ goto out;
+out_cleanup_true_inode_list:
+ hlist_for_each_entry_safe(inode, cur, tmp, &true_inodes, i_hlist)
+ hlist_del_init(&inode->i_hlist);
+out:
+ return ret;
+}
+
+static int
+fix_inodes(struct wim_inode_table *table, struct list_head *inode_list,
+ bool *ino_changes_needed)
+{
+ struct wim_inode *inode;
+ struct hlist_node *cur, *tmp;
+ int ret;
+ INIT_LIST_HEAD(inode_list);