+#include <string.h>
+#include <sys/stat.h>
+
+#include "wimlib/alloca.h"
+#include "wimlib/assert.h"
+#include "wimlib/blob_table.h"
+#include "wimlib/capture.h"
+#include "wimlib/dentry.h"
+#include "wimlib/encoding.h"
+#include "wimlib/endianness.h"
+#include "wimlib/error.h"
+#include "wimlib/metadata.h"
+#include "wimlib/paths.h"
+#include "wimlib/progress.h"
+#include "wimlib/xml.h"
+
+/* Saved specification of a "primitive" update operation that was performed. */
+struct update_primitive {
+ enum {
+ /* Unlinked a dentry from its parent directory. */
+ UNLINK_DENTRY,
+
+ /* Linked a dentry into its parent directory. */
+ LINK_DENTRY,
+
+ /* Changed the file name of a dentry. */
+ CHANGE_FILE_NAME,
+
+ /* Changed the short name of a dentry. */
+ CHANGE_SHORT_NAME,
+ } type;
+
+ union {
+ /* For UNLINK_DENTRY and LINK_DENTRY operations */
+ struct {
+ /* Dentry that was linked or unlinked. */
+ struct wim_dentry *subject;
+
+ /* For link operations, the directory into which
+ * @subject was linked, or NULL if @subject was set as
+ * the root of the image.
+ *
+ * For unlink operations, the directory from which
+ * @subject was unlinked, or NULL if @subject was unset
+ * as the root of the image. */
+ struct wim_dentry *parent;
+ } link;
+
+ /* For CHANGE_FILE_NAME and CHANGE_SHORT_NAME operations */
+ struct {
+ /* Dentry that had its name changed. */
+ struct wim_dentry *subject;
+
+ /* The old name. */
+ utf16lechar *old_name;
+ } name;
+ };
+};
+
+/* Chronological list of primitive operations that were executed for a single
+ * logical update command, such as 'add', 'delete', or 'rename'. */
+struct update_primitive_list {
+ struct update_primitive *entries;
+ struct update_primitive inline_entries[4];
+ size_t num_entries;
+ size_t num_alloc_entries;
+};
+
+/* Journal for managing the executing of zero or more logical update commands,
+ * such as 'add', 'delete', or 'rename'. This allows either committing or
+ * rolling back the commands. */
+struct update_command_journal {
+ /* Number of update commands this journal contains. */
+ size_t num_cmds;
+
+ /* Index of currently executing update command. */
+ size_t cur_cmd;
+
+ /* Location of the WIM image's root pointer. */
+ struct wim_dentry **root_p;
+
+ /* Pointer to the blob table of the WIM (may needed for rollback) */
+ struct blob_table *blob_table;
+
+ /* List of dentries that are currently unlinked from the WIM image.
+ * These must be freed when no longer needed for commit or rollback. */
+ struct list_head orphans;
+
+ /* Per-command logs. */
+ struct update_primitive_list cmd_prims[];
+};
+
+static void
+init_update_primitive_list(struct update_primitive_list *l)
+{
+ l->entries = l->inline_entries;
+ l->num_entries = 0;
+ l->num_alloc_entries = ARRAY_LEN(l->inline_entries);
+}
+
+/* Allocates a new journal for managing the execution of up to @num_cmds update
+ * commands. */
+static struct update_command_journal *
+new_update_command_journal(size_t num_cmds, struct wim_dentry **root_p,
+ struct blob_table *blob_table)
+{
+ struct update_command_journal *j;
+
+ j = MALLOC(sizeof(*j) + num_cmds * sizeof(j->cmd_prims[0]));
+ if (j) {
+ j->num_cmds = num_cmds;
+ j->cur_cmd = 0;
+ j->root_p = root_p;
+ j->blob_table = blob_table;
+ INIT_LIST_HEAD(&j->orphans);
+ for (size_t i = 0; i < num_cmds; i++)
+ init_update_primitive_list(&j->cmd_prims[i]);
+ }
+ return j;
+}
+
+/* Don't call this directly; use commit_update() or rollback_update() instead.
+ */
+static void
+free_update_command_journal(struct update_command_journal *j)
+{
+ struct wim_dentry *orphan;
+
+ /* Free orphaned dentry trees */
+ while (!list_empty(&j->orphans)) {
+ orphan = list_first_entry(&j->orphans,
+ struct wim_dentry, d_tmp_list);
+ list_del(&orphan->d_tmp_list);
+ free_dentry_tree(orphan, j->blob_table);
+ }
+
+ for (size_t i = 0; i < j->num_cmds; i++)
+ if (j->cmd_prims[i].entries != j->cmd_prims[i].inline_entries)
+ FREE(j->cmd_prims[i].entries);
+ FREE(j);
+}
+
+/* Add the entry @prim to the update command journal @j. */
+static int
+record_update_primitive(struct update_command_journal *j,
+ struct update_primitive prim)
+{
+ struct update_primitive_list *l;
+
+ l = &j->cmd_prims[j->cur_cmd];
+
+ if (l->num_entries == l->num_alloc_entries) {
+ struct update_primitive *new_entries;
+ size_t new_num_alloc_entries;
+ size_t new_size;
+
+ new_num_alloc_entries = l->num_alloc_entries * 2;
+ new_size = new_num_alloc_entries * sizeof(new_entries[0]);
+ if (l->entries == l->inline_entries) {
+ new_entries = MALLOC(new_size);
+ if (!new_entries)
+ return WIMLIB_ERR_NOMEM;
+ memcpy(new_entries, l->inline_entries,
+ sizeof(l->inline_entries));
+ } else {
+ new_entries = REALLOC(l->entries, new_size);
+ if (!new_entries)
+ return WIMLIB_ERR_NOMEM;
+ }
+ l->entries = new_entries;
+ l->num_alloc_entries = new_num_alloc_entries;
+ }
+ l->entries[l->num_entries++] = prim;
+ return 0;
+}
+
+static void
+do_unlink(struct wim_dentry *subject, struct wim_dentry *parent,
+ struct wim_dentry **root_p)
+{
+ if (parent) {
+ /* Unlink @subject from its @parent. */
+ wimlib_assert(subject->d_parent == parent);
+ unlink_dentry(subject);
+ } else {
+ /* Unset @subject as the root of the image. */
+ *root_p = NULL;
+ }
+ subject->d_parent = subject;
+}
+
+static void
+do_link(struct wim_dentry *subject, struct wim_dentry *parent,
+ struct wim_dentry **root_p)
+{
+ if (parent) {
+ /* Link @subject to its @parent */
+ struct wim_dentry *existing;
+
+ existing = dentry_add_child(parent, subject);
+ wimlib_assert(!existing);
+ } else {
+ /* Set @subject as root of the image */
+ *root_p = subject;
+ }
+}
+
+/* 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->d_tmp_list, orphans);
+ subject->d_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->d_tmp_list);
+ subject->d_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_len_bytes(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->d_name,
+ &prim->name.subject->d_name_nbytes);
+ break;
+ case CHANGE_SHORT_NAME:
+ rollback_name_change(prim->name.old_name,
+ &prim->name.subject->d_short_name,
+ &prim->name.subject->d_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);
+}
+
+/****************************************************************************/