[ostree] [INCOMPATIBLE CHANGE] Implement new deployment model



commit bb6eedfb258d3001f61c42c7e920c03dae2bdc1a
Author: Colin Walters <walters verbum org>
Date:   Sat Jun 29 11:45:53 2013 -0400

    [INCOMPATIBLE CHANGE] Implement new deployment model
    
    See https://wiki.gnome.org/OSTree/DeploymentModel2
    
    This is a major rework of the on-disk filesystem layout, and the boot
    process.  OSTree now explicitly supports upgrading kernels, and these
    upgrades are also atomic.
    
    The core concept of the new model is the "deployment list", which is
    an ordered list of bootable operating system trees.  The deployment
    list is reflected in the bootloader configuration; which has a kernel
    argument that tells the initramfs (dracut) which operating system root
    to use.
    
    Invidiual notable changes that come along with this:
    
    1) Operating systems should now come with their etc in usr/etc; OSTree
       will perform a 3-way merge at deployment time, and place etc in
       the actual root.  This avoids the need for a bind mount, and is
       just a lot cleaner.
    2) OSTree no longer bind mounts /root, /home, and /tmp.  It is expected
       that the the OS/ has these as symbolic links into /var.
    
    At the moment, OSTree only supports managing syslinux; other
    bootloader backends will follow.

 Makefile-ostree.am                          |   15 +-
 Makefile-switchroot.am                      |    5 -
 Makefile-tests.am                           |    1 +
 src/ostree/ot-admin-builtin-deploy.c        |  718 ++-------------
 src/ostree/ot-admin-builtin-diff.c          |   57 +-
 src/ostree/ot-admin-builtin-init-fs.c       |    3 +-
 src/ostree/ot-admin-builtin-install.c       |   26 +-
 src/ostree/ot-admin-builtin-os-init.c       |    5 +-
 src/ostree/ot-admin-builtin-prune.c         |   42 +-
 src/ostree/ot-admin-builtin-pull-deploy.c   |  178 ----
 src/ostree/ot-admin-builtin-status.c        |  100 ++
 src/ostree/ot-admin-builtin-update-kernel.c |  296 ------
 src/ostree/ot-admin-builtin-upgrade.c       |  134 ++-
 src/ostree/ot-admin-builtins.h              |    6 +-
 src/ostree/ot-admin-deploy.c                | 1245 +++++++++++++++++++++++++
 src/ostree/ot-admin-deploy.h                |   51 +
 src/ostree/ot-admin-functions.c             | 1326 +++++++++++++++++++++------
 src/ostree/ot-admin-functions.h             |  100 ++-
 src/ostree/ot-bootloader-syslinux.c         |  312 +++++++
 src/ostree/ot-bootloader-syslinux.h         |   40 +
 src/ostree/ot-bootloader.c                  |   49 +
 src/ostree/ot-bootloader.h                  |   59 ++
 src/ostree/ot-builtin-admin.c               |   25 +-
 src/ostree/ot-config-parser.c               |  230 +++++
 src/ostree/ot-config-parser.h               |   58 ++
 src/ostree/ot-deployment.c                  |  209 +++++
 src/ostree/ot-deployment.h                  |   66 ++
 src/ostree/ot-ordered-hash.c                |   82 ++
 src/ostree/ot-ordered-hash.h                |   46 +
 src/switchroot/ostree-prepare-root.c        |  116 +--
 src/switchroot/ostree-switch-root.c         |  337 -------
 tests/libtest.sh                            |   97 ++
 tests/t0015-admin-deploy.sh                 |  134 +++
 33 files changed, 4139 insertions(+), 2029 deletions(-)
---
diff --git a/Makefile-ostree.am b/Makefile-ostree.am
index 94d5d08..cfbd456 100644
--- a/Makefile-ostree.am
+++ b/Makefile-ostree.am
@@ -56,15 +56,26 @@ ostree_SOURCES += \
        src/ostree/ot-admin-builtin-diff.c \
        src/ostree/ot-admin-builtin-deploy.c \
        src/ostree/ot-admin-builtin-prune.c \
-       src/ostree/ot-admin-builtin-pull-deploy.c \
        src/ostree/ot-admin-builtin-os-init.c \
        src/ostree/ot-admin-builtin-install.c \
+       src/ostree/ot-admin-builtin-status.c \
        src/ostree/ot-admin-builtin-run-triggers.c \
        src/ostree/ot-admin-builtin-upgrade.c \
-       src/ostree/ot-admin-builtin-update-kernel.c \
        src/ostree/ot-admin-builtins.h \
        src/ostree/ot-admin-functions.h \
        src/ostree/ot-admin-functions.c \
+       src/ostree/ot-admin-deploy.h \
+       src/ostree/ot-admin-deploy.c \
+       src/ostree/ot-bootloader.h \
+       src/ostree/ot-bootloader.c \
+       src/ostree/ot-bootloader-syslinux.h \
+       src/ostree/ot-bootloader-syslinux.c \
+       src/ostree/ot-config-parser.h \
+       src/ostree/ot-config-parser.c \
+       src/ostree/ot-deployment.h \
+       src/ostree/ot-deployment.c \
+       src/ostree/ot-ordered-hash.h \
+       src/ostree/ot-ordered-hash.c \
        $(NULL)
 
 ostree_bin_shared_cflags = $(AM_CFLAGS) -I$(srcdir)/src/libgsystem -I$(srcdir)/src/libotutil 
-I$(srcdir)/src/libostree -I$(srcdir)/src/ostree  -DLOCALEDIR=\"$(datadir)/locale\"
diff --git a/Makefile-switchroot.am b/Makefile-switchroot.am
index a1f05cc..9930106 100644
--- a/Makefile-switchroot.am
+++ b/Makefile-switchroot.am
@@ -16,7 +16,6 @@
 # Boston, MA 02111-1307, USA.
 
 if !TRIGGERS_ONLY
-sbin_PROGRAMS += ostree-switch-root
 if BUILDOPT_DRACUT
 sbin_PROGRAMS += ostree-prepare-root
 sbin_PROGRAMS += ostree-remount
@@ -33,10 +32,6 @@ ostree_prepare_root_SOURCES = src/switchroot/ostree-prepare-root.c
 ostree_prepare_root_LDADD = libswitchroot-mountutil.la
 ostree_prepare_root_CFLAGS = $(AM_CFLAGS) -Isrc/switchroot
 
-ostree_switch_root_SOURCES = src/switchroot/ostree-switch-root.c
-ostree_switch_root_LDADD = libswitchroot-mountutil.la
-ostree_switch_root_CFLAGS = $(AM_CFLAGS) -Isrc/switchroot
-
 ostree_remount_SOURCES = src/switchroot/ostree-remount.c
 ostree_remount_LDADD = libswitchroot-mountutil.la
 ostree_remount_CFLAGS = $(AM_CFLAGS) -Isrc/switchroot
diff --git a/Makefile-tests.am b/Makefile-tests.am
index cd22ab2..b81866b 100644
--- a/Makefile-tests.am
+++ b/Makefile-tests.am
@@ -28,6 +28,7 @@ testfiles = t0000-basic \
        t0005-corruption \
        t0006-libarchive \
        t0011-pull-archive-z \
+       t0015-admin-deploy \
        $(NULL)
 insttest_SCRIPTS = $(addprefix tests/,$(testfiles:=.sh))
 
diff --git a/src/ostree/ot-admin-builtin-deploy.c b/src/ostree/ot-admin-builtin-deploy.c
index c13ac56..93e3837 100644
--- a/src/ostree/ot-admin-builtin-deploy.c
+++ b/src/ostree/ot-admin-builtin-deploy.c
@@ -1,6 +1,6 @@
 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
  *
- * Copyright (C) 2012 Colin Walters <walters verbum org>
+ * Copyright (C) 2012,2013 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
@@ -24,701 +24,109 @@
 
 #include "ot-admin-builtins.h"
 #include "ot-admin-functions.h"
+#include "ot-admin-deploy.h"
+#include "ot-ordered-hash.h"
 #include "ostree.h"
 
 #include <glib/gi18n.h>
 
