[ostree/wip/etc-writable] [wip] Make /ostree/etc a writable mount



commit 872845904461472115b009eaf0e54cdb813e8b45
Author: Colin Walters <walters verbum org>
Date:   Fri Jun 22 18:41:59 2012 -0400

    [wip] Make /ostree/etc a writable mount

 Makefile-ostadmin.am                   |    2 +
 src/libotutil/ot-gio-utils.c           |  180 +++++++++++--
 src/libotutil/ot-gio-utils.h           |   15 +
 src/ostadmin/ot-admin-builtin-deploy.c |  472 +++++++++++++++++++++++++++++---
 src/ostadmin/ot-admin-builtin-init.c   |   35 +---
 src/ostadmin/ot-admin-functions.c      |  317 +++++++++++++++++++++
 src/ostadmin/ot-admin-functions.h      |   42 +++
 src/ostree/ot-builtin-checkout.c       |  161 +----------
 src/switchroot/ostree-switch-root.c    |    4 +-
 9 files changed, 988 insertions(+), 240 deletions(-)
---
diff --git a/Makefile-ostadmin.am b/Makefile-ostadmin.am
index 636e219..8076c11 100644
--- a/Makefile-ostadmin.am
+++ b/Makefile-ostadmin.am
@@ -24,6 +24,8 @@ ostadmin_SOURCES = src/ostadmin/main.c \
 	src/ostadmin/ot-admin-builtin-init.c \
 	src/ostadmin/ot-admin-builtin-deploy.c \
 	src/ostadmin/ot-admin-builtin-update-kernel.c \
+	src/ostadmin/ot-admin-functions.h \
+	src/ostadmin/ot-admin-functions.c \
 	src/ostadmin/ot-admin-main.h \
 	src/ostadmin/ot-admin-main.c \
 	$(NULL)
diff --git a/src/libotutil/ot-gio-utils.c b/src/libotutil/ot-gio-utils.c
index e31e47b..71ab383 100644
--- a/src/libotutil/ot-gio-utils.c
+++ b/src/libotutil/ot-gio-utils.c
@@ -131,6 +131,35 @@ ot_gfile_get_child_strconcat (GFile *parent,
   return ret;
 }
 
