From bb6eedfb258d3001f61c42c7e920c03dae2bdc1a Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 29 Jun 2013 11:45:53 -0400 Subject: [PATCH] [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 | 730 +-------- 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 | 150 +- 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 | 1484 ++++++++++++++----- src/ostree/ot-admin-functions.h | 96 +- 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 | 130 +- src/switchroot/ostree-switch-root.c | 337 ----- tests/libtest.sh | 97 ++ tests/t0015-admin-deploy.sh | 134 ++ 33 files changed, 4237 insertions(+), 2127 deletions(-) delete mode 100644 src/ostree/ot-admin-builtin-pull-deploy.c create mode 100644 src/ostree/ot-admin-builtin-status.c delete mode 100644 src/ostree/ot-admin-builtin-update-kernel.c create mode 100644 src/ostree/ot-admin-deploy.c create mode 100644 src/ostree/ot-admin-deploy.h create mode 100644 src/ostree/ot-bootloader-syslinux.c create mode 100644 src/ostree/ot-bootloader-syslinux.h create mode 100644 src/ostree/ot-bootloader.c create mode 100644 src/ostree/ot-bootloader.h create mode 100644 src/ostree/ot-config-parser.c create mode 100644 src/ostree/ot-config-parser.h create mode 100644 src/ostree/ot-deployment.c create mode 100644 src/ostree/ot-deployment.h create mode 100644 src/ostree/ot-ordered-hash.c create mode 100644 src/ostree/ot-ordered-hash.h delete mode 100644 src/switchroot/ostree-switch-root.c create mode 100755 tests/t0015-admin-deploy.sh diff --git a/Makefile-ostree.am b/Makefile-ostree.am index 94d5d088..cfbd4569 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 a1f05cc2..99301064 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 cd22ab2c..b81866be 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 c13ac56b..93e38375 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 + * Copyright (C) 2012,2013 Colin Walters * * 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 -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 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; -} - -/** - * 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); - - 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 (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)) - { - 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)); - 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); - - /* Delete any previous temporary data */ - if (!gs_shutil_rm_rf (deploy_target_path_tmp, 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 - { - g_propagate_error (error, temp_error); - 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) - { - 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; - } - - 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 (!ostree_repo_write_ref (self->repo, NULL, self->previous_deployment_ref, - self->previous_deployment_revision, 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; + 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; - 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)"); + context = g_option_context_new ("REFSPEC - Checkout revision REFSPEC as the new default deployment"); g_option_context_add_main_entries (context, options, NULL); if (!g_option_context_parse (context, &argc, &argv, error)) goto out; - if (argc < 3) + if (argc < 2) { - ot_util_usage_error (context, "OSNAME and TREENAME must be specified", error); + ot_util_usage_error (context, "REF/REV must be specified", error); goto out; } - self->admin_opts = admin_opts; - self->ostree_dir = g_object_ref (admin_opts->ostree_dir); + refspec = argv[1]; - if (!ot_admin_ensure_initialized (self->ostree_dir, cancellable, error)) + if (!ot_admin_get_repo (sysroot, &repo, cancellable, error)) goto out; - repo_path = g_file_get_child (self->ostree_dir, "repo"); - self->repo = ostree_repo_new (repo_path); - if (!ostree_repo_check (self->repo, error)) - goto out; - - 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 (!ot_admin_list_deployments (sysroot, ¤t_bootversion, ¤t_deployments, + cancellable, error)) { - if (!do_update_kernel (self, cancellable, error)) - goto out; + g_prefix_error (error, "While listing deployments: "); + goto out; } - if (!complete_deployment (self, cancellable, error)) + /* 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)) + { + g_prefix_error (error, "Looking for booted deployment: "); + goto out; + } + + if (opt_origin_path) + { + origin = g_key_file_new (); + + if (!g_key_file_load_from_file (origin, opt_origin_path, 0, error)) + goto out; + } + else + { + origin = ot_origin_new_from_refspec (refspec); + } + + if (!ostree_repo_resolve_rev (repo, refspec, FALSE, &revision, error)) + goto out; + + 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 7792db38..97fb16aa 100644 --- a/src/ostree/ot-admin-builtin-diff.c +++ b/src/ostree/ot-admin-builtin-diff.c @@ -28,7 +28,10 @@ #include +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) + 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) { - 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_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 d8a53552..dc1a45a6 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 5f5fb3d1..675f52c6 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 0536167a..8d725fda 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 4d600991..024abd24 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, ¤t_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-pull-deploy.c b/src/ostree/ot-admin-builtin-pull-deploy.c deleted file mode 100644 index cfdd9e64..00000000 --- a/src/ostree/ot-admin-builtin-pull-deploy.c +++ /dev/null @@ -1,178 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012 Colin Walters - * - * 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 - */ - -#include "config.h" - -#include "ot-admin-builtins.h" -#include "ot-admin-functions.h" -#include "ostree.h" - -#include - -static gboolean opt_no_kernel; - -static GOptionEntry options[] = { - { "no-kernel", 0, 0, G_OPTION_ARG_NONE, &opt_no_kernel, "Don't update kernel related config (initramfs, bootloader)", NULL }, - { NULL } -}; - -static gboolean -ensure_remote_branch (OstreeRepo *repo, - const char *remote, - const char *branch, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - gchar **iter = NULL; - gsize len; - gs_free char *remote_key = NULL; - gs_unref_ptrarray GPtrArray *new_branches = NULL; - GKeyFile *config = NULL; - gchar **branches = NULL; - gboolean have_branch = FALSE; - - config = ostree_repo_copy_config (repo); - remote_key = g_strdup_printf ("remote \"%s\"", remote); - - new_branches = g_ptr_array_new (); - - branches = g_key_file_get_string_list (config, remote_key, "branches", &len, error); - if (!branches) - goto out; - - for (iter = branches; *iter; iter++) - { - char *item = *iter; - if (!have_branch) - have_branch = strcmp (item, branch) == 0; - g_ptr_array_add (new_branches, item); - } - - if (!have_branch) - { - g_ptr_array_add (new_branches, (char*)branch); - g_key_file_set_string_list (config, remote_key, "branches", - (const char *const *)new_branches->pdata, - new_branches->len); - - if (!ostree_repo_write_config (repo, config, error)) - goto out; - } - - ret = TRUE; - out: - if (config) - g_key_file_free (config); - if (branches) - g_strfreev (branches); - return ret; -} - -gboolean -ot_admin_builtin_pull_deploy (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error) -{ - GOptionContext *context; - gboolean ret = FALSE; - const char *osname; - const char *target; - GFile *ostree_dir = admin_opts->ostree_dir; - ot_lobj OstreeRepo *repo = NULL; - ot_lobj GFile *repo_path = NULL; - ot_lobj GFile *current_deployment = NULL; - ot_lfree char *deploy_name = NULL; - ot_lobj GFile *deploy_dir = NULL; - ot_lfree char *remote_name = NULL; - ot_lptrarray GPtrArray *subproc_args = NULL; - __attribute__((unused)) GCancellable *cancellable = NULL; - - context = g_option_context_new ("OSNAME [TREE] - Ensure TREE (default current) is in list of remotes, then download and deploy"); - - g_option_context_add_main_entries (context, options, NULL); - - if (!g_option_context_parse (context, &argc, &argv, error)) - goto out; - - if (argc < 2) - { - ot_util_usage_error (context, "OSNAME must be specified", error); - goto out; - } - - osname = argv[1]; - - repo_path = g_file_get_child (ostree_dir, "repo"); - - repo = ostree_repo_new (repo_path); - if (!ostree_repo_check (repo, error)) - goto out; - - if (argc > 2) - { - target = argv[2]; - if (!ensure_remote_branch (repo, osname, target, - cancellable, error)) - goto out; - - deploy_name = g_strdup (target); - } - else - { - if (!ot_admin_get_current_deployment (ostree_dir, osname, ¤t_deployment, - cancellable, error)) - goto out; - - if (!current_deployment) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "No current deployment"); - goto out; - } - - ot_admin_parse_deploy_name (ostree_dir, osname, current_deployment, - &deploy_name, NULL); - } - - if (!ot_admin_pull (ostree_dir, osname, cancellable, error)) - goto out; - - { - ot_lfree char *opt_ostree_dir_arg = g_strconcat ("--ostree-dir=", - gs_file_get_path_cached (ostree_dir), - NULL); - ot_lfree char *opt_boot_dir_arg = g_strconcat ("--boot-dir=", - gs_file_get_path_cached (admin_opts->boot_dir), - NULL); - if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir), - GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT, - cancellable, error, - "ostree", "admin", opt_ostree_dir_arg, opt_boot_dir_arg, "deploy", osname, - deploy_name, NULL)) - goto out; - } - - ret = TRUE; - out: - if (context) - g_option_context_free (context); - return ret; -} diff --git a/src/ostree/ot-admin-builtin-status.c b/src/ostree/ot-admin-builtin-status.c new file mode 100644 index 00000000..3e26d9c6 --- /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 + * + * 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 + */ + +#include "config.h" + +#include "ot-admin-builtins.h" +#include "ot-admin-functions.h" +#include "ostree.h" + +#include + +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-update-kernel.c b/src/ostree/ot-admin-builtin-update-kernel.c deleted file mode 100644 index 1437ba30..00000000 --- a/src/ostree/ot-admin-builtin-update-kernel.c +++ /dev/null @@ -1,296 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012 Colin Walters - * - * 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 - */ - -#include "config.h" - -#include "ot-admin-builtins.h" -#include "ostree.h" - -#include -#include - -typedef struct { - OtAdminBuiltinOpts *admin_opts; - GFile *ostree_dir; - GFile *boot_ostree_dir; - GFile *deploy_path; - GFile *kernel_path; - char *release; - char *osname; -} OtAdminUpdateKernel; - -static gboolean opt_no_bootloader; - -static GOptionEntry options[] = { - { "no-bootloader", 0, 0, G_OPTION_ARG_NONE, &opt_no_bootloader, "Don't update bootloader", NULL }, - { NULL } -}; - -static gboolean -get_kernel_from_boot (GFile *path, - GFile **out_kernel, - GFile **out_initramfs, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - 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 (path, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - NULL, error); - if (!dir_enum) - goto out; - - while ((file_info = g_file_enumerator_next_file (dir_enum, cancellable, error)) != NULL) - { - const char *name; - - name = g_file_info_get_name (file_info); - - if (ret_kernel == NULL && g_str_has_prefix (name, "vmlinuz-")) - ret_kernel = g_file_get_child (path, name); - else if (ret_initramfs == NULL && g_str_has_prefix (name, "initramfs-")) - ret_initramfs = g_file_get_child (path, name); - - if (ret_kernel && ret_initramfs) - break; - } - - ot_transfer_out_value (out_kernel, &ret_kernel); - ot_transfer_out_value (out_initramfs, &ret_initramfs); - ret = TRUE; - out: - return ret; -} - -static gboolean -grep_literal (GFile *f, - const char *string, - gboolean *out_matches, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - gboolean ret_matches = FALSE; - gs_unref_object GInputStream *in = NULL; - gs_unref_object GDataInputStream *datain = NULL; - ot_lfree char *line = NULL; - - in = (GInputStream*)g_file_read (f, cancellable, error); - if (!in) - goto out; - datain = (GDataInputStream*)g_data_input_stream_new (in); - if (!in) - goto out; - - while ((line = g_data_input_stream_read_line (datain, NULL, cancellable, error)) != NULL) - { - if (strstr (line, string)) - { - ret_matches = TRUE; - break; - } - - g_free (line); - } - - ret = TRUE; - if (out_matches) - *out_matches = ret_matches; - out: - return ret; -} - -static gboolean -update_grub (OtAdminUpdateKernel *self, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - gs_unref_object GFile *grub_path = g_file_resolve_relative_path (self->admin_opts->boot_dir, "grub/grub.conf"); - - if (g_file_query_exists (grub_path, cancellable)) - { - gboolean have_grub_entry; - if (!grep_literal (grub_path, "OSTree", &have_grub_entry, - cancellable, error)) - goto out; - - if (!have_grub_entry) - { - ot_lfree char *add_kernel_arg = NULL; - ot_lfree char *initramfs_arg = NULL; - ot_lfree char *initramfs_name = NULL; - gs_unref_object GFile *initramfs_path = NULL; - - initramfs_name = g_strconcat ("initramfs-", self->release, ".img", NULL); - initramfs_path = g_file_get_child (self->boot_ostree_dir, initramfs_name); - - add_kernel_arg = g_strconcat ("--add-kernel=", gs_file_get_path_cached (self->kernel_path), NULL); - initramfs_arg = g_strconcat ("--initrd=", gs_file_get_path_cached (initramfs_path), NULL); - - g_print ("Adding OSTree grub entry...\n"); - if (!gs_subprocess_simple_run_sync (NULL, GS_SUBPROCESS_STREAM_DISPOSITION_NULL, - cancellable, error, - "grubby", "--grub", add_kernel_arg, initramfs_arg, - "--copy-default", "--title=OSTree", NULL)) - goto out; - } - else - g_print ("Already have OSTree entry in grub config\n"); - } - else - { - g_print ("/boot/grub/grub.conf not found, assuming you have GRUB 2\n"); - } - - ret = TRUE; - out: - return ret; -} - -gboolean -ot_admin_builtin_update_kernel (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error) -{ - gboolean ret = FALSE; - GOptionContext *context; - OtAdminUpdateKernel self_data; - OtAdminUpdateKernel *self = &self_data; - GFile *ostree_dir = admin_opts->ostree_dir; - gs_unref_object GFile *deploy_boot_path = NULL; - gs_unref_object GFile *src_kernel_path = NULL; - gs_unref_object GFile *src_initramfs_path = NULL; - gs_free char *prefix = NULL; - gs_free char *initramfs_name = NULL; - gs_unref_object GFile *expected_initramfs_path = NULL; - const char *release = NULL; - const char *kernel_name = NULL; - GCancellable *cancellable = NULL; - - memset (self, 0, sizeof (*self)); - - self->admin_opts = admin_opts; - - context = g_option_context_new ("OSNAME [DEPLOY_PATH] - Update kernel and regenerate initial ramfs"); - g_option_context_add_main_entries (context, options, NULL); - - if (!g_option_context_parse (context, &argc, &argv, error)) - goto out; - - if (argc < 2) - { - ot_util_usage_error (context, "OSNAME must be specified", error); - goto out; - } - - self->osname = g_strdup (argv[1]); - - if (argc > 2) - self->deploy_path = g_file_new_for_path (argv[2]); - else - { - gs_unref_object GFile *osdir = ot_gfile_get_child_build_path (admin_opts->ostree_dir, "deploy", self->osname, NULL); - self->deploy_path = g_file_get_child (osdir, "current"); - } - - self->ostree_dir = g_object_ref (ostree_dir); - self->boot_ostree_dir = g_file_get_child (admin_opts->boot_dir, "ostree"); - - deploy_boot_path = g_file_get_child (self->deploy_path, "boot"); - - if (!get_kernel_from_boot (deploy_boot_path, &src_kernel_path, &src_initramfs_path, - cancellable, error)) - goto out; - - if (src_kernel_path == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "No kernel found in %s", gs_file_get_path_cached (deploy_boot_path)); - goto out; - } - - if (!gs_file_ensure_directory (self->boot_ostree_dir, TRUE, cancellable, error)) - goto out; - - kernel_name = gs_file_get_basename_cached (src_kernel_path); - release = strchr (kernel_name, '-'); - if (release == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Invalid kernel name %s, no - found", gs_file_get_path_cached (src_kernel_path)); - goto out; - } - - self->release = g_strdup (release + 1); - prefix = g_strndup (kernel_name, release - kernel_name); - self->kernel_path = ot_gfile_get_child_strconcat (self->boot_ostree_dir, prefix, "-", self->release, NULL); - - if (!g_file_query_exists(self->kernel_path, NULL)) - { - if (!gs_file_linkcopy_sync_data (src_kernel_path, self->kernel_path, G_FILE_COPY_OVERWRITE, - cancellable, error)) - goto out; - g_print ("ostadmin: Deployed kernel %s\n", gs_file_get_path_cached (self->kernel_path)); - } - - initramfs_name = g_strconcat ("initramfs-", self->release, ".img", NULL); - expected_initramfs_path = g_file_get_child (self->boot_ostree_dir, initramfs_name); - - if (!g_file_query_exists (expected_initramfs_path, NULL)) - { - if (!gs_file_linkcopy_sync_data (src_initramfs_path, expected_initramfs_path, G_FILE_COPY_OVERWRITE, - cancellable, error)) - goto out; - - /* In the fuse case, we need to chown after copying */ - if (getuid () != 0) - { - if (!gs_file_chown (expected_initramfs_path, 0, 0, cancellable, error)) - { - g_prefix_error (error, "Failed to chown initramfs: "); - goto out; - } - } - - g_print ("Deployed initramfs: %s\n", gs_file_get_path_cached (expected_initramfs_path)); - } - - if (!opt_no_bootloader) - { - if (!update_grub (self, cancellable, error)) - goto out; - } - - ret = TRUE; - out: - g_clear_object (&self->ostree_dir); - g_clear_object (&self->boot_ostree_dir); - g_clear_object (&self->kernel_path); - g_free (self->release); - 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 f13ce218..f64837f3 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 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; - 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_free char *ostree_dir_arg = NULL; __attribute__((unused)) GCancellable *cancellable = NULL; + GOptionContext *context; + GFile *sysroot = admin_opts->sysroot; + gs_free char *booted_osname = NULL; + gs_unref_object OstreeRepo *repo = 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; + 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) + if (!ot_admin_list_deployments (admin_opts->sysroot, ¤t_bootversion, + ¤t_deployments, + cancellable, error)) { - osname = argv[1]; + g_prefix_error (error, "While listing deployments: "); + goto out; + } + + if (!ot_admin_require_deployment_or_osname (admin_opts->sysroot, current_deployments, + opt_osname, + &booted_deployment, + cancellable, error)) + goto out; + 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; + + 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) + { + 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; + + 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, 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 { - 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; - } - - 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, ¤t_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)) - 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)) - goto out; - - if (opt_reboot) - { - repo_path = g_file_get_child (ostree_dir, "repo"); - - repo = ostree_repo_new (repo_path); - if (!ostree_repo_check (repo, error)) + 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 (!ostree_repo_resolve_rev (repo, deploy_name, TRUE, &new_rev, - 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 5fbdacd6..99240867 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 00000000..f0f95577 --- /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 + * + * 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 + */ + +#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 00000000..1eab703c --- /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 + * + * 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 + */ + +#ifndef __OT_ADMIN_DEPLOY__ +#define __OT_ADMIN_DEPLOY_ + +#include +#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 28eb0778..208c2f8d 100644 --- a/src/ostree/ot-admin-functions.c +++ b/src/ostree/ot-admin-functions.c @@ -20,19 +20,118 @@ * Author: Colin Walters */ +#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,25 +164,506 @@ ot_admin_ensure_initialized (GFile *ostree_dir, return ret; } -static gboolean -query_file_info_allow_noent (GFile *path, - GFileInfo **out_info, - GCancellable *cancellable, - GError **error) +gboolean +ot_admin_check_os (GFile *sysroot, + const char *osname, + GCancellable *cancellable, + GError **error) { gboolean ret = FALSE; - ot_lobj GFileInfo *ret_file_info = NULL; + 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 +parse_bootlink (const char *bootlink, + int *out_entry_bootversion, + char **out_osname, + char **out_bootcsum, + int *out_treebootserial, + GError **error) +{ + gboolean ret = FALSE; + __attribute__((cleanup(match_info_cleanup))) GMatchInfo *match = NULL; + gs_free char *bootversion_str = NULL; + gs_free char *treebootserial_str = NULL; + + static gsize regex_initialized; + static GRegex *regex; + + if (g_once_init_enter (®ex_initialized)) + { + regex = g_regex_new ("^/ostree/boot.([01])/([^/]+)/([^/]+)/([0-9]+)$", 0, 0, NULL); + g_assert (regex); + g_once_init_leave (®ex_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 (®ex_initialized)) + { + regex = g_regex_new ("^([0-9a-f]+)\\.([0-9]+)$", 0, 0, NULL); + g_assert (regex); + g_once_init_leave (®ex_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) + { + 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 + { + /* Not an ostree system */ + } + } + + ret = TRUE; + ot_transfer_out_value (out_deployment, &ret_deployment); + out: + return ret; +} + +gboolean +ot_admin_require_deployment_or_osname (GFile *sysroot, + GPtrArray *deployments, + const char *osname, + OtDeployment **out_deployment, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_unref_object OtDeployment *ret_deployment = NULL; + + if (!ot_admin_find_booted_deployment (sysroot, deployments, &ret_deployment, + cancellable, error)) + goto out; + + 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 (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) + { + 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, + "Invalid target '%s' in %s", target, gs_file_get_path_cached (boot_loader_path)); + goto out; + } + } + + ret = TRUE; + *out_bootversion = ret_bootversion; + out: + return ret; +} + +gboolean +ot_admin_read_current_subbootversion (GFile *sysroot, + int bootversion, + int *out_subbootversion, + GCancellable *cancellable, + GError **error) +{ + 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; + + if (!ot_gfile_query_symlink_target_allow_noent (ostree_bootdir, &ostree_subbootdir, + cancellable, error)) + goto out; + + 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; +} + +gboolean +ot_admin_read_boot_loader_configs (GFile *sysroot, + int bootversion, + GPtrArray **out_loader_configs, + GCancellable *cancellable, + GError **error) +{ + 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; - 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) + 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); + + 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); @@ -91,419 +671,599 @@ query_file_info_allow_noent (GFile *path, } } - ret = TRUE; - ot_transfer_out_value (out_info, &ret_file_info); - out: - return ret; -} - -static gboolean -query_symlink_target_allow_noent (GFile *path, - GFile **out_target, - 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; - - if (!query_file_info_allow_noent (path, &file_info, - cancellable, error)) - goto out; - - path_parent = g_file_get_parent (path); - - if (file_info != NULL) - { - const char *target; - - if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_SYMBOLIC_LINK) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Not a symbolic link"); - goto out; - } - target = g_file_info_get_symlink_target (file_info); - g_assert (target); - ret_target = g_file_resolve_relative_path (path_parent, target); - } - - ret = TRUE; - ot_transfer_out_value (out_target, &ret_target); - 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_lobj GFile *current_path = NULL; - - current_path = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, - "current", NULL); - - return query_symlink_target_allow_noent (current_path, out_deployment, - cancellable, error); -} - -/** - * 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_lobj GFile *previous_path = NULL; - - previous_path = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, - "previous", NULL); - - return query_symlink_target_allow_noent (previous_path, out_deployment, - cancellable, error); -} - -/* -static gboolean -ot_admin_list_osnames (GFile *ostree_dir, - GPtrArray **out_osnames, - 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; - GError *temp_error = NULL; - - deploy_dir = g_file_get_child (ostree_dir, "deploy"); - - dir_enum = g_file_enumerate_children (deploy_dir, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - NULL, 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) - { - char *name = g_strdup (g_file_info_get_name (file_info)); - g_ptr_array_add (ret_osnames, name); - } - g_clear_object (&file_info); - } - - if (temp_error != NULL) - { - g_propagate_error (error, temp_error); - goto out; - } - - 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) -{ - gboolean ret = FALSE; - 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, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - NULL, error); - if (!dir_enum) - goto out; - - while ((file_info = g_file_enumerator_next_file (dir_enum, cancellable, error)) != NULL) + while (TRUE) { + GFileInfo *file_info; + GFile *child; const char *name; - ot_lobj GFile *child = NULL; - ot_lobj GFile *possible_etc = NULL; - ot_lobj GFile *possible_usr = NULL; + + 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_suffix (name, "-etc")) - goto next; - if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY) - goto next; - - child = g_file_get_child (from_dir, name); - - possible_etc = ot_gfile_get_child_strconcat (from_dir, name, "-etc", NULL); - /* Bit of a hack... */ - possible_usr = g_file_get_child (child, "usr"); - - 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 + 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) { - if (!list_deployments_internal (child, inout_deployments, - cancellable, error)) - goto out; + 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)); } - - next: - g_clear_object (&file_info); - } - if (temp_error != NULL) - { - g_propagate_error (error, temp_error); - goto out; } + done: + ot_transfer_out_value (out_loader_configs, &ret_loader_configs); ret = TRUE; out: return ret; } -gboolean -ot_admin_list_deployments (GFile *ostree_dir, - const char *osname, - GPtrArray **out_deployments, - GCancellable *cancellable, - GError **error) +static gboolean +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 *osdir = NULL; - ot_lptrarray GPtrArray *ret_deployments = 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; + + osdeploy_dir = g_file_get_child (osdir, "deploy"); + + dir_enum = g_file_enumerate_children (osdeploy_dir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + 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) + { + 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; + out: + return ret; +} + +static gboolean +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; + + deploydir = g_file_resolve_relative_path (sysroot, "ostree/deploy"); - 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 (!list_deployments_internal (osdir, ret_deployments, cancellable, error)) - goto out; + dir_enum = g_file_enumerate_children (deploydir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, &temp_error); + if (!dir_enum) + { + if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_clear_error (&temp_error); + goto done; + } + else + { + g_propagate_error (error, temp_error); + goto out; + } + } + + while (TRUE) + { + GFileInfo *file_info = NULL; + GFile *child = NULL; + + if (!gs_file_enumerator_iterate (dir_enum, &file_info, &child, + NULL, error)) + goto out; + if (file_info == NULL) + break; + + if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY) + 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; } -gboolean -ot_admin_get_booted_os (char **out_osname, - char **out_tree, - GCancellable *cancellable, - GError **error) +static char * +get_ostree_kernel_arg_from_config (OtConfigParser *config) { - 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; + const char *options; + char *ret; + char **opts, **iter; - if (!g_file_get_contents ("/proc/cmdline", &cmdline_contents, &len, - error)) - goto out; + options = ot_config_parser_get (config, "options"); + if (!options) + return NULL; - iter = cmdline_contents; - do + opts = g_strsplit (options, " ", -1); + for (iter = opts; *iter; iter++) { - const char *next = strchr (iter, ' '); - if (next) - next += 1; - if (g_str_has_prefix (iter, "ostree=")) + const char *opt = *iter; + if (g_str_has_prefix (opt, "ostree=")) { - 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; - } + ret = g_strdup (opt + strlen ("ostree=")); + break; } - iter = next; } - while (iter != NULL); + g_strfreev (opts); - 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 +list_deployments_process_one_boot_entry (GFile *sysroot, + OtConfigParser *config, + GPtrArray *inout_deployments, + 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 *ostree_arg = NULL; + gs_unref_object OtDeployment *deployment = NULL; - if (!ot_admin_get_booted_os (&ret_osname, NULL, cancellable, error)) + ostree_arg = get_ostree_kernel_arg_from_config (config); + if (ostree_arg == NULL) + { + 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 *sysroot, + int *out_current_bootversion, + GPtrArray **out_deployments, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_unref_ptrarray GPtrArray *boot_loader_configs = NULL; + gs_unref_ptrarray GPtrArray *ret_deployments = NULL; + guint i; + int bootversion; + + if (!read_current_bootversion (sysroot, &bootversion, cancellable, error)) goto out; - if (ret_osname != NULL && out_deployment != NULL) + 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++) { - gs_unref_object GFile *rootfs_path = NULL; - gs_unref_object GFileInfo *rootfs_info = NULL; - guint32 root_dev; - guint64 root_inode; - guint i; + OtConfigParser *config = boot_loader_configs->pdata[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) + if (!list_deployments_process_one_boot_entry (sysroot, config, ret_deployments, + cancellable, 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"); - - 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; - - deploy_dev = g_file_info_get_attribute_uint32 (deployment_info, "unix::device"); - deploy_inode = g_file_info_get_attribute_uint64 (deployment_info, "unix::inode"); - - if (root_dev == deploy_dev && root_inode == deploy_inode) - { - ret_deployment = g_object_ref (deployment); - break; - } - } - - g_assert (ret_deployment != NULL); + 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; - ot_transfer_out_value (out_osname, &ret_osname); - ot_transfer_out_value (out_deployment, &ret_deployment); + *out_current_bootversion = bootversion; + ot_transfer_out_value (out_deployments, &ret_deployments); out: return ret; } gboolean -ot_admin_get_default_ostree_dir (GFile **out_ostree_dir, - 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; - - host_usr = g_file_new_for_path ("/usr"); - - 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) - { - 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); - } - - ret = TRUE; - ot_transfer_out_value (out_ostree_dir, &ret_ostree_dir); - return ret; -} - -gboolean -ot_admin_pull (GFile *ostree_dir, - const char *osname, +ot_admin_pull (GFile *sysroot, + const char *remote, + const char *ref, GCancellable *cancellable, GError **error) { - gs_unref_object GFile *repo_path = g_file_get_child (ostree_dir, "repo"); + 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 (gs_file_get_path_cached (ostree_dir), + return gs_subprocess_simple_run_sync (NULL, GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT, cancellable, error, - "ostree", repo_arg, "pull", osname, NULL); + "ostree", repo_arg, "pull", remote, ref, NULL); } -void -ot_admin_parse_deploy_name (GFile *ostree_dir, - const char *osname, - GFile *deployment, - char **out_name, - char **out_rev) +GFile * +ot_admin_get_deployment_directory (GFile *sysroot, + OtDeployment *deployment) { - 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; - - 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); + 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; + 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 (!get_devino (active_root, &root_device, &root_inode, + cancellable, error)) + goto out; + + 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++) + { + 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)) + { + 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; + } + } + + ret = TRUE; + out: + return ret; +} + +static gboolean +cleanup_ref_prefix (OstreeRepo *repo, + int bootversion, + int subbootversion, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + 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 (!ostree_repo_list_refs (repo, prefix, &refs, cancellable, error)) + goto out; + + g_hash_table_iter_init (&hashiter, refs); + while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue)) + { + const char *suffix = hashkey; + gs_free char *ref = g_strconcat (prefix, "/", suffix, NULL); + if (!ostree_repo_write_refspec (repo, ref, NULL, error)) + goto out; + } + + ret = TRUE; + out: + return ret; +} + +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; + + cleanup_bootversion = (bootversion == 0) ? 1 : 0; + cleanup_subbootversion = (subbootversion == 0) ? 1 : 0; + + 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; + out: + return ret; +} + +gboolean +ot_admin_cleanup (GFile *sysroot, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_unref_ptrarray GPtrArray *deployments = NULL; + gs_unref_object OstreeRepo *repo = NULL; + int bootversion; + int subbootversion; + + if (!ot_admin_list_deployments (sysroot, &bootversion, &deployments, + cancellable, error)) + goto out; + + 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) + { + 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; + out: + return ret; +} + +OtBootloader * +ot_admin_query_bootloader (GFile *sysroot) +{ + OtBootloaderSyslinux *syslinux; + + syslinux = ot_bootloader_syslinux_new (sysroot); + if (ot_bootloader_query ((OtBootloader*)syslinux)) + return (OtBootloader*) (syslinux); + + return NULL; +} + +GKeyFile * +ot_origin_new_from_refspec (const char *refspec) +{ + GKeyFile *ret = g_key_file_new (); + g_key_file_set_string (ret, "origin", "refspec", refspec); + return ret; +} + +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 a03ef59d..5a3c0763 100644 --- a/src/ostree/ot-admin-functions.h +++ b/src/ostree/ot-admin-functions.h @@ -24,6 +24,10 @@ #define __OT_ADMIN_FUNCTIONS__ #include +#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_check_os (GFile *sysroot, + const char *osname, + 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); +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); -gboolean ot_admin_list_deployments (GFile *ostree_dir, - const char *osname, +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 00000000..9fe6ae1a --- /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 + * + * 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 + +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 00000000..8a810895 --- /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 + * + * 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 00000000..68ac223f --- /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 + * + * 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 00000000..6043b83c --- /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 + * + * 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 + +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 c8f60fb3..b1dfac72 100644 --- a/src/ostree/ot-builtin-admin.c +++ b/src/ostree/ot-builtin-admin.c @@ -31,12 +31,10 @@ #include -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 00000000..e87d5b9e --- /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 + * + * 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 00000000..8b7b47a9 --- /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 + * + * 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 + +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 00000000..4f51b95f --- /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 + * + * 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 00000000..66470d46 --- /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 + * + * 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 +#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 00000000..a7709e47 --- /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 + * + * 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 00000000..a3a4ef82 --- /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 + * + * 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 + +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 cfd7481e..72172dc8 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,15 +100,39 @@ 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); } + ostree_target = parse_ostree_cmdline (); + if (!ostree_target) + { + fprintf (stderr, "No OSTree target; expected ostree=/ostree/boot.N/...\n"); + exit (1); + } + + snprintf (destpath, sizeof(destpath), "%s/%s", root_mountpoint, ostree_target); + fprintf (stderr, "Examining %s\n", destpath); + if (lstat (destpath, &stbuf) < 0) + { + perrorv ("Couldn't find specified OSTree root '%s': ", destpath); + exit (1); + } + if (!S_ISLNK (stbuf.st_mode)) + { + fprintf (stderr, "OSTree target is not a symbolic link: %s\n", destpath); + exit (1); + } + deploy_path = realpath (destpath, NULL); + if (deploy_path == NULL) + { + perrorv ("realpath(%s) failed: ", destpath); + exit (1); + } + 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 @@ -129,31 +145,6 @@ main(int argc, char *argv[]) exit (1); } - snprintf (destpath, sizeof(destpath), "%s/ostree/deploy/%s/%s", - root_mountpoint, ostree_osname, ostree_target); - fprintf (stderr, "Examining %s\n", destpath); - if (lstat (destpath, &stbuf) < 0) - { - perrorv ("Couldn't find specified OSTree root '%s': ", destpath); - exit (1); - } - if (!S_ISLNK (stbuf.st_mode)) - { - fprintf (stderr, "OSTree target is not a symbolic link: %s\n", destpath); - exit (1); - } - if (readlink (destpath, ostree_target_path, PATH_MAX) < 0) - { - perrorv ("readlink(%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); - /* 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/src/switchroot/ostree-switch-root.c b/src/switchroot/ostree-switch-root.c deleted file mode 100644 index 14b8cc24..00000000 --- a/src/switchroot/ostree-switch-root.c +++ /dev/null @@ -1,337 +0,0 @@ -/* -*- c-file-style: "gnu" -*- - * Switch to new root directory and start init. - * - * Copyright 2011,2012 Colin Walters - * - * Based on code from util-linux/sys-utils/switch_root.c, - * Copyright 2002-2009 Red Hat, Inc. All rights reserved. - * Authors: - * Peter Jones - * Jeremy Katz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ostree-mount-util.h" - -/* remove all files/directories below dirName -- don't cross mountpoints */ -static int -recursive_remove (int fd) -{ - struct stat rb; - DIR *dir; - int rc = -1; - int dfd; - - if (!(dir = fdopendir (fd))) - { - perrorv ("failed to open directory"); - goto done; - } - - /* fdopendir() precludes us from continuing to use the input fd */ - dfd = dirfd (dir); - - if (fstat(dfd, &rb)) - { - perrorv("failed to stat directory"); - goto done; - } - - while (1) - { - struct dirent *d; - - errno = 0; - if (!(d = readdir (dir))) - { - if (errno) - { - perrorv ("failed to read directory"); - goto done; - } - break; /* end of directory */ - } - - if (!strcmp (d->d_name, ".") || !strcmp (d->d_name, "..")) - continue; - - if (d->d_type == DT_DIR) - { - struct stat sb; - - if (fstatat (dfd, d->d_name, &sb, AT_SYMLINK_NOFOLLOW)) - { - perrorv ("failed to stat %s", d->d_name); - continue; - } - - /* remove subdirectories if device is same as dir */ - if (sb.st_dev == rb.st_dev) - { - int cfd; - - cfd = openat (dfd, d->d_name, O_RDONLY); - if (cfd >= 0) - { - recursive_remove (cfd); - close (cfd); - } - } - else - { - continue; - } - } - - if (unlinkat (dfd, d->d_name, - d->d_type == DT_DIR ? AT_REMOVEDIR : 0)) - perrorv ("failed to unlink %s", d->d_name); - } - - rc = 0; /* success */ - - done: - if (dir) - closedir (dir); - return rc; -} - -int -main(int argc, char *argv[]) -{ - const char *initramfs_move_mounts[] = { "/dev", "/proc", "/sys", "/run", NULL }; - const char *toproot_bind_mounts[] = { "/home", "/root", "/tmp", NULL }; - const char *ostree_bind_mounts[] = { "/var", NULL }; - const char *readonly_bind_mounts[] = { "/usr", NULL }; - const char *root_mountpoint = NULL; - const char *ostree_target = NULL; - const char *ostree_subinit = NULL; - const char *p = NULL; - char *ostree_osname = NULL; - char ostree_target_path[PATH_MAX]; - char *deploy_path = NULL; - char srcpath[PATH_MAX]; - char destpath[PATH_MAX]; - struct stat stbuf; - char **init_argv = NULL; - size_t len; - int initramfs_fd; - int i; - int before_init_argc = 0; - pid_t cleanup_pid; - - if (argc < 4) - { - fprintf (stderr, "usage: ostree-switch-root NEWROOT TARGET INIT [ARGS...]\n"); - exit (1); - } - - before_init_argc++; - root_mountpoint = argv[1]; - before_init_argc++; - ostree_target = argv[2]; - before_init_argc++; - ostree_subinit = argv[3]; - before_init_argc++; - - p = strchr (ostree_target, '/'); - if (p == NULL) - { - fprintf (stderr, "Malformed OSTree target %s; expected OSNAME/TREENAME\n", ostree_target); - exit (1); - } - ostree_osname = strndup (ostree_target, p - ostree_target); - - snprintf (destpath, sizeof(destpath), "%s/ostree/deploy/%s", - root_mountpoint, ostree_target); - if (stat (destpath, &stbuf) < 0) - { - perrorv ("Invalid ostree root '%s'", destpath); - 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) - { - perrorv ("mount(/, MS_PRIVATE): "); - exit (1); - } - - initramfs_fd = open ("/", O_RDONLY); - - for (i = 0; initramfs_move_mounts[i] != NULL; i++) - { - const char *path = initramfs_move_mounts[i]; - snprintf (destpath, sizeof(destpath), "%s/ostree/deploy/%s%s", root_mountpoint, ostree_target, path); - if (mount (path, destpath, NULL, MS_MOVE, NULL) < 0) - { - perrorv ("failed to move mount of %s to %s", path, destpath); - exit (1); - } - } - - snprintf (destpath, sizeof(destpath), "%s/ostree/deploy/%s", root_mountpoint, ostree_target); - fprintf (stderr, "Examining %s\n", destpath); - if (lstat (destpath, &stbuf) < 0) - { - perrorv ("Second stat of ostree root '%s' failed: ", destpath); - exit (1); - } - if (!S_ISLNK (stbuf.st_mode)) - { - fprintf (stderr, "OSTree target is not a symbolic link: %s\n", destpath); - exit (1); - } - if (readlink (destpath, ostree_target_path, PATH_MAX) < 0) - { - perrorv ("readlink(%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); - - /* Make deploy_path a bind mount, so we can move it later */ - if (mount (deploy_path, deploy_path, NULL, MS_BIND, NULL) < 0) - { - perrorv ("failed to initial bind mount %s", deploy_path); - exit (1); - } - - snprintf (destpath, sizeof(destpath), "%s/sysroot", deploy_path); - if (mount (root_mountpoint, destpath, NULL, MS_BIND, NULL) < 0) - { - perrorv ("Failed to bind mount %s to '%s'", root_mountpoint, destpath); - 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) - { - 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]); - 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]); - if (mount (destpath, destpath, NULL, MS_BIND, NULL) < 0) - { - perrorv ("failed to bind mount (class:readonly) %s", destpath); - exit (1); - } - if (mount (destpath, destpath, NULL, MS_BIND | MS_REMOUNT | MS_RDONLY, NULL) < 0) - { - perrorv ("failed to bind mount (class:readonly) %s", destpath); - exit (1); - } - } - - if (chdir (deploy_path) < 0) - { - perrorv ("failed to chdir to subroot (initial)"); - exit (1); - } - - if (mount (deploy_path, "/", NULL, MS_MOVE, NULL) < 0) - { - perrorv ("failed to MS_MOVE %s to /", deploy_path); - exit (1); - } - - if (chroot (".") < 0) - { - perrorv ("failed to change root to '%s'", deploy_path); - exit (1); - } - - if (chdir ("/") < 0) - { - perrorv ("failed to chdir to / (after MS_MOVE of /)"); - exit (1); - } - - if (initramfs_fd >= 0) - { - cleanup_pid = fork (); - if (cleanup_pid == 0) - { - recursive_remove (initramfs_fd); - exit (0); - } - close (initramfs_fd); - } - - init_argv = malloc (sizeof (char*)*((argc-before_init_argc)+2)); - init_argv[0] = (char*)ostree_subinit; - for (i = 0; i < argc-before_init_argc; i++) - init_argv[i+1] = argv[i+before_init_argc]; - init_argv[i+1] = NULL; - - fprintf (stderr, "ostree-init: Running real init %s (argc=%d)\n", init_argv[0], argc-before_init_argc); - fflush (stderr); - execv (init_argv[0], init_argv); - perrorv ("Failed to exec init '%s'", init_argv[0]); - exit (1); -} - diff --git a/tests/libtest.sh b/tests/libtest.sh index 0da1564a..c6eb3f2c 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 < 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 00000000..32cca0c4 --- /dev/null +++ b/tests/t0015-admin-deploy.sh @@ -0,0 +1,134 @@ +#!/bin/bash +# +# Copyright (C) 2011 Colin Walters +# +# 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"