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"