+GFile *
+ot_gfile_get_child_build_path (GFile      *parent,
+                               const char *first, ...)
+{
+  va_list args;
+  const char *arg;
+  ot_lfree char *path = NULL;
+  ot_lptrarray GPtrArray *components = NULL;  
+
+  va_start (args, first);
+
+  components = g_ptr_array_new ();
+  
+  arg = first;
+  while (arg != NULL)
+    {
+      g_ptr_array_add (components, (char*)arg);
+      arg = va_arg (args, const char *);
+    }
+
+  va_end (args);
+
+  g_ptr_array_add (components, NULL);
+
+  path = g_build_filenamev ((char**)components->pdata);
+
+  return g_file_resolve_relative_path (parent, path);
+}
+
 /**
  * ot_gfile_unlink:
  * @path: Path to file
@@ -394,25 +423,12 @@ ot_gio_checksum_stream_finish (GInputStream   *in,
   return g_memdup (g_simple_async_result_get_op_res_gpointer (simple), 32);
 }
 
-/**
- * ot_gio_shutil_cp_al_or_fallback:
- * @src: Source path
- * @dest: Destination path
- * @cancellable:
- * @error:
- *
- * Recursively copy path @src (which must be a directory) to the
- * target @dest.  If possible, hardlinks are used; if a hardlink is
- * not possible, a regular copy is created.  Any existing files are
- * overwritten.
- *
- * Returns: %TRUE on success
- */
-gboolean
-ot_gio_shutil_cp_al_or_fallback (GFile         *src,
-                                 GFile         *dest,
-                                 GCancellable  *cancellable,
-                                 GError       **error)
+static gboolean
+cp_internal (GFile         *src,
+             GFile         *dest,
+             gboolean       use_hardlinks,
+             GCancellable  *cancellable,
+             GError       **error)
 {
   gboolean ret = FALSE;
   ot_lobj GFileEnumerator *enumerator = NULL;
@@ -453,19 +469,29 @@ ot_gio_shutil_cp_al_or_fallback (GFile         *src,
               goto out;
             }
 
-          if (!ot_gio_shutil_cp_al_or_fallback (src_child, dest_child, cancellable, error))
+          if (!cp_internal (src_child, dest_child, use_hardlinks, cancellable, error))
             goto out;
         }
       else
         {
+          gboolean did_link = FALSE;
           (void) unlink (ot_gfile_get_path_cached (dest_child));
-          if (link (ot_gfile_get_path_cached (src_child), ot_gfile_get_path_cached (dest_child)) == -1)
+          if (use_hardlinks)
             {
-              if (!(errno == EMLINK || errno == EXDEV))
+              if (link (ot_gfile_get_path_cached (src_child), ot_gfile_get_path_cached (dest_child)) == -1)
                 {
-                  ot_util_set_error_from_errno (error, errno);
-                  goto out;
+                  if (!(errno == EMLINK || errno == EXDEV))
+                    {
+                      ot_util_set_error_from_errno (error, errno);
+                      goto out;
+                    }
+                  use_hardlinks = FALSE;
                 }
+              else
+                did_link = TRUE;
+            }
+          if (!did_link)
+            {
               if (!g_file_copy (src_child, dest_child,
                                 G_FILE_COPY_OVERWRITE | G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS,
                                 cancellable, NULL, NULL, error))
@@ -485,3 +511,109 @@ ot_gio_shutil_cp_al_or_fallback (GFile         *src,
   return ret;
 }
 
+/**
+ * ot_gio_shutil_cp_al_or_fallback:
+ * @src: Source path
+ * @dest: Destination path
+ * @cancellable:
+ * @error:
+ *
+ * Recursively copy path @src (which must be a directory) to the
+ * target @dest.  If possible, hardlinks are used; if a hardlink is
+ * not possible, a regular copy is created.  Any existing files are
+ * overwritten.
+ *
+ * Returns: %TRUE on success
+ */
+gboolean
+ot_gio_shutil_cp_al_or_fallback (GFile         *src,
+                                 GFile         *dest,
+                                 GCancellable  *cancellable,
+                                 GError       **error)
+{
+  return cp_internal (src, dest, TRUE, cancellable, error);
+}
+
+/**
+ * ot_gio_shutil_cp_a:
+ * @src: Source path
+ * @dest: Destination path
+ * @cancellable:
+ * @error:
+ *
+ * Recursively copy path @src (which must be a directory) to the
+ * target @dest.  Any existing files are overwritten.
+ *
+ * Returns: %TRUE on success
+ */
+gboolean
+ot_gio_shutil_cp_a (GFile         *src,
+                    GFile         *dest,
+                    GCancellable  *cancellable,
+                    GError       **error)
+{
+  return cp_internal (src, dest, FALSE, cancellable, error);
+}
+
+gboolean
+ot_gio_shutil_rm_rf (GFile        *path,
+                     GCancellable *cancellable,
+                     GError      **error)
+{
+  gboolean ret = FALSE;
+  ot_lobj GFileEnumerator *dir_enum = NULL;
+  ot_lobj GFileInfo *file_info = NULL;
+  GError *temp_error = NULL;
+
+  dir_enum = g_file_enumerate_children (path, OSTREE_GIO_FAST_QUERYINFO, 
+                                        G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                        cancellable, &temp_error);
+  if (!dir_enum)
+    {
+      if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        {
+          g_clear_error (&temp_error);
+          ret = TRUE;
+        }
+      else
+        g_propagate_error (error, temp_error);
+
+      goto out;
+    }
+
+  while ((file_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
+    {
+      ot_lobj GFile *subpath = NULL;
+      GFileType type;
+      const char *name;
+
+      type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
+      name = g_file_info_get_attribute_byte_string (file_info, "standard::name");
+      
+      subpath = g_file_get_child (path, name);
+
+      if (type == G_FILE_TYPE_DIRECTORY)
+        {
+          if (!ot_gio_shutil_rm_rf (subpath, cancellable, error))
+            goto out;
+        }
+      else
+        {
+          if (!ot_gfile_unlink (subpath, cancellable, error))
+            goto out;
+        }
+      g_clear_object (&file_info);
+    }
+  if (temp_error)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+
+  if (!g_file_delete (path, cancellable, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  return ret;
+}
diff --git a/src/libotutil/ot-gio-utils.h b/src/libotutil/ot-gio-utils.h
index 5855a97..32e80f1 100644
--- a/src/libotutil/ot-gio-utils.h
+++ b/src/libotutil/ot-gio-utils.h
@@ -40,6 +40,8 @@ GFile *ot_gfile_from_build_path (const char *first, ...) G_GNUC_NULL_TERMINATED;
 
 GFile *ot_gfile_get_child_strconcat (GFile *parent, const char *first, ...) G_GNUC_NULL_TERMINATED;
 
+GFile *ot_gfile_get_child_build_path (GFile *parent, const char *first, ...) G_GNUC_NULL_TERMINATED;
+
 const char *ot_gfile_get_path_cached (GFile *file);
 
 const char *ot_gfile_get_basename_cached (GFile *file);
@@ -51,6 +53,11 @@ gboolean ot_gfile_rename (GFile          *src,
                           GCancellable   *cancellable,
                           GError        **error);
 
+gboolean ot_gfile_hardlink (GFile          *src,
+                            GFile          *dest,
+                            GCancellable   *cancellable,
+                            GError        **error);
+
 gboolean ot_gfile_unlink (GFile          *path,
                           GCancellable   *cancellable,
                           GError        **error);
@@ -96,11 +103,19 @@ guchar * ot_gio_checksum_stream_finish (GInputStream   *in,
                                         GAsyncResult   *result,
                                         GError        **error);
 
+gboolean ot_gio_shutil_cp_a (GFile         *src,
+                             GFile         *dest,
+                             GCancellable  *cancellable,
+                             GError       **error);
+
 gboolean ot_gio_shutil_cp_al_or_fallback (GFile         *src,
                                           GFile         *dest,
                                           GCancellable  *cancellable,
                                           GError       **error);
 
+gboolean ot_gio_shutil_rm_rf (GFile        *path,
+                              GCancellable *cancellable,
+                              GError      **error);
 
 G_END_DECLS
 
diff --git a/src/ostadmin/ot-admin-builtin-deploy.c b/src/ostadmin/ot-admin-builtin-deploy.c
index 94eadfd..a857e42 100644
--- a/src/ostadmin/ot-admin-builtin-deploy.c
+++ b/src/ostadmin/ot-admin-builtin-deploy.c
@@ -23,86 +23,473 @@
 #include "config.h"
 
 #include "ot-admin-builtins.h"
+#include "ot-admin-functions.h"
 #include "ostree.h"
 
 #include <glib/gi18n.h>
 
 typedef struct {
   OstreeRepo  *repo;
+  GFile *ostree_dir;
 } OtAdminDeploy;
 
 static gboolean opt_no_kernel;
+static gboolean opt_force;
 static char *opt_ostree_dir = "/ostree";
 
 static GOptionEntry options[] = {
   { "ostree-dir", 0, 0, G_OPTION_ARG_STRING, &opt_ostree_dir, "Path to OSTree root directory (default: /ostree)", NULL },
   { "no-kernel", 0, 0, G_OPTION_ARG_NONE, &opt_no_kernel, "Don't update kernel related config (initramfs, bootloader)", NULL },
+  { "force", 0, 0, G_OPTION_ARG_NONE, &opt_force, "Overwrite any existing deployment", NULL },
   { NULL }
 };
 
+/**
+ * update_current:
+ *
+ * Atomically swap the /ostree/current symbolic link to point to a new
+ * path.  If successful, the old current will be saved as
+ * /ostree/previous.
+ */
 static gboolean
-update_current (const char         *deploy_target,
+update_current (OtAdminDeploy      *self,
+                GFile              *current_deployment,
+                GFile              *deploy_target,
                 GCancellable       *cancellable,
                 GError            **error)
 {
   gboolean ret = FALSE;
-  ot_lfree char *tmp_symlink = NULL;
-  ot_lfree char *current_name = NULL;
+  ot_lobj GFile *current_path = NULL;
+  ot_lobj GFile *previous_path = NULL;
+  ot_lobj GFile *tmp_current_path = NULL;
+  ot_lobj GFile *tmp_previous_path = NULL;
+  ot_lobj GFileInfo *previous_info = NULL;
+  ot_lfree char *relative_current = NULL;
+  ot_lfree char *relative_previous = NULL;
+
+  current_path = g_file_get_child (self->ostree_dir, "current");
+  previous_path = g_file_get_child (self->ostree_dir, "previous");
+
+  if (current_deployment)
+    {
+      ot_lfree char *relative_previous = NULL;
+
+      tmp_previous_path = g_file_get_child (self->ostree_dir, "tmp-previous");
+      (void) ot_gfile_unlink (tmp_previous_path, NULL, NULL);
+
+      relative_previous = g_file_get_relative_path (self->ostree_dir, current_deployment);
+      g_assert (relative_previous);
+      if (symlink (relative_previous, ot_gfile_get_path_cached (tmp_previous_path)) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+
+  tmp_current_path = g_file_get_child (self->ostree_dir, "tmp-current");
+  (void) ot_gfile_unlink (tmp_current_path, NULL, NULL);
 
-  tmp_symlink = g_build_filename (opt_ostree_dir, "tmp-current", NULL);
-  (void) unlink (tmp_symlink);
+  relative_current = g_file_get_relative_path (self->ostree_dir, deploy_target);
+  g_assert (relative_current);
 
-  if (symlink (deploy_target, tmp_symlink) < 0)
+  if (symlink (relative_current, ot_gfile_get_path_cached (tmp_current_path)) < 0)
     {
       ot_util_set_error_from_errno (error, errno);
       goto out;
     }
 
-  current_name = g_build_filename (opt_ostree_dir, "current", NULL);
-  if (rename (tmp_symlink, current_name) < 0)
+  if (!ot_gfile_rename (tmp_current_path, current_path,
+                        cancellable, error))
+    goto out;
+
+  if (tmp_previous_path)
     {
-      ot_util_set_error_from_errno (error, errno);
+      if (!ot_gfile_rename (tmp_previous_path, previous_path,
+                            cancellable, error))
+        goto out;
+    }
+
+  g_print ("ostadmin: %s set to %s\n", ot_gfile_get_path_cached (current_path),
+           relative_current);
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+/**
+ * get_current_deployment:
+ * 
+ * Returns in @out_deployment the full file path of the current
+ * deployment that the /ostree/current symbolic link points to, or
+ * %NULL if none.
+ */
+static gboolean
+get_current_deployment (OtAdminDeploy   *self,
+                        GFile          **out_deployment,
+                        GCancellable    *cancellable,
+                        GError         **error)
+{
+  gboolean ret = FALSE;
+  ot_lobj GFile *current_path = NULL;
+  ot_lobj GFileInfo *file_info = NULL;
+  ot_lobj GFile *ret_deployment = NULL;
+  GError *temp_error = NULL;
+
+  current_path = g_file_get_child (self->ostree_dir, "current");
+
+  file_info = g_file_query_info (current_path, OSTREE_GIO_FAST_QUERYINFO,
+                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                 cancellable, &temp_error);
+  if (!file_info)
+    {
+      if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        {
+          g_clear_error (&temp_error);
+        }
+      else
+        {
+          g_propagate_error (error, temp_error);
+          goto out;
+        }
+    }
+  else
+    {
+      const char *target;
+      if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_SYMBOLIC_LINK)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Not a symbolic link");
+          goto out;
+        }
+      target = g_file_info_get_symlink_target (file_info);
+      g_assert (target);
+      ret_deployment = g_file_resolve_relative_path (self->ostree_dir, target);
+    }
+
+  ret = TRUE;
+  ot_transfer_out_value (out_deployment, &ret_deployment);
+ out:
+  return ret;
+}
+
+typedef struct {
+  GError **error;
+  gboolean caught_error;
+
+  GMainLoop *loop;
+} ProcessOneCheckoutData;
+
+static void
+on_checkout_complete (GObject         *object,
+                      GAsyncResult    *result,
+                      gpointer         user_data)
+{
+  ProcessOneCheckoutData *data = user_data;
+  GError *local_error = NULL;
+
+  if (!ostree_repo_checkout_tree_finish ((OstreeRepo*)object, result,
+                                         &local_error))
+    goto out;
+
+ out:
+  if (local_error)
+    {
+      data->caught_error = TRUE;
+      g_propagate_error (data->error, local_error);
+    }
+  g_main_loop_quit (data->loop);
+}
+
+
+/**
+ * ensure_unlinked:
+ *
+ * Like ot_gfile_unlink(), but return successfully if the file doesn't
+ * exist.
+ */
+static gboolean
+ensure_unlinked (GFile         *path,
+                 GCancellable  *cancellable,
+                 GError       **error)
+{
+  gboolean ret = FALSE;
+  GError *temp_error = NULL;
+
+  if (!ot_gfile_unlink (path, cancellable, &temp_error))
+    {
+      if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        {
+          g_clear_error (&temp_error);
+        }
+      else
+        {
+          g_propagate_error (error, temp_error);
+          goto out;
+        }
+    }
+  
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+/**
+ * merge_etc_changes:
+ *
+ * Compute the difference between @orig_etc and @modified_etc,
+ * and apply that to @new_etc.
+ *
+ * The algorithm for computing the difference is pretty simple; it's
+ * approximately equivalent to "diff -unR orig_etc modified_etc",
+ * except that rather than attempting a 3-way merge if a file is also
+ * changed in @new_etc, the modified version always wins.
+ */
+static gboolean
+merge_etc_changes (OtAdminDeploy  *self,
+                   GFile          *orig_etc,
+                   GFile          *modified_etc,
+                   GFile          *new_etc,
+                   GCancellable   *cancellable,
+                   GError        **error)
+{
+  gboolean ret = FALSE;
+  ot_lobj GFile *ostree_etc = NULL;
+  ot_lobj GFile *tmp_etc = NULL;
+  ot_lptrarray GPtrArray *modified = NULL;
+  guint i;
+
+  modified = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+
+  if (!ot_admin_diff_dirs (orig_etc, modified_etc, modified,
+                           cancellable, error))
+    {
+      g_prefix_error (error, "While computing configuration diff: ");
       goto out;
     }
 
-  g_print ("%s set to %s\n", current_name, deploy_target);
+  if (modified->len > 0)
+    g_print ("ostadmin: Processing %u modified etc files\n", modified->len);
+
+  for (i = 0; i < modified->len; i++)
+    {
+      GFile *file = modified->pdata[i];
+      ot_lobj GFileInfo *orig_info = NULL;
+      ot_lobj GFile *target_file = NULL;
+      ot_lfree char *path = NULL;
+      GError *temp_error = NULL;
+
+      path = g_file_get_relative_path (modified_etc, file);
+      g_assert (path);
+      
+      target_file = g_file_resolve_relative_path (new_etc, path);
+
+      orig_info = g_file_query_info (file, OSTREE_GIO_FAST_QUERYINFO,
+                                     G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                     cancellable, &temp_error);
+      if (!orig_info)
+        {
+          if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+            {
+              g_clear_error (&temp_error);
+              if (!ensure_unlinked (target_file, cancellable, error))
+                goto out;
+            }
+        }
+      else
+        {
+          ot_lobj GFile *target_parent = g_file_get_parent (target_file);
+
+          /* FIXME actually we need to copy permissions and xattrs */
+          if (!ot_gfile_ensure_directory (target_parent, TRUE, error))
+            goto out;
+
+          if (!g_file_copy (file, target_file, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA,
+                            cancellable, NULL, NULL, error))
+            goto out;
+        }
+    }
 
   ret = TRUE;
  out:
   return ret;
 }
 
+/**
+ * deploy_tree:
+ *
+ * Look up @revision in the repository, and check it out in
+ * OSTREE_DIR/deploy/DEPLOY_TARGET.
+ *
+ * Merge configuration changes from the old deployment, if any.
+ *
+ * Update the OSTREE_DIR/current and OSTREE_DIR/previous symbolic
+ * links.
+ */
 static gboolean
-do_checkout (OtAdminDeploy     *self,
+deploy_tree (OtAdminDeploy     *self,
              const char        *deploy_target,
              const char        *revision,
              GCancellable      *cancellable,
              GError           **error)
 {
   gboolean ret = FALSE;
-  ot_lobj GFile *deploy_path = NULL;
+  ot_lobj GFile *deploy_dir = NULL;
+  ot_lfree char *deploy_target_fullname = NULL;
+  ot_lfree char *deploy_target_fullname_tmp = NULL;
+  ot_lobj GFile *deploy_target_path = NULL;
+  ot_lobj GFile *deploy_target_path_tmp = NULL;
+  ot_lfree char *deploy_target_etc_name = NULL;
+  ot_lobj GFile *deploy_target_etc_path = NULL;
+  ot_lobj GFile *deploy_target_default_etc_path = NULL;
   ot_lobj GFile *deploy_parent = NULL;
-  ot_lfree char *tree_ref = NULL;
-  ot_lfree char *repo_path = NULL;
-  ot_lfree char *repo_arg = NULL;
-  ot_lptrarray GPtrArray *checkout_args = NULL;
+  ot_lobj GFile *previous_deployment = NULL;
+  ot_lobj GFile *previous_deployment_etc = NULL;
+  ot_lobj GFile *previous_deployment_etc_default = NULL;
+  ot_lobj OstreeRepoFile *root = NULL;
+  ot_lobj GFileInfo *file_info = NULL;
+  ot_lobj GFileInfo *existing_checkout_info = NULL;
+  ot_lfree char *checkout_target_name = NULL;
+  ot_lfree char *checkout_target_tmp_name = NULL;
+  ot_lfree char *resolved_commit = NULL;
+  GError *temp_error = NULL;
+  gboolean skip_checkout;
+
+  if (!revision)
+    revision = deploy_target;
+
+  deploy_dir = g_file_get_child (self->ostree_dir, "deploy");
+
+  if (!ostree_repo_resolve_rev (self->repo, revision, FALSE, &resolved_commit, error))
+    goto out;
+
+  root = (OstreeRepoFile*)ostree_repo_file_new_root (self->repo, resolved_commit);
+  if (!ostree_repo_file_ensure_resolved (root, error))
+    goto out;
 
-  repo_path = g_build_filename (opt_ostree_dir, "repo", NULL);
-  repo_arg = g_strconcat ("--repo=", repo_path, NULL);
+  file_info = g_file_query_info ((GFile*)root, OSTREE_GIO_FAST_QUERYINFO,
+                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                 cancellable, error);
+  if (!file_info)
+    goto out;
+
+  deploy_target_fullname = g_strconcat (deploy_target, "-", resolved_commit, NULL);
+  deploy_target_path = g_file_resolve_relative_path (deploy_dir, deploy_target_fullname);
+
+  deploy_target_fullname_tmp = g_strconcat (deploy_target_fullname, ".tmp", NULL);
+  deploy_target_path_tmp = g_file_resolve_relative_path (deploy_dir, deploy_target_fullname_tmp);
 
-  deploy_path = ot_gfile_from_build_path (opt_ostree_dir, deploy_target, NULL);
-  deploy_parent = g_file_get_parent (deploy_path);
+  deploy_parent = g_file_get_parent (deploy_target_path);
   if (!ot_gfile_ensure_directory (deploy_parent, TRUE, error))
     goto out;
 
-  checkout_args = g_ptr_array_new ();
-  ot_ptrarray_add_many (checkout_args, "ostree", repo_arg,
-                        "checkout", "--atomic-retarget", revision ? revision : deploy_target,
-                        ot_gfile_get_path_cached (deploy_path), NULL);
-  g_ptr_array_add (checkout_args, NULL);
+  deploy_target_etc_name = g_strconcat (deploy_target, "-", resolved_commit, "-etc", NULL);
+  deploy_target_etc_path = g_file_resolve_relative_path (deploy_dir, deploy_target_etc_name);
 
-  if (!ot_spawn_sync_checked (opt_ostree_dir, (char**)checkout_args->pdata, NULL, G_SPAWN_SEARCH_PATH,
-                              NULL, NULL, NULL, NULL, error))
+  /* Delete any previous temporary data */
+  if (!ot_gio_shutil_rm_rf (deploy_target_path_tmp, cancellable, error))
+    goto out;
+
+  existing_checkout_info = g_file_query_info (deploy_target_path, OSTREE_GIO_FAST_QUERYINFO,
+                                              G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                              cancellable, &temp_error);
+  if (existing_checkout_info)
+    {
+      if (opt_force)
+        {
+          if (!ot_gio_shutil_rm_rf (deploy_target_path, cancellable, error))
+            goto out;
+          if (!ot_gio_shutil_rm_rf (deploy_target_etc_path, cancellable, error))
+            goto out;
+          
+          skip_checkout = FALSE;
+        }
+      else
+        skip_checkout = TRUE;
+    }
+  else if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+    {
+      g_clear_error (&temp_error);
+      skip_checkout = FALSE;
+    }
+  else
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+
+  if (!get_current_deployment (self, &previous_deployment, cancellable, error))
+    goto out;
+  if (previous_deployment)
+    {
+      ot_lfree char *etc_name;
+      ot_lobj GFile *parent;
+
+      etc_name = g_strconcat (ot_gfile_get_basename_cached (previous_deployment), "-etc", NULL);
+      parent = g_file_get_parent (previous_deployment);
+
+      previous_deployment_etc = g_file_get_child (parent, etc_name);
+
+      if (!g_file_query_exists (previous_deployment_etc, cancellable)
+          || g_file_equal (previous_deployment, deploy_target_path))
+        g_clear_object (&previous_deployment_etc);
+      else
+        previous_deployment_etc_default = g_file_get_child (previous_deployment, "etc");
+    }
+
+
+  if (!skip_checkout)
+    {
+      ProcessOneCheckoutData checkout_data;
+
+      g_print ("ostadmin: Creating deployment %s at revision %s\n",
+               ot_gfile_get_path_cached (deploy_target_path),
+               resolved_commit);
+
+      memset (&checkout_data, 0, sizeof (checkout_data));
+      checkout_data.loop = g_main_loop_new (NULL, TRUE);
+      checkout_data.error = error;
+
+      ostree_repo_checkout_tree_async (self->repo, 0, 0, deploy_target_path_tmp, root,
+                                       file_info, cancellable,
+                                       on_checkout_complete, &checkout_data);
+
+      g_main_loop_run (checkout_data.loop);
+
+      g_main_loop_unref (checkout_data.loop);
+
+      if (checkout_data.caught_error)
+        goto out;
+
+      if (!ostree_run_triggers_in_root (deploy_target_path_tmp, cancellable, error))
+        goto out;
+
+      deploy_target_default_etc_path = ot_gfile_get_child_strconcat (deploy_target_path_tmp, "etc", NULL);
+
+      if (!ot_gio_shutil_rm_rf (deploy_target_etc_path, cancellable, error))
+        goto out;
+
+      if (!ot_gio_shutil_cp_a (deploy_target_default_etc_path, deploy_target_etc_path,
+                               cancellable, error))
+        goto out;
+
+      g_print ("ostadmin: Created %s\n", ot_gfile_get_path_cached (deploy_target_etc_path));
+
+      if (previous_deployment_etc)
+        {
+          if (!merge_etc_changes (self, previous_deployment_etc_default,
+                                  previous_deployment_etc, deploy_target_etc_path, 
+                                  cancellable, error))
+            goto out;
+        }
+
+      if (!ot_gfile_rename (deploy_target_path_tmp, deploy_target_path,
+                            cancellable, error))
+        goto out;
+    }
+
+  if (!update_current (self, previous_deployment, deploy_target_path,
+                       cancellable, error))
     goto out;
 
   ret = TRUE;
@@ -110,6 +497,11 @@ do_checkout (OtAdminDeploy     *self,
   return ret;
 }
 
+/**
+ * do_update_kernel:
+ *
+ * Ensure we have a GRUB entry, initramfs set up, etc.
+ */
 static gboolean
 do_update_kernel (OtAdminDeploy     *self,
                   const char        *deploy_target,
@@ -121,11 +513,12 @@ do_update_kernel (OtAdminDeploy     *self,
 
   args = g_ptr_array_new ();
   ot_ptrarray_add_many (args, "ostadmin", "update-kernel",
-                        "--ostree-dir", opt_ostree_dir,
+                        "--ostree-dir", ot_gfile_get_path_cached (self->ostree_dir),
                         deploy_target, NULL);
   g_ptr_array_add (args, NULL);
 
-  if (!ot_spawn_sync_checked (opt_ostree_dir, (char**)args->pdata, NULL, G_SPAWN_SEARCH_PATH,
+  if (!ot_spawn_sync_checked (ot_gfile_get_path_cached (self->ostree_dir),
+                              (char**)args->pdata, NULL, G_SPAWN_SEARCH_PATH,
                               NULL, NULL, NULL, NULL, error))
     goto out;
 
@@ -142,6 +535,7 @@ ot_admin_builtin_deploy (int argc, char **argv, GError **error)
   OtAdminDeploy self_data;
   OtAdminDeploy *self = &self_data;
   gboolean ret = FALSE;
+  ot_lobj GFile *repo_path = NULL;
   const char *deploy_target = NULL;
   const char *revision = NULL;
   __attribute__((unused)) GCancellable *cancellable = NULL;
@@ -149,6 +543,7 @@ ot_admin_builtin_deploy (int argc, char **argv, GError **error)
   memset (self, 0, sizeof (*self));
 
   context = g_option_context_new ("NAME [REVISION] - Check out revision NAME (or REVISION as NAME)");
+
   g_option_context_add_main_entries (context, options, NULL);
 
   if (!g_option_context_parse (context, &argc, &argv, error))
@@ -159,12 +554,22 @@ ot_admin_builtin_deploy (int argc, char **argv, GError **error)
       ot_util_usage_error (context, "NAME must be specified", error);
       goto out;
     }
-    
+
+  self->ostree_dir = g_file_new_for_path (opt_ostree_dir);
+
+  if (!ot_admin_ensure_initialized (self->ostree_dir, cancellable, error))
+    goto out;
+
+  repo_path = g_file_get_child (self->ostree_dir, "repo");
+  self->repo = ostree_repo_new (repo_path);
+  if (!ostree_repo_check (self->repo, error))
+    goto out;
+
   deploy_target = argv[1];
   if (argc > 2)
     revision = argv[2];
 
-  if (!do_checkout (self, deploy_target, revision, cancellable, error))
+  if (!deploy_tree (self, deploy_target, revision, cancellable, error))
     goto out;
 
   if (!opt_no_kernel)
@@ -172,12 +577,11 @@ ot_admin_builtin_deploy (int argc, char **argv, GError **error)
       if (!do_update_kernel (self, deploy_target, cancellable, error))
         goto out;
     }
-  
-  if (!update_current (deploy_target, cancellable, error))
-    goto out;
 
   ret = TRUE;
  out:
+  g_clear_object (&self->repo);
+  g_clear_object (&self->ostree_dir);
   if (context)
     g_option_context_free (context);
   return ret;
diff --git a/src/ostadmin/ot-admin-builtin-init.c b/src/ostadmin/ot-admin-builtin-init.c
index efef9d3..703e14f 100644
--- a/src/ostadmin/ot-admin-builtin-init.c
+++ b/src/ostadmin/ot-admin-builtin-init.c
@@ -23,6 +23,7 @@
 #include "config.h"
 
 #include "ot-admin-builtins.h"
+#include "ot-admin-functions.h"
 #include "otutil.h"
 
 #include <glib/gi18n.h>
@@ -49,40 +50,10 @@ ot_admin_builtin_init (int argc, char **argv, GError **error)
   if (!g_option_context_parse (context, &argc, &argv, error))
     goto out;
 
-  g_clear_object (&dir);
-  dir = ot_gfile_from_build_path (opt_ostree_dir, "repo", NULL);
-  if (!ot_gfile_ensure_directory (dir, TRUE, error))
-    goto out;
-  g_clear_object (&dir);
-  dir = ot_gfile_from_build_path (opt_ostree_dir, "repo", "objects", NULL);
-  if (!g_file_query_exists (dir, NULL))
-    {
-      ot_lfree char *opt_repo_path = g_strdup_printf ("--repo=%s/repo", opt_ostree_dir);
-      const char *child_argv[] = { "ostree", opt_repo_path, "init", NULL };
-
-      if (!ot_spawn_sync_checked (NULL, (char**)child_argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
-                                  NULL, NULL, error))
-        {
-          g_prefix_error (error, "Failed to initialize repository: ");
-          goto out;
-        }
-    }
+  dir = g_file_new_for_path (opt_ostree_dir);
 
-  /* Ensure a few subdirectories of /var exist, since we need them for
-     dracut generation */
-  g_clear_object (&dir);
-  dir = ot_gfile_from_build_path (opt_ostree_dir, "var", "log", NULL);
-  if (!ot_gfile_ensure_directory (dir, TRUE, error))
-    goto out;
-  g_clear_object (&dir);
-  dir = ot_gfile_from_build_path (opt_ostree_dir, "var", "tmp", NULL);
-  if (!ot_gfile_ensure_directory (dir, TRUE, error))
+  if (!ot_admin_ensure_initialized (dir, cancellable, error))
     goto out;
-  if (chmod (ot_gfile_get_path_cached (dir), 01777) < 0)
-    {
-      ot_util_set_error_from_errno (error, errno);
-      goto out;
-    }
 
   g_print ("%s initialized as OSTree root\n", opt_ostree_dir);
 
diff --git a/src/ostadmin/ot-admin-functions.c b/src/ostadmin/ot-admin-functions.c
new file mode 100644
index 0000000..b3508d0
--- /dev/null
+++ b/src/ostadmin/ot-admin-functions.c
@@ -0,0 +1,317 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters verbum org>
+ *
+ * This library 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 2 of the License, or (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#include "config.h"
+
+#include "ot-admin-functions.h"
+#include "otutil.h"
+#include "ostree-core.h"
+
+gboolean
+ot_admin_ensure_initialized (GFile         *ostree_dir,
+                             GCancellable  *cancellable,
+                             GError       **error)
+{
+  gboolean ret = FALSE;
+  ot_lobj GFile *dir = NULL;
+
+  g_clear_object (&dir);
+  dir = g_file_get_child (ostree_dir, "repo");
+  if (!ot_gfile_ensure_directory (dir, TRUE, error))
+    goto out;
+
+  g_clear_object (&dir);
+  dir = g_file_get_child (ostree_dir, "deploy");
+  if (!ot_gfile_ensure_directory (dir, TRUE, error))
+    goto out;
+
+  g_clear_object (&dir);
+  dir = ot_gfile_get_child_build_path (ostree_dir, "repo", "objects", NULL);
+  if (!g_file_query_exists (dir, NULL))
+    {
+      ot_lfree char *opt_repo_arg = g_strdup_printf ("--repo=%s/repo",
+                                                      ot_gfile_get_path_cached (ostree_dir));
+      const char *child_argv[] = { "ostree", opt_repo_arg, "init", NULL };
+
+      if (!ot_spawn_sync_checked (NULL, (char**)child_argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
+                                  NULL, NULL, error))
+        {
+          g_prefix_error (error, "Failed to initialize repository: ");
+          goto out;
+        }
+    }
+
+  /* Ensure a few subdirectories of /var exist, since we need them for
+     dracut generation */
+  g_clear_object (&dir);
+  dir = ot_gfile_get_child_build_path (ostree_dir, "var", "log", NULL);
+  if (!ot_gfile_ensure_directory (dir, TRUE, error))
+    goto out;
+  g_clear_object (&dir);
+  dir = ot_gfile_get_child_build_path (ostree_dir, "var", "tmp", NULL);
+  if (!ot_gfile_ensure_directory (dir, TRUE, error))
+    goto out;
+  if (chmod (ot_gfile_get_path_cached (dir), 01777) < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+diff_files (GFile          *a,
+            GFileInfo      *a_info,
+            GFile          *b,
+            GFileInfo      *b_info,
+            gboolean       *out_files_differ,
+            GCancellable   *cancellable,
+            GError        **error)
+{
+  gboolean ret = FALSE;
+  ot_lfree guchar *csum_a = NULL;
+  ot_lfree guchar *csum_b = NULL;
+
+  if (!ostree_checksum_file (a, OSTREE_OBJECT_TYPE_FILE,
+                             &csum_a, cancellable, error))
+    goto out;
+  if (!ostree_checksum_file (b, OSTREE_OBJECT_TYPE_FILE,
+                             &csum_b, cancellable, error))
+    goto out;
+
+  ret = TRUE;
+  *out_files_differ = ostree_cmp_checksum_bytes (csum_a, csum_b) != 0;
+ out:
+  return ret;
+}
+
+static gboolean
+diff_add_dir_recurse (GFile          *d,
+                      GPtrArray      *modified,
+                      GCancellable   *cancellable,
+                      GError        **error)
+{
+  gboolean ret = FALSE;
+  GError *temp_error = NULL;
+  ot_lobj GFileEnumerator *dir_enum = NULL;
+  ot_lobj GFile *child = NULL;
+  ot_lobj GFileInfo *child_info = NULL;
+
+  dir_enum = g_file_enumerate_children (d, OSTREE_GIO_FAST_QUERYINFO, 
+                                        G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                        cancellable, 
+                                        error);
+  if (!dir_enum)
+    goto out;
+
+  while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
+    {
+      const char *name;
+
+      name = g_file_info_get_name (child_info);
+
+      g_clear_object (&child);
+      child = g_file_get_child (d, name);
+
+      g_ptr_array_add (modified, g_object_ref (child));
+
+      if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY)
+        {
+          if (!diff_add_dir_recurse (child, modified, cancellable, error))
+            goto out;
+        }
+      
+      g_clear_object (&child_info);
+    }
+  if (temp_error != NULL)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+gboolean
+ot_admin_diff_dirs (GFile          *a,
+                    GFile          *b,
+                    GPtrArray      *modified,
+                    GCancellable   *cancellable,
+                    GError        **error)
+{
+  gboolean ret = FALSE;
+  GError *temp_error = NULL;
+  ot_lobj GFileEnumerator *dir_enum = NULL;
+  ot_lobj GFile *child_a = NULL;
+  ot_lobj GFile *child_b = NULL;
+  ot_lobj GFileInfo *child_a_info = NULL;
+  ot_lobj GFileInfo *child_b_info = NULL;
+
+  child_a_info = g_file_query_info (a, OSTREE_GIO_FAST_QUERYINFO,
+                                    G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                    cancellable, error);
+  if (!child_a_info)
+    goto out;
+
+  child_b_info = g_file_query_info (b, OSTREE_GIO_FAST_QUERYINFO,
+                                    G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                    cancellable, error);
+  if (!child_b_info)
+    goto out;
+
+  g_clear_object (&child_a_info);
+  g_clear_object (&child_b_info);
+
+  dir_enum = g_file_enumerate_children (a, OSTREE_GIO_FAST_QUERYINFO, 
+                                        G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                        cancellable, error);
+  if (!dir_enum)
+    goto out;
+
+  while ((child_a_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
+    {
+      const char *name;
+      GFileType child_a_type;
+      GFileType child_b_type;
+
+      name = g_file_info_get_name (child_a_info);
+
+      g_clear_object (&child_a);
+      child_a = g_file_get_child (a, name);
+      child_a_type = g_file_info_get_file_type (child_a_info);
+
+      g_clear_object (&child_b);
+      child_b = g_file_get_child (b, name);
+
+      g_clear_object (&child_b_info);
+      child_b_info = g_file_query_info (child_b, OSTREE_GIO_FAST_QUERYINFO,
+                                        G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                        cancellable,
+                                        &temp_error);
+      if (!child_b_info)
+        {
+          if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+            {
+              g_clear_error (&temp_error);
+              g_ptr_array_add (modified, g_object_ref (child_b));
+            }
+          else
+            {
+              g_propagate_error (error, temp_error);
+              goto out;
+            }
+        }
+      else
+        {
+          child_b_type = g_file_info_get_file_type (child_b_info);
+          if (child_a_type != child_b_type)
+            {
+              g_ptr_array_add (modified, child_b);
+            }
+          else
+            {
+              gboolean files_differ;
+
+              if (!diff_files (child_a, child_a_info, child_b, child_b_info,
+                               &files_differ, cancellable, error))
+                goto out;
+              
+              if (files_differ)
+                g_ptr_array_add (modified, child_b); /* Transfer ownership */
+
+              if (child_a_type == G_FILE_TYPE_DIRECTORY)
+                {
+                  if (!ot_admin_diff_dirs (child_a, child_b, modified,
+                                           cancellable, error))
+                    goto out;
+                }
+            }
+        }
+      
+      g_clear_object (&child_a_info);
+    }
+  if (temp_error != NULL)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+
+  g_clear_object (&dir_enum);
+  dir_enum = g_file_enumerate_children (b, OSTREE_GIO_FAST_QUERYINFO, 
+                                        G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                        cancellable, error);
+  if (!dir_enum)
+    goto out;
+
+  g_clear_object (&child_b_info);
+  while ((child_b_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
+    {
+      const char *name;
+
+      name = g_file_info_get_name (child_b_info);
+
+      g_clear_object (&child_a);
+      child_a = g_file_get_child (a, name);
+
+      g_clear_object (&child_b);
+      child_b = g_file_get_child (b, name);
+
+      g_clear_object (&child_a_info);
+      child_a_info = g_file_query_info (child_a, OSTREE_GIO_FAST_QUERYINFO,
+                                        G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                        cancellable,
+                                        &temp_error);
+      if (!child_a_info)
+        {
+          if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+            {
+              g_clear_error (&temp_error);
+              g_ptr_array_add (modified, g_object_ref (child_b));
+              if (g_file_info_get_file_type (child_b_info) == G_FILE_TYPE_DIRECTORY)
+                {
+                  if (!diff_add_dir_recurse (child_b, modified, cancellable, error))
+                    goto out;
+                }
+            }
+          else
+            {
+              g_propagate_error (error, temp_error);
+              goto out;
+            }
+        }
+      g_clear_object (&child_b_info);
+    }
+  if (temp_error != NULL)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
diff --git a/src/ostadmin/ot-admin-functions.h b/src/ostadmin/ot-admin-functions.h
new file mode 100644
index 0000000..4ffb150
--- /dev/null
+++ b/src/ostadmin/ot-admin-functions.h
@@ -0,0 +1,42 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters verbum org>
+ *
+ * This library 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 2 of the License, or (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#ifndef __OT_ADMIN_FUNCTIONS__
+#define __OT_ADMIN_FUNCTIONS__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+gboolean ot_admin_ensure_initialized (GFile         *ostree_dir, 
+				      GCancellable  *cancellable,
+				      GError       **error);
+
+gboolean ot_admin_diff_dirs (GFile          *a,
+                             GFile          *b,
+                             GPtrArray      *modified,
+                             GCancellable   *cancellable,
+                             GError        **error);
+
+G_END_DECLS
+
+#endif
diff --git a/src/ostree/ot-builtin-checkout.c b/src/ostree/ot-builtin-checkout.c
index 12a8f39..2b63851 100644
--- a/src/ostree/ot-builtin-checkout.c
+++ b/src/ostree/ot-builtin-checkout.c
@@ -30,7 +30,6 @@
 #include <glib/gi18n.h>
 
 static gboolean opt_user_mode;
-static gboolean opt_atomic_retarget;
 static gboolean opt_no_triggers;
 static char *opt_subpath;
 static gboolean opt_union;
@@ -41,89 +40,12 @@ static GOptionEntry options[] = {
   { "user-mode", 'U', 0, G_OPTION_ARG_NONE, &opt_user_mode, "Do not change file ownership or initialize extended attributes", NULL },
   { "subpath", 0, 0, G_OPTION_ARG_STRING, &opt_subpath, "Checkout sub-directory PATH", "PATH" },
   { "union", 0, 0, G_OPTION_ARG_NONE, &opt_union, "Keep existing directories, overwrite existing files", NULL },
-  { "atomic-retarget", 0, 0, G_OPTION_ARG_NONE, &opt_atomic_retarget, "Make a symbolic link for destination, suffix with checksum", NULL },
   { "no-triggers", 0, 0, G_OPTION_ARG_NONE, &opt_no_triggers, "Don't run triggers", NULL },
   { "from-stdin", 0, 0, G_OPTION_ARG_NONE, &opt_from_stdin, "Process many checkouts from standard input", NULL },
   { "from-file", 0, 0, G_OPTION_ARG_STRING, &opt_from_file, "Process many checkouts from input file", NULL },
   { NULL }
 };
 
-static gboolean
-atomic_symlink_swap (GFile          *dest,
-                     const char     *target,
-                     GCancellable   *cancellable,
-                     GError        **error)
-{
-  gboolean ret = FALSE;
-  ot_lobj GFile *parent = NULL;
-  ot_lfree char *tmp_name = NULL;
-  ot_lobj GFile *tmp_link = NULL;
-
-  parent = g_file_get_parent (dest);
-  /* HACK - should use randomly generated temporary target name */
-  tmp_name = g_strconcat (ot_gfile_get_basename_cached (dest),
-                          "-tmplink", NULL);
-  tmp_link = g_file_get_child (parent, tmp_name);
-  if (symlink (target, ot_gfile_get_path_cached (tmp_link)) < 0)
-    {
-      ot_util_set_error_from_errno (error, errno);
-      goto out;
-    }
-  if (!ot_gfile_rename (tmp_link, dest, cancellable, error))
-    goto out;
-
-  ret = TRUE;
- out:
-  return ret;
-}
-
-static gboolean
-parse_commit_from_symlink (GFile        *symlink,
-                           char        **out_commit,
-                           GCancellable  *cancellable,
-                           GError       **error)
-{
-  gboolean ret = FALSE;
-  const char *target;
-  const char *last_dash;
-  const char *checksum;
-  ot_lobj GFileInfo *file_info = NULL;
-  ot_lfree char *ret_commit = NULL;
-
-  file_info = g_file_query_info (symlink, OSTREE_GIO_FAST_QUERYINFO,
-                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                 cancellable, error);
-  if (!file_info)
-    goto out;
-
-  target = g_file_info_get_symlink_target (file_info);
-  if (target == NULL)
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Not a symbolic link");
-      goto out;
-    }
-
-  last_dash = strrchr (target, '-');
-  if (last_dash == NULL)
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Invalid existing symlink target; no trailing dash");
-      goto out;
-    }
-  checksum = last_dash + 1;
-
-  if (!ostree_validate_structureof_checksum_string (checksum, error))
-    goto out;
-  
-  ret_commit = g_strdup (checksum);
-
-  ret = TRUE;
-  ot_transfer_out_value (out_commit, &ret_commit);
- out:
-  return ret;
-}
-
 typedef struct {
   gboolean caught_error;
   GError **error;
@@ -263,7 +185,10 @@ process_many_checkouts (OstreeRepo         *repo,
 
       if (!process_one_checkout (repo, resolved_commit, subpath, target,
                                  cancellable, error))
-        goto out;
+        {
+          g_prefix_error (error, "Processing tree %s: ", resolved_commit);
+          goto out;
+        }
 
       g_free (revision);
     }
@@ -286,11 +211,9 @@ ostree_builtin_checkout (int argc, char **argv, GFile *repo_path, GError **error
   gboolean ret = FALSE;
   const char *commit;
   const char *destination;
-  gboolean skip_checkout;
   ot_lobj OstreeRepo *repo = NULL;
   ot_lfree char *existing_commit = NULL;
   ot_lfree char *resolved_commit = NULL;
-  ot_lfree char *suffixed_destination = NULL;
   ot_lfree char *tmp_destination = NULL;
   ot_lobj GFileInfo *symlink_file_info = NULL;
   ot_lobj GFile *checkout_target = NULL;
@@ -319,13 +242,6 @@ ostree_builtin_checkout (int argc, char **argv, GFile *repo_path, GError **error
 
   if (opt_from_stdin || opt_from_file)
     {
-      if (opt_atomic_retarget)
-        {
-          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                       "--atomic-retarget may not be used with --from-stdin or --from-file");
-          goto out;
-        }
-
       destination = argv[1];
       checkout_target = g_file_new_for_path (destination);
 
@@ -349,69 +265,18 @@ ostree_builtin_checkout (int argc, char **argv, GFile *repo_path, GError **error
       if (!ostree_repo_resolve_rev (repo, commit, FALSE, &resolved_commit, error))
         goto out;
 
-      if (opt_atomic_retarget)
-        {
-          GError *temp_error = NULL;
-
-          suffixed_destination = g_strconcat (destination, "-", resolved_commit, NULL);
-          checkout_target = g_file_new_for_path (suffixed_destination);
-          tmp_destination = g_strconcat (suffixed_destination, ".tmp", NULL);
-          checkout_target_tmp = g_file_new_for_path (tmp_destination);
-          symlink_target = g_file_new_for_path (destination);
-
-          if (!parse_commit_from_symlink (symlink_target, &existing_commit,
-                                          cancellable, &temp_error))
-            {
-              if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
-                {
-                  skip_checkout = FALSE;
-                  g_clear_error (&temp_error);
-                }
-              else
-                {
-                  g_propagate_error (error, temp_error);
-                  goto out;
-                }
-            }
-          else
-            {
-              skip_checkout = strcmp (existing_commit, resolved_commit) == 0;
-            }
-        }
-      else
-        {
-          checkout_target = g_file_new_for_path (destination);
-          skip_checkout = FALSE;
-        }
+      checkout_target = g_file_new_for_path (destination);
 
-      if (skip_checkout)
-        {
-          g_print ("ostree-checkout: Rev %s is already checked out as %s\n", commit, resolved_commit);
-        }
-      else
+      if (!process_one_checkout (repo, resolved_commit, opt_subpath,
+                                 checkout_target_tmp ? checkout_target_tmp : checkout_target,
+                                 cancellable, error))
+        goto out;
+
+      if (!opt_no_triggers)
         {
-          if (!process_one_checkout (repo, resolved_commit, opt_subpath,
-                                     checkout_target_tmp ? checkout_target_tmp : checkout_target,
-                                     cancellable, error))
+          if (!ostree_run_triggers_in_root (checkout_target_tmp ? checkout_target_tmp : checkout_target,
+                                            cancellable, error))
             goto out;
-
-          if (!opt_no_triggers)
-            {
-              if (!ostree_run_triggers_in_root (checkout_target_tmp ? checkout_target_tmp : checkout_target,
-                                                cancellable, error))
-                goto out;
-            }
-
-          if (opt_atomic_retarget)
-            {
-              if (!ot_gfile_rename (checkout_target_tmp, checkout_target, cancellable, error))
-                goto out;
-              if (!atomic_symlink_swap (symlink_target,
-                                        ot_gfile_get_basename_cached (checkout_target),
-                                        cancellable, error))
-                goto out;
-              g_print ("ostree-checkout: Rev %s checked out as %s\n", commit, resolved_commit);
-            }
         }
     }
 
diff --git a/src/switchroot/ostree-switch-root.c b/src/switchroot/ostree-switch-root.c
index ff16b16..f0bf49b 100644
--- a/src/switchroot/ostree-switch-root.c
+++ b/src/switchroot/ostree-switch-root.c
@@ -152,9 +152,9 @@ main(int argc, char *argv[])
 {
   const char *initramfs_move_mounts[] = { "/dev", "/proc", "/sys", "/run", NULL };
   const char *toproot_bind_mounts[] = { "/home", "/root", "/tmp", NULL };
-  const char *ostree_bind_mounts[] = { "/var", NULL };
+  const char *ostree_bind_mounts[] = { "/var", "/etc", NULL };
   /* ostree_readonly_bind_mounts /lib/modules -> modules */
-  const char *readonly_bind_mounts[] = { "/bin", "/etc", "/lib", "/sbin", "/usr",
+  const char *readonly_bind_mounts[] = { "/bin", "/lib", "/sbin", "/usr",
 					 NULL };
   const char *root_mountpoint = NULL;
   const char *ostree_target = NULL;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]