-typedef struct {
-  OstreeRepo  *repo;
-  OtAdminBuiltinOpts *admin_opts;
-  GFile *ostree_dir;
-  char  *osname;
-  GFile *osname_dir;
-
-  char        *current_deployment_ref;
-  char        *previous_deployment_ref;
-  char        *resolved_commit;
-  char        *resolved_previous_commit;
-  
-  char        *previous_deployment_revision;
-  GFile       *deploy_target_path;
-  GFile       *previous_deployment;
-} OtAdminDeploy;
-
-static gboolean opt_no_kernel;
-static gboolean opt_force;
+static gboolean opt_no_bootloader;
+static gboolean opt_retain;
+static char **opt_kernel_argv;
+static char *opt_osname;
+static char *opt_origin_path;
 
 static GOptionEntry options[] = {
-  { "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 },
+  { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Specify operating system root to use", NULL },
+  { "origin-file", 0, 0, G_OPTION_ARG_FILENAME, &opt_origin_path, "Specify origin file", NULL },
+  { "no-bootloader", 0, 0, G_OPTION_ARG_NONE, &opt_no_bootloader, "Don't update bootloader", NULL },
+  { "retain", 0, 0, G_OPTION_ARG_NONE, &opt_retain, "Do not delete previous deployment", NULL },
+  { "karg", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_kernel_argv, "Set kernel argument, like 
--karg=root=/dev/sda1", 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, and /ostree/current-etc will be a link to the
- * current /etc subdirectory.
- *
- * Unless the new-current equals current, in which case, do nothing.
- */
-static gboolean
-update_current (OtAdminDeploy      *self,
-                GFile              *current_deployment,
-                GFile              *deploy_target,
-                GCancellable       *cancellable,
-                GError            **error)
-{
-  gboolean ret = FALSE;
-  ot_lobj GFile *current_path = NULL;
-  ot_lobj GFile *current_etc_path = NULL;
-  ot_lobj GFile *previous_path = NULL;
-  ot_lobj GFile *tmp_current_path = NULL;
-  ot_lobj GFile *tmp_current_etc_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_current_etc = NULL;
-  ot_lfree char *relative_previous = NULL;
-
-  current_path = g_file_get_child (self->osname_dir, "current");
-  current_etc_path = g_file_get_child (self->osname_dir, "current-etc");
-  previous_path = g_file_get_child (self->osname_dir, "previous");
-
-  relative_current = g_file_get_relative_path (self->osname_dir, deploy_target);
-  g_assert (relative_current);
-  relative_current_etc = g_strconcat (relative_current, "-etc", NULL);
-
-  if (current_deployment)
-    {
-      ot_lfree char *relative_previous = NULL;
-
-      if (g_file_equal (current_deployment, deploy_target))
-        {
-          g_print ("ostadmin: %s already points to %s\n", gs_file_get_path_cached (current_path),
-                   relative_current);
-          return TRUE;
-        }
-
-      tmp_previous_path = g_file_get_child (self->osname_dir, "tmp-previous");
-      (void) gs_file_unlink (tmp_previous_path, NULL, NULL);
-
-      relative_previous = g_file_get_relative_path (self->osname_dir, current_deployment);
-      g_assert (relative_previous);
-      if (symlink (relative_previous, gs_file_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->osname_dir, "tmp-current");
-  (void) gs_file_unlink (tmp_current_path, NULL, NULL);
-
-  if (symlink (relative_current, gs_file_get_path_cached (tmp_current_path)) < 0)
-    {
-      ot_util_set_error_from_errno (error, errno);
-      goto out;
-    }
-
-  tmp_current_etc_path = g_file_get_child (self->osname_dir, "tmp-current-etc");
-  (void) gs_file_unlink (tmp_current_etc_path, NULL, NULL);
-  if (symlink (relative_current_etc, gs_file_get_path_cached (tmp_current_etc_path)) < 0)
-    {
-      ot_util_set_error_from_errno (error, errno);
-      goto out;
-    }
-
-  if (!gs_file_rename (tmp_current_path, current_path,
-                       cancellable, error))
-    goto out;
-  if (!gs_file_rename (tmp_current_etc_path, current_etc_path,
-                       cancellable, error))
-    goto out;
-
-  if (tmp_previous_path)
-    {
-      if (!gs_file_rename (tmp_previous_path, previous_path,
-                           cancellable, error))
-        goto out;
-    }
-
-  g_print ("ostadmin: %s set to %s\n", gs_file_get_path_cached (current_path),
-           relative_current);
-
-  ret = TRUE;
- 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 gs_file_unlink(), but return successfully if the file doesn't
- * exist.
- */
-static gboolean
-ensure_unlinked (GFile         *path,
-                 GCancellable  *cancellable,
-                 GError       **error)
+gboolean
+ot_admin_builtin_deploy (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error)
 {
   gboolean ret = FALSE;
-  GError *temp_error = NULL;
-
-  if (!gs_file_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;
-}
+  __attribute__((unused)) GCancellable *cancellable = NULL;
+  const char *refspec;
+  GOptionContext *context;
+  GFile *sysroot = admin_opts->sysroot;
+  GKeyFile *origin = NULL;
+  int current_bootversion;
+  int new_bootversion;
+  gs_unref_object OstreeRepo *repo = NULL;
+  gs_unref_ptrarray GPtrArray *current_deployments = NULL;
+  gs_unref_ptrarray GPtrArray *new_deployments = NULL;
+  gs_unref_object OtDeployment *new_deployment = NULL;
+  gs_unref_object OtDeployment *booted_deployment = NULL;
+  gs_free char *revision = NULL;
+
+  context = g_option_context_new ("REFSPEC - Checkout revision REFSPEC as the new default deployment");
 
-/**
- * copy_one_config_file:
- *
- * Copy @file from @modified_etc to @new_etc, overwriting any existing
- * file there.
- */
-static gboolean
-copy_one_config_file (OtAdminDeploy      *self,
-                      GFile              *orig_etc,
-                      GFile              *modified_etc,
-                      GFile              *new_etc,
-                      GFile              *src,
-                      GCancellable       *cancellable,
-                      GError            **error)
-{
-  gboolean ret = FALSE;
-  ot_lobj GFileInfo *src_info = NULL;
-  ot_lobj GFile *dest = NULL;
-  ot_lobj GFile *parent = NULL;
-  ot_lfree char *relative_path = NULL;
-  
-  relative_path = g_file_get_relative_path (modified_etc, src);
-  g_assert (relative_path);
-  dest = g_file_resolve_relative_path (new_etc, relative_path);
+  g_option_context_add_main_entries (context, options, NULL);
 
-  src_info = g_file_query_info (src, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                cancellable, error);
-  if (!src_info)
+  if (!g_option_context_parse (context, &argc, &argv, error))
     goto out;
 
-  if (g_file_info_get_file_type (src_info) == G_FILE_TYPE_DIRECTORY)
-    {
-      ot_lobj GFileEnumerator *src_enum = NULL;
-      ot_lobj GFileInfo *child_info = NULL;
-      GError *temp_error = NULL;
-
-      /* FIXME actually we need to copy permissions and xattrs */
-      if (!gs_file_ensure_directory (dest, TRUE, cancellable, error))
-        goto out;
-
-      src_enum = g_file_enumerate_children (src, OSTREE_GIO_FAST_QUERYINFO,
-                                            G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                            cancellable, error);
-
-      while ((child_info = g_file_enumerator_next_file (src_enum, cancellable, error)) != NULL)
-        {
-          ot_lobj GFile *child = g_file_get_child (src, g_file_info_get_name (child_info));
-
-          if (!copy_one_config_file (self, orig_etc, modified_etc, new_etc, child,
-                                     cancellable, error))
-            goto out;
-        }
-      g_clear_object (&child_info);
-      if (temp_error != NULL)
-        {
-          g_propagate_error (error, temp_error);
-          goto out;
-        }
-    }
-  else
-    {
-      parent = g_file_get_parent (dest);
-
-      /* FIXME actually we need to copy permissions and xattrs */
-      if (!gs_file_ensure_directory (parent, TRUE, cancellable, error))
-        goto out;
-      
-      if (!g_file_copy (src, dest, 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;
-}
-
-/**
- * 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;
-  ot_lptrarray GPtrArray *removed = NULL;
-  ot_lptrarray GPtrArray *added = NULL;
-  guint i;
-
-  modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref);
-  removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
-  added = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
-
-  if (!ostree_diff_dirs (orig_etc, modified_etc, modified, removed, added,
-                         cancellable, error))
-    {
-      g_prefix_error (error, "While computing configuration diff: ");
-      goto out;
-    }
-
-  if (modified->len > 0 || removed->len > 0 || added->len > 0)
-    g_print ("ostadmin: Processing config: %u modified, %u removed, %u added\n", 
-             modified->len,
-             removed->len,
-             added->len);
-  else
-    g_print ("ostadmin: No modified configuration\n");
-
-  for (i = 0; i < removed->len; i++)
-    {
-      GFile *file = removed->pdata[i];
-      ot_lobj GFile *target_file = NULL;
-      ot_lfree char *path = NULL;
-
-      path = g_file_get_relative_path (orig_etc, file);
-      g_assert (path);
-      target_file = g_file_resolve_relative_path (new_etc, path);
-
-      if (!ensure_unlinked (target_file, cancellable, error))
-        goto out;
-    }
-
-  for (i = 0; i < modified->len; i++)
-    {
-      OstreeDiffItem *diff = modified->pdata[i];
-
-      if (!copy_one_config_file (self, orig_etc, modified_etc, new_etc, diff->target,
-                                 cancellable, error))
-        goto out;
-    }
-  for (i = 0; i < added->len; i++)
-    {
-      GFile *file = added->pdata[i];
-
-      if (!copy_one_config_file (self, orig_etc, modified_etc, new_etc, file,
-                                 cancellable, error))
-        goto out;
-    }
-
-  ret = TRUE;
- out:
-  return ret;
-}
-
-/**
- * deploy_tree:
- *
- * Look up @revision in the repository, and check it out in
- * OSTREE_DIR/deploy/OS/DEPLOY_TARGET.
- *
- * Merge configuration changes from the old deployment, if any.
- *
- * Update the OSTREE_DIR/current{,-etc} and OSTREE_DIR/previous symbolic
- * links.
- */
-static gboolean
-deploy_tree (OtAdminDeploy     *self,
-             const char        *deploy_target,
-             const char        *revision,
-             GCancellable      *cancellable,
-             GError           **error)
-{
-  gboolean ret = FALSE;
-  ot_lfree char *deploy_target_fullname = NULL;
-  ot_lfree char *deploy_target_fullname_tmp = 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_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;
-  GError *temp_error = NULL;
-  gboolean skip_checkout;
-
-  if (!revision)
-    revision = deploy_target;
-
-  if (!g_file_query_exists (self->osname_dir, cancellable))
+  if (argc < 2)
     {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "No OS \"%s\" found in \"%s\"", self->osname,
-                   gs_file_get_path_cached (self->osname_dir));
+      ot_util_usage_error (context, "REF/REV must be specified", error);
       goto out;
     }
 
-  if (!ostree_repo_resolve_rev (self->repo, revision, FALSE, &self->resolved_commit, error))
-    goto out;
-  if (!ostree_repo_resolve_rev (self->repo, revision, TRUE, &self->resolved_previous_commit, error))
-    goto out;
-
-  root = (OstreeRepoFile*)ostree_repo_file_new_root (self->repo, self->resolved_commit);
-  if (!ostree_repo_file_ensure_resolved (root, error))
-    goto out;
-
-  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, "-", self->resolved_commit, NULL);
-  self->deploy_target_path = g_file_resolve_relative_path (self->osname_dir, deploy_target_fullname);
-
-  deploy_target_fullname_tmp = g_strconcat (deploy_target_fullname, ".tmp", NULL);
-  deploy_target_path_tmp = g_file_resolve_relative_path (self->osname_dir, deploy_target_fullname_tmp);
-
-  deploy_parent = g_file_get_parent (self->deploy_target_path);
-  if (!gs_file_ensure_directory (deploy_parent, TRUE, cancellable, error))
-    goto out;
-
-  deploy_target_etc_name = g_strconcat (deploy_target, "-", self->resolved_commit, "-etc", NULL);
-  deploy_target_etc_path = g_file_resolve_relative_path (self->osname_dir, deploy_target_etc_name);
+  refspec = argv[1];
 
-  /* Delete any previous temporary data */
-  if (!gs_shutil_rm_rf (deploy_target_path_tmp, cancellable, error))
+  if (!ot_admin_get_repo (sysroot, &repo, cancellable, error))
     goto out;
 
-  existing_checkout_info = g_file_query_info (self->deploy_target_path, OSTREE_GIO_FAST_QUERYINFO,
-                                              G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                              cancellable, &temp_error);
-  if (existing_checkout_info)
-    {
-      if (opt_force)
-        {
-          if (!gs_shutil_rm_rf (self->deploy_target_path, cancellable, error))
-            goto out;
-          if (!gs_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
+  if (!ot_admin_list_deployments (sysroot, &current_bootversion, &current_deployments,
+                                  cancellable, error))
     {
-      g_propagate_error (error, temp_error);
+      g_prefix_error (error, "While listing deployments: ");
       goto out;
     }
 
-  if (!ot_admin_get_current_deployment (self->ostree_dir, self->osname, &self->previous_deployment,
-                                        cancellable, error))
-    goto out;
-  if (self->previous_deployment)
-    {
-      ot_lfree char *etc_name;
-      ot_lobj GFile *parent;
-
-      etc_name = g_strconcat (gs_file_get_basename_cached (self->previous_deployment), "-etc", NULL);
-      parent = g_file_get_parent (self->previous_deployment);
-
-      previous_deployment_etc = g_file_get_child (parent, etc_name);
-
-      if (!g_file_query_exists (previous_deployment_etc, cancellable)
-          || g_file_equal (self->previous_deployment, self->deploy_target_path))
-        g_clear_object (&previous_deployment_etc);
-      else
-        previous_deployment_etc_default = g_file_get_child (self->previous_deployment, "etc");
-
-      if (!ostree_repo_resolve_rev (self->repo, self->current_deployment_ref, TRUE,
-                                    &self->previous_deployment_revision, error))
-        goto out;
-    }
-
-
-  if (!skip_checkout)
+  /* Find the currently booted deployment, if any; we will ensure it
+   * is present in the new deployment list.
+   */
+  if (!ot_admin_require_deployment_or_osname (sysroot, current_deployments,
+                                              opt_osname,
+                                              &booted_deployment,
+                                              cancellable, error))
     {
-      ProcessOneCheckoutData checkout_data;
-      ot_lobj GFile *triggers_run_path = NULL;
-      gs_unref_object GFile *usr_etc_path = NULL;
-
-      g_print ("ostadmin: Creating deployment %s\n",
-               gs_file_get_path_cached (self->deploy_target_path));
-
-      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;
-
-      usr_etc_path = g_file_resolve_relative_path (deploy_target_path_tmp, "usr/etc");
-      if (g_file_query_exists (usr_etc_path, NULL))
-        {
-          g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
-                       "Error: This tree contains usr/etc; it is likely an OS in version 2.0 format, and 
this version of OSTree does not support it");
-          goto out;
-        }
-
-      triggers_run_path = g_file_resolve_relative_path (deploy_target_path_tmp, 
"usr/share/ostree/triggers-run");
-
-      if (!g_file_query_exists (triggers_run_path, NULL))
-        {
-          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 (!gs_shutil_rm_rf (deploy_target_etc_path, cancellable, error))
-        goto out;
-
-      if (!gs_shutil_cp_a (deploy_target_default_etc_path, deploy_target_etc_path,
-                           cancellable, error))
-        goto out;
-
-      g_print ("ostadmin: Created %s\n", gs_file_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;
-        }
-      else
-        g_print ("ostadmin: No previous deployment; therefore, no configuration changes to merge\n");
-
-      if (!gs_file_rename (deploy_target_path_tmp, self->deploy_target_path,
-                           cancellable, error))
-        goto out;
+      g_prefix_error (error, "Looking for booted deployment: ");
+      goto out;
     }
 
-  ret = TRUE;
- out:
-  return ret;
-}
-
-/**
- * do_update_kernel:
- *
- * Ensure we have a GRUB entry, initramfs set up, etc.
- */
-static gboolean
-do_update_kernel (OtAdminDeploy     *self,
-                  GCancellable      *cancellable,
-                  GError           **error)
-{
-  gboolean ret = FALSE;
-  gs_unref_object GSSubprocess *proc = NULL;
-  gs_unref_ptrarray GPtrArray *args = NULL;
-
-  args = g_ptr_array_new ();
-  ot_ptrarray_add_many (args, "ostree", "admin",
-                        "--ostree-dir", gs_file_get_path_cached (self->ostree_dir),
-                        "--boot-dir", gs_file_get_path_cached (self->admin_opts->boot_dir),
-                        "update-kernel",
-                        self->osname,
-                        gs_file_get_path_cached (self->deploy_target_path), NULL);
-  g_ptr_array_add (args, NULL);
-
-  proc = gs_subprocess_new_simple_argv ((char**)args->pdata,
-                                        GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
-                                        GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
-                                        cancellable, error);
-  if (!proc)
-    goto out;
-  if (!gs_subprocess_wait_sync_check (proc, cancellable, error))
-    goto out;
-
-  ret = TRUE;
- out:
-  return ret;
-}
-
-static gboolean
-complete_deployment (OtAdminDeploy     *self,
-                     GCancellable      *cancellable,
-                     GError           **error)
-{
-  gboolean ret = FALSE;
-
-  /* Write out a ref so that any "ostree prune" on the raw repo
-   * doesn't GC the currently deployed tree.
-   */
-  if (!ostree_repo_write_ref (self->repo, NULL, self->current_deployment_ref,
-                              self->resolved_commit, error))
-    goto out;
-  /* Only overwrite previous if it's different from what we're deploying now.
-   */
-  if (self->resolved_previous_commit != NULL
-      && strcmp (self->resolved_previous_commit, self->resolved_commit) != 0)
+  if (opt_origin_path)
     {
-      if (!ostree_repo_write_ref (self->repo, NULL, self->previous_deployment_ref,
-                                  self->previous_deployment_revision, error))
+      origin = g_key_file_new ();
+      
+      if (!g_key_file_load_from_file (origin, opt_origin_path, 0, error))
         goto out;
     }
-
-  if (!update_current (self, self->previous_deployment, self->deploy_target_path,
-                       cancellable, error))
-    goto out;
-
-  ret = TRUE;
- out:
-  return ret;
-}
-
-gboolean
-ot_admin_builtin_deploy (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error)
-{
-  GOptionContext *context;
-  OtAdminDeploy self_data;
-  OtAdminDeploy *self = &self_data;
-  gboolean ret = FALSE;
-  ot_lobj GFile *repo_path = NULL;
-  ot_lobj GFile *deploy_path = NULL;
-  const char *osname = NULL;
-  const char *deploy_target = NULL;
-  const char *revision = NULL;
-  __attribute__((unused)) GCancellable *cancellable = NULL;
-
-  memset (self, 0, sizeof (*self));
-
-  context = g_option_context_new ("OSNAME TREENAME [REVISION] - In operating system OS, check out revision 
TREENAME (or REVISION as TREENAME)");
-
-  g_option_context_add_main_entries (context, options, NULL);
-
-  if (!g_option_context_parse (context, &argc, &argv, error))
-    goto out;
-
-  if (argc < 3)
+  else
     {
-      ot_util_usage_error (context, "OSNAME and TREENAME must be specified", error);
-      goto out;
+      origin = ot_origin_new_from_refspec (refspec);
     }
 
-  self->admin_opts = admin_opts;
-  self->ostree_dir = g_object_ref (admin_opts->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))
+  if (!ostree_repo_resolve_rev (repo, refspec, FALSE, &revision, error))
     goto out;
 
-  osname = argv[1];
-  deploy_target = argv[2];
-  if (argc > 3)
-    revision = argv[3];
-
-  self->osname = g_strdup (osname);
-  self->osname_dir = ot_gfile_get_child_build_path (self->ostree_dir, "deploy", osname, NULL);
-  self->current_deployment_ref = g_strdup_printf ("deployment/%s/current", self->osname);
-  self->previous_deployment_ref = g_strdup_printf ("deployment/%s/previous", self->osname);
-
-  if (!deploy_tree (self, deploy_target, revision, cancellable, error))
-    goto out;
-
-  if (!opt_no_kernel)
-    {
-      if (!do_update_kernel (self, cancellable, error))
-        goto out;
-    }
-
-  if (!complete_deployment (self, cancellable, error))
+  if (!ot_admin_deploy (sysroot, current_bootversion, current_deployments,
+                        opt_osname, revision, origin,
+                        opt_kernel_argv, opt_retain,
+                        booted_deployment, NULL,
+                        &new_deployment, &new_bootversion, &new_deployments,
+                        cancellable, error))
     goto out;
 
   ret = TRUE;
  out:
-  g_clear_object (&self->repo);
-  g_free (self->osname);
-  g_free (self->current_deployment_ref);
-  g_free (self->previous_deployment_ref);
-  g_free (self->resolved_commit);
-  g_free (self->resolved_previous_commit);
-  g_free (self->previous_deployment_revision);
-  g_clear_object (&self->previous_deployment);
-  g_clear_object (&self->ostree_dir);
-  g_clear_object (&self->osname_dir);
+  if (origin)
+    g_key_file_unref (origin);
   if (context)
     g_option_context_free (context);
   return ret;
diff --git a/src/ostree/ot-admin-builtin-diff.c b/src/ostree/ot-admin-builtin-diff.c
index 7792db3..97fb16a 100644
--- a/src/ostree/ot-admin-builtin-diff.c
+++ b/src/ostree/ot-admin-builtin-diff.c
@@ -28,7 +28,10 @@
 
 #include <glib/gi18n.h>
 
+static char *opt_osname;
+
 static GOptionEntry options[] = {
+  { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Specify operating system root to use", NULL },
   { NULL }
 };
 
@@ -37,57 +40,55 @@ ot_admin_builtin_diff (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GE
 {
   GOptionContext *context;
   gboolean ret = FALSE;
-  const char *osname;
-  GFile *ostree_dir = admin_opts->ostree_dir;
+  gs_free char *booted_osname = NULL;
   ot_lobj GFile *repo_path = NULL;
-  ot_lobj GFile *deployment = NULL;
+  gs_unref_object OtDeployment *deployment = NULL;
+  gs_unref_object GFile *deployment_dir = NULL;
   ot_lobj GFile *deploy_parent = NULL;
   ot_lptrarray GPtrArray *modified = NULL;
   ot_lptrarray GPtrArray *removed = NULL;
   ot_lptrarray GPtrArray *added = NULL;
+  gs_unref_ptrarray GPtrArray *deployments = NULL;
   ot_lobj GFile *orig_etc_path = NULL;
   ot_lobj GFile *new_etc_path = NULL;
   __attribute__((unused)) GCancellable *cancellable = NULL;
+  int bootversion;
 
-  context = g_option_context_new ("OSNAME [REVISION] - Diff configuration for OSNAME");
+  context = g_option_context_new ("Diff current /etc configuration versus default");
 
   g_option_context_add_main_entries (context, options, NULL);
 
   if (!g_option_context_parse (context, &argc, &argv, error))
     goto out;
   
-  repo_path = g_file_get_child (ostree_dir, "repo");
+  repo_path = g_file_resolve_relative_path (admin_opts->sysroot, "ostree/repo");
 
-  if (argc < 2)
+  if (!ot_admin_list_deployments (admin_opts->sysroot, &bootversion, &deployments,
+                                  cancellable, error))
     {
-      ot_util_usage_error (context, "OSNAME must be specified", error);
+      g_prefix_error (error, "While listing deployments: ");
       goto out;
     }
 
-  osname = argv[1];
-
-  if (argc > 2)
-    {
-      deployment = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, argv[2], NULL);
-      if (!g_file_query_exists (deployment, NULL))
-        {
-          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                       "Deployment %s doesn't exist", gs_file_get_path_cached (deployment));
-          goto out;
-        }
-    }
-  else
+  if (!ot_admin_require_deployment_or_osname (admin_opts->sysroot, deployments,
+                                              opt_osname, &deployment,
+                                              cancellable, error))
+    goto out;
+  if (deployment != NULL)
+    opt_osname = (char*)ot_deployment_get_osname (deployment);
+  if (deployment == NULL)
+    deployment = ot_admin_get_merge_deployment (deployments, opt_osname, deployment, NULL);
+  if (deployment == NULL)
     {
-      if (!ot_admin_get_current_deployment (ostree_dir, osname, &deployment,
-                                            cancellable, error))
-        goto out;
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+                   "No deployment for OS '%s'", opt_osname);
+      goto out;
     }
 
-  orig_etc_path = g_file_resolve_relative_path (deployment, "etc");
-  deploy_parent = g_file_get_parent (deployment);
-  new_etc_path = ot_gfile_get_child_strconcat (deploy_parent,
-                                               gs_file_get_basename_cached (deployment),
-                                               "-etc", NULL);
+  deployment_dir = ot_admin_get_deployment_directory (admin_opts->sysroot, deployment);
+
+  orig_etc_path = g_file_resolve_relative_path (deployment_dir, "usr/etc");
+  new_etc_path = g_file_resolve_relative_path (deployment_dir, "etc");
   
   modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref);
   removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
diff --git a/src/ostree/ot-admin-builtin-init-fs.c b/src/ostree/ot-admin-builtin-init-fs.c
index d8a5355..dc1a45a 100644
--- a/src/ostree/ot-admin-builtin-init-fs.c
+++ b/src/ostree/ot-admin-builtin-init-fs.c
@@ -75,8 +75,7 @@ ot_admin_builtin_init_fs (int argc, char **argv, OtAdminBuiltinOpts *admin_opts,
     goto out;
   g_clear_object (&child);
 
-  child = g_file_get_child (dir, "ostree");
-  if (!ot_admin_ensure_initialized (child, cancellable, error))
+  if (!ot_admin_ensure_initialized (dir, cancellable, error))
     goto out;
 
   ret = TRUE;
diff --git a/src/ostree/ot-admin-builtin-install.c b/src/ostree/ot-admin-builtin-install.c
index 5f5fb3d..675f52c 100644
--- a/src/ostree/ot-admin-builtin-install.c
+++ b/src/ostree/ot-admin-builtin-install.c
@@ -70,7 +70,6 @@ ot_admin_builtin_install (int argc, char **argv, OtAdminBuiltinOpts *admin_opts,
   gboolean ret = FALSE;
   const char *keyfile_arg = NULL;
   const char *treename_arg = NULL;
-  GFile *ostree_dir = admin_opts->ostree_dir;
   ot_lobj GFile *deploy_dir = NULL;
   ot_lobj GFile *osdir = NULL;
   ot_lobj GFile *dest_osconfig_path = NULL;
@@ -96,14 +95,7 @@ ot_admin_builtin_install (int argc, char **argv, OtAdminBuiltinOpts *admin_opts,
       goto out;
     }
 
-  if (admin_opts->ostree_dir == NULL)
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "No existing /ostree found; use --ostree-dir");
-      goto out;
-    }
-
-  if (!ot_admin_ensure_initialized (admin_opts->ostree_dir, cancellable, error))
+  if (!ot_admin_ensure_initialized (admin_opts->sysroot, cancellable, error))
     goto out;
 
   self->loop = g_main_loop_new (NULL, TRUE);
@@ -136,11 +128,11 @@ ot_admin_builtin_install (int argc, char **argv, OtAdminBuiltinOpts *admin_opts,
 
   osname = g_key_file_get_string (keyfile, "os", "Name", error);
 
-  ostree_dir_arg = g_strconcat ("--ostree-dir=",
-                                gs_file_get_path_cached (ostree_dir),
+  ostree_dir_arg = g_strconcat ("--sysroot=",
+                                gs_file_get_path_cached (admin_opts->sysroot),
                                 NULL);
 
-  if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+  if (!gs_subprocess_simple_run_sync (NULL,
                                       GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
                                       cancellable, error,
                                       "ostree", "admin", ostree_dir_arg, "os-init", osname, NULL))
@@ -157,7 +149,7 @@ ot_admin_builtin_install (int argc, char **argv, OtAdminBuiltinOpts *admin_opts,
         goto out;
     }
 
-  osdir = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, NULL);
+  osdir = ot_gfile_get_child_build_path (admin_opts->sysroot, "ostree", "deploy", osname, NULL);
   dest_osconfig_path = ot_gfile_get_child_strconcat (osdir, osname, ".cfg", NULL);
 
   if (!g_file_copy (self->osconfig_path, dest_osconfig_path, G_FILE_COPY_OVERWRITE | 
G_FILE_COPY_TARGET_DEFAULT_PERMS,
@@ -168,7 +160,7 @@ ot_admin_builtin_install (int argc, char **argv, OtAdminBuiltinOpts *admin_opts,
     goto out;
 
   repoarg = g_strconcat ("--repo=",
-                         gs_file_get_path_cached (ostree_dir), "/repo",
+                         gs_file_get_path_cached (admin_opts->sysroot), "/ostree/repo",
                          NULL);
 
   {
@@ -178,7 +170,7 @@ ot_admin_builtin_install (int argc, char **argv, OtAdminBuiltinOpts *admin_opts,
     if (!repourl)
       goto out;
     
-    if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+    if (!gs_subprocess_simple_run_sync (NULL,
                                         GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
                                         cancellable, error,
                                         "ostree", repoarg, "remote", "add",
@@ -186,13 +178,13 @@ ot_admin_builtin_install (int argc, char **argv, OtAdminBuiltinOpts *admin_opts,
       goto out;
   }
 
-  if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+  if (!gs_subprocess_simple_run_sync (NULL,
                                       GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
                                       cancellable, error,
                                         "ostree", "pull", repoarg, osname, NULL))
     goto out;
 
-  if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+  if (!gs_subprocess_simple_run_sync (NULL,
                                       GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
                                       cancellable, error,
                                       "ostree", "admin", ostree_dir_arg, "deploy", osname,
diff --git a/src/ostree/ot-admin-builtin-os-init.c b/src/ostree/ot-admin-builtin-os-init.c
index 0536167..8d725fd 100644
--- a/src/ostree/ot-admin-builtin-os-init.c
+++ b/src/ostree/ot-admin-builtin-os-init.c
@@ -38,7 +38,6 @@ ot_admin_builtin_os_init (int argc, char **argv, OtAdminBuiltinOpts *admin_opts,
   GOptionContext *context;
   gboolean ret = FALSE;
   const char *osname = NULL;
-  GFile *ostree_dir = admin_opts->ostree_dir;
   ot_lobj GFile *deploy_dir = NULL;
   ot_lobj GFile *dir = NULL;
   __attribute__((unused)) GCancellable *cancellable = NULL;
@@ -49,7 +48,7 @@ ot_admin_builtin_os_init (int argc, char **argv, OtAdminBuiltinOpts *admin_opts,
   if (!g_option_context_parse (context, &argc, &argv, error))
     goto out;
 
-  if (!ot_admin_ensure_initialized (ostree_dir, cancellable, error))
+  if (!ot_admin_ensure_initialized (admin_opts->sysroot, cancellable, error))
     goto out;
 
   if (argc < 2)
@@ -60,7 +59,7 @@ ot_admin_builtin_os_init (int argc, char **argv, OtAdminBuiltinOpts *admin_opts,
 
   osname = argv[1];
 
-  deploy_dir = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, NULL);
+  deploy_dir = ot_gfile_get_child_build_path (admin_opts->sysroot, "ostree", "deploy", osname, NULL);
 
   /* Ensure core subdirectories of /var exist, since we need them for
    * dracut generation, and the host will want them too.  Note that at
diff --git a/src/ostree/ot-admin-builtin-prune.c b/src/ostree/ot-admin-builtin-prune.c
index 4d60099..024abd2 100644
--- a/src/ostree/ot-admin-builtin-prune.c
+++ b/src/ostree/ot-admin-builtin-prune.c
@@ -41,15 +41,12 @@ ot_admin_builtin_prune (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, G
 {
   GOptionContext *context;
   gboolean ret = FALSE;
-  guint i;
   const char *osname;
-  GFile *ostree_dir = admin_opts->ostree_dir;
   ot_lobj GFile *repo_path = NULL;
   ot_lobj GFile *deploy_dir = NULL;
   ot_lobj GFile *current_deployment = NULL;
   ot_lobj GFile *previous_deployment = NULL;
   ot_lobj GFile *active_deployment = NULL;
-  ot_lptrarray GPtrArray *deployments = NULL;
   gs_free char *active_osname = NULL;
   __attribute__((unused)) GCancellable *cancellable = NULL;
 
@@ -68,43 +65,10 @@ ot_admin_builtin_prune (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, G
 
   osname = argv[1];
 
-  if (!ot_admin_list_deployments (ostree_dir, osname, &deployments,
-                                  cancellable, error))
+  if (!ot_admin_cleanup (admin_opts->sysroot, cancellable, error))
     goto out;
 
-  if (!ot_admin_get_current_deployment (ostree_dir, osname, &current_deployment,
-                                        cancellable, error));
-  if (!ot_admin_get_previous_deployment (ostree_dir, osname, &previous_deployment,
-                                         cancellable, error));
-  if (!ot_admin_get_active_deployment (ostree_dir, &active_osname, &active_deployment,
-                                       cancellable, error));
-
-  for (i = 0; i < deployments->len; i++)
-    {
-      GFile *deployment = deployments->pdata[i];
-      ot_lobj GFile *deployment_etc = NULL;
-      ot_lobj GFile *parent = NULL;
-
-      if ((current_deployment && g_file_equal (deployment, current_deployment))
-          || (previous_deployment && g_file_equal (deployment, previous_deployment))
-          || (active_deployment && g_file_equal (deployment, active_deployment)))
-        continue;
-
-      parent = g_file_get_parent (deployment);
-      deployment_etc = ot_gfile_get_child_strconcat (parent, gs_file_get_basename_cached (deployment),
-                                                     "-etc", NULL);
-      
-      g_print ("Deleting deployment %s\n", gs_file_get_path_cached (deployment));
-      if (!gs_shutil_rm_rf (deployment, cancellable, error))
-        goto out;
-      /* Note - not atomic; we may be leaving the -etc directory around
-       * if this fails in the middle =/
-       */
-      if (!gs_shutil_rm_rf (deployment_etc, cancellable, error))
-        goto out;
-    }
-  
-  repo_path = g_file_get_child (ostree_dir, "repo");
+  repo_path = g_file_resolve_relative_path (admin_opts->sysroot, "ostree/repo");
 
   if (!opt_no_repo_prune)
     {
@@ -112,7 +76,7 @@ ot_admin_builtin_prune (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, G
 
       repo_arg = g_strconcat ("--repo=", gs_file_get_path_cached (repo_path), NULL);
       
-      if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+      if (!gs_subprocess_simple_run_sync (NULL,
                                           GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
                                           cancellable, error,
                                           "ostree", repo_arg, "prune", "--refs-only",
diff --git a/src/ostree/ot-admin-builtin-status.c b/src/ostree/ot-admin-builtin-status.c
new file mode 100644
index 0000000..3e26d9c
--- /dev/null
+++ b/src/ostree/ot-admin-builtin-status.c
@@ -0,0 +1,100 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012,2013 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-builtins.h"
+#include "ot-admin-functions.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static GOptionEntry options[] = {
+  { NULL }
+};
+
+gboolean
+ot_admin_builtin_status (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error)
+{
+  GOptionContext *context;
+  gboolean ret = FALSE;
+  int bootversion;
+  gs_unref_object OtDeployment *booted_deployment = NULL;
+  gs_unref_ptrarray GPtrArray *deployments = NULL;
+  __attribute__((unused)) GCancellable *cancellable = NULL;
+  guint i;
+
+  context = g_option_context_new ("List deployments");
+
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (!ot_admin_list_deployments (admin_opts->sysroot, &bootversion, &deployments,
+                                  cancellable, error))
+    {
+      g_prefix_error (error, "While listing deployments: ");
+      goto out;
+    }
+
+  /* Find the currently booted deployment, if any; we will
+   * ensure it is present in the new deployment list.
+   */
+  if (!ot_admin_find_booted_deployment (admin_opts->sysroot, deployments,
+                                        &booted_deployment,
+                                        cancellable, error))
+    goto out;
+
+  if (deployments->len == 0)
+    {
+      g_print ("No deployments.\n");
+    }
+  else
+    {
+      int subbootversion;
+
+      if (!ot_admin_read_current_subbootversion (admin_opts->sysroot, bootversion,
+                                                 &subbootversion,
+                                                 cancellable, error))
+        goto out;
+
+      g_print ("bootversion: %d.%d\n", bootversion, subbootversion);
+
+      for (i = 0; i < deployments->len; i++)
+        {
+          OtDeployment *deployment = deployments->pdata[i];
+          g_print ("%u: %c %s %s.%d\n",
+                   i,
+                   deployment == booted_deployment ? '*' : ' ',
+                   ot_deployment_get_osname (deployment),
+                   ot_deployment_get_csum (deployment),
+                   ot_deployment_get_deployserial (deployment));
+        }
+    }
+
+  ret = TRUE;
+ out:
+  if (context)
+    g_option_context_free (context);
+  return ret;
+}
diff --git a/src/ostree/ot-admin-builtin-upgrade.c b/src/ostree/ot-admin-builtin-upgrade.c
index f13ce21..f64837f 100644
--- a/src/ostree/ot-admin-builtin-upgrade.c
+++ b/src/ostree/ot-admin-builtin-upgrade.c
@@ -24,6 +24,7 @@
 
 #include "ot-admin-builtins.h"
 #include "ot-admin-functions.h"
+#include "ot-admin-deploy.h"
 #include "ostree.h"
 #include "otutil.h"
 
@@ -32,8 +33,10 @@
 #include <glib/gi18n.h>
 
 static gboolean opt_reboot;
+static char *opt_osname;
 
 static GOptionEntry options[] = {
+  { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Specify operating system root to use", NULL },
   { "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Reboot after a successful upgrade", NULL },
   { NULL }
 };
@@ -41,79 +44,108 @@ static GOptionEntry options[] = {
 gboolean
 ot_admin_builtin_upgrade (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error)
 {
-  GOptionContext *context;
   gboolean ret = FALSE;
-  GFile *ostree_dir = admin_opts->ostree_dir;
+  __attribute__((unused)) GCancellable *cancellable = NULL;
+  GOptionContext *context;
+  GFile *sysroot = admin_opts->sysroot;
   gs_free char *booted_osname = NULL;
-  const char *osname = NULL;
-  gs_unref_object GFile *deployment = NULL;
-  gs_unref_object GFile *repo_path = NULL;
   gs_unref_object OstreeRepo *repo = NULL;
-  gs_free char *deploy_name = NULL;
-  gs_free char *current_rev = NULL;
-  gs_free char *new_rev = NULL;
+  gs_unref_object GFile *repo_path = NULL;
+  gs_free char *origin_refspec = NULL;
+  gs_free char *origin_remote = NULL;
+  gs_free char *origin_ref = NULL;
+  gs_free char *new_revision = NULL;
+  gs_unref_object GFile *deployment_path = NULL;
+  gs_unref_object GFile *deployment_origin_path = NULL;
+  gs_unref_object OtDeployment *booted_deployment = NULL;
+  gs_unref_object OtDeployment *merge_deployment = NULL;
+  gs_unref_ptrarray GPtrArray *current_deployments = NULL;
+  gs_unref_ptrarray GPtrArray *new_deployments = NULL;
+  gs_unref_object OtDeployment *new_deployment = NULL;
   gs_free char *ostree_dir_arg = NULL;
-  __attribute__((unused)) GCancellable *cancellable = NULL;
+  int current_bootversion;
+  int new_bootversion;
+  GKeyFile *origin;
 
-  context = g_option_context_new ("[OSNAME] - pull, deploy, and prune");
+  context = g_option_context_new ("Construct new tree from current origin and deploy it, if it changed");
   g_option_context_add_main_entries (context, options, NULL);
 
   if (!g_option_context_parse (context, &argc, &argv, error))
     goto out;
 
-  if (argc > 1)
-    {
-      osname = argv[1];
-    }
-  else
+  if (!ot_admin_list_deployments (admin_opts->sysroot, &current_bootversion,
+                                  &current_deployments,
+                                  cancellable, error))
     {
-      if (!ot_admin_get_booted_os (&booted_osname, NULL,
-                                   cancellable, error))
-        goto out;
-      if (booted_osname == NULL)
-        {
-          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                       "Not in an active OSTree system; must specify OSNAME");
-          goto out;
-        }
-      osname = booted_osname;
+      g_prefix_error (error, "While listing deployments: ");
+      goto out;
     }
-  
-  if (!ot_admin_get_current_deployment (ostree_dir, osname, &deployment,
-                                        cancellable, error))
-    goto out;
-
-  ot_admin_parse_deploy_name (ostree_dir, osname, deployment, &deploy_name, &current_rev);
 
-  ostree_dir_arg = g_strconcat ("--ostree-dir=",
-                                gs_file_get_path_cached (ostree_dir),
-                                NULL);
-  
-  if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
-                                      GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
-                                      cancellable, error,
-                                      "ostree", "admin", ostree_dir_arg, "pull-deploy", osname, NULL))
+  if (!ot_admin_require_deployment_or_osname (admin_opts->sysroot, current_deployments,
+                                              opt_osname,
+                                              &booted_deployment,
+                                              cancellable, error))
     goto out;
-
-  if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
-                                      GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
-                                      cancellable, error,
-                                      "ostree", "admin", ostree_dir_arg, "prune", osname, NULL))
+  if (!opt_osname)
+    opt_osname = (char*)ot_deployment_get_osname (booted_deployment);
+  merge_deployment = ot_admin_get_merge_deployment (current_deployments, opt_osname,
+                                                    booted_deployment,
+                                                    NULL);
+
+  deployment_path = ot_admin_get_deployment_directory (admin_opts->sysroot, merge_deployment);
+  deployment_origin_path = ot_admin_get_deployment_origin_path (deployment_path);
+
+  repo_path = g_file_resolve_relative_path (admin_opts->sysroot, "ostree/repo");
+  repo = ostree_repo_new (repo_path);
+  if (!ostree_repo_check (repo, error))
     goto out;
 
-  if (opt_reboot)
+  origin = ot_deployment_get_origin (merge_deployment);
+  if (!origin)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "No origin known for current deployment");
+      goto out;
+    }
+  origin_refspec = g_key_file_get_string (origin, "origin", "refspec", NULL);
+  if (!origin_refspec)
     {
-      repo_path = g_file_get_child (ostree_dir, "repo");
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "No origin/refspec in current deployment origin; cannot upgrade via ostree");
+      goto out;
+    }
+  if (!ostree_parse_refspec (origin_refspec, &origin_remote, &origin_ref, error))
+    goto out;
 
-      repo = ostree_repo_new (repo_path);
-      if (!ostree_repo_check (repo, error))
+  if (origin_remote)
+    {
+      g_print ("Fetching remote %s ref %s\n", origin_remote, origin_ref);
+      if (!ot_admin_pull (admin_opts->sysroot, origin_remote, origin_ref,
+                          cancellable, error))
         goto out;
+    }
 
-      if (!ostree_repo_resolve_rev (repo, deploy_name, TRUE, &new_rev,
-                                    error))
+  if (!ostree_repo_resolve_rev (repo, origin_ref, FALSE, &new_revision,
+                                error))
+    goto out;
+
+  if (strcmp (ot_deployment_get_csum (merge_deployment), new_revision) == 0)
+    {
+      g_print ("Refspec %s is unchanged\n", origin_refspec);
+    }
+  else
+    {
+      gs_unref_object GFile *real_sysroot = g_file_new_for_path ("/");
+      if (!ot_admin_deploy (admin_opts->sysroot,
+                            current_bootversion, current_deployments,
+                            opt_osname, new_revision, origin,
+                            NULL, FALSE,
+                            booted_deployment, merge_deployment,
+                            &new_deployment, &new_bootversion, &new_deployments,
+                            cancellable, error))
         goto out;
 
-      if (strcmp (current_rev, new_rev) != 0 && opt_reboot)
+      if (opt_reboot && g_file_equal (sysroot, real_sysroot))
         {
           gs_subprocess_simple_run_sync (NULL, GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
                                          cancellable, error,
diff --git a/src/ostree/ot-admin-builtins.h b/src/ostree/ot-admin-builtins.h
index 5fbdacd..9924086 100644
--- a/src/ostree/ot-admin-builtins.h
+++ b/src/ostree/ot-admin-builtins.h
@@ -28,8 +28,7 @@
 G_BEGIN_DECLS
 
 typedef struct {
-  GFile *ostree_dir;
-  GFile *boot_dir;
+  GFile *sysroot;
 } OtAdminBuiltinOpts;
 
 gboolean ot_admin_builtin_os_init (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
@@ -37,10 +36,9 @@ gboolean ot_admin_builtin_install (int argc, char **argv, OtAdminBuiltinOpts *ad
 gboolean ot_admin_builtin_init_fs (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
 gboolean ot_admin_builtin_deploy (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
 gboolean ot_admin_builtin_prune (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
-gboolean ot_admin_builtin_pull_deploy (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError 
**error);
+gboolean ot_admin_builtin_status (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
 gboolean ot_admin_builtin_diff (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
 gboolean ot_admin_builtin_run_triggers (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError 
**error);
-gboolean ot_admin_builtin_update_kernel (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError 
**error);
 gboolean ot_admin_builtin_upgrade (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
 
 G_END_DECLS
diff --git a/src/ostree/ot-admin-deploy.c b/src/ostree/ot-admin-deploy.c
new file mode 100644
index 0000000..f0f9557
--- /dev/null
+++ b/src/ostree/ot-admin-deploy.c
@@ -0,0 +1,1245 @@
+/* -*- 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 "ot-admin-deploy.h"
+#include "ot-deployment.h"
+#include "ot-config-parser.h"
+#include "ot-bootloader-syslinux.h"
+#include "otutil.h"
+#include "ostree-core.h"
+#include "libgsystem.h"
+
+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);
+}
+
+
+/**
+ * copy_one_config_file:
+ *
+ * Copy @file from @modified_etc to @new_etc, overwriting any existing
+ * file there.
+ */
+static gboolean
+copy_one_config_file (GFile              *orig_etc,
+                      GFile              *modified_etc,
+                      GFile              *new_etc,
+                      GFile              *src,
+                      GCancellable       *cancellable,
+                      GError            **error)
+{
+  gboolean ret = FALSE;
+  ot_lobj GFileInfo *src_info = NULL;
+  ot_lobj GFile *dest = NULL;
+  ot_lobj GFile *parent = NULL;
+  ot_lfree char *relative_path = NULL;
+  
+  relative_path = g_file_get_relative_path (modified_etc, src);
+  g_assert (relative_path);
+  dest = g_file_resolve_relative_path (new_etc, relative_path);
+
+  src_info = g_file_query_info (src, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                cancellable, error);
+  if (!src_info)
+    goto out;
+
+  if (g_file_info_get_file_type (src_info) == G_FILE_TYPE_DIRECTORY)
+    {
+      ot_lobj GFileEnumerator *src_enum = NULL;
+      ot_lobj GFileInfo *child_info = NULL;
+      GError *temp_error = NULL;
+
+      /* FIXME actually we need to copy permissions and xattrs */
+      if (!gs_file_ensure_directory (dest, TRUE, cancellable, error))
+        goto out;
+
+      src_enum = g_file_enumerate_children (src, OSTREE_GIO_FAST_QUERYINFO,
+                                            G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                            cancellable, error);
+
+      while ((child_info = g_file_enumerator_next_file (src_enum, cancellable, error)) != NULL)
+        {
+          ot_lobj GFile *child = g_file_get_child (src, g_file_info_get_name (child_info));
+
+          if (!copy_one_config_file (orig_etc, modified_etc, new_etc, child,
+                                     cancellable, error))
+            goto out;
+        }
+      g_clear_object (&child_info);
+      if (temp_error != NULL)
+        {
+          g_propagate_error (error, temp_error);
+          goto out;
+        }
+    }
+  else
+    {
+      parent = g_file_get_parent (dest);
+
+      /* FIXME actually we need to copy permissions and xattrs */
+      if (!gs_file_ensure_directory (parent, TRUE, cancellable, error))
+        goto out;
+      
+      /* We unlink here because otherwise gio throws an error on
+       * dangling symlinks.
+       */
+      if (!ot_gfile_ensure_unlinked (dest, cancellable, error))
+        goto out;
+
+      if (!g_file_copy (src, dest, 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;
+}
+
+/**
+ * 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 (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;
+  ot_lptrarray GPtrArray *removed = NULL;
+  ot_lptrarray GPtrArray *added = NULL;
+  guint i;
+
+  modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref);
+  removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+  added = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+
+  if (!ostree_diff_dirs (orig_etc, modified_etc, modified, removed, added,
+                         cancellable, error))
+    {
+      g_prefix_error (error, "While computing configuration diff: ");
+      goto out;
+    }
+
+  if (modified->len > 0 || removed->len > 0 || added->len > 0)
+    g_print ("ostadmin: Processing config: %u modified, %u removed, %u added\n", 
+             modified->len,
+             removed->len,
+             added->len);
+  else
+    g_print ("ostadmin: No modified configuration\n");
+
+  for (i = 0; i < removed->len; i++)
+    {
+      GFile *file = removed->pdata[i];
+      ot_lobj GFile *target_file = NULL;
+      ot_lfree char *path = NULL;
+
+      path = g_file_get_relative_path (orig_etc, file);
+      g_assert (path);
+      target_file = g_file_resolve_relative_path (new_etc, path);
+
+      if (!ot_gfile_ensure_unlinked (target_file, cancellable, error))
+        goto out;
+    }
+
+  for (i = 0; i < modified->len; i++)
+    {
+      OstreeDiffItem *diff = modified->pdata[i];
+
+      if (!copy_one_config_file (orig_etc, modified_etc, new_etc, diff->target,
+                                 cancellable, error))
+        goto out;
+    }
+  for (i = 0; i < added->len; i++)
+    {
+      GFile *file = added->pdata[i];
+
+      if (!copy_one_config_file (orig_etc, modified_etc, new_etc, file,
+                                 cancellable, error))
+        goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+/**
+ * checkout_deployment_tree:
+ *
+ * Look up @revision in the repository, and check it out in
+ * /ostree/deploy/OS/deploy/${treecsum}.${deployserial}.
+ */
+static gboolean
+checkout_deployment_tree (GFile             *sysroot,
+                          OstreeRepo        *repo,
+                          OtDeployment      *deployment,
+                          GFile            **out_deployment_path,
+                          GCancellable      *cancellable,
+                          GError           **error)
+{
+  gboolean ret = FALSE;
+  const char *csum = ot_deployment_get_csum (deployment);
+  gs_unref_object OstreeRepoFile *root = NULL;
+  gs_unref_object GFileInfo *file_info = NULL;
+  gs_unref_object GFileInfo *existing_checkout_info = NULL;
+  gs_free char *checkout_target_name = NULL;
+  gs_free char *checkout_target_tmp_name = NULL;
+  gs_unref_object GFile *osdeploy_path = NULL;
+  gs_unref_object GFile *deploy_target_path = NULL;
+  gs_unref_object GFile *deploy_parent = NULL;
+  ProcessOneCheckoutData checkout_data;
+
+  root = (OstreeRepoFile*)ostree_repo_file_new_root (repo, csum);
+  if (!ostree_repo_file_ensure_resolved (root, error))
+    goto out;
+
+  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;
+
+  osdeploy_path = ot_gfile_get_child_build_path (sysroot, "ostree", "deploy",
+                                                 ot_deployment_get_osname (deployment),
+                                                 "deploy", NULL);
+  checkout_target_name = g_strdup_printf ("%s.%d", csum, ot_deployment_get_deployserial (deployment));
+  deploy_target_path = g_file_get_child (osdeploy_path, checkout_target_name);
+
+  deploy_parent = g_file_get_parent (deploy_target_path);
+  if (!gs_file_ensure_directory (deploy_parent, TRUE, cancellable, error))
+    goto out;
+  
+  g_print ("ostadmin: Creating deployment %s\n",
+           gs_file_get_path_cached (deploy_target_path));
+
+  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 (repo, 0, 0, deploy_target_path, 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;
+
+  ret = TRUE;
+  ot_transfer_out_value (out_deployment_path, &deploy_target_path);
+ out:
+  return ret;
+}
+
+static gboolean
+merge_configuration (GFile             *sysroot,
+                     OtDeployment      *previous_deployment,
+                     OtDeployment      *deployment,
+                     GFile             *deployment_path,
+                     GCancellable      *cancellable,
+                     GError           **error)
+{
+  gboolean ret = FALSE;
+  gs_unref_object GFile *source_etc_path = NULL;
+  gs_unref_object GFile *source_etc_pristine_path = NULL;
+  gs_unref_object GFile *deployment_usretc_path = NULL;
+  gs_unref_object GFile *deployment_etc_path = NULL;
+  gboolean etc_exists;
+  gboolean usretc_exists;
+
+  if (previous_deployment)
+    {
+      gs_unref_object GFile *previous_path = NULL;
+      OtConfigParser *previous_bootconfig;
+
+      previous_path = ot_admin_get_deployment_directory (sysroot, previous_deployment);
+      source_etc_path = g_file_resolve_relative_path (previous_path, "etc");
+      source_etc_pristine_path = g_file_resolve_relative_path (previous_path, "usr/etc");
+
+      previous_bootconfig = ot_deployment_get_bootconfig (previous_deployment);
+      if (previous_bootconfig)
+        {
+          const char *previous_options = ot_config_parser_get (previous_bootconfig, "options");
+          /* Completely overwrite the previous options here; we will extend
+           * them later.
+           */
+          ot_config_parser_set (ot_deployment_get_bootconfig (deployment), "options",
+                                previous_options);
+        }
+    }
+
+  deployment_etc_path = g_file_get_child (deployment_path, "etc");
+  deployment_usretc_path = g_file_resolve_relative_path (deployment_path, "usr/etc");
+  
+  etc_exists = g_file_query_exists (deployment_etc_path, NULL);
+  usretc_exists = g_file_query_exists (deployment_usretc_path, NULL);
+
+  if (etc_exists && usretc_exists)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+                           "Tree contains both /etc and /usr/etc");
+      goto out;
+    }
+  else if (etc_exists)
+    {
+      /* Compatibility hack */
+      if (!gs_file_rename (deployment_etc_path, deployment_usretc_path,
+                           cancellable, error))
+        goto out;
+      usretc_exists = TRUE;
+      etc_exists = FALSE;
+    }
+  
+  if (usretc_exists)
+    {
+      g_assert (!etc_exists);
+      if (!gs_shutil_cp_a (deployment_usretc_path, deployment_etc_path,
+                           cancellable, error))
+        goto out;
+      g_print ("ostadmin: Created %s\n", gs_file_get_path_cached (deployment_etc_path));
+    }
+
+  if (source_etc_path)
+    {
+      if (!merge_etc_changes (source_etc_pristine_path, source_etc_path, deployment_etc_path, 
+                              cancellable, error))
+        goto out;
+    }
+  else
+    {
+      g_print ("ostadmin: No previous configuration changes to merge\n");
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+write_origin_file (GFile             *sysroot,
+                   OtDeployment      *deployment,
+                   GCancellable      *cancellable,
+                   GError           **error)
+{
+  gboolean ret = FALSE;
+  GKeyFile *origin = ot_deployment_get_origin (deployment);
+
+  if (origin)
+    {
+      gs_unref_object GFile *deployment_path = ot_admin_get_deployment_directory (sysroot, deployment);
+      gs_unref_object GFile *origin_path = ot_admin_get_deployment_origin_path (deployment_path);
+      gs_free char *contents = NULL;
+      gsize len;
+
+      contents = g_key_file_to_data (origin, &len, error);
+      if (!contents)
+        goto out;
+
+      if (!g_file_replace_contents (origin_path, contents, len, NULL, FALSE,
+                                    G_FILE_CREATE_REPLACE_DESTINATION, NULL,
+                                    cancellable, error))
+        goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+get_kernel_from_tree (GFile         *deployroot,
+                      GFile        **out_kernel,
+                      GFile        **out_initramfs,
+                      GCancellable  *cancellable,
+                      GError       **error)
+{
+  gboolean ret = FALSE;
+  gs_unref_object GFile *bootdir = g_file_get_child (deployroot, "boot");
+  gs_unref_object GFileEnumerator *dir_enum = NULL;
+  gs_unref_object GFileInfo *file_info = NULL;
+  gs_unref_object GFile *ret_kernel = NULL;
+  gs_unref_object GFile *ret_initramfs = NULL;
+
+  dir_enum = g_file_enumerate_children (bootdir, OSTREE_GIO_FAST_QUERYINFO,
+                                        G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                        NULL, error);
+  if (!dir_enum)
+    goto out;
+
+  while (TRUE)
+    {
+      GFileInfo *file_info = NULL;
+      const char *name;
+
+      if (!gs_file_enumerator_iterate (dir_enum, &file_info, NULL,
+                                       cancellable, error))
+        goto out;
+      if (file_info == NULL)
+        break;
+
+      name = g_file_info_get_name (file_info);
+
+      if (ret_kernel == NULL && g_str_has_prefix (name, "vmlinuz-"))
+        ret_kernel = g_file_get_child (bootdir, name);
+      else if (ret_initramfs == NULL && g_str_has_prefix (name, "initramfs-"))
+        ret_initramfs = g_file_get_child (bootdir, name);
+      
+      if (ret_kernel && ret_initramfs)
+        break;
+    }
+
+  if (ret_kernel == NULL)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+                   "Failed to find boot/vmlinuz-CHECKSUM in %s",
+                   gs_file_get_path_cached (deployroot));
+      goto out;
+    }
+
+  ot_transfer_out_value (out_kernel, &ret_kernel);
+  ot_transfer_out_value (out_initramfs, &ret_initramfs);
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+checksum_from_kernel_src (GFile        *src,
+                          char        **out_checksum,
+                          GError     **error)
+{
+  const char *last_dash = strrchr (gs_file_get_path_cached (src), '-');
+  if (!last_dash)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Malformed initramfs name '%s', missing '-'", gs_file_get_basename_cached (src));
+      return FALSE;
+    }
+  *out_checksum = g_strdup (last_dash + 1);
+  return TRUE;
+}
+
+static int
+sort_by_bootserial (gconstpointer ap, gconstpointer bp)
+{
+  OtDeployment **a_loc = (OtDeployment**)ap;
+  OtDeployment *a = *a_loc;
+  OtDeployment **b_loc = (OtDeployment**)bp;
+  OtDeployment *b = *b_loc;
+
+  if (ot_deployment_get_bootserial (a) == ot_deployment_get_bootserial (b))
+    return 0;
+  else if (ot_deployment_get_bootserial (a) < ot_deployment_get_bootserial (b))
+    return -1;
+  return 1;
+}
+
+static GPtrArray *
+filter_deployments_by_bootcsum (GPtrArray    *deployments,
+                                const char   *osname,
+                                const char   *bootcsum)
+{
+  GPtrArray *ret = g_ptr_array_new ();
+  guint i;
+
+  for (i = 0; i < deployments->len; i++)
+    {
+      OtDeployment *deployment = deployments->pdata[i];
+      
+      if (strcmp (ot_deployment_get_osname (deployment), osname) != 0)
+        continue;
+      if (strcmp (ot_deployment_get_bootcsum (deployment), bootcsum) != 0)
+        continue;
+      
+      g_ptr_array_add (ret, deployment);
+    }
+  g_ptr_array_sort (ret, sort_by_bootserial);
+
+  return ret;
+}
+
+static void
+compute_new_deployment_list (int           current_bootversion,
+                             GPtrArray    *current_deployments,
+                             const char   *osname,
+                             OtDeployment *booted_deployment,
+                             OtDeployment *merge_deployment,
+                             gboolean      retain,
+                             const char   *revision,
+                             const char   *bootcsum,
+                             GPtrArray   **out_new_deployments,
+                             int          *out_new_bootversion)
+{
+  guint i;
+  int new_index;
+  guint new_deployserial = 0;
+  int new_bootserial = 0;
+  gs_unref_object OtDeployment *new_deployment = NULL;
+  gs_unref_ptrarray GPtrArray *matching_deployments_by_bootserial = NULL;
+  OtDeployment *deployment_to_delete = NULL;
+  gs_unref_ptrarray GPtrArray *ret_new_deployments = NULL;
+  gboolean requires_new_bootversion;
+
+  if (osname == NULL)
+    osname = ot_deployment_get_osname (booted_deployment);
+
+  /* First, compute the serial for this deployment; we look
+   * for other ones in this os with the same checksum.
+   */
+  for (i = 0; i < current_deployments->len; i++)
+    {
+      OtDeployment *deployment = current_deployments->pdata[i];
+      
+      if (strcmp (ot_deployment_get_osname (deployment), osname) != 0)
+        continue;
+      if (strcmp (ot_deployment_get_csum (deployment), revision) != 0)
+        continue;
+
+      new_deployserial = MAX(new_deployserial, ot_deployment_get_deployserial (deployment)+1);
+    }
+
+  /* We retain by default (well, hardcoded now) one previous
+   * deployment for this OS, plus the booted deployment.  Usually, we
+   * have one previous, one into which we're booted, and we're
+   * deploying a new one.  So the old previous will get swapped out,
+   * and booted becomes previous.
+   *
+   * But if the user then upgrades again, we will end up pruning the
+   * front of the deployment list.  We never delete the running
+   * deployment.
+   */
+  if (!retain)
+    {
+      for (i = 0; i < current_deployments->len; i++)
+        {
+          OtDeployment *deployment = current_deployments->pdata[i];
+      
+          if (strcmp (ot_deployment_get_osname (deployment), osname) != 0)
+            continue;
+
+          // Keep both the booted and merge deployments
+          if (ot_deployment_equal (deployment, booted_deployment) || 
+              ot_deployment_equal (deployment, merge_deployment))
+            continue;
+
+          deployment_to_delete = deployment;
+        }
+    }
+
+  /* We need to update the bootloader only if the deployment we're
+   * removing uses a different kernel.
+   */
+  requires_new_bootversion =
+    (deployment_to_delete == NULL) ||
+    (strcmp (ot_deployment_get_bootcsum (deployment_to_delete), bootcsum) != 0);
+
+  ret_new_deployments = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
+
+  new_deployment = ot_deployment_new (0, osname, revision, new_deployserial,
+                                      bootcsum, new_bootserial);
+  g_ptr_array_add (ret_new_deployments, g_object_ref (new_deployment));
+  new_index = 1;
+  for (i = 0; i < current_deployments->len; i++)
+    {
+      OtDeployment *orig_deployment = current_deployments->pdata[i];
+      gs_unref_object OtDeployment *deployment_clone = NULL;
+
+      if (orig_deployment == deployment_to_delete)
+        continue;
+
+      deployment_clone = ot_deployment_clone (orig_deployment);
+      ot_deployment_set_index (deployment_clone, new_index);
+      new_index++;
+      g_ptr_array_add (ret_new_deployments, g_object_ref (deployment_clone));
+    }
+
+  /* Just renumber the deployments for the OS we're adding; we don't
+   * handle anything else at the moment.
+   */
+  matching_deployments_by_bootserial = filter_deployments_by_bootcsum (ret_new_deployments,
+                                                                       osname, bootcsum);
+  for (i = 0; i < matching_deployments_by_bootserial->len; i++)
+    {
+      OtDeployment *deployment = matching_deployments_by_bootserial->pdata[i];
+      ot_deployment_set_bootserial (deployment, i);
+    }
+
+  *out_new_deployments = ret_new_deployments;
+  ret_new_deployments = NULL;
+  g_assert (current_bootversion == 0 || current_bootversion == 1);
+  if (requires_new_bootversion)
+    *out_new_bootversion = (current_bootversion == 0) ? 1 : 0;
+  else
+    *out_new_bootversion = current_bootversion;
+}
+
+static GHashTable *
+object_array_to_set (GPtrArray   *objlist,
+                     GHashFunc    hashfunc,
+                     GEqualFunc   equalfunc)
+{
+  GHashTable *ret = g_hash_table_new_full (hashfunc, equalfunc, g_object_unref, NULL);
+  guint i;
+
+  for (i = 0; i < objlist->len; i++)
+    {
+      GObject *obj = g_object_ref (objlist->pdata[i]);
+      g_hash_table_insert (ret, obj, obj);
+    }
+  
+  return ret;
+}
+
+static GHashTable *
+object_set_subtract (GHashTable *a, GHashTable *b)
+{
+  GHashTable *ret = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
+  GHashTableIter hashiter;
+  gpointer hashkey, hashvalue;
+  
+  g_hash_table_iter_init (&hashiter, a);
+  while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue))
+    {
+      if (!g_hash_table_contains (b, hashkey))
+        {
+          GObject *o = g_object_ref (hashkey);
+          g_hash_table_insert (ret, o, o);
+        }
+    }
+
+  return ret;
+}
+
+static void
+print_deployment_set (gboolean    for_removal,
+                      GHashTable *set)
+{
+  GHashTableIter hashiter;
+  gpointer hashkey, hashvalue;
+
+  if (g_hash_table_size (set) == 0)
+    return;
+
+  g_print ("%s\n", for_removal ? "removed:" : "added: ");
+
+  g_hash_table_iter_init (&hashiter, set);
+  while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue))
+    {
+      OtDeployment *deployment = hashkey;
+
+      g_print ("  %c %s %s.%d",
+               for_removal ? '-' : '+', ot_deployment_get_osname (deployment),
+               ot_deployment_get_csum (deployment),
+               ot_deployment_get_deployserial (deployment));
+
+      if (!for_removal)
+        g_print (" index=%d", ot_deployment_get_index (deployment));
+      g_print ("\n");
+    }
+}
+
+static void
+print_deployment_diff (GPtrArray   *current_deployments,
+                       GPtrArray   *new_deployments)
+{
+  gs_unref_hashtable GHashTable *curset = object_array_to_set (current_deployments, ot_deployment_hash, 
ot_deployment_equal);
+  gs_unref_hashtable GHashTable *newset = object_array_to_set (new_deployments, ot_deployment_hash, 
ot_deployment_equal);
+  gs_unref_hashtable GHashTable *removed = NULL;
+  gs_unref_hashtable GHashTable *added = NULL;
+
+  removed = object_set_subtract (curset, newset);
+  added = object_set_subtract (newset, curset);
+
+  print_deployment_set (TRUE, removed);
+  print_deployment_set (FALSE, added);
+}
+
+/* FIXME: We should really do individual fdatasync() on files/dirs,
+ * since this causes us to block on unrelated I/O.  However, it's just
+ * safer for now.
+ */
+static gboolean
+full_system_sync (GCancellable      *cancellable,
+                  GError           **error)
+{
+  sync ();
+  return TRUE;
+}
+
+static gboolean
+swap_bootlinks (GFile        *sysroot,
+                int           current_bootversion,
+                GPtrArray    *new_deployments,
+                GCancellable *cancellable,
+                GError      **error)
+{
+  gboolean ret = FALSE;
+  guint i;
+  int old_subbootversion, new_subbootversion;
+  gs_unref_object GFile *ostree_dir = g_file_get_child (sysroot, "ostree");
+  gs_free char *ostree_bootdir_name = g_strdup_printf ("boot.%d", current_bootversion);
+  gs_unref_object GFile *ostree_bootdir = g_file_resolve_relative_path (ostree_dir, ostree_bootdir_name);
+  gs_free char *ostree_subbootdir_name = NULL;
+  gs_unref_object GFile *ostree_subbootdir = NULL;
+  gs_unref_ptrarray GPtrArray *deployments_to_swap = NULL;
+
+  if (!ot_admin_read_current_subbootversion (sysroot, current_bootversion,
+                                             &old_subbootversion,
+                                             cancellable, error))
+    goto out;
+
+  new_subbootversion = old_subbootversion == 0 ? 1 : 0;
+
+  ostree_subbootdir_name = g_strdup_printf ("boot.%d.%d", current_bootversion, new_subbootversion);
+  ostree_subbootdir = g_file_resolve_relative_path (ostree_dir, ostree_subbootdir_name);
+
+  if (!gs_file_ensure_directory (ostree_subbootdir, TRUE, cancellable, error))
+    goto out;
+
+  for (i = 0; i < new_deployments->len; i++)
+    {
+      OtDeployment *deployment = new_deployments->pdata[i];
+      gs_free char *bootlink_pathname = g_strdup_printf ("%s/%s/%d",
+                                                         ot_deployment_get_osname (deployment),
+                                                         ot_deployment_get_bootcsum (deployment),
+                                                         ot_deployment_get_bootserial (deployment));
+      gs_free char *bootlink_target = g_strdup_printf ("../../../deploy/%s/deploy/%s.%d",
+                                                       ot_deployment_get_osname (deployment),
+                                                       ot_deployment_get_csum (deployment),
+                                                       ot_deployment_get_deployserial (deployment));
+      gs_unref_object GFile *linkname = g_file_get_child (ostree_subbootdir, bootlink_pathname);
+      gs_unref_object GFile *linkname_parent = g_file_get_parent (linkname);
+
+      if (!gs_file_ensure_directory (linkname_parent, TRUE, cancellable, error))
+        goto out;
+
+      if (!g_file_make_symbolic_link (linkname, bootlink_target, cancellable, error))
+        goto out;
+    }
+
+  if (!ot_gfile_atomic_symlink_swap (ostree_bootdir, ostree_subbootdir_name,
+                                     cancellable, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static char *
+remove_checksum_from_kernel_name (const char *name,
+                                  const char *csum)
+{
+  const char *p = strrchr (name, '-');
+  g_assert_cmpstr (p+1, ==, csum);
+  return g_strndup (name, p-name);
+}
+
+static GHashTable *
+parse_os_release (const char *contents,
+                  const char *split)
+{
+  char **lines = g_strsplit (contents, split, -1);
+  char **iter;
+  GHashTable *ret = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+  for (iter = lines; *iter; iter++)
+    {
+      char *line = *iter;
+      char *eq;
+      const char *quotedval;
+      char *val;
+
+      if (g_str_has_prefix (line, "#"))
+        continue;
+      
+      eq = strchr (line, '=');
+      if (!eq)
+        continue;
+      
+      *eq = '\0';
+      quotedval = eq + 1;
+      val = g_shell_unquote (quotedval, NULL);
+      if (!val)
+        continue;
+      
+      g_hash_table_insert (ret, line, val);
+    }
+
+  return ret;
+}
+
+static gboolean
+install_deployment_kernel (GFile          *sysroot,
+                           int             new_bootversion,
+                           OtDeployment   *deployment,
+                           GCancellable   *cancellable,
+                           GError        **error)
+
+{
+  gboolean ret = FALSE;
+  const char *osname = ot_deployment_get_osname (deployment);
+  const char *bootcsum = ot_deployment_get_bootcsum (deployment);
+  gs_unref_object GFile *bootdir = NULL;
+  gs_unref_object GFile *bootcsumdir = NULL;
+  gs_unref_object GFile *bootconfpath = NULL;
+  gs_unref_object GFile *bootconfpath_parent = NULL;
+  gs_free char *dest_kernel_name = NULL;
+  gs_unref_object GFile *dest_kernel_path = NULL;
+  gs_unref_object GFile *dest_initramfs_path = NULL;
+  gs_unref_object GFile *tree_kernel_path = NULL;
+  gs_unref_object GFile *tree_initramfs_path = NULL;
+  gs_unref_object GFile *etc_os_release = NULL;
+  gs_unref_object GFile *deployment_dir = NULL;
+  gs_free char *contents = NULL;
+  gs_unref_hashtable GHashTable *osrelease_values = NULL;
+  gs_free char *linux_relpath = NULL;
+  gs_free char *linux_key = NULL;
+  gs_free char *initramfs_relpath = NULL;
+  gs_free char *title_key = NULL;
+  gs_free char *initrd_key = NULL;
+  gs_free char *version_key = NULL;
+  gs_free char *ostree_kernel_arg = NULL;
+  gs_free char *options_key = NULL;
+  __attribute__((cleanup(ot_ordered_hash_cleanup))) OtOrderedHash *ohash = NULL;
+  const char *val;
+  OtConfigParser *bootconfig;
+  gsize len;
+
+  bootconfig = ot_deployment_get_bootconfig (deployment);
+  deployment_dir = ot_admin_get_deployment_directory (sysroot, deployment);
+
+  if (!get_kernel_from_tree (deployment_dir, &tree_kernel_path, &tree_initramfs_path,
+                             cancellable, error))
+    goto out;
+
+  bootdir = g_file_get_child (sysroot, "boot");
+  bootcsumdir = ot_gfile_resolve_path_printf (bootdir, "ostree/%s-%s",
+                                              osname,
+                                              bootcsum);
+  bootconfpath = ot_gfile_resolve_path_printf (bootdir, "loader.%d/entries/ostree-%s-%s-%d.conf",
+                                               new_bootversion, osname, 
+                                               ot_deployment_get_csum (deployment),
+                                               ot_deployment_get_bootserial (deployment));
+
+  if (!gs_file_ensure_directory (bootcsumdir, TRUE, cancellable, error))
+    goto out;
+  bootconfpath_parent = g_file_get_parent (bootconfpath);
+  if (!gs_file_ensure_directory (bootconfpath_parent, TRUE, cancellable, error))
+    goto out;
+
+  dest_kernel_name = remove_checksum_from_kernel_name (gs_file_get_basename_cached (tree_kernel_path),
+                                                       bootcsum);
+  dest_kernel_path = g_file_get_child (bootcsumdir, dest_kernel_name);
+  if (!g_file_query_exists (dest_kernel_path, NULL))
+    {
+      if (!gs_file_linkcopy_sync_data (tree_kernel_path, dest_kernel_path, G_FILE_COPY_OVERWRITE | 
G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA,
+                                       cancellable, error))
+        goto out;
+    }
+
+  if (tree_initramfs_path)
+    {
+      gs_free char *dest_initramfs_name = remove_checksum_from_kernel_name (gs_file_get_basename_cached 
(tree_initramfs_path),
+                                                                       bootcsum);
+      dest_initramfs_path = g_file_get_child (bootcsumdir, dest_initramfs_name);
+
+      if (!g_file_query_exists (dest_initramfs_path, NULL))
+        {
+          if (!gs_file_linkcopy_sync_data (tree_initramfs_path, dest_initramfs_path, G_FILE_COPY_OVERWRITE | 
G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA,
+                                           cancellable, error))
+            goto out;
+        }
+    }
+
+  etc_os_release = g_file_resolve_relative_path (deployment_dir, "etc/os-release");
+
+  if (!g_file_load_contents (etc_os_release, cancellable,
+                             &contents, &len, NULL, error))
+    {
+      g_prefix_error (error, "Reading /etc/os-release: ");
+      goto out;
+    }
+
+  osrelease_values = parse_os_release (contents, "\n");
+
+  /* title */
+  val = g_hash_table_lookup (osrelease_values, "PRETTY_NAME");
+  if (val == NULL)
+      val = g_hash_table_lookup (osrelease_values, "ID");
+  if (val == NULL)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "No PRETTY_NAME or ID in /etc/os-release");
+      goto out;
+    }
+  
+  title_key = g_strdup_printf ("ostree:%s:%d %s", ot_deployment_get_osname (deployment),
+                               ot_deployment_get_index (deployment),
+                               val);
+  ot_config_parser_set (bootconfig, "title", title_key);
+
+  version_key = g_strdup_printf ("%d", ot_deployment_get_bootserial (deployment));
+  ot_config_parser_set (bootconfig, "version", version_key);
+
+  linux_relpath = g_file_get_relative_path (bootdir, dest_kernel_path);
+  linux_key = g_strconcat ("/", linux_relpath, NULL);
+  ot_config_parser_set (bootconfig, "linux", linux_key);
+
+  if (dest_initramfs_path)
+    {
+      initramfs_relpath = g_file_get_relative_path (bootdir, dest_initramfs_path);
+      initrd_key = g_strconcat ("/", initramfs_relpath, NULL);
+      ot_config_parser_set (bootconfig, "initrd", initrd_key);
+    }
+
+  val = ot_config_parser_get (bootconfig, "options");
+  ostree_kernel_arg = g_strdup_printf ("/ostree/boot.%d/%s/%s/%d",
+                                       new_bootversion, osname, bootcsum,
+                                       ot_deployment_get_bootserial (deployment));
+  ohash = ot_admin_parse_kernel_args (val);
+  ot_ordered_hash_replace_key (ohash, "ostree", ostree_kernel_arg);
+  options_key = ot_admin_kernel_arg_string_serialize (ohash);
+  ot_config_parser_set (bootconfig, "options", options_key);
+  
+  if (!ot_config_parser_write (ot_deployment_get_bootconfig (deployment), bootconfpath,
+                               cancellable, error))
+      goto out;
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+swap_bootloader (GFile          *sysroot,
+                 int             current_bootversion,
+                 int             new_bootversion,
+                 GCancellable   *cancellable,
+                 GError        **error)
+{
+  gboolean ret = FALSE;
+  gs_unref_object GFile *boot_loader_link = NULL;
+  gs_free char *new_target = NULL;
+
+  g_assert ((current_bootversion == 0 && new_bootversion == 1) ||
+            (current_bootversion == 1 && new_bootversion == 0));
+
+  boot_loader_link = g_file_resolve_relative_path (sysroot, "boot/loader");
+  new_target = g_strdup_printf ("loader.%d", new_bootversion);
+
+  if (!ot_gfile_atomic_symlink_swap (boot_loader_link, new_target,
+                                     cancellable, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+gboolean
+ot_admin_deploy (GFile             *sysroot,
+                 int                current_bootversion,
+                 GPtrArray         *current_deployments,
+                 const char        *osname,
+                 const char        *revision,
+                 GKeyFile          *origin,
+                 char             **add_kernel_argv,
+                 gboolean           retain,
+                 OtDeployment      *booted_deployment,
+                 OtDeployment      *provided_merge_deployment,
+                 OtDeployment     **out_new_deployment,
+                 int               *out_new_bootversion,
+                 GPtrArray        **out_new_deployments,
+                 GCancellable      *cancellable,
+                 GError           **error)
+{
+  gboolean ret = FALSE;
+  OtDeployment *new_deployment;
+  gs_unref_object OtDeployment *merge_deployment = NULL;
+  gs_unref_object OtBootloader *bootloader = NULL;
+  gs_unref_object GFile *rootfs = NULL;
+  gs_unref_object OstreeRepo *repo = NULL;
+  gs_unref_object GFile *commit_root = NULL;
+  gs_unref_object GFile *tree_kernel_path = NULL;
+  gs_unref_object GFile *tree_initramfs_path = NULL;
+  gs_unref_object GFile *new_deployment_path = NULL;
+  gs_unref_object GFile *deploy_path = NULL;
+  gs_unref_object GFile *osdir = NULL;
+  gs_free char *new_bootcsum = NULL;
+  gs_unref_object GFile *source_etc_path = NULL;
+  gs_unref_object GFile *source_etc_pristine_path = NULL;
+  gs_unref_object OtConfigParser *bootconfig = NULL;
+  gs_free char *source_etc_kernel_args = NULL;
+  gs_unref_ptrarray GPtrArray *new_deployments = NULL;
+  int new_bootversion;
+  int i;
+
+  if (!ot_admin_get_repo (sysroot, &repo, cancellable, error))
+    goto out;
+
+  /* Here we perform cleanup of any leftover data from previous
+   * partial failures.  This avoids having to call gs_shutil_rm_rf()
+   * at random points throughout the process.
+   *
+   * TODO: Add /ostree/transaction file, and only do this cleanup if
+   * we find it.
+   */
+  if (!ot_admin_cleanup (sysroot, cancellable, error))
+    {
+      g_prefix_error (error, "Performing initial cleanup: ");
+      goto out;
+    }
+
+  if (!ostree_repo_read_commit (repo, revision, &commit_root, cancellable, error))
+    goto out;
+
+  if (!get_kernel_from_tree (commit_root, &tree_kernel_path, &tree_initramfs_path,
+                             cancellable, error))
+    goto out;
+  
+  if (tree_initramfs_path != NULL)
+    {
+      if (!checksum_from_kernel_src (tree_initramfs_path, &new_bootcsum, error))
+        goto out;
+    }
+  else
+    {
+      if (!checksum_from_kernel_src (tree_kernel_path, &new_bootcsum, error))
+        goto out;
+    }
+
+  bootloader = ot_admin_query_bootloader (sysroot);
+  if (!bootloader)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "No known bootloader configuration detected");
+      goto out;
+    }
+
+  /* If we're booted into the OS into which we're deploying, then
+   * merge the currently *booted* configuration, rather than the most
+   * recently deployed.
+   */
+  if (provided_merge_deployment != NULL)
+    merge_deployment = g_object_ref (provided_merge_deployment);
+  else
+    merge_deployment = ot_admin_get_merge_deployment (current_deployments, osname,
+                                                      booted_deployment,
+                                                      new_deployment);
+
+  compute_new_deployment_list (current_bootversion,
+                               current_deployments, osname,
+                               booted_deployment, merge_deployment,
+                               retain,
+                               revision, new_bootcsum,
+                               &new_deployments,
+                               &new_bootversion);
+  new_deployment = g_object_ref (new_deployments->pdata[0]);
+  ot_deployment_set_origin (new_deployment, origin);
+
+  print_deployment_diff (current_deployments, new_deployments);
+
+  /* Check out the userspace tree onto the filesystem */
+  if (!checkout_deployment_tree (sysroot, repo, new_deployment, &new_deployment_path,
+                                 cancellable, error))
+    {
+      g_prefix_error (error, "Checking out tree: ");
+      goto out;
+    }
+
+  if (!write_origin_file (sysroot, new_deployment, cancellable, error))
+    {
+      g_prefix_error (error, "Writing out origin file: ");
+      goto out;
+    }
+
+  /* Create an empty boot configuration; we will merge things into
+   * it as we go.
+   */
+  bootconfig = ot_config_parser_new (" ");
+  ot_deployment_set_bootconfig (new_deployment, bootconfig);
+
+  if (!merge_configuration (sysroot, merge_deployment, new_deployment,
+                            new_deployment_path,
+                            cancellable, error))
+    {
+      g_prefix_error (error, "During /etc merge: ");
+      goto out;
+    }
+
+  /* We have inherited kernel arguments from the previous deployment;
+   * now, override/extend that with arguments provided by the command
+   * line.
+   * 
+   * After this, install_deployment_kernel() will set the other boot
+   * options and write it out to disk.
+   */
+  if (add_kernel_argv)
+    {
+      char **strviter;
+      __attribute__((cleanup(ot_ordered_hash_cleanup))) OtOrderedHash *ohash = NULL;
+      gs_free char *new_options = NULL;
+
+      ohash = ot_admin_parse_kernel_args (ot_config_parser_get (bootconfig, "options"));
+
+      for (strviter = add_kernel_argv; *strviter; strviter++)
+        {
+          char *karg = g_strdup (*strviter);
+          const char *val = ot_admin_split_keyeq (karg);
+        
+          ot_ordered_hash_replace_key_take (ohash, karg, val);
+        }
+
+      new_options = ot_admin_kernel_arg_string_serialize (ohash);
+      ot_config_parser_set (bootconfig, "options", new_options);
+    }
+
+  if (current_bootversion == new_bootversion)
+    {
+      if (!full_system_sync (cancellable, error))
+        {
+          g_prefix_error (error, "Full sync: ");
+          goto out;
+        }
+
+      if (!swap_bootlinks (sysroot, current_bootversion,
+                           new_deployments,
+                           cancellable, error))
+        {
+          g_prefix_error (error, "Swapping current bootlinks: ");
+          goto out;
+        }
+    }
+  else
+    {
+      for (i = 0; i < new_deployments->len; i++)
+        {
+          OtDeployment *deployment = new_deployments->pdata[i];
+          if (!install_deployment_kernel (sysroot, new_bootversion, deployment,
+                                          cancellable, error))
+            {
+              g_prefix_error (error, "Installing kernel: ");
+              goto out;
+            }
+        }
+
+      /* Swap bootlinks for *new* version */
+      if (!swap_bootlinks (sysroot, new_bootversion, new_deployments,
+                           cancellable, error))
+        {
+          g_prefix_error (error, "Generating new bootlinks: ");
+          goto out;
+        }
+
+      if (!full_system_sync (cancellable, error))
+        {
+          g_prefix_error (error, "Full sync: ");
+          goto out;
+        }
+
+      if (!ot_bootloader_write_config (bootloader, new_bootversion,
+                                       cancellable, error))
+        goto out;
+
+      if (!swap_bootloader (sysroot, current_bootversion, new_bootversion,
+                            cancellable, error))
+        {
+          g_prefix_error (error, "Final bootloader swap: ");
+          goto out;
+        }
+    }
+
+  /* TEMPORARY HACK: Add a "current" symbolic link that's easy to
+   * follow inside the gnome-ostree build scripts.  This isn't atomic,
+   * but that doesn't matter because it's only used by deployments
+   * done from the host.
+   */
+  {
+    gs_unref_object GFile *osdir = ot_gfile_resolve_path_printf (sysroot, "ostree/deploy/%s", 
ot_deployment_get_osname (new_deployment));
+    gs_unref_object GFile *os_current_path = g_file_get_child (osdir, "current");
+    gs_free char *target = g_file_get_relative_path (osdir, new_deployment_path);
+    g_assert (target != NULL);
+    if (!ot_gfile_atomic_symlink_swap (os_current_path, target,
+                                       cancellable, error))
+      goto out;
+  }
+
+  /* And finally, cleanup of any leftover data.
+   */
+  if (!ot_admin_cleanup (sysroot, cancellable, error))
+    {
+      g_prefix_error (error, "Performing final cleanup: ");
+      goto out;
+    }
+
+  ret = TRUE;
+  ot_transfer_out_value (out_new_deployment, &new_deployment);
+  *out_new_bootversion = new_bootversion;
+  ot_transfer_out_value (out_new_deployments, &new_deployments)
+ out:
+  return ret;
+}
+
diff --git a/src/ostree/ot-admin-deploy.h b/src/ostree/ot-admin-deploy.h
new file mode 100644
index 0000000..1eab703
--- /dev/null
+++ b/src/ostree/ot-admin-deploy.h
@@ -0,0 +1,51 @@
+/* -*- 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_DEPLOY__
+#define __OT_ADMIN_DEPLOY_
+
+#include <gio/gio.h>
+#include "ot-deployment.h"
+#include "ot-bootloader.h"
+#include "ot-ordered-hash.h"
+
+G_BEGIN_DECLS
+
+gboolean ot_admin_deploy (GFile             *sysroot,
+                          int                current_bootversion,
+                          GPtrArray         *current_deployments,
+                          const char        *osname,
+                          const char        *revision,
+                          GKeyFile          *origin,
+                          char             **add_kernel_argv,
+                          gboolean           retain,
+                          OtDeployment      *booted_deployment,
+                          OtDeployment      *merge_deployment,
+                          OtDeployment     **out_new_deployment,
+                          int               *out_new_bootversion,
+                          GPtrArray        **out_new_deployments,
+                          GCancellable      *cancellable,
+                          GError           **error);
+
+G_END_DECLS
+
+#endif
diff --git a/src/ostree/ot-admin-functions.c b/src/ostree/ot-admin-functions.c
index 28eb077..208c2f8 100644
--- a/src/ostree/ot-admin-functions.c
+++ b/src/ostree/ot-admin-functions.c
@@ -20,19 +20,118 @@
  * Author: Colin Walters <walters verbum org>
  */
 
+#define _GNU_SOURCE
 #include "config.h"
 
 #include "ot-admin-functions.h"
+#include "ot-deployment.h"
+#include "ot-config-parser.h"
+#include "ot-bootloader-syslinux.h"
 #include "otutil.h"
 #include "ostree-core.h"
+#include "ostree-prune.h"
+#include "libgsystem.h"
+
+/*
+ * Modify @arg which should be of the form key=value to make @arg just
+ * contain key.  Return a pointer to the start of value.
+ */
+char *
+ot_admin_split_keyeq (char *arg)
+{
+  char *eq;
+      
+  eq = strchr (arg, '=');
+  if (eq)
+    {
+      /* Note key/val are in one malloc block,
+       * so we don't free val...
+       */
+      *eq = '\0';
+      return eq+1;
+    }
+  else
+    {
+      /* ...and this allows us to insert a constant
+       * string.
+       */
+      return "";
+    }
+}
+
+OtOrderedHash *
+ot_admin_parse_kernel_args (const char *options)
+{
+  OtOrderedHash *ret;
+  char **args;
+  char **iter;
+
+  ret = ot_ordered_hash_new ();
+
+  if (!options)
+    return ret;
+  
+  args = g_strsplit (options, " ", -1);
+  for (iter = args; *iter; iter++)
+    {
+      char *arg = *iter;
+      char *val;
+      
+      val = ot_admin_split_keyeq (arg);
+
+      g_ptr_array_add (ret->order, arg);
+      g_hash_table_insert (ret->table, arg, val);
+    }
+
+  return ret;
+}
+
+char *
+ot_admin_kernel_arg_string_serialize (OtOrderedHash *ohash)
+{
+  guint i;
+  GString *buf = g_string_new ("");
+  gboolean first = TRUE;
+
+  for (i = 0; i < ohash->order->len; i++)
+    {
+      const char *key = ohash->order->pdata[i];
+      const char *val = g_hash_table_lookup (ohash->table, key);
+
+      g_assert (val != NULL);
+
+      if (first)
+        first = FALSE;
+      else
+        g_string_append_c (buf, ' ');
+
+      if (*val)
+        g_string_append_printf (buf, "%s=%s", key, val);
+      else
+        g_string_append (buf, key);
+    }
+
+  return g_string_free (buf, FALSE);
+}
+
+
+static void
+match_info_cleanup (void *loc)
+{
+  GMatchInfo **match = (GMatchInfo**)loc;
+  if (*match) g_match_info_unref (*match);
+}
 
 gboolean
-ot_admin_ensure_initialized (GFile         *ostree_dir,
+ot_admin_ensure_initialized (GFile         *sysroot,
                              GCancellable  *cancellable,
                              GError       **error)
 {
   gboolean ret = FALSE;
-  ot_lobj GFile *dir = NULL;
+  gs_unref_object GFile *dir = NULL;
+  gs_unref_object GFile *ostree_dir = NULL;
+
+  ostree_dir = g_file_get_child (sysroot, "ostree");
 
   g_clear_object (&dir);
   dir = g_file_get_child (ostree_dir, "repo");
@@ -65,445 +164,1106 @@ ot_admin_ensure_initialized (GFile         *ostree_dir,
   return ret;
 }
 
+gboolean
+ot_admin_check_os (GFile         *sysroot, 
+                   const char    *osname,
+                   GCancellable  *cancellable,
+                   GError       **error)
+{
+  gboolean ret = FALSE;
+  gs_unref_object GFile *osdir = NULL;
+
+  osdir = ot_gfile_resolve_path_printf (sysroot, "ostree/deploy/%s/var", osname);
+  if (!g_file_query_exists (osdir, NULL))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "No such OS '%s', use os-init to create it", osname);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
 static gboolean
-query_file_info_allow_noent (GFile         *path,
-                             GFileInfo    **out_info,
-                             GCancellable  *cancellable,
-                             GError       **error)
+parse_bootlink (const char    *bootlink,
+                int           *out_entry_bootversion,
+                char         **out_osname,
+                char         **out_bootcsum,
+                int           *out_treebootserial,
+                GError       **error)
 {
   gboolean ret = FALSE;
-  ot_lobj GFileInfo *ret_file_info = NULL;
-  GError *temp_error = NULL;
+  __attribute__((cleanup(match_info_cleanup))) GMatchInfo *match = NULL;
+  gs_free char *bootversion_str = NULL;
+  gs_free char *treebootserial_str = NULL;
 
-  ret_file_info = g_file_query_info (path, OSTREE_GIO_FAST_QUERYINFO,
-                                     G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                     cancellable, &temp_error);
-  if (!ret_file_info)
+  static gsize regex_initialized;
+  static GRegex *regex;
+
+  if (g_once_init_enter (&regex_initialized))
     {
-      if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+      regex = g_regex_new ("^/ostree/boot.([01])/([^/]+)/([^/]+)/([0-9]+)$", 0, 0, NULL);
+      g_assert (regex);
+      g_once_init_leave (&regex_initialized, 1);
+    }
+
+  if (!g_regex_match (regex, bootlink, 0, &match))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Invalid ostree= argument '%s', expected 
ostree=/ostree/boot.BOOTVERSION/OSNAME/BOOTCSUM/TREESERIAL", bootlink);
+      goto out;
+    }
+    
+  bootversion_str = g_match_info_fetch (match, 1);
+  *out_entry_bootversion = (int)g_ascii_strtoll (bootversion_str, NULL, 10);
+  *out_osname = g_match_info_fetch (match, 2);
+  *out_bootcsum = g_match_info_fetch (match, 3);
+  treebootserial_str = g_match_info_fetch (match, 4);
+  *out_treebootserial = (int)g_ascii_strtoll (treebootserial_str, NULL, 10);
+  
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+parse_deploy_path_name (const char *name,
+                        char      **out_csum,
+                        int        *out_serial,
+                        GError    **error)
+{
+  gboolean ret = FALSE;
+  __attribute__((cleanup(match_info_cleanup))) GMatchInfo *match = NULL;
+  gs_free char *serial_str = NULL;
+
+  static gsize regex_initialized;
+  static GRegex *regex;
+
+  if (g_once_init_enter (&regex_initialized))
+    {
+      regex = g_regex_new ("^([0-9a-f]+)\\.([0-9]+)$", 0, 0, NULL);
+      g_assert (regex);
+      g_once_init_leave (&regex_initialized, 1);
+    }
+
+  if (!g_regex_match (regex, name, 0, &match))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Invalid deploy name '%s', expected CHECKSUM.TREESERIAL", name);
+      goto out;
+    }
+
+  *out_csum = g_match_info_fetch (match, 1);
+  serial_str = g_match_info_fetch (match, 2);
+  *out_serial = (int)g_ascii_strtoll (serial_str, NULL, 10);
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+GFile *
+ot_admin_get_deployment_origin_path (GFile   *deployment_path)
+{
+  gs_unref_object GFile *deployment_parent = g_file_get_parent (deployment_path);
+  return ot_gfile_resolve_path_printf (deployment_parent,
+                                       "%s.origin",
+                                       gs_file_get_path_cached (deployment_path));
+}
+
+static gboolean
+parse_origin (GFile           *sysroot,
+              GFile           *deployment_path,
+              GKeyFile       **out_origin,
+              GCancellable    *cancellable,
+              GError         **error)
+{
+  gboolean ret = FALSE;
+  GKeyFile *ret_origin = NULL;
+  gs_unref_object GFile *origin_path = ot_admin_get_deployment_origin_path (deployment_path);
+  gs_free char *origin_contents = NULL;
+  
+  if (!ot_gfile_load_contents_utf8_allow_noent (origin_path, &origin_contents,
+                                                cancellable, error))
+    goto out;
+
+  if (origin_contents)
+    {
+      ret_origin = g_key_file_new ();
+      if (!g_key_file_load_from_data (ret_origin, origin_contents, -1, 0, error))
+        goto out;
+    }
+
+  ret = TRUE;
+  ot_transfer_out_value (out_origin, &ret_origin);
+ out:
+  if (error)
+    g_prefix_error (error, "Parsing %s: ", gs_file_get_path_cached (origin_path));
+  if (ret_origin)
+    g_key_file_unref (ret_origin);
+  return ret;
+}
+
+static gboolean
+parse_deployment (GFile           *sysroot,
+                  const char      *boot_link,
+                  OtDeployment   **out_deployment,
+                  GCancellable    *cancellable,
+                  GError         **error)
+{
+  gboolean ret = FALSE;
+  const char *relative_boot_link;
+  gs_unref_object OtDeployment *ret_deployment = NULL;
+  int entry_boot_version;
+  int treebootserial;
+  int deployserial;
+  gs_free char *osname = NULL;
+  gs_free char *bootcsum = NULL;
+  gs_free char *treecsum = NULL;
+  gs_unref_object GFile *treebootserial_link = NULL;
+  gs_unref_object GFileInfo *treebootserial_info = NULL;
+  gs_unref_object GFile *treebootserial_target = NULL;
+  GKeyFile *origin = NULL;
+      
+  if (!parse_bootlink (boot_link, &entry_boot_version,
+                       &osname, &bootcsum, &treebootserial,
+                       error))
+    goto out;
+
+  relative_boot_link = boot_link;
+  if (*relative_boot_link == '/')
+    relative_boot_link++;
+  treebootserial_link = g_file_resolve_relative_path (sysroot, relative_boot_link);
+  treebootserial_info = g_file_query_info (treebootserial_link, OSTREE_GIO_FAST_QUERYINFO,
+                                           G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                           cancellable, error);
+  if (!treebootserial_info)
+    goto out;
+
+  if (!ot_gfile_get_symlink_target_from_info (treebootserial_link, treebootserial_info,
+                                              &treebootserial_target, cancellable, error))
+    goto out;
+
+  if (!parse_deploy_path_name (gs_file_get_basename_cached (treebootserial_target),
+                               &treecsum, &deployserial, error))
+    goto out;
+
+  if (!parse_origin (sysroot, treebootserial_target, &origin,
+                     cancellable, error))
+    goto out;
+
+  ret_deployment = ot_deployment_new (-1, osname, treecsum, deployserial,
+                                      bootcsum, treebootserial);
+  if (origin)
+    ot_deployment_set_origin (ret_deployment, origin);
+
+  ret = TRUE;
+  ot_transfer_out_value (out_deployment, &ret_deployment);
+ out:
+  if (origin)
+    g_key_file_unref (origin);
+  return ret;
+}
+
+static gboolean
+parse_kernel_commandline (OtOrderedHash  **out_args,
+                          GCancellable    *cancellable,
+                          GError         **error)
+{
+  gboolean ret = FALSE;
+  gs_unref_object GFile *proc_cmdline = g_file_new_for_path ("/proc/cmdline");
+  gs_free char *contents = NULL;
+  gsize len;
+
+  if (!g_file_load_contents (proc_cmdline, cancellable, &contents, &len, NULL,
+                             error))
+    goto out;
+
+  ret = TRUE;
+  *out_args = ot_admin_parse_kernel_args (contents);;
+ out:
+  return ret;
+}
+
+static gboolean
+get_devino (GFile         *path,
+            guint32       *out_device,
+            guint64       *out_inode,
+            GCancellable  *cancellable,
+            GError       **error)
+{
+  gboolean ret = FALSE;
+  gs_unref_object GFileInfo *finfo = g_file_query_info (path, "unix::device,unix::inode",
+                                                        G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                                        cancellable, error);
+
+  if (!finfo)
+    goto out;
+
+  ret = TRUE;
+  *out_device = g_file_info_get_attribute_uint32 (finfo, "unix::device");
+  *out_inode = g_file_info_get_attribute_uint64 (finfo, "unix::inode");
+ out:
+  return ret;
+}
+
+/**
+ * ot_admin_find_booted_deployment:
+ * 
+ * Returns in @out_deployment the currently booted deployment using
+ * the list in @deployments.  Will always return %NULL if
+ * @target_sysroot is not equal to "/".
+ */
+gboolean
+ot_admin_find_booted_deployment (GFile               *target_sysroot,
+                                 GPtrArray           *deployments,
+                                 OtDeployment       **out_deployment,
+                                 GCancellable        *cancellable,
+                                 GError             **error)
+{
+  gboolean ret = FALSE;
+  gs_unref_object GFile *active_root = g_file_new_for_path ("/");
+  gs_unref_object OtDeployment *ret_deployment = NULL;
+
+  if (g_file_equal (active_root, target_sysroot))
+    { 
+      guint i;
+      const char *bootlink_arg;
+      __attribute__((cleanup(ot_ordered_hash_cleanup))) OtOrderedHash *kernel_args = NULL;
+      guint32 root_device;
+      guint64 root_inode;
+      
+      if (!get_devino (active_root, &root_device, &root_inode,
+                       cancellable, error))
+        goto out;
+
+      if (!parse_kernel_commandline (&kernel_args, cancellable, error))
+        goto out;
+      
+      bootlink_arg = g_hash_table_lookup (kernel_args->table, "ostree");
+      if (bootlink_arg)
         {
-          g_clear_error (&temp_error);
+          for (i = 0; i < deployments->len; i++)
+            {
+              OtDeployment *deployment = deployments->pdata[i];
+              gs_unref_object GFile *deployment_path = ot_admin_get_deployment_directory (active_root, 
deployment);
+              guint32 device;
+              guint64 inode;
+
+              if (!get_devino (deployment_path, &device, &inode,
+                               cancellable, error))
+                goto out;
+
+              if (device == root_device && inode == root_inode)
+                {
+                  ret_deployment = g_object_ref (deployment);
+                  break;
+                }
+            }
+          if (ret_deployment == NULL)
+            {
+              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Unexpected state: ostree= kernel argument found, but / is not a deployment 
root");
+              goto out;
+            }
         }
       else
         {
-          g_propagate_error (error, temp_error);
-          goto out;
+          /* Not an ostree system */
         }
     }
 
   ret = TRUE;
-  ot_transfer_out_value (out_info, &ret_file_info);
+  ot_transfer_out_value (out_deployment, &ret_deployment);
  out:
   return ret;
 }
 
-static gboolean
-query_symlink_target_allow_noent (GFile         *path,
-                                  GFile        **out_target,
-                                  GCancellable  *cancellable,
-                                  GError       **error)
+gboolean
+ot_admin_require_deployment_or_osname (GFile               *sysroot,
+                                       GPtrArray           *deployments,
+                                       const char          *osname,
+                                       OtDeployment       **out_deployment,
+                                       GCancellable        *cancellable,
+                                       GError             **error)
 {
   gboolean ret = FALSE;
-  ot_lobj GFileInfo *file_info = NULL;
-  ot_lobj GFile *ret_target = NULL;
-  ot_lobj GFile *path_parent = NULL;
+  gs_unref_object OtDeployment *ret_deployment = NULL;
 
-  if (!query_file_info_allow_noent (path, &file_info,
-                                    cancellable, error))
+  if (!ot_admin_find_booted_deployment (sysroot, deployments, &ret_deployment,
+                                        cancellable, error))
     goto out;
 
-  path_parent = g_file_get_parent (path);
+  if (ret_deployment == NULL && osname == NULL)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Not currently booted into an OSTree system and no --os= argument given");
+      goto out;
+    }
+  
+  ret = TRUE;
+  ot_transfer_out_value (out_deployment, &ret_deployment);
+ out:
+  return ret;
+}
+
+OtDeployment *
+ot_admin_get_merge_deployment (GPtrArray         *deployments,
+                               const char        *osname,
+                               OtDeployment      *booted_deployment,
+                               OtDeployment      *new_deployment)
+{
+  g_return_val_if_fail (osname != NULL || booted_deployment != NULL, NULL);
 
-  if (file_info != NULL)
+  if (osname == NULL)
+    osname = ot_deployment_get_osname (booted_deployment);
+
+  if (booted_deployment &&
+      new_deployment &&
+      g_strcmp0 (ot_deployment_get_osname (booted_deployment),
+                 ot_deployment_get_osname (new_deployment)) == 0)
     {
-      const char *target;
-      
-      if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_SYMBOLIC_LINK)
+      return g_object_ref (booted_deployment);
+    }
+  else
+    {
+      guint i;
+      for (i = 0; i < deployments->len; i++)
+        {
+          OtDeployment *deployment = deployments->pdata[i];
+
+          if (strcmp (ot_deployment_get_osname (deployment), osname) != 0)
+            continue;
+          if (deployment == new_deployment)
+            continue;
+          
+          return g_object_ref (deployment);
+        }
+    }
+  return NULL;
+}
+
+static gboolean
+read_current_bootversion (GFile         *sysroot,
+                          int           *out_bootversion,
+                          GCancellable  *cancellable,
+                          GError       **error)
+{
+  gboolean ret = FALSE;
+  gs_unref_object GFile *boot_loader_path = g_file_resolve_relative_path (sysroot, "boot/loader");
+  gs_unref_object GFileInfo *info = NULL;
+  const char *target;
+  int ret_bootversion;
+
+  if (!ot_gfile_query_info_allow_noent (boot_loader_path, OSTREE_GIO_FAST_QUERYINFO,
+                                        G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                        &info,
+                                        cancellable, error))
+    goto out;
+
+  if (info == NULL)
+    ret_bootversion = 0;
+  else
+    {
+      if (g_file_info_get_file_type (info) != G_FILE_TYPE_SYMBOLIC_LINK)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Not a symbolic link: %s", gs_file_get_path_cached (boot_loader_path));
+          goto out;
+        }
+
+      target = g_file_info_get_symlink_target (info);
+      if (g_strcmp0 (target, "loader.0") == 0)
+        ret_bootversion = 0;
+      else if (g_strcmp0 (target, "loader.1") == 0)
+        ret_bootversion = 1;
+      else
         {
           g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                       "Not a symbolic link");
+                       "Invalid target '%s' in %s", target, gs_file_get_path_cached (boot_loader_path));
           goto out;
         }
-      target = g_file_info_get_symlink_target (file_info);
-      g_assert (target);
-      ret_target = g_file_resolve_relative_path (path_parent, target);
     }
 
   ret = TRUE;
-  ot_transfer_out_value (out_target, &ret_target);
+  *out_bootversion = ret_bootversion;
  out:
   return ret;
 }
 
-/**
- * ot_admin_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.
- */
 gboolean
-ot_admin_get_current_deployment (GFile           *ostree_dir,
-                                 const char      *osname,
-                                 GFile          **out_deployment,
-                                 GCancellable    *cancellable,
-                                 GError         **error)
+ot_admin_read_current_subbootversion (GFile         *sysroot,
+                                      int            bootversion,
+                                      int           *out_subbootversion,
+                                      GCancellable  *cancellable,
+                                      GError       **error)
 {
-  ot_lobj GFile *current_path = NULL;
+  gboolean ret = FALSE;
+  gs_unref_object GFile *ostree_dir = g_file_get_child (sysroot, "ostree");
+  gs_free char *ostree_bootdir_name = g_strdup_printf ("boot.%d", bootversion);
+  gs_unref_object GFile *ostree_bootdir = g_file_resolve_relative_path (ostree_dir, ostree_bootdir_name);
+  gs_free char *ostree_subbootdir_name = NULL;
+  gs_unref_object GFile *ostree_subbootdir = NULL;
+  gs_unref_ptrarray GPtrArray *deployments_to_swap = NULL;
 
-  current_path = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname,
-                                                "current", NULL);
+  if (!ot_gfile_query_symlink_target_allow_noent (ostree_bootdir, &ostree_subbootdir,
+                                                  cancellable, error))
+    goto out;
 
-  return query_symlink_target_allow_noent (current_path, out_deployment,
-                                           cancellable, error);
+  if (ostree_subbootdir == NULL)
+    {
+      *out_subbootversion = 0;
+    }
+  else
+    {
+      const char *current_subbootdir_name = gs_file_get_basename_cached (ostree_subbootdir);
+      if (g_str_has_suffix (current_subbootdir_name, ".0"))
+        *out_subbootversion = 0;
+      else if (g_str_has_suffix (current_subbootdir_name, ".1"))
+        *out_subbootversion = 1;
+      else
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Invalid target '%s' in %s",
+                       gs_file_get_path_cached (ostree_subbootdir),
+                       gs_file_get_path_cached (ostree_bootdir));
+          goto out;
+        }
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
 }
 
-/**
- * ot_admin_get_previous_deployment:
- * 
- * Returns in @out_deployment the full file path of the current
- * deployment that the /ostree/previous symbolic link points to, or
- * %NULL if none.
- */
 gboolean
-ot_admin_get_previous_deployment (GFile           *ostree_dir,
-                                  const char      *osname,
-                                  GFile          **out_deployment,
-                                  GCancellable    *cancellable,
-                                  GError         **error)
+ot_admin_read_boot_loader_configs (GFile         *sysroot,
+                                   int            bootversion,
+                                   GPtrArray    **out_loader_configs,
+                                   GCancellable  *cancellable,
+                                   GError       **error)
 {
-  ot_lobj GFile *previous_path = NULL;
+  gboolean ret = FALSE;
+  gs_unref_object GFileEnumerator *dir_enum = NULL;
+  gs_unref_object GFile *loader_entries_dir = NULL;
+  gs_unref_ptrarray GPtrArray *ret_loader_configs = NULL;
+  GError *temp_error = NULL;
 
-  previous_path = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname,
-                                                 "previous", NULL);
+  loader_entries_dir = ot_gfile_resolve_path_printf (sysroot, "boot/loader.%d/entries",
+                                                     bootversion);
+  ret_loader_configs = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
 
-  return query_symlink_target_allow_noent (previous_path, out_deployment,
-                                           cancellable, error);
+  dir_enum = g_file_enumerate_children (loader_entries_dir, OSTREE_GIO_FAST_QUERYINFO,
+                                        0, NULL, &temp_error);
+  if (!dir_enum)
+    {
+      if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        {
+          g_clear_error (&temp_error);
+          goto done;
+        } 
+      else
+        {
+          g_propagate_error (error, temp_error);
+          goto out;
+        }
+    }
+
+  while (TRUE)
+    {
+      GFileInfo *file_info;
+      GFile *child;
+      const char *name;
+
+      if (!gs_file_enumerator_iterate (dir_enum, &file_info, &child,
+                                       cancellable, error))
+        goto out;
+      if (file_info == NULL)
+        break;
+
+      name = g_file_info_get_name (file_info);
+
+      if (g_str_has_prefix (name, "ostree-") &&
+          g_str_has_suffix (name, ".conf") &&
+          g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
+        {
+          gs_unref_object OtConfigParser *config = ot_config_parser_new (" \t");
+  
+          if (!ot_config_parser_parse (config, child, cancellable, error))
+            {
+              g_prefix_error (error, "Parsing %s: ", gs_file_get_path_cached (child));
+              goto out;
+            }
+
+          g_ptr_array_add (ret_loader_configs, g_object_ref (config));
+        }
+    }
+
+ done:
+  ot_transfer_out_value (out_loader_configs, &ret_loader_configs);
+  ret = TRUE;
+ out:
+  return ret;
 }
 
-/*
 static gboolean
-ot_admin_list_osnames (GFile               *ostree_dir,
-                       GPtrArray          **out_osnames,
-                       GCancellable        *cancellable,
-                       GError             **error)
+list_deployment_dirs_for_os (GFile               *osdir,
+                             GPtrArray           *inout_deployments,
+                             GCancellable        *cancellable,
+                             GError             **error)
 {
   gboolean ret = FALSE;
-  ot_lobj GFileEnumerator *dir_enum = NULL;
-  ot_lobj GFileInfo *file_info = NULL;
-  ot_lobj GFile *deploy_dir = NULL;
-  ot_lptrarray GPtrArray *ret_osnames = NULL;
+  const char *osname = gs_file_get_basename_cached (osdir);
+  gs_unref_object GFileEnumerator *dir_enum = NULL;
+  gs_unref_object GFile *osdeploy_dir = NULL;
   GError *temp_error = NULL;
 
-  deploy_dir = g_file_get_child (ostree_dir, "deploy");
+  osdeploy_dir = g_file_get_child (osdir, "deploy");
 
-  dir_enum = g_file_enumerate_children (deploy_dir, OSTREE_GIO_FAST_QUERYINFO,
+  dir_enum = g_file_enumerate_children (osdeploy_dir, OSTREE_GIO_FAST_QUERYINFO,
                                         G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                        NULL, error);
+                                        NULL, &temp_error);
   if (!dir_enum)
-    goto out;
-
-  while ((file_info = g_file_enumerator_next_file (dir_enum, NULL, error)) != NULL)
     {
-      if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
+      if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
         {
-          char *name = g_strdup (g_file_info_get_name (file_info));
-          g_ptr_array_add (ret_osnames, name);
+          g_clear_error (&temp_error);
+          goto done;
+        } 
+      else
+        {
+          g_propagate_error (error, temp_error);
+          goto out;
         }
-      g_clear_object (&file_info);
     }
 
-  if (temp_error != NULL)
+  while (TRUE)
     {
-      g_propagate_error (error, temp_error);
-      goto out;
+      const char *name;
+      GFileInfo *file_info = NULL;
+      GFile *child = NULL;
+      gs_unref_object OtDeployment *deployment = NULL;
+      gs_free char *csum = NULL;
+      gint deployserial;
+
+      if (!gs_file_enumerator_iterate (dir_enum, &file_info, &child,
+                                       cancellable, error))
+        goto out;
+      if (file_info == NULL)
+        break;
+
+      name = g_file_info_get_name (file_info);
+
+      if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY)
+        continue;
+
+      if (!parse_deploy_path_name (name, &csum, &deployserial, error))
+        goto out;
+      
+      deployment = ot_deployment_new (-1, osname, csum, deployserial, NULL, -1);
+      g_ptr_array_add (inout_deployments, g_object_ref (deployment));
     }
-  
+
+ done:
   ret = TRUE;
-  ot_transfer_out_value (out_osnames, &ret_osnames);
  out:
   return ret;
 }
-*/
 
 static gboolean
-list_deployments_internal (GFile               *from_dir,
-                           GPtrArray           *inout_deployments,
-                           GCancellable        *cancellable,
-                           GError             **error)
+list_all_deployment_directories (GFile               *sysroot,
+                                 GPtrArray          **out_deployments,
+                                 GCancellable        *cancellable,
+                                 GError             **error)
 {
   gboolean ret = FALSE;
+  gs_unref_object GFileEnumerator *dir_enum = NULL;
+  gs_unref_object GFile *deploydir = NULL;
+  gs_unref_object GFile *osdir = NULL;
+  gs_unref_ptrarray GPtrArray *ret_deployments = NULL;
   GError *temp_error = NULL;
-  ot_lobj GFileEnumerator *dir_enum = NULL;
-  ot_lobj GFileInfo *file_info = NULL;
 
-  dir_enum = g_file_enumerate_children (from_dir, OSTREE_GIO_FAST_QUERYINFO,
+  deploydir = g_file_resolve_relative_path (sysroot, "ostree/deploy");
+
+  ret_deployments = g_ptr_array_new_with_free_func (g_object_unref);
+
+  dir_enum = g_file_enumerate_children (deploydir, OSTREE_GIO_FAST_QUERYINFO,
                                         G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                        NULL, error);
+                                        cancellable, &temp_error);
   if (!dir_enum)
-    goto out;
+    {
+      if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        {
+          g_clear_error (&temp_error);
+          goto done;
+        } 
+      else
+        {
+          g_propagate_error (error, temp_error);
+          goto out;
+        }
+    }
 
-  while ((file_info = g_file_enumerator_next_file (dir_enum, cancellable, error)) != NULL)
+  while (TRUE)
     {
-      const char *name;
-      ot_lobj GFile *child = NULL;
-      ot_lobj GFile *possible_etc = NULL;
-      ot_lobj GFile *possible_usr = NULL;
+      GFileInfo *file_info = NULL;
+      GFile *child = NULL;
 
-      name = g_file_info_get_name (file_info);
+      if (!gs_file_enumerator_iterate (dir_enum, &file_info, &child,
+                                       NULL, error))
+        goto out;
+      if (file_info == NULL)
+        break;
 
-      if (g_str_has_suffix (name, "-etc"))
-        goto next;
       if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY)
-        goto next;
+        continue;
+      
+      if (!list_deployment_dirs_for_os (child, ret_deployments, cancellable, error))
+        goto out;
+    }
+  
+ done:
+  ret = TRUE;
+  ot_transfer_out_value (out_deployments, &ret_deployments);
+ out:
+  return ret;
+}
 
-      child = g_file_get_child (from_dir, name);
+static char *
+get_ostree_kernel_arg_from_config (OtConfigParser  *config)
+{
+  const char *options;
+  char *ret;
+  char **opts, **iter;
 
-      possible_etc = ot_gfile_get_child_strconcat (from_dir, name, "-etc", NULL);
-      /* Bit of a hack... */
-      possible_usr = g_file_get_child (child, "usr");
+  options = ot_config_parser_get (config, "options");
+  if (!options)
+    return NULL;
 
-      if (g_file_query_exists (possible_etc, cancellable))
-        g_ptr_array_add (inout_deployments, g_file_get_child (from_dir, name));
-      else if (g_file_query_exists (possible_usr, cancellable))
-        goto next;
-      else
+  opts = g_strsplit (options, " ", -1);
+  for (iter = opts; *iter; iter++)
+    {
+      const char *opt = *iter;
+      if (g_str_has_prefix (opt, "ostree="))
         {
-          if (!list_deployments_internal (child, inout_deployments,
-                                          cancellable, error))
-            goto out;
+          ret = g_strdup (opt + strlen ("ostree="));
+          break;
         }
-
-    next:
-      g_clear_object (&file_info);
     }
-  if (temp_error != NULL)
+  g_strfreev (opts);
+
+  return ret;
+}
+
+static gboolean
+list_deployments_process_one_boot_entry (GFile               *sysroot,
+                                         OtConfigParser      *config,
+                                         GPtrArray           *inout_deployments,
+                                         GCancellable        *cancellable,
+                                         GError             **error)
+{
+  gboolean ret = FALSE;
+  gs_free char *ostree_arg = NULL;
+  gs_unref_object OtDeployment *deployment = NULL;
+
+  ostree_arg = get_ostree_kernel_arg_from_config (config);
+  if (ostree_arg == NULL)
     {
-      g_propagate_error (error, temp_error);
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "No ostree= kernel argument found");
       goto out;
     }
+  
+  if (!parse_deployment (sysroot, ostree_arg, &deployment,
+                         cancellable, error))
+    goto out;
+  
+  ot_deployment_set_bootconfig (deployment, config);
 
+  g_ptr_array_add (inout_deployments, g_object_ref (deployment));
+  
   ret = TRUE;
  out:
   return ret;
 }
 
+static gint
+compare_deployments_by_boot_loader_version (gconstpointer     a_pp,
+                                            gconstpointer     b_pp)
+{
+  OtDeployment *a = *((OtDeployment**)a_pp);
+  OtDeployment *b = *((OtDeployment**)b_pp);
+  OtConfigParser *a_bootconfig = ot_deployment_get_bootconfig (a);
+  OtConfigParser *b_bootconfig = ot_deployment_get_bootconfig (b);
+  const char *a_version = ot_config_parser_get (a_bootconfig, "version");
+  const char *b_version = ot_config_parser_get (b_bootconfig, "version");
+  
+  if (a_version && b_version)
+    return strverscmp (a_version, b_version);
+  else if (a_version)
+    return 1;
+  else
+    return -1;
+}
+
 gboolean
-ot_admin_list_deployments (GFile               *ostree_dir,
-                           const char          *osname,
+ot_admin_list_deployments (GFile               *sysroot,
+                           int                 *out_current_bootversion,
                            GPtrArray          **out_deployments,
                            GCancellable        *cancellable,
                            GError             **error)
 {
   gboolean ret = FALSE;
-  ot_lobj GFileEnumerator *dir_enum = NULL;
-  ot_lobj GFileInfo *file_info = NULL;
-  ot_lobj GFile *osdir = NULL;
-  ot_lptrarray GPtrArray *ret_deployments = NULL;
+  gs_unref_ptrarray GPtrArray *boot_loader_configs = NULL;
+  gs_unref_ptrarray GPtrArray *ret_deployments = NULL;
+  guint i;
+  int bootversion;
 
-  osdir = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, NULL);
-  ret_deployments = g_ptr_array_new_with_free_func (g_object_unref);
+  if (!read_current_bootversion (sysroot, &bootversion, cancellable, error))
+    goto out;
 
-  if (!list_deployments_internal (osdir, ret_deployments, cancellable, error))
+  if (!ot_admin_read_boot_loader_configs (sysroot, bootversion, &boot_loader_configs,
+                                          cancellable, error))
     goto out;
-  
+
+  ret_deployments = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
+
+  for (i = 0; i < boot_loader_configs->len; i++)
+    {
+      OtConfigParser *config = boot_loader_configs->pdata[i];
+
+      if (!list_deployments_process_one_boot_entry (sysroot, config, ret_deployments,
+                                                    cancellable, error))
+        goto out;
+    }
+
+  g_ptr_array_sort (ret_deployments, compare_deployments_by_boot_loader_version);
+  for (i = 0; i < ret_deployments->len; i++)
+    {
+      OtDeployment *deployment = ret_deployments->pdata[i];
+      ot_deployment_set_index (deployment, i);
+    }
+
   ret = TRUE;
+  *out_current_bootversion = bootversion;
   ot_transfer_out_value (out_deployments, &ret_deployments);
  out:
   return ret;
 }
 
 gboolean
-ot_admin_get_booted_os (char        **out_osname,
-                        char        **out_tree,
-                        GCancellable *cancellable,
-                        GError      **error)
+ot_admin_pull (GFile         *sysroot,
+               const char    *remote,
+               const char    *ref,
+               GCancellable  *cancellable,
+               GError       **error)
+{
+  gs_unref_object GFile *repo_path = g_file_resolve_relative_path (sysroot, "ostree/repo");
+  gs_free char *repo_arg = g_strconcat ("--repo=",
+                                        gs_file_get_path_cached (repo_path),
+                                        NULL);
+
+  return gs_subprocess_simple_run_sync (NULL,
+                                        GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
+                                        cancellable, error,
+                                        "ostree", repo_arg, "pull", remote, ref, NULL);
+}
+
+GFile *
+ot_admin_get_deployment_directory (GFile        *sysroot,
+                                   OtDeployment *deployment)
+{
+  gs_free char *path = g_strdup_printf ("ostree/deploy/%s/deploy/%s.%d",
+                                        ot_deployment_get_osname (deployment),
+                                        ot_deployment_get_csum (deployment),
+                                        ot_deployment_get_deployserial (deployment));
+  return g_file_resolve_relative_path (sysroot, path);
+}
+
+static gboolean
+cleanup_other_bootversions (GFile               *sysroot,
+                            int                  bootversion,
+                            int                  subbootversion,
+                            GCancellable        *cancellable,
+                            GError             **error)
 {
   gboolean ret = FALSE;
-  gs_free char *ret_osname = NULL;
-  gs_free char *ret_tree = NULL;
-  gs_free char *cmdline_contents = NULL;
-  const char *iter;
-  gsize len;
+  int cleanup_bootversion;
+  int cleanup_subbootversion;
+  gs_free char *cleanup_boot_name = NULL;
+  gs_unref_object GFile *cleanup_boot_dir = NULL;
+
+  cleanup_bootversion = bootversion == 0 ? 1 : 0;
+  cleanup_subbootversion = subbootversion == 0 ? 1 : 0;
+
+  cleanup_boot_dir = ot_gfile_resolve_path_printf (sysroot, "boot/loader.%d", cleanup_bootversion);
+  if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
+    goto out;
+  g_clear_object (&cleanup_boot_dir);
+
+  cleanup_boot_dir = ot_gfile_resolve_path_printf (sysroot, "ostree/boot.%d", cleanup_bootversion);
+  if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
+    goto out;
+  g_clear_object (&cleanup_boot_dir);
+
+  cleanup_boot_dir = ot_gfile_resolve_path_printf (sysroot, "ostree/boot.%d.0", cleanup_bootversion);
+  if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
+    goto out;
+  g_clear_object (&cleanup_boot_dir);
+
+  cleanup_boot_dir = ot_gfile_resolve_path_printf (sysroot, "ostree/boot.%d.1", cleanup_bootversion);
+  if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
+    goto out;
+  g_clear_object (&cleanup_boot_dir);
+
+  cleanup_boot_dir = ot_gfile_resolve_path_printf (sysroot, "ostree/boot.%d.%d", bootversion,
+                                                   cleanup_subbootversion);
+  if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
+    goto out;
+  g_clear_object (&cleanup_boot_dir);
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+cleanup_old_deployments (GFile               *sysroot,
+                         GPtrArray           *deployments,
+                         GCancellable        *cancellable,
+                         GError             **error)
+{
+  gboolean ret = FALSE;
+  guint32 root_device;
+  guint64 root_inode;
+  guint i;
+  gs_unref_object GFile *active_root = g_file_new_for_path ("/");
+  gs_unref_hashtable GHashTable *active_deployment_dirs = NULL;
+  gs_unref_ptrarray GPtrArray *all_deployment_dirs = NULL;
 
-  if (!g_file_get_contents ("/proc/cmdline", &cmdline_contents, &len,
-                            error))
+  if (!get_devino (active_root, &root_device, &root_inode,
+                   cancellable, error))
     goto out;
 
-  iter = cmdline_contents;
-  do
+  active_deployment_dirs = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, NULL, 
g_object_unref);
+
+  for (i = 0; i < deployments->len; i++)
+    {
+      OtDeployment *deployment = deployments->pdata[i];
+      GFile *deployment_path = ot_admin_get_deployment_directory (sysroot, deployment);
+      /* Transfer ownership */
+      g_hash_table_insert (active_deployment_dirs, deployment_path, deployment_path);
+    }
+
+  if (!list_all_deployment_directories (sysroot, &all_deployment_dirs,
+                                        cancellable, error))
+    goto out;
+  
+  for (i = 0; i < all_deployment_dirs->len; i++)
     {
-      const char *next = strchr (iter, ' ');
-      if (next)
-       next += 1;
-      if (g_str_has_prefix (iter, "ostree="))
+      OtDeployment *deployment = all_deployment_dirs->pdata[i];
+      gs_unref_object GFile *deployment_path = ot_admin_get_deployment_directory (sysroot, deployment);
+      gs_unref_object GFile *origin_path = ot_admin_get_deployment_origin_path (deployment_path);
+      if (!g_hash_table_lookup (active_deployment_dirs, deployment_path))
         {
-          const char *slash = strchr (iter, '/');
-          if (slash)
-            {
-              const char *start = iter + strlen ("ostree=");
-              ret_osname = g_strndup (start, slash - start);
-              if (next)
-                ret_tree = g_strndup (slash + 1, next - slash - 1);
-              else
-                ret_tree = g_strdup (slash + 1);
-              break;
-            }
+          guint32 device;
+          guint64 inode;
+
+          if (!get_devino (deployment_path, &device, &inode,
+                           cancellable, error))
+            goto out;
+
+          /* This shouldn't happen, because higher levels should
+           * disallow having the booted deployment not in the active
+           * deployment list, but let's be extra safe. */
+          if (device == root_device && inode == root_inode)
+            continue;
+
+          g_print ("ostadmin: Deleting deployment %s\n", gs_file_get_path_cached (deployment_path));
+          if (!gs_shutil_rm_rf (deployment_path, cancellable, error))
+            goto out;
+          if (!gs_shutil_rm_rf (origin_path, cancellable, error))
+            goto out;
         }
-      iter = next;
     }
-  while (iter != NULL);
 
   ret = TRUE;
  out:
-  ot_transfer_out_value (out_osname, &ret_osname);
-  ot_transfer_out_value (out_tree, &ret_tree);
   return ret;
 }
 
-gboolean
-ot_admin_get_active_deployment (GFile           *ostree_dir,
-                                char           **out_osname,
-                                GFile          **out_deployment,
-                                GCancellable    *cancellable,
-                                GError         **error)
+static gboolean
+cleanup_ref_prefix (OstreeRepo         *repo,
+                    int                 bootversion,
+                    int                 subbootversion,
+                    GCancellable       *cancellable,
+                    GError            **error)
 {
   gboolean ret = FALSE;
-  ot_lptrarray GPtrArray *osnames = NULL;
-  ot_lptrarray GPtrArray *deployments = NULL;
-  gs_free char *ret_osname = NULL;
-  gs_unref_object GFile *ret_deployment = NULL;
+  gs_free char *prefix = NULL;
+  gs_unref_hashtable GHashTable *refs = NULL;
+  GHashTableIter hashiter;
+  gpointer hashkey, hashvalue;
+
+  prefix = g_strdup_printf ("ostree/%d/%d", bootversion, subbootversion);
 
-  if (!ot_admin_get_booted_os (&ret_osname, NULL, cancellable, error))
+  if (!ostree_repo_list_refs (repo, prefix, &refs, cancellable, error))
     goto out;
 
-  if (ret_osname != NULL && out_deployment != NULL)
+  g_hash_table_iter_init (&hashiter, refs);
+  while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue))
     {
-      gs_unref_object GFile *rootfs_path = NULL;
-      gs_unref_object GFileInfo *rootfs_info = NULL;
-      guint32 root_dev;
-      guint64 root_inode;
-      guint i;
-
-      rootfs_path = g_file_new_for_path ("/");
-      rootfs_info = g_file_query_info (rootfs_path, OSTREE_GIO_FAST_QUERYINFO,
-                                       G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                       cancellable, error);
-      if (!rootfs_info)
+      const char *suffix = hashkey;
+      gs_free char *ref = g_strconcat (prefix, "/", suffix, NULL);
+      if (!ostree_repo_write_refspec (repo, ref, NULL, error))
         goto out;
+    }
 
-      root_dev = g_file_info_get_attribute_uint32 (rootfs_info, "unix::device");
-      root_inode = g_file_info_get_attribute_uint64 (rootfs_info, "unix::inode");
+  ret = TRUE;
+ out:
+  return ret;
+}
 
-      if (!ot_admin_list_deployments (ostree_dir, ret_osname, &deployments,
-                                      cancellable, error))
-        goto out;
-      
-      for (i = 0; i < deployments->len; i++)
-        {
-          GFile *deployment = deployments->pdata[i];
-          gs_unref_object GFileInfo *deployment_info = NULL;
-          guint32 deploy_dev;
-          guint64 deploy_inode;
-          
-          deployment_info = g_file_query_info (deployment, OSTREE_GIO_FAST_QUERYINFO,
-                                               G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                               cancellable, error);
-          if (!deployment_info)
-            goto out;
+static gboolean
+generate_deployment_refs_and_prune (GFile               *sysroot,
+                                    OstreeRepo          *repo,
+                                    int                  bootversion,
+                                    int                  subbootversion,
+                                    GPtrArray           *deployments,
+                                    GCancellable        *cancellable,
+                                    GError             **error)
+{
+  gboolean ret = FALSE;
+  int cleanup_bootversion;
+  int cleanup_subbootversion;
+  guint i;
+  gint n_objects_total, n_objects_pruned;
+  guint64 freed_space;
+  gs_free char *cleanup_boot_name = NULL;
+  gs_unref_object GFile *cleanup_boot_dir = NULL;
 
-          deploy_dev = g_file_info_get_attribute_uint32 (deployment_info, "unix::device");
-          deploy_inode = g_file_info_get_attribute_uint64 (deployment_info, "unix::inode");
+  cleanup_bootversion = (bootversion == 0) ? 1 : 0;
+  cleanup_subbootversion = (subbootversion == 0) ? 1 : 0;
 
-          if (root_dev == deploy_dev && root_inode == deploy_inode)
-            {
-              ret_deployment = g_object_ref (deployment);
-              break;
-            }
-        }
-      
-      g_assert (ret_deployment != NULL);
+  if (!cleanup_ref_prefix (repo, cleanup_bootversion, 0,
+                           cancellable, error))
+    goto out;
+
+  if (!cleanup_ref_prefix (repo, cleanup_bootversion, 1,
+                           cancellable, error))
+    goto out;
+
+  if (!cleanup_ref_prefix (repo, bootversion, cleanup_subbootversion,
+                           cancellable, error))
+    goto out;
+
+  for (i = 0; i < deployments->len; i++)
+    {
+      OtDeployment *deployment = deployments->pdata[i];
+      gs_free char *refname = g_strdup_printf ("ostree/%d/%d/%u",
+                                               bootversion, subbootversion,
+                                               i);
+      if (!ostree_repo_write_refspec (repo, refname, ot_deployment_get_csum (deployment),
+                                      error))
+        goto out;
+    }
+
+  if (!ostree_prune (repo, OSTREE_PRUNE_FLAGS_REFS_ONLY, 0,
+                     &n_objects_total, &n_objects_pruned, &freed_space,
+                     cancellable, error))
+    goto out;
+  if (freed_space > 0)
+    {
+      char *freed_space_str = g_format_size_full (freed_space, 0);
+      g_print ("Freed objects: %s\n", freed_space_str);
     }
 
   ret = TRUE;
-  ot_transfer_out_value (out_osname, &ret_osname);
-  ot_transfer_out_value (out_deployment, &ret_deployment);
  out:
   return ret;
 }
-
+  
 gboolean
-ot_admin_get_default_ostree_dir (GFile        **out_ostree_dir,
-                                 GCancellable  *cancellable,
-                                 GError       **error)
+ot_admin_cleanup (GFile               *sysroot,
+                  GCancellable        *cancellable,
+                  GError             **error)
 {
   gboolean ret = FALSE;
-  gs_unref_object GFile *possible_ostree_dir = NULL;
-  gs_unref_object GFile *ret_ostree_dir = NULL;
-  gs_unref_object GFile *host_usr = NULL;
+  gs_unref_ptrarray GPtrArray *deployments = NULL;
+  gs_unref_object OstreeRepo *repo = NULL;
+  int bootversion;
+  int subbootversion;
 
-  host_usr = g_file_new_for_path ("/usr");
+  if (!ot_admin_list_deployments (sysroot, &bootversion, &deployments,
+                                  cancellable, error))
+    goto out;
 
-  if (ret_ostree_dir == NULL)
-    {
-      g_clear_object (&possible_ostree_dir);
-      possible_ostree_dir = g_file_new_for_path ("/sysroot/ostree");
-      if (g_file_query_exists (possible_ostree_dir, NULL))
-        ret_ostree_dir = g_object_ref (possible_ostree_dir);
-    }
-  if (ret_ostree_dir == NULL)
+  if (!ot_admin_read_current_subbootversion (sysroot, bootversion, &subbootversion,
+                                             cancellable, error))
+    goto out;
+
+  if (!cleanup_other_bootversions (sysroot, bootversion, subbootversion,
+                                   cancellable, error))
+    goto out;
+
+  if (!cleanup_old_deployments (sysroot, deployments,
+                                cancellable, error))
+    goto out;
+
+  if (deployments->len > 0)
     {
-      g_clear_object (&possible_ostree_dir);
-      possible_ostree_dir = g_file_new_for_path ("/ostree");
-      /* If there's also /usr, we assume we're outside an ostree root
-       * and thus should use /ostree.
-       */
-      if (g_file_query_exists (possible_ostree_dir, NULL) ||
-          g_file_query_exists (host_usr, NULL))
-        ret_ostree_dir = g_object_ref (possible_ostree_dir);
+      if (!ot_admin_get_repo (sysroot, &repo, cancellable, error))
+        goto out;
+
+      if (!generate_deployment_refs_and_prune (sysroot, repo, bootversion,
+                                               subbootversion, deployments,
+                                               cancellable, error))
+        goto out;
     }
 
   ret = TRUE;
-  ot_transfer_out_value (out_ostree_dir, &ret_ostree_dir);
+ out:
   return ret;
 }
 
-gboolean
-ot_admin_pull (GFile         *ostree_dir,
-               const char    *osname,
-               GCancellable  *cancellable,
-               GError       **error)
+OtBootloader *
+ot_admin_query_bootloader (GFile         *sysroot)
 {
-  gs_unref_object GFile *repo_path = g_file_get_child (ostree_dir, "repo");
-  gs_free char *repo_arg = g_strconcat ("--repo=",
-                                        gs_file_get_path_cached (repo_path),
-                                        NULL);
+  OtBootloaderSyslinux *syslinux;
 
-  return gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
-                                        GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
-                                        cancellable, error,
-                                        "ostree", repo_arg, "pull", osname, NULL);
+  syslinux = ot_bootloader_syslinux_new (sysroot);
+  if (ot_bootloader_query ((OtBootloader*)syslinux))
+    return (OtBootloader*) (syslinux);
+
+  return NULL;
 }
 
-void
-ot_admin_parse_deploy_name (GFile       *ostree_dir,
-                            const char  *osname,
-                            GFile       *deployment,
-                            char       **out_name,
-                            char       **out_rev)
+GKeyFile *
+ot_origin_new_from_refspec (const char *refspec)
 {
-  gs_unref_object GFile *deploy_dir = g_file_get_child (ostree_dir, "deploy");
-  gs_unref_object GFile *os_dir = g_file_get_child (deploy_dir, osname);
-  gs_free char *relpath = g_file_get_relative_path (os_dir, deployment);
-  const char *last_dash;
+  GKeyFile *ret = g_key_file_new ();
+  g_key_file_set_string (ret, "origin", "refspec", refspec);
+  return ret;
+}
 
-  g_assert (relpath);
-  last_dash = strrchr (relpath, '-');
-  if (!last_dash)
-    g_error ("Failed to parse deployment name %s", relpath);
-  
-  if (out_name)
-    *out_name = g_strndup (relpath, last_dash - relpath);
-  if (out_rev)
-    *out_rev = g_strdup (last_dash + 1);
+gboolean
+ot_admin_get_repo (GFile         *sysroot,
+                   OstreeRepo   **out_repo,
+                   GCancellable  *cancellable,
+                   GError       **error)
+{
+  gboolean ret = FALSE;
+  gs_unref_object OstreeRepo *ret_repo = NULL;
+  gs_unref_object GFile *repo_path = g_file_resolve_relative_path (sysroot, "ostree/repo");
+
+  ret_repo = ostree_repo_new (repo_path);
+  if (!ostree_repo_check (ret_repo, error))
+    goto out;
+    
+  ret = TRUE;
+  ot_transfer_out_value (out_repo, &ret_repo);
+ out:
+  return ret;
 }
diff --git a/src/ostree/ot-admin-functions.h b/src/ostree/ot-admin-functions.h
index a03ef59..5a3c076 100644
--- a/src/ostree/ot-admin-functions.h
+++ b/src/ostree/ot-admin-functions.h
@@ -24,6 +24,10 @@
 #define __OT_ADMIN_FUNCTIONS__
 
 #include <gio/gio.h>
+#include "ostree.h"
+#include "ot-deployment.h"
+#include "ot-bootloader.h"
+#include "ot-ordered-hash.h"
 
 G_BEGIN_DECLS
 
@@ -31,50 +35,84 @@ gboolean ot_admin_ensure_initialized (GFile         *ostree_dir,
                                      GCancellable  *cancellable,
                                      GError       **error);
 
-gboolean ot_admin_get_booted_os (char        **out_osname,
-                                 char        **out_tree,
-                                 GCancellable *cancellable,
-                                 GError      **error);
-
-gboolean ot_admin_get_current_deployment (GFile           *ostree_dir,
-                                          const char      *osname,
-                                          GFile          **out_deployment,
-                                          GCancellable    *cancellable,
-                                          GError         **error);
-gboolean ot_admin_get_previous_deployment (GFile           *ostree_dir,
-                                           const char      *osname,
-                                           GFile          **out_deployment,
-                                           GCancellable    *cancellable,
-                                           GError         **error);
-
-gboolean ot_admin_list_deployments (GFile               *ostree_dir,
-                                    const char          *osname,
+gboolean ot_admin_check_os (GFile         *sysroot, 
+                            const char    *osname,
+                            GCancellable  *cancellable,
+                            GError       **error);
+
+char *ot_admin_split_keyeq (char *str);
+OtOrderedHash *ot_admin_parse_kernel_args (const char *options);
+char * ot_admin_kernel_arg_string_serialize (OtOrderedHash *ohash);
+
+OtBootloader *ot_admin_query_bootloader (GFile         *sysroot);
+
+gboolean ot_admin_read_current_subbootversion (GFile         *sysroot,
+                                               int            bootversion,
+                                               int           *out_subbootversion,
+                                               GCancellable  *cancellable,
+                                               GError       **error);
+
+gboolean ot_admin_read_boot_loader_configs (GFile         *boot_dir,
+                                            int            bootversion,
+                                            GPtrArray    **out_loader_configs,
+                                            GCancellable  *cancellable,
+                                            GError       **error);
+
+gboolean ot_admin_list_deployments (GFile               *sysroot,
+                                    int                 *out_bootversion,
                                     GPtrArray          **out_deployments,
                                     GCancellable        *cancellable,
                                     GError             **error);
 
-gboolean ot_admin_get_active_deployment (GFile           *ostree_dir,
-                                         char           **out_osname,
-                                         GFile          **out_deployment,
-                                         GCancellable    *cancellable,
-                                         GError         **error);
+gboolean ot_admin_find_booted_deployment (GFile               *sysroot,
+                                          GPtrArray           *deployments,
+                                          OtDeployment       **out_deployment,
+                                          GCancellable        *cancellable,
+                                          GError             **error);
+
+gboolean ot_admin_require_booted_deployment (GFile               *sysroot,
+                                             OtDeployment       **out_deployment,
+                                             GCancellable        *cancellable,
+                                             GError             **error);
+
+gboolean ot_admin_require_deployment_or_osname (GFile               *sysroot,
+                                                GPtrArray           *deployment_list,
+                                                const char          *osname,
+                                                OtDeployment       **out_deployment,
+                                                GCancellable        *cancellable,
+                                                GError             **error);
+
+OtDeployment *ot_admin_get_merge_deployment (GPtrArray         *deployment_list,
+                                             const char        *osname,
+                                             OtDeployment      *booted_deployment,
+                                             OtDeployment      *new_deployment);
+
+GFile *ot_admin_get_deployment_origin_path (GFile   *deployment_path);
+
+GFile *ot_admin_get_deployment_directory (GFile        *sysroot,
+                                          OtDeployment *deployment);
+
+gboolean ot_admin_get_repo (GFile         *sysroot,
+                            OstreeRepo   **out_repo,
+                            GCancellable  *cancellable,
+                            GError       **error);
+
+gboolean ot_admin_cleanup (GFile               *sysroot,
+                           GCancellable        *cancellable,
+                           GError             **error);
 
 gboolean ot_admin_get_default_ostree_dir (GFile        **out_ostree_dir,
                                           GCancellable  *cancellable,
                                           GError       **error);
 
+GKeyFile *ot_origin_new_from_refspec (const char *refspec);
+
 gboolean ot_admin_pull (GFile         *ostree_dir,
-                        const char    *osname,
+                        const char    *remote,
+                        const char    *ref,
                         GCancellable  *cancellable,
                         GError       **error);
 
-void
-ot_admin_parse_deploy_name (GFile   *ostree_dir,
-                            const char *osname,
-                            GFile   *deployment,
-                            char   **out_name,
-                            char   **out_rev);
-
 G_END_DECLS
 
 #endif
diff --git a/src/ostree/ot-bootloader-syslinux.c b/src/ostree/ot-bootloader-syslinux.c
new file mode 100644
index 0000000..9fe6ae1
--- /dev/null
+++ b/src/ostree/ot-bootloader-syslinux.c
@@ -0,0 +1,312 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This program 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 licence 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.
+ */
+
+#include "config.h"
+
+#include "ot-bootloader-syslinux.h"
+#include "libgsystem.h"
+#include "otutil.h"
+#include "ot-admin-functions.h"
+
+#include <string.h>
+
+struct _OtBootloaderSyslinux
+{
+  GObject       parent_instance;
+
+  GFile        *sysroot;
+  GFile        *config_path;
+};
+
+typedef GObjectClass OtBootloaderSyslinuxClass;
+
+static void ot_bootloader_syslinux_bootloader_iface_init (OtBootloaderInterface *iface);
+G_DEFINE_TYPE_WITH_CODE (OtBootloaderSyslinux, ot_bootloader_syslinux, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (OT_TYPE_BOOTLOADER, 
ot_bootloader_syslinux_bootloader_iface_init));
+
+static gboolean
+ot_bootloader_syslinux_query (OtBootloader *bootloader)
+{
+  OtBootloaderSyslinux *self = OT_BOOTLOADER_SYSLINUX (bootloader);
+
+  return g_file_query_file_type (self->config_path, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == 
G_FILE_TYPE_SYMBOLIC_LINK;
+}
+
+static gboolean
+append_config_from_boot_loader_entries (OtBootloaderSyslinux  *self,
+                                        gboolean               regenerate_default,
+                                        int                    bootversion,
+                                        GPtrArray             *new_lines,
+                                        GCancellable          *cancellable,
+                                        GError               **error)
+{
+  gboolean ret = FALSE;
+  gs_unref_ptrarray GPtrArray *boot_loader_configs = NULL;
+  guint i;
+
+  if (!ot_admin_read_boot_loader_configs (self->sysroot, bootversion, &boot_loader_configs,
+                                          cancellable, error))
+    goto out;
+
+  for (i = 0; i < boot_loader_configs->len; i++)
+    {
+      OtConfigParser *config = boot_loader_configs->pdata[i];
+      const char *val;
+
+      val = ot_config_parser_get (config, "title");
+      if (!val)
+        val = "(Untitled)";
+
+      if (regenerate_default && i == 0)
+        {
+          g_ptr_array_add (new_lines, g_strdup_printf ("DEFAULT %s", val));
+        }
+
+      g_ptr_array_add (new_lines, g_strdup_printf ("LABEL %s", val));
+      
+      val = ot_config_parser_get (config, "linux");
+      if (!val)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "No \"linux\" key in bootloader config");
+          goto out;
+        }
+      g_ptr_array_add (new_lines, g_strdup_printf ("\tKERNEL %s", val));
+
+      val = ot_config_parser_get (config, "initrd");
+      if (val)
+        g_ptr_array_add (new_lines, g_strdup_printf ("\tINITRD %s", val));
+
+      val = ot_config_parser_get (config, "options");
+      if (val)
+        g_ptr_array_add (new_lines, g_strdup_printf ("\tAPPEND %s", val));
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static char *
+join_lines (GPtrArray  *lines)
+{
+  GString *buf = g_string_new ("");
+  guint i;
+  gboolean prev_was_empty = FALSE;
+
+  for (i = 0; i < lines->len; i++)
+    {
+      const char *line = lines->pdata[i];
+      /* Special bit to remove extraneous empty lines */
+      if (*line == '\0')
+        {
+          if (prev_was_empty || i == 0)
+            continue;
+          else
+            prev_was_empty = TRUE;
+        }
+      g_string_append (buf, line);
+      g_string_append_c (buf, '\n');
+    }
+  return g_string_free (buf, FALSE);
+}
+
+static gboolean
+ot_bootloader_syslinux_write_config (OtBootloader          *bootloader,
+                                     int                    bootversion,
+                                     GCancellable          *cancellable,
+                                     GError               **error)
+{
+  gboolean ret = FALSE;
+  OtBootloaderSyslinux *self = OT_BOOTLOADER_SYSLINUX (bootloader);
+  gs_unref_object GFile *new_config_path = NULL;
+  gs_free char *config_contents = NULL;
+  gs_free char *new_config_contents = NULL;
+  gs_unref_ptrarray GPtrArray *new_lines = NULL;
+  gs_unref_ptrarray GPtrArray *tmp_lines = NULL;
+  gs_free char *kernel_arg = NULL;
+  gboolean saw_default = FALSE;
+  gboolean regenerate_default = FALSE;
+  gboolean parsing_label = FALSE;
+  char **lines = NULL;
+  char **iter;
+  guint i;
+
+  new_config_path = ot_gfile_resolve_path_printf (self->sysroot, "boot/loader.%d/syslinux.cfg",
+                                                  bootversion);
+
+  /* This should follow the symbolic link to the current bootversion. */
+  config_contents = gs_file_load_contents_utf8 (self->config_path, cancellable, error);
+  if (!config_contents)
+    goto out;
+
+  lines = g_strsplit (config_contents, "\n", -1);
+  new_lines = g_ptr_array_new_with_free_func (g_free);
+  tmp_lines = g_ptr_array_new_with_free_func (g_free);
+  
+  /* Note special iteration condition here; we want to also loop one
+   * more time at the end where line = NULL to ensure we finish off
+   * processing the last LABEL.
+   */
+  iter = lines;
+  while (TRUE)
+    {
+      char *line = *iter;
+      gboolean skip = FALSE;
+
+      if (parsing_label && 
+          (line == NULL || !g_str_has_prefix (line, "\t")))
+        {
+          parsing_label = FALSE;
+          if (kernel_arg == NULL)
+            {
+              g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                                   "No KERNEL argument found after LABEL");
+              goto out;
+            }
+
+          /* If this is a non-ostree kernel, just emit the lines
+           * we saw.
+           */
+          if (!g_str_has_prefix (kernel_arg, "/ostree/"))
+            {
+              for (i = 0; i < tmp_lines->len; i++)
+                {
+                  g_ptr_array_add (new_lines, tmp_lines->pdata[i]);
+                  tmp_lines->pdata[i] = NULL; /* Transfer ownership */
+                }
+            }
+          else
+            {
+              /* Otherwise, we drop the config on the floor - it
+               * will be regenerated.
+               */
+              g_ptr_array_set_size (tmp_lines, 0);
+            }
+        }
+
+      if (line == NULL)
+        break;
+
+      if (!parsing_label &&
+          (g_str_has_prefix (line, "LABEL ")))
+        {
+          parsing_label = TRUE;
+          g_ptr_array_set_size (tmp_lines, 0);
+        }
+      else if (parsing_label && g_str_has_prefix (line, "\tKERNEL "))
+        {
+          g_free (kernel_arg);
+          kernel_arg = g_strdup (line + strlen ("\tKERNEL "));
+        }
+      else if (!parsing_label &&
+               (g_str_has_prefix (line, "DEFAULT ")))
+        {
+          saw_default = TRUE;
+          if (g_str_has_prefix (line, "DEFAULT ostree:"))
+            regenerate_default = TRUE;
+          skip = TRUE;
+        }
+      
+      if (skip)
+        {
+          g_free (line);
+        }
+      else
+        {
+          if (parsing_label)
+            {
+              g_ptr_array_add (tmp_lines, line);
+            }
+          else
+            {
+              g_ptr_array_add (new_lines, line);
+            }
+        }
+      /* Transfer ownership */
+      *iter = NULL;
+      iter++;
+    }
+
+  if (!saw_default)
+    regenerate_default = TRUE;
+
+  if (!append_config_from_boot_loader_entries (self, regenerate_default,
+                                               bootversion, new_lines,
+                                               cancellable, error))
+    goto out;
+
+  new_config_contents = join_lines (new_lines);
+
+  if (strcmp (new_config_contents, config_contents) != 0)
+    {
+      if (!g_file_replace_contents (new_config_path, new_config_contents,
+                                    strlen (new_config_contents), 
+                                    NULL, FALSE, G_FILE_CREATE_NONE,
+                                    NULL, cancellable, error))
+        goto out;
+      g_print ("Saved new version of %s\n", gs_file_get_path_cached (self->config_path));
+    }
+  
+  ret = TRUE;
+ out:
+  g_free (lines); /* Note we freed elements individually */
+  return ret;
+}
+
+static void
+ot_bootloader_syslinux_finalize (GObject *object)
+{
+  OtBootloaderSyslinux *self = OT_BOOTLOADER_SYSLINUX (object);
+
+  g_clear_object (&self->sysroot);
+  g_clear_object (&self->config_path);
+
+  G_OBJECT_CLASS (ot_bootloader_syslinux_parent_class)->finalize (object);
+}
+
+void
+ot_bootloader_syslinux_init (OtBootloaderSyslinux *self)
+{
+}
+
+static void
+ot_bootloader_syslinux_bootloader_iface_init (OtBootloaderInterface *iface)
+{
+  iface->query = ot_bootloader_syslinux_query;
+  iface->write_config = ot_bootloader_syslinux_write_config;
+}
+
+void
+ot_bootloader_syslinux_class_init (OtBootloaderSyslinuxClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = ot_bootloader_syslinux_finalize;
+}
+
+OtBootloaderSyslinux *
+ot_bootloader_syslinux_new (GFile *sysroot)
+{
+  OtBootloaderSyslinux *self = g_object_new (OT_TYPE_BOOTLOADER_SYSLINUX, NULL);
+  self->sysroot = g_object_ref (sysroot);
+  self->config_path = g_file_resolve_relative_path (self->sysroot, "boot/syslinux/syslinux.cfg");
+  return self;
+}
diff --git a/src/ostree/ot-bootloader-syslinux.h b/src/ostree/ot-bootloader-syslinux.h
new file mode 100644
index 0000000..8a81089
--- /dev/null
+++ b/src/ostree/ot-bootloader-syslinux.h
@@ -0,0 +1,40 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This program 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 licence 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.
+ */
+
+#ifndef __OT_BOOTLOADER_SYSLINUX_H__
+#define __OT_BOOTLOADER_SYSLINUX_H__
+
+#include "ot-bootloader.h"
+
+G_BEGIN_DECLS
+
+#define OT_TYPE_BOOTLOADER_SYSLINUX (ot_bootloader_syslinux_get_type ())
+#define OT_BOOTLOADER_SYSLINUX(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OT_TYPE_BOOTLOADER_SYSLINUX, 
OtBootloaderSyslinux))
+#define OT_IS_BOOTLOADER_SYSLINUX(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OT_TYPE_BOOTLOADER_SYSLINUX))
+
+typedef struct _OtBootloaderSyslinux OtBootloaderSyslinux;
+
+GType ot_bootloader_syslinux_get_type (void) G_GNUC_CONST;
+
+OtBootloaderSyslinux * ot_bootloader_syslinux_new (GFile *sysroot);
+
+G_END_DECLS
+
+#endif /* __OT_BOOTLOADER_SYSLINUX_H__ */
diff --git a/src/ostree/ot-bootloader.c b/src/ostree/ot-bootloader.c
new file mode 100644
index 0000000..68ac223
--- /dev/null
+++ b/src/ostree/ot-bootloader.c
@@ -0,0 +1,49 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This program 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 licence 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.
+ */
+
+#include "config.h"
+#include "ot-bootloader.h"
+
+G_DEFINE_INTERFACE (OtBootloader, ot_bootloader, G_TYPE_OBJECT)
+
+static void
+ot_bootloader_default_init (OtBootloaderInterface *iface)
+{
+}
+
+gboolean
+ot_bootloader_query (OtBootloader  *self)
+{
+  g_return_val_if_fail (OT_IS_BOOTLOADER (self), FALSE);
+
+  return OT_BOOTLOADER_GET_IFACE (self)->query (self);
+}
+
+gboolean
+ot_bootloader_write_config (OtBootloader  *self,
+                            int            bootversion,
+                            GCancellable  *cancellable,
+                            GError       **error)
+{
+  g_return_val_if_fail (OT_IS_BOOTLOADER (self), FALSE);
+
+  return OT_BOOTLOADER_GET_IFACE (self)->write_config (self, bootversion, 
+                                                       cancellable, error);
+}
diff --git a/src/ostree/ot-bootloader.h b/src/ostree/ot-bootloader.h
new file mode 100644
index 0000000..6043b83
--- /dev/null
+++ b/src/ostree/ot-bootloader.h
@@ -0,0 +1,59 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This program 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 licence 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.
+ */
+
+#ifndef __OT_BOOTLOADER_H__
+#define __OT_BOOTLOADER_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define OT_TYPE_BOOTLOADER (ot_bootloader_get_type ())
+#define OT_BOOTLOADER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OT_TYPE_BOOTLOADER, OtBootloader))
+#define OT_IS_BOOTLOADER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OT_TYPE_BOOTLOADER))
+#define OT_BOOTLOADER_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), OT_TYPE_BOOTLOADER, 
OtBootloaderInterface))
+
+typedef struct _OtBootloader OtBootloader;
+typedef struct _OtBootloaderInterface                            OtBootloaderInterface;
+
+struct _OtBootloaderInterface
+{
+  GTypeInterface g_iface;
+
+  /* virtual functions */
+  gboolean             (* query)                  (OtBootloader  *self);
+  gboolean             (* write_config)           (OtBootloader  *self,
+                                                   int            bootversion,
+                                                   GCancellable  *cancellable,
+                                                   GError       **error);
+};
+
+GType ot_bootloader_get_type (void) G_GNUC_CONST;
+
+gboolean ot_bootloader_query (OtBootloader *self);
+
+gboolean ot_bootloader_write_config (OtBootloader  *self,
+                                     int            bootversion,
+                                     GCancellable  *cancellable,
+                                     GError       **error);
+
+G_END_DECLS
+
+#endif /* __OT_BOOTLOADER_H__ */
diff --git a/src/ostree/ot-builtin-admin.c b/src/ostree/ot-builtin-admin.c
index c8f60fb..b1dfac7 100644
--- a/src/ostree/ot-builtin-admin.c
+++ b/src/ostree/ot-builtin-admin.c
@@ -31,12 +31,10 @@
 
 #include <glib/gi18n.h>
 
-static char *opt_ostree_dir = NULL;
-static char *opt_boot_dir = "/boot";
+static char *opt_sysroot = "/";
 
 static GOptionEntry options[] = {
-  { "ostree-dir", 0, 0, G_OPTION_ARG_STRING, &opt_ostree_dir, "Path to OSTree root directory (default: 
/ostree)", NULL },
-  { "boot-dir", 0, 0, G_OPTION_ARG_STRING, &opt_boot_dir, "Path to system boot directory (default: /boot)", 
NULL },
+  { "sysroot", 0, 0, G_OPTION_ARG_STRING, &opt_sysroot, "Path to root directory (default: /)", NULL },
   { NULL }
 };
 
@@ -51,9 +49,8 @@ static OstreeAdminCommand admin_subcommands[] = {
   { "deploy", ot_admin_builtin_deploy },
   { "install", ot_admin_builtin_install },
   { "upgrade", ot_admin_builtin_upgrade },
-  { "pull-deploy", ot_admin_builtin_pull_deploy },
   { "prune", ot_admin_builtin_prune },
-  { "update-kernel", ot_admin_builtin_update_kernel },
+  { "status", ot_admin_builtin_status },
   { "config-diff", ot_admin_builtin_diff },
   { "run-triggers", ot_admin_builtin_run_triggers },
   { NULL, NULL }
@@ -70,8 +67,6 @@ ostree_builtin_admin (int argc, char **argv, GFile *repo_path, GError **error)
   int subcmd_argc;
   OtAdminBuiltinOpts admin_opts;
   char **subcmd_argv = NULL;
-  ot_lobj GFile *ostree_dir = NULL;
-  ot_lobj GFile *boot_dir = NULL;
 
   context = g_option_context_new ("[OPTIONS] SUBCOMMAND - Run an administrative subcommand");
 
@@ -117,21 +112,9 @@ ostree_builtin_admin (int argc, char **argv, GFile *repo_path, GError **error)
       goto out;
     }
 
-  if (opt_ostree_dir != NULL)
-    {
-      ostree_dir = g_file_new_for_path (opt_ostree_dir);
-    }
-  else
-    {
-      if (!ot_admin_get_default_ostree_dir (&ostree_dir, cancellable, error))
-        goto out;
-    }
-  boot_dir = g_file_new_for_path (opt_boot_dir);
-
   ostree_prep_builtin_argv (subcommand_name, argc-2, argv+2, &subcmd_argc, &subcmd_argv);
 
-  admin_opts.ostree_dir = ostree_dir;
-  admin_opts.boot_dir = boot_dir;
+  admin_opts.sysroot = g_file_new_for_path (opt_sysroot);
   if (!subcommand->fn (subcmd_argc, subcmd_argv, &admin_opts, error))
     goto out;
  
diff --git a/src/ostree/ot-config-parser.c b/src/ostree/ot-config-parser.c
new file mode 100644
index 0000000..e87d5b9
--- /dev/null
+++ b/src/ostree/ot-config-parser.c
@@ -0,0 +1,230 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This program 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 licence 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.
+ */
+
+#include "config.h"
+
+#include "ot-config-parser.h"
+#include "libgsystem.h"
+
+struct _OtConfigParser
+{
+  GObject       parent_instance;
+
+  gboolean      parsed;
+  char         *separators;
+
+  GHashTable   *options;
+  GPtrArray    *lines;
+};
+
+typedef GObjectClass OtConfigParserClass;
+
+G_DEFINE_TYPE (OtConfigParser, ot_config_parser, G_TYPE_OBJECT)
+
+gboolean
+ot_config_parser_parse (OtConfigParser  *self,
+                        GFile           *path,
+                        GCancellable    *cancellable,
+                        GError         **error)
+{
+  gboolean ret = FALSE;
+  gs_free char *contents = NULL;
+  char **lines = NULL;
+  char **iter = NULL;
+
+  g_return_val_if_fail (!self->parsed, FALSE);
+
+  contents = gs_file_load_contents_utf8 (path, cancellable, error);
+  if (!contents)
+    goto out;
+
+  lines = g_strsplit (contents, "\n", -1);
+  for (iter = lines; *iter; iter++)
+    {
+      const char *line = *iter;
+      char *keyname = "";
+      
+      if (g_ascii_isalpha (*line))
+        {
+          char **items = NULL;
+          items = g_strsplit_set (line, self->separators, 2);
+          if (g_strv_length (items) == 2 && items[0][0] != '\0')
+            {
+              keyname = items[0];
+              g_hash_table_insert (self->options, items[0], items[1]);
+              g_free (items); /* Transfer ownership */
+            }
+          else
+            {
+              g_strfreev (items);
+            }
+        }
+      g_ptr_array_add (self->lines, g_variant_new ("(ss)", keyname, line));
+    }
+
+  self->parsed = TRUE;
+  
+  ret = TRUE;
+ out:
+  g_strfreev (lines);
+  return ret;
+}
+
+void
+ot_config_parser_set (OtConfigParser  *self,
+                      const char      *key,
+                      const char      *value)
+{
+  g_hash_table_replace (self->options, g_strdup (key), g_strdup (value));
+}
+
+const char *
+ot_config_parser_get (OtConfigParser  *self,
+                      const char      *key)
+{
+  return g_hash_table_lookup (self->options, key);
+}
+
+static gboolean
+write_key (OtConfigParser         *self,
+           GDataOutputStream      *out,
+           const char             *key,
+           const char             *value,
+           GCancellable           *cancellable,
+           GError                **error)
+{
+  gboolean ret = FALSE;
+
+  if (!g_data_output_stream_put_string (out, key, cancellable, error))
+    goto out;
+  if (!g_data_output_stream_put_byte (out, self->separators[0], cancellable, error))
+    goto out;
+  if (!g_data_output_stream_put_string (out, value, cancellable, error))
+    goto out;
+  if (!g_data_output_stream_put_byte (out, '\n', cancellable, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+           
+gboolean
+ot_config_parser_write (OtConfigParser   *self,
+                        GFile            *output,
+                        GCancellable     *cancellable,
+                        GError          **error)
+{
+  gboolean ret = FALSE;
+  GHashTableIter hashiter;
+  gpointer hashkey, hashvalue;
+  gs_unref_object GOutputStream *out = NULL;
+  gs_unref_object GDataOutputStream *dataout = NULL;
+  guint i;
+  gs_unref_hashtable GHashTable *written_overrides = NULL;
+
+  written_overrides = g_hash_table_new (g_str_hash, g_str_equal);
+
+  out = (GOutputStream*)g_file_replace (output, NULL, FALSE, 0, cancellable, error);
+  if (!out)
+    goto out;
+
+  dataout = g_data_output_stream_new (out);
+
+  for (i = 0; i < self->lines->len; i++)
+    {
+      GVariant *linedata = self->lines->pdata[i];
+      const char *key;
+      const char *value;
+      const char *line;
+
+      g_variant_get (linedata, "(&s&s)", &key, &line);
+
+      value = g_hash_table_lookup (self->options, key);
+      if (value == NULL)
+        {
+          if (!g_data_output_stream_put_string (dataout, line, cancellable, error))
+            goto out;
+          if (!g_data_output_stream_put_byte (dataout, '\n', cancellable, error))
+            goto out;
+        }
+      else
+        {
+          if (!write_key (self, dataout, key, value, cancellable, error))
+            goto out;
+          g_hash_table_insert (written_overrides, (gpointer)key, (gpointer)key);
+        }
+    }
+
+  g_hash_table_iter_init (&hashiter, self->options);
+  while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue))
+    {
+      if (g_hash_table_lookup (written_overrides, hashkey))
+        continue;
+      if (!write_key (self, dataout, hashkey, hashvalue, cancellable, error))
+        goto out;
+    }
+
+  if (!g_output_stream_close ((GOutputStream*)dataout, cancellable, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static void
+ot_config_parser_finalize (GObject *object)
+{
+  OtConfigParser *self = OT_CONFIG_PARSER (object);
+
+  g_hash_table_unref (self->options);
+  g_ptr_array_unref (self->lines);
+  g_free (self->separators);
+
+  G_OBJECT_CLASS (ot_config_parser_parent_class)->finalize (object);
+}
+
+static void
+ot_config_parser_init (OtConfigParser *self)
+{
+  self->options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+  self->lines = g_ptr_array_new ();
+}
+
+void
+ot_config_parser_class_init (OtConfigParserClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = ot_config_parser_finalize;
+}
+
+OtConfigParser *
+ot_config_parser_new (const char *separators)
+{
+  OtConfigParser *self = NULL;
+
+  g_return_val_if_fail (separators != NULL && separators[0], NULL);
+
+  self = g_object_new (OT_TYPE_CONFIG_PARSER, NULL);
+  self->separators = g_strdup (separators);
+  return self;
+}
diff --git a/src/ostree/ot-config-parser.h b/src/ostree/ot-config-parser.h
new file mode 100644
index 0000000..8b7b47a
--- /dev/null
+++ b/src/ostree/ot-config-parser.h
@@ -0,0 +1,58 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This program 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 licence 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.
+ */
+
+#ifndef __OT_CONFIG_PARSER_H__
+#define __OT_CONFIG_PARSER_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define OT_TYPE_CONFIG_PARSER (ot_config_parser_get_type ())
+#define OT_CONFIG_PARSER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OT_TYPE_CONFIG_PARSER, OtConfigParser))
+#define OT_IS_CONFIG_PARSER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OT_TYPE_CONFIG_PARSER))
+
+typedef struct _OtConfigParser OtConfigParser;
+
+GType ot_config_parser_get_type (void) G_GNUC_CONST;
+
+OtConfigParser * ot_config_parser_new (const char *separator);
+
+gboolean ot_config_parser_parse (OtConfigParser  *self,
+                                 GFile           *path,
+                                 GCancellable    *cancellable,
+                                 GError         **error);
+
+gboolean ot_config_parser_write (OtConfigParser   *self,
+                                 GFile            *output,
+                                 GCancellable     *cancellable,
+                                 GError          **error);
+
+void ot_config_parser_set (OtConfigParser  *self,
+                           const char      *key,
+                           const char      *value);
+
+const char *ot_config_parser_get (OtConfigParser  *self,
+                                  const char      *key);
+
+
+G_END_DECLS
+
+#endif /* __OT_CONFIG_PARSER_H__ */
diff --git a/src/ostree/ot-deployment.c b/src/ostree/ot-deployment.c
new file mode 100644
index 0000000..4f51b95
--- /dev/null
+++ b/src/ostree/ot-deployment.c
@@ -0,0 +1,209 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This program 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 licence 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.
+ */
+
+#include "config.h"
+
+#include "ot-deployment.h"
+
+struct _OtDeployment
+{
+  GObject       parent_instance;
+
+  int index;  /* Global offset */
+  char *osname;  /* osname */
+  char *csum;  /* OSTree checksum of tree */
+  int deployserial;  /* How many times this particular csum appears in deployment list */
+  char *bootcsum;  /* Checksum of kernel+initramfs */
+  int bootserial; /* An integer assigned to this tree per its ${bootcsum} */
+  OtConfigParser *bootconfig; /* Bootloader configuration */
+  GKeyFile *origin; /* How to construct an upgraded version of this tree */
+};
+
+typedef GObjectClass OtDeploymentClass;
+
+G_DEFINE_TYPE (OtDeployment, ot_deployment, G_TYPE_OBJECT)
+
+const char *
+ot_deployment_get_csum (OtDeployment *self)
+{
+  return self->csum;
+}
+
+const char *
+ot_deployment_get_bootcsum (OtDeployment *self)
+{
+  return self->bootcsum;
+}
+
+const char *
+ot_deployment_get_osname (OtDeployment *self)
+{
+  return self->osname;
+}
+
+int
+ot_deployment_get_deployserial (OtDeployment *self)
+{
+  return self->deployserial;
+}
+
+int
+ot_deployment_get_bootserial (OtDeployment *self)
+{
+  return self->bootserial;
+}
+
+OtConfigParser *
+ot_deployment_get_bootconfig (OtDeployment *self)
+{
+  return self->bootconfig;
+}
+
+GKeyFile *
+ot_deployment_get_origin (OtDeployment *self)
+{
+  return self->origin;
+}
+
+int
+ot_deployment_get_index (OtDeployment *self)
+{
+  return self->index;
+}
+
+void
+ot_deployment_set_index (OtDeployment *self, int index)
+{
+  self->index = index;
+}
+
+void
+ot_deployment_set_bootserial (OtDeployment *self, int index)
+{
+  self->bootserial = index;
+}
+
+void
+ot_deployment_set_bootconfig (OtDeployment *self, OtConfigParser *bootconfig)
+{
+  g_clear_object (&self->bootconfig);
+  if (bootconfig)
+    self->bootconfig = g_object_ref (bootconfig);
+}
+
+void
+ot_deployment_set_origin (OtDeployment *self, GKeyFile *origin)
+{
+  g_clear_pointer (&self->origin, g_key_file_unref);
+  if (origin)
+    self->origin = g_key_file_ref (origin);
+}
+
+OtDeployment *
+ot_deployment_clone (OtDeployment *self)
+{
+  OtDeployment *ret = ot_deployment_new (self->index, self->osname, self->csum,
+                                         self->deployserial,
+                                         self->bootcsum, self->bootserial);
+  ot_deployment_set_bootconfig (ret, self->bootconfig);
+  ot_deployment_set_origin (ret, self->origin);
+  return ret;
+}
+
+guint
+ot_deployment_hash (gconstpointer v)
+{
+  OtDeployment *d = (OtDeployment*)v;
+  return g_str_hash (ot_deployment_get_osname (d)) +
+    g_str_hash (ot_deployment_get_csum (d)) +
+    ot_deployment_get_deployserial (d);
+}
+
+gboolean
+ot_deployment_equal (gconstpointer ap, gconstpointer bp)
+{
+  OtDeployment *a = (OtDeployment*)ap;
+  OtDeployment *b = (OtDeployment*)bp;
+  
+  if (a == NULL && b == NULL)
+    return TRUE;
+  else if (a != NULL && b != NULL)
+    return g_str_equal (ot_deployment_get_osname (a),
+                        ot_deployment_get_osname (b)) &&
+      g_str_equal (ot_deployment_get_csum (a),
+                   ot_deployment_get_csum (b)) &&
+      ot_deployment_get_deployserial (a) == ot_deployment_get_deployserial (b);
+  else 
+    return FALSE;
+}
+
+static void
+ot_deployment_finalize (GObject *object)
+{
+  OtDeployment *self = OT_DEPLOYMENT (object);
+
+  g_free (self->osname);
+  g_free (self->csum);
+  g_free (self->bootcsum);
+  g_clear_object (&self->bootconfig);
+  g_clear_pointer (&self->origin, g_key_file_unref);
+
+  G_OBJECT_CLASS (ot_deployment_parent_class)->finalize (object);
+}
+
+void
+ot_deployment_init (OtDeployment *self)
+{
+}
+
+void
+ot_deployment_class_init (OtDeploymentClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = ot_deployment_finalize;
+}
+
+OtDeployment *
+ot_deployment_new (int    index,
+                   const char  *osname,
+                   const char  *csum,
+                   int    deployserial,
+                   const char  *bootcsum,
+                   int    bootserial)
+{
+  OtDeployment *self;
+  
+  /* index may be -1 */
+  g_return_val_if_fail (osname != NULL, NULL);
+  g_return_val_if_fail (csum != NULL, NULL);
+  g_return_val_if_fail (deployserial >= 0, NULL);
+  /* We can have "disconnected" deployments that don't have a
+     bootcsum/serial */
+
+  self = g_object_new (OT_TYPE_DEPLOYMENT, NULL);
+  self->index = index;
+  self->osname = g_strdup (osname);
+  self->csum = g_strdup (csum);
+  self->deployserial = deployserial;
+  self->bootcsum = g_strdup (bootcsum);
+  self->bootserial = bootserial;
+  return self;
+}
diff --git a/src/ostree/ot-deployment.h b/src/ostree/ot-deployment.h
new file mode 100644
index 0000000..66470d4
--- /dev/null
+++ b/src/ostree/ot-deployment.h
@@ -0,0 +1,66 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This program 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 licence 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.
+ */
+
+#ifndef __OT_DEPLOYMENT_H__
+#define __OT_DEPLOYMENT_H__
+
+#include <gio/gio.h>
+#include "ot-config-parser.h"
+
+G_BEGIN_DECLS
+
+#define OT_TYPE_DEPLOYMENT (ot_deployment_get_type ())
+#define OT_DEPLOYMENT(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OT_TYPE_DEPLOYMENT, OtDeployment))
+#define OT_IS_DEPLOYMENT(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OT_TYPE_DEPLOYMENT))
+
+typedef struct _OtDeployment OtDeployment;
+
+GType ot_deployment_get_type (void) G_GNUC_CONST;
+
+guint ot_deployment_hash (gconstpointer v);
+gboolean ot_deployment_equal (gconstpointer a, gconstpointer b);
+
+OtDeployment * ot_deployment_new (int    index,
+                                  const char  *osname,
+                                  const char  *csum,
+                                  int    deployserial,
+                                  const char  *bootcsum,
+                                  int    bootserial);
+
+int ot_deployment_get_index (OtDeployment *self);
+const char *ot_deployment_get_osname (OtDeployment *self);
+int ot_deployment_get_deployserial (OtDeployment *self);
+const char *ot_deployment_get_csum (OtDeployment *self);
+const char *ot_deployment_get_bootcsum (OtDeployment *self);
+int ot_deployment_get_bootserial (OtDeployment *self);
+OtConfigParser *ot_deployment_get_bootconfig (OtDeployment *self);
+GKeyFile *ot_deployment_get_origin (OtDeployment *self);
+
+void ot_deployment_set_index (OtDeployment *self, int index);
+void ot_deployment_set_bootserial (OtDeployment *self, int index);
+void ot_deployment_set_bootconfig (OtDeployment *self, OtConfigParser *bootconfig);
+void ot_deployment_set_origin (OtDeployment *self, GKeyFile *origin);
+
+OtDeployment *ot_deployment_clone (OtDeployment *self);
+
+
+G_END_DECLS
+
+#endif /* __OT_DEPLOYMENT_H__ */
diff --git a/src/ostree/ot-ordered-hash.c b/src/ostree/ot-ordered-hash.c
new file mode 100644
index 0000000..a7709e4
--- /dev/null
+++ b/src/ostree/ot-ordered-hash.c
@@ -0,0 +1,82 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This program 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 licence 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.
+ */
+
+#include "config.h"
+
+#include "ot-ordered-hash.h"
+
+OtOrderedHash *
+ot_ordered_hash_new (void)
+{
+  OtOrderedHash *ret;
+  ret = g_new0 (OtOrderedHash, 1);
+  ret->order = g_ptr_array_new_with_free_func (g_free);
+  ret->table = g_hash_table_new (g_str_hash, g_str_equal);
+  return ret;
+}
+
+void
+ot_ordered_hash_free (OtOrderedHash *ohash)
+{
+  if (!ohash)
+    return;
+  g_ptr_array_unref (ohash->order);
+  g_hash_table_unref (ohash->table);
+  g_free (ohash);
+}
+
+void
+ot_ordered_hash_cleanup (void *loc)
+{
+  ot_ordered_hash_free (*((OtOrderedHash**)loc));
+}
+
+void
+ot_ordered_hash_replace_key_take (OtOrderedHash   *ohash,
+                                  char            *key,
+                                  const char      *value)
+{
+  gboolean existed;
+
+  existed = g_hash_table_remove (ohash->table, key);
+  if (!existed)
+    g_ptr_array_add (ohash->order, key);
+  g_hash_table_insert (ohash->table, key, (char*)value);
+}
+
+void
+ot_ordered_hash_replace_key (OtOrderedHash  *ohash,
+                             const char     *key,
+                             const char     *val)
+{
+  GString *buf;
+  gsize keylen;
+  char *valp;
+  char *valblock;
+  
+  buf = g_string_new (key);
+  keylen = buf->len;
+  g_string_append_c (buf, '\0');
+  g_string_append (buf, val);
+  valblock = g_string_free (buf, FALSE);
+  valp = valblock + keylen + 1;
+
+  ot_ordered_hash_replace_key_take (ohash, valblock, valp);
+}
diff --git a/src/ostree/ot-ordered-hash.h b/src/ostree/ot-ordered-hash.h
new file mode 100644
index 0000000..a3a4ef8
--- /dev/null
+++ b/src/ostree/ot-ordered-hash.h
@@ -0,0 +1,46 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This program 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 licence 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.
+ */
+
+#ifndef __OT_ORDERED_HASH_H__
+#define __OT_ORDERED_HASH_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+  GPtrArray  *order;
+  GHashTable *table;
+} OtOrderedHash;
+
+OtOrderedHash *ot_ordered_hash_new (void);
+void ot_ordered_hash_free (OtOrderedHash *ohash);
+void ot_ordered_hash_cleanup (void *loc);
+void ot_ordered_hash_replace_key_take (OtOrderedHash  *ohash,
+                                       char         *key,
+                                       const char   *value);
+void ot_ordered_hash_replace_key (OtOrderedHash  *ohash,
+                                  const char   *key,
+                                  const char   *val);
+
+
+G_END_DECLS
+
+#endif /* __OT_ORDERED_HASH_H__ */
diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c
index cfd7481..72172dc 100644
--- a/src/switchroot/ostree-prepare-root.c
+++ b/src/switchroot/ostree-prepare-root.c
@@ -41,18 +41,19 @@
 
 #include "ostree-mount-util.h"
 
-static void
-parse_ostree_cmdline (char **out_osname,
-                     char **out_tree)
+static char *
+parse_ostree_cmdline (void)
 {
   FILE *f = fopen("/proc/cmdline", "r");
   char *cmdline = NULL;
   const char *iter;
+  char *ret = NULL;
   size_t len;
+
   if (!f)
-    return;
+    return NULL;
   if (getline (&cmdline, &len, f) < 0)
-    return;
+    return NULL;
 
   if (cmdline[len-1] == '\n')
     cmdline[len-1] = '\0';
@@ -66,39 +67,30 @@ parse_ostree_cmdline (char **out_osname,
        next_nonspc += 1;
       if (strncmp (iter, "ostree=", strlen ("ostree=")) == 0)
         {
-          const char *slash = strchr (iter, '/');
-          if (slash)
-            {
-              const char *start = iter + strlen ("ostree=");
-              *out_osname = strndup (start, slash - start);
-              if (next)
-                *out_tree = strndup (slash + 1, next - slash - 1);
-              else
-                *out_tree = strdup (slash + 1);
-              break;
-            }
+         const char *start = iter + strlen ("ostree=");
+         if (next)
+           ret = strndup (start, next - start);
+         else
+           ret = strdup (start);
+         break;
         }
       iter = next_nonspc;
     }
 
   free (cmdline);
+  return ret;
 }
 
 int
 main(int argc, char *argv[])
 {
-  const char *toproot_bind_mounts[] = { "/home", "/root", "/tmp", NULL };
-  const char *ostree_bind_mounts[] = { "/var", NULL };
   const char *readonly_bind_mounts[] = { "/usr", NULL };
   const char *root_mountpoint = NULL;
-  char *ostree_osname = NULL;
   char *ostree_target = NULL;
-  char ostree_target_path[PATH_MAX];
   char *deploy_path = NULL;
   char srcpath[PATH_MAX];
   char destpath[PATH_MAX];
   struct stat stbuf;
-  size_t len;
   int i;
 
   if (argc < 2)
@@ -108,29 +100,20 @@ main(int argc, char *argv[])
     }
 
   root_mountpoint = argv[1];
-
-  parse_ostree_cmdline (&ostree_osname, &ostree_target);
-
-  if (!ostree_osname)
+  if (strcmp (root_mountpoint, "/sysroot") != 0)
     {
-      fprintf (stderr, "No OSTree target; expected ostree=OSNAME/TREENAME\n");
+      fprintf (stderr, "ostree-prepare-root: Expected /sysroot\n");
       exit (1);
     }
 
-  /* Work-around for a kernel bug: for some reason the kernel
-   * refuses switching root if any file systems are mounted
-   * MS_SHARED. Hence remount them MS_PRIVATE here as a
-   * work-around.
-   *
-   * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
-  if (mount (NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
+  ostree_target = parse_ostree_cmdline ();
+  if (!ostree_target)
     {
-      perrorv ("Failed to make \"/\" private mount: %m");
+      fprintf (stderr, "No OSTree target; expected ostree=/ostree/boot.N/...\n");
       exit (1);
     }
 
-  snprintf (destpath, sizeof(destpath), "%s/ostree/deploy/%s/%s",
-           root_mountpoint, ostree_osname, ostree_target);
+  snprintf (destpath, sizeof(destpath), "%s/%s", root_mountpoint, ostree_target);
   fprintf (stderr, "Examining %s\n", destpath);
   if (lstat (destpath, &stbuf) < 0)
     {
@@ -142,18 +125,26 @@ main(int argc, char *argv[])
       fprintf (stderr, "OSTree target is not a symbolic link: %s\n", destpath);
       exit (1);
     }
-  if (readlink (destpath, ostree_target_path, PATH_MAX) < 0)
+  deploy_path = realpath (destpath, NULL);
+  if (deploy_path == NULL)
     {
-      perrorv ("readlink(%s) failed: ", destpath);
+      perrorv ("realpath(%s) failed: ", destpath);
       exit (1);
     }
-  len = strlen (ostree_target_path);
-  if (ostree_target_path[len-1] == '/')
-    ostree_target_path[len-1] = '\0';
-  fprintf (stderr, "Resolved OSTree target to: %s\n", ostree_target_path);
-  (void) asprintf (&deploy_path, "%s/ostree/deploy/%s/%s", root_mountpoint,
-                  ostree_osname, ostree_target_path);
+  fprintf (stderr, "Resolved OSTree target to: %s\n", deploy_path);
   
+  /* Work-around for a kernel bug: for some reason the kernel
+   * refuses switching root if any file systems are mounted
+   * MS_SHARED. Hence remount them MS_PRIVATE here as a
+   * work-around.
+   *
+   * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
+  if (mount (NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
+    {
+      perrorv ("Failed to make \"/\" private mount: %m");
+      exit (1);
+    }
+
   /* Make deploy_path a bind mount, so we can move it later */
   if (mount (deploy_path, deploy_path, NULL, MS_BIND, NULL) < 0)
     {
@@ -168,43 +159,14 @@ main(int argc, char *argv[])
       exit (1);
     }
 
-  snprintf (srcpath, sizeof(srcpath), "%s-etc", deploy_path);
-  snprintf (destpath, sizeof(destpath), "%s/etc", deploy_path);
-  if (mount (srcpath, destpath, NULL, MS_BIND, NULL) < 0)
+  snprintf (srcpath, sizeof(srcpath), "%s/../../var", deploy_path);
+  snprintf (destpath, sizeof(destpath), "%s/var", deploy_path);
+  if (mount (srcpath, destpath, NULL, MS_MGC_VAL|MS_BIND, NULL) < 0)
     {
-      perrorv ("Failed to bind mount '%s' to '%s'", srcpath, destpath);
+      perrorv ("failed to bind mount %s to %s", srcpath, destpath);
       exit (1);
     }
 
-  for (i = 0; toproot_bind_mounts[i] != NULL; i++)
-    {
-      snprintf (srcpath, sizeof(srcpath), "%s%s", root_mountpoint, toproot_bind_mounts[i]);
-      snprintf (destpath, sizeof(destpath), "%s%s", deploy_path, toproot_bind_mounts[i]);
-      /* Only do these bind mounts if the target exists and is a real directory,
-       * not a symbolic link.
-       */
-      if (lstat (destpath, &stbuf) == 0 && S_ISDIR(stbuf.st_mode))
-       {
-         if (mount (srcpath, destpath, NULL, MS_BIND & ~MS_RDONLY, NULL) < 0)
-           {
-             perrorv ("failed to bind mount (class:toproot) %s to %s", toproot_bind_mounts[i], destpath);
-             exit (1);
-           }
-       }
-    }
-
-  for (i = 0; ostree_bind_mounts[i] != NULL; i++)
-    {
-      snprintf (srcpath, sizeof(srcpath), "%s/ostree/deploy/%s%s", root_mountpoint,
-               ostree_osname, ostree_bind_mounts[i]);
-      snprintf (destpath, sizeof(destpath), "%s%s", deploy_path, ostree_bind_mounts[i]);
-      if (mount (srcpath, destpath, NULL, MS_MGC_VAL|MS_BIND, NULL) < 0)
-       {
-         perrorv ("failed to bind mount (class:bind) %s to %s", srcpath, destpath);
-         exit (1);
-       }
-    }
-
   for (i = 0; readonly_bind_mounts[i] != NULL; i++)
     {
       snprintf (destpath, sizeof(destpath), "%s%s", deploy_path, readonly_bind_mounts[i]);
diff --git a/tests/libtest.sh b/tests/libtest.sh
index 0da1564..c6eb3f2 100644
--- a/tests/libtest.sh
+++ b/tests/libtest.sh
@@ -34,10 +34,18 @@ assert_streq () {
     test "$1" = "$2" || (echo 1>&2 "$1 != $2"; exit 1)
 }
 
+assert_not_streq () {
+    (! test "$1" = "$2") || (echo 1>&2 "$1 == $2"; exit 1)
+}
+
 assert_has_file () {
     test -f "$1" || (echo 1>&2 "Couldn't find '$1'"; exit 1)
 }
 
+assert_has_dir () {
+    test -d "$1" || (echo 1>&2 "Couldn't find '$1'"; exit 1)
+}
+
 assert_not_has_file () {
     if test -f "$1"; then
        echo 1>&2 "File '$1' exists"; exit 1
@@ -50,6 +58,12 @@ assert_not_file_has_content () {
     fi
 }
 
+assert_not_has_dir () {
+    if test -d "$1"; then
+       echo 1>&2 "Directory '$1' exists"; exit 1
+    fi
+}
+
 assert_file_has_content () {
     if ! grep -q -e "$2" "$1"; then
        echo 1>&2 "File '$1' doesn't match regexp '$2'"; exit 1
@@ -136,3 +150,86 @@ setup_fake_remote_repo1() {
 
     export OSTREE="ostree --repo=repo"
 }
+
+setup_os_repository () {
+    mode=$1
+    shift
+
+    oldpwd=`pwd`
+
+    cd ${test_tmpdir}
+    mkdir testos-repo
+    if test -n "$mode"; then
+       ostree --repo=testos-repo init --mode=${mode}
+    else
+       ostree --repo=testos-repo init
+    fi
+
+    cd ${test_tmpdir}
+    mkdir osdata
+    cd osdata
+    mkdir -p boot usr/bin usr/lib/modules/3.6.0 usr/share usr/etc
+    echo "a kernel" > boot/vmlinuz-3.6.0
+    echo "an initramfs" > boot/initramfs-3.6.0
+    echo "a kernel module" > usr/lib/modules/3.6.0/foofs.ko
+    bootcsum=$(cat boot/vmlinuz-3-6.0 boot/initramfs-3.6.0 usr/lib/modules/3.6.0/foofs.ko | sha256sum | cut 
-f 1 -d ' ')
+    export bootcsum
+    mv boot/vmlinuz-3.6.0 boot/vmlinuz-3.6.0-${bootcsum}
+    mv boot/initramfs-3.6.0 boot/initramfs-3.6.0-${bootcsum}
+    
+    echo "an executable" > usr/bin/sh
+    echo "some shared data" > usr/share/langs.txt
+    echo "a library" > usr/lib/libfoo.so.0
+    ln -s usr/bin bin
+cat > usr/etc/os-release <<EOF
+NAME=TestOS
+VERSION=42
+ID=testos
+VERSION_ID=42
+PRETTY_NAME="TestOS 42"
+EOF
+    echo "a config file" > usr/etc/aconfigfile
+    mkdir -p usr/etc/NetworkManager
+    echo "a default daemon file" > usr/etc/NetworkManager/nm.conf
+
+    ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Build"
+    
+    echo "a new executable" > usr/bin/sh
+    ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Build"
+
+    ostree --repo=${test_tmpdir}/testos-repo fsck -q
+
+    cd ${test_tmpdir}
+    mkdir sysroot
+    ostree admin --sysroot=sysroot init-fs sysroot
+    ostree admin --sysroot=sysroot os-init testos
+
+    # Stub syslinux configuration
+    mkdir -p sysroot/boot/loader.0
+    ln -s loader.0 sysroot/boot/loader
+    touch sysroot/boot/loader/syslinux.cfg
+    # And a compatibility symlink
+    mkdir -p sysroot/boot/syslinux
+    ln -s ../loader/syslinux.cfg sysroot/boot/syslinux/syslinux.cfg
+}
+
+os_repository_new_commit ()
+{
+    cd ${test_tmpdir}/osdata
+    rm boot/*
+    echo "new: a kernel" > boot/vmlinuz-3.6.0
+    echo "new: an initramfs" > boot/initramfs-3.6.0
+    echo "new: a kernel module" > usr/lib/modules/3.6.0/foofs.ko
+    echo "new: another kernel module" > usr/lib/modules/3.6.0/othermod.ko
+    bootcsum=$(cat boot/vmlinuz-3.6.0 boot/initramfs-3.6.0 usr/lib/modules/3.6.0/foofs.ko 
usr/lib/modules/3.6.0/othermod.ko | sha256sum | cut -f 1 -d ' ')
+    export bootcsum
+    mv boot/vmlinuz-3.6.0 boot/vmlinuz-3.6.0-${bootcsum}
+    mv boot/initramfs-3.6.0 boot/initramfs-3.6.0-${bootcsum}
+
+    echo "a new default config file" > usr/etc/a-new-default-config-file
+    mkdir -p usr/etc/new-default-dir
+    echo "a new default dir and file" > usr/etc/new-default-dir/moo
+
+    ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Build"
+    cd ${test_tmpdir}
+}
diff --git a/tests/t0015-admin-deploy.sh b/tests/t0015-admin-deploy.sh
new file mode 100755
index 0000000..32cca0c
--- /dev/null
+++ b/tests/t0015-admin-deploy.sh
@@ -0,0 +1,134 @@
+#!/bin/bash
+#
+# Copyright (C) 2011 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.
+
+set -e
+
+. $(dirname $0)/libtest.sh
+
+echo "1..1"
+
+setup_os_repository "archive-z2"
+
+echo "ok setup"
+
+echo "1..7"
+
+ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime
+rev=$(ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime)
+export rev
+# This initial deployment gets kicked off with some kernel arguments 
+ostree admin --sysroot=sysroot deploy --karg=root=LABEL=MOO --karg=quiet --os=testos 
testos:testos/buildmaster/x86_64-runtime
+
+echo "ok deploy command"
+
+assert_not_has_dir sysroot/boot/loader.0
+assert_has_dir sysroot/boot/loader.1
+assert_has_dir sysroot/ostree/boot.1.1
+assert_has_file sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf
+assert_file_has_content sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf 'options.* root=LABEL=MOO'
+assert_file_has_content sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf 'options.* quiet'
+assert_file_has_content sysroot/boot/ostree/testos-${bootcsum}/vmlinuz-3.6.0 'a kernel'
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.0/etc/os-release 'NAME=TestOS'
+assert_file_has_content sysroot/ostree/boot.1/testos/${bootcsum}/0/etc/os-release 'NAME=TestOS'
+
+echo "ok layout"
+
+ostree admin --sysroot=sysroot deploy --os=testos testos:testos/buildmaster/x86_64-runtime
+# Need a new bootversion, sine we now have two deployments
+assert_has_dir sysroot/boot/loader.0
+assert_not_has_dir sysroot/boot/loader.1
+assert_has_dir sysroot/ostree/boot.0.1
+assert_not_has_dir sysroot/ostree/boot.0.0
+assert_not_has_dir sysroot/ostree/boot.1.0
+assert_not_has_dir sysroot/ostree/boot.1.1
+# Ensure we propagated kernel arguments from previous deployment
+assert_file_has_content sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf 'options.* root=LABEL=MOO'
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.1/etc/os-release 'NAME=TestOS'
+assert_file_has_content sysroot/ostree/boot.0/testos/${bootcsum}/0/etc/os-release 'NAME=TestOS'
+
+echo "ok second deploy"
+
+ostree admin --sysroot=sysroot deploy --os=testos testos:testos/buildmaster/x86_64-runtime
+# Keep the same bootversion
+assert_has_dir sysroot/boot/loader.0
+assert_not_has_dir sysroot/boot/loader.1
+# But swap subbootversion
+assert_has_dir sysroot/ostree/boot.0.0
+assert_not_has_dir sysroot/ostree/boot.0.1
+
+echo "ok third deploy (swap)"
+
+ostree admin --sysroot=sysroot deploy --os=otheros testos/buildmaster/x86_64-runtime
+assert_not_has_dir sysroot/boot/loader.0
+assert_has_dir sysroot/boot/loader.1
+assert_has_file sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf
+assert_has_file sysroot/boot/loader/entries/ostree-otheros-${rev}-0.conf
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.1/etc/os-release 'NAME=TestOS'
+assert_file_has_content sysroot/ostree/deploy/otheros/deploy/${rev}.0/etc/os-release 'NAME=TestOS'
+
+echo "ok independent deploy"
+
+ostree admin --sysroot=sysroot deploy --retain --os=testos testos:testos/buildmaster/x86_64-runtime
+assert_has_dir sysroot/boot/loader.0
+assert_not_has_dir sysroot/boot/loader.1
+assert_has_file sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.2/etc/os-release 'NAME=TestOS'
+assert_has_file sysroot/boot/loader/entries/ostree-testos-${rev}-2.conf
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/os-release 'NAME=TestOS'
+
+echo "ok fourth deploy (retain)"
+
+echo "a new local config file" > sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/a-new-config-file
+rm sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/aconfigfile
+ln -s /ENOENT sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/a-new-broken-symlink
+ostree admin --sysroot=sysroot deploy --retain --os=testos testos:testos/buildmaster/x86_64-runtime
+linktarget=$(readlink sysroot/ostree/deploy/testos/deploy/${rev}.4/etc/a-new-broken-symlink)
+test "${linktarget}" = /ENOENT
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/os-release 'NAME=TestOS'
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.4/etc/os-release 'NAME=TestOS'
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.4/etc/a-new-config-file 'a new local 
config file'
+assert_not_has_file sysroot/ostree/deploy/testos/deploy/${rev}.4/etc/aconfigfile
+
+echo "ok deploy with modified /etc"
+
+os_repository_new_commit
+ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime
+newrev=$(ostree --repo=sysroot/ostree/repo rev-parse testos:testos/buildmaster/x86_64-runtime)
+export newrev
+assert_not_streq ${rev} ${newrev}
+
+ostree admin --sysroot=sysroot deploy --os=testos testos:testos/buildmaster/x86_64-runtime
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/os-release 'NAME=TestOS'
+# New files in /usr/etc
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/a-new-default-config-file "a new 
default config file"
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/new-default-dir/moo "a new 
default dir and file"
+# And persist /etc changes from before
+assert_not_has_file sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/aconfigfile
+
+echo "ok upgrade bare"
+
+os_repository_new_commit
+ostree --repo=sysroot/ostree/repo remote add testos file://$(pwd)/testos-repo 
testos/buildmaster/x86_64-runtime
+ostree admin --sysroot=sysroot upgrade --os=testos
+rev=${newrev}
+newrev=$(ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime)
+assert_not_streq ${rev} ${newrev}
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/os-release 'NAME=TestOS'
+
+echo "ok upgrade"


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