+/* Undo a link operation. */
+static void
+rollback_link(struct wim_dentry *subject, struct wim_dentry *parent,
+ struct wim_dentry **root_p, struct list_head *orphans)
+{
+ /* Unlink is the opposite of link */
+ do_unlink(subject, parent, root_p);
+
+ /* @subject is now unlinked. Add it to orphans. */
+ list_add(&subject->tmp_list, orphans);
+ subject->is_orphan = 1;
+}
+
+/* Undo an unlink operation. */
+static void
+rollback_unlink(struct wim_dentry *subject, struct wim_dentry *parent,
+ struct wim_dentry **root_p)
+{
+ /* Link is the opposite of unlink */
+ do_link(subject, parent, root_p);
+
+ /* @subject is no longer unlinked. Delete it from orphans. */
+ list_del(&subject->tmp_list);
+ subject->is_orphan = 0;
+}
+
+/* Rollback a name change operation. */
+static void
+rollback_name_change(utf16lechar *old_name,
+ utf16lechar **name_ptr, u16 *name_nbytes_ptr)
+{
+ /* Free the new name, then replace it with the old name. */
+ FREE(*name_ptr);
+ if (old_name) {
+ *name_ptr = old_name;
+ *name_nbytes_ptr = utf16le_strlen(old_name);
+ } else {
+ *name_ptr = NULL;
+ *name_nbytes_ptr = 0;
+ }
+}
+
+/* Rollback a primitive update operation. */
+static void
+rollback_update_primitive(const struct update_primitive *prim,
+ struct wim_dentry **root_p,
+ struct list_head *orphans)
+{
+ switch (prim->type) {
+ case LINK_DENTRY:
+ rollback_link(prim->link.subject, prim->link.parent, root_p,
+ orphans);
+ break;
+ case UNLINK_DENTRY:
+ rollback_unlink(prim->link.subject, prim->link.parent, root_p);
+ break;
+ case CHANGE_FILE_NAME:
+ rollback_name_change(prim->name.old_name,
+ &prim->name.subject->file_name,
+ &prim->name.subject->file_name_nbytes);
+ break;
+ case CHANGE_SHORT_NAME:
+ rollback_name_change(prim->name.old_name,
+ &prim->name.subject->short_name,
+ &prim->name.subject->short_name_nbytes);
+ break;
+ }
+}
+
+/* Rollback a logical update command */
+static void
+rollback_update_command(const struct update_primitive_list *l,
+ struct wim_dentry **root_p,
+ struct list_head *orphans)
+{
+ size_t i = l->num_entries;
+
+ /* Rollback each primitive operation, in reverse order. */
+ while (i--)
+ rollback_update_primitive(&l->entries[i], root_p, orphans);
+}
+
+/****************************************************************************/
+
+/* Link @subject into the directory @parent; or, if @parent is NULL, set
+ * @subject as the root of the WIM image.
+ *
+ * This is the journaled version, so it can be rolled back. */
+static int
+journaled_link(struct update_command_journal *j,
+ struct wim_dentry *subject, struct wim_dentry *parent)
+{
+ struct update_primitive prim;
+ int ret;
+
+ prim.type = LINK_DENTRY;
+ prim.link.subject = subject;
+ prim.link.parent = parent;
+
+ ret = record_update_primitive(j, prim);
+ if (ret)
+ return ret;
+
+ do_link(subject, parent, j->root_p);
+
+ if (subject->is_orphan) {
+ list_del(&subject->tmp_list);
+ subject->is_orphan = 0;
+ }
+ return 0;
+}
+
+/* Unlink @subject from the WIM image.
+ *
+ * This is the journaled version, so it can be rolled back. */
+static int
+journaled_unlink(struct update_command_journal *j, struct wim_dentry *subject)
+{
+ struct wim_dentry *parent;
+ struct update_primitive prim;
+ int ret;
+
+ if (dentry_is_root(subject))
+ parent = NULL;
+ else
+ parent = subject->parent;
+
+ prim.type = UNLINK_DENTRY;
+ prim.link.subject = subject;
+ prim.link.parent = parent;
+
+ ret = record_update_primitive(j, prim);
+ if (ret)
+ return ret;
+
+ do_unlink(subject, parent, j->root_p);
+
+ list_add(&subject->tmp_list, &j->orphans);
+ subject->is_orphan = 1;
+ return 0;
+}
+
+/* Change the name of @dentry to @new_name_tstr.
+ *
+ * This is the journaled version, so it can be rolled back. */
+static int
+journaled_change_name(struct update_command_journal *j,
+ struct wim_dentry *dentry, const tchar *new_name_tstr)
+{
+ int ret;
+ utf16lechar *new_name = NULL;
+ u16 new_name_nbytes = 0;
+ struct update_primitive prim;
+
+ /* Set the long name. */
+ ret = get_utf16le_string(new_name_tstr, &new_name, &new_name_nbytes);
+ if (ret)
+ return ret;
+
+ prim.type = CHANGE_FILE_NAME;
+ prim.name.subject = dentry;
+ prim.name.old_name = dentry->file_name;
+ ret = record_update_primitive(j, prim);
+ if (ret)
+ return ret;
+
+ dentry->file_name = new_name;
+ dentry->file_name_nbytes = new_name_nbytes;
+
+ /* Clear the short name. */
+ prim.type = CHANGE_SHORT_NAME;
+ prim.name.subject = dentry;
+ prim.name.old_name = dentry->short_name;
+ ret = record_update_primitive(j, prim);
+ if (ret)
+ return ret;
+
+ dentry->short_name = NULL;
+ dentry->short_name_nbytes = 0;
+ return 0;
+}
+
+static void
+next_command(struct update_command_journal *j)
+{
+ j->cur_cmd++;
+}
+
+static void
+commit_update(struct update_command_journal *j)
+{
+ for (size_t i = 0; i < j->num_cmds; i++)
+ {
+ for (size_t k = 0; k < j->cmd_prims[i].num_entries; k++)
+ {
+ if (j->cmd_prims[i].entries[k].type == CHANGE_FILE_NAME ||
+ j->cmd_prims[i].entries[k].type == CHANGE_SHORT_NAME)
+ {
+ FREE(j->cmd_prims[i].entries[k].name.old_name);