[INCOMPATIBLE CHANGE] Implement new deployment model

See https://wiki.gnome.org/OSTree/DeploymentModel2

This is a major rework of the on-disk filesystem layout, and the boot
process.  OSTree now explicitly supports upgrading kernels, and these
upgrades are also atomic.

The core concept of the new model is the "deployment list", which is
an ordered list of bootable operating system trees.  The deployment
list is reflected in the bootloader configuration; which has a kernel
argument that tells the initramfs (dracut) which operating system root
to use.

Invidiual notable changes that come along with this:

1) Operating systems should now come with their etc in usr/etc; OSTree
   will perform a 3-way merge at deployment time, and place etc in
   the actual root.  This avoids the need for a bind mount, and is
   just a lot cleaner.
2) OSTree no longer bind mounts /root, /home, and /tmp.  It is expected
   that the the OS/ has these as symbolic links into /var.

At the moment, OSTree only supports managing syslinux; other
bootloader backends will follow.
This commit is contained in:
Colin Walters 2013-06-29 11:45:53 -04:00
parent ecb3f0de03
commit bb6eedfb25
33 changed files with 4237 additions and 2127 deletions

View File

@ -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\"

View File

@ -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

View File

@ -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))

View File

@ -1,6 +1,6 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2012 Colin Walters <walters@verbum.org>
* Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -24,701 +24,109 @@
#include "ot-admin-builtins.h"
#include "ot-admin-functions.h"
#include "ot-admin-deploy.h"
#include "ot-ordered-hash.h"
#include "ostree.h"
#include <glib/gi18n.h>
typedef struct {
OstreeRepo *repo;
OtAdminBuiltinOpts *admin_opts;
GFile *ostree_dir;
char *osname;
GFile *osname_dir;
char *current_deployment_ref;
char *previous_deployment_ref;
char *resolved_commit;
char *resolved_previous_commit;
char *previous_deployment_revision;
GFile *deploy_target_path;
GFile *previous_deployment;
} OtAdminDeploy;
static gboolean opt_no_kernel;
static gboolean opt_force;
static gboolean opt_no_bootloader;
static gboolean opt_retain;
static char **opt_kernel_argv;
static char *opt_osname;
static char *opt_origin_path;
static GOptionEntry options[] = {
{ "no-kernel", 0, 0, G_OPTION_ARG_NONE, &opt_no_kernel, "Don't update kernel related config (initramfs, bootloader)", NULL },
{ "force", 0, 0, G_OPTION_ARG_NONE, &opt_force, "Overwrite any existing deployment", NULL },
{ "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Specify operating system root to use", NULL },
{ "origin-file", 0, 0, G_OPTION_ARG_FILENAME, &opt_origin_path, "Specify origin file", NULL },
{ "no-bootloader", 0, 0, G_OPTION_ARG_NONE, &opt_no_bootloader, "Don't update bootloader", NULL },
{ "retain", 0, 0, G_OPTION_ARG_NONE, &opt_retain, "Do not delete previous deployment", NULL },
{ "karg", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_kernel_argv, "Set kernel argument, like --karg=root=/dev/sda1", NULL },
{ NULL }
};
/**
* update_current:
*
* Atomically swap the /ostree/current symbolic link to point to a new
* path. If successful, the old current will be saved as
* /ostree/previous, and /ostree/current-etc will be a link to the
* current /etc subdirectory.
*
* Unless the new-current equals current, in which case, do nothing.
*/
static gboolean
update_current (OtAdminDeploy *self,
GFile *current_deployment,
GFile *deploy_target,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
ot_lobj GFile *current_path = NULL;
ot_lobj GFile *current_etc_path = NULL;
ot_lobj GFile *previous_path = NULL;
ot_lobj GFile *tmp_current_path = NULL;
ot_lobj GFile *tmp_current_etc_path = NULL;
ot_lobj GFile *tmp_previous_path = NULL;
ot_lobj GFileInfo *previous_info = NULL;
ot_lfree char *relative_current = NULL;
ot_lfree char *relative_current_etc = NULL;
ot_lfree char *relative_previous = NULL;
current_path = g_file_get_child (self->osname_dir, "current");
current_etc_path = g_file_get_child (self->osname_dir, "current-etc");
previous_path = g_file_get_child (self->osname_dir, "previous");
relative_current = g_file_get_relative_path (self->osname_dir, deploy_target);
g_assert (relative_current);
relative_current_etc = g_strconcat (relative_current, "-etc", NULL);
if (current_deployment)
{
ot_lfree char *relative_previous = NULL;
if (g_file_equal (current_deployment, deploy_target))
{
g_print ("ostadmin: %s already points to %s\n", gs_file_get_path_cached (current_path),
relative_current);
return TRUE;
}
tmp_previous_path = g_file_get_child (self->osname_dir, "tmp-previous");
(void) gs_file_unlink (tmp_previous_path, NULL, NULL);
relative_previous = g_file_get_relative_path (self->osname_dir, current_deployment);
g_assert (relative_previous);
if (symlink (relative_previous, gs_file_get_path_cached (tmp_previous_path)) < 0)
{
ot_util_set_error_from_errno (error, errno);
goto out;
}
}
tmp_current_path = g_file_get_child (self->osname_dir, "tmp-current");
(void) gs_file_unlink (tmp_current_path, NULL, NULL);
if (symlink (relative_current, gs_file_get_path_cached (tmp_current_path)) < 0)
{
ot_util_set_error_from_errno (error, errno);
goto out;
}
tmp_current_etc_path = g_file_get_child (self->osname_dir, "tmp-current-etc");
(void) gs_file_unlink (tmp_current_etc_path, NULL, NULL);
if (symlink (relative_current_etc, gs_file_get_path_cached (tmp_current_etc_path)) < 0)
{
ot_util_set_error_from_errno (error, errno);
goto out;
}
if (!gs_file_rename (tmp_current_path, current_path,
cancellable, error))
goto out;
if (!gs_file_rename (tmp_current_etc_path, current_etc_path,
cancellable, error))
goto out;
if (tmp_previous_path)
{
if (!gs_file_rename (tmp_previous_path, previous_path,
cancellable, error))
goto out;
}
g_print ("ostadmin: %s set to %s\n", gs_file_get_path_cached (current_path),
relative_current);
ret = TRUE;
out:
return ret;
}
typedef struct {
GError **error;
gboolean caught_error;
GMainLoop *loop;
} ProcessOneCheckoutData;
static void
on_checkout_complete (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
ProcessOneCheckoutData *data = user_data;
GError *local_error = NULL;
if (!ostree_repo_checkout_tree_finish ((OstreeRepo*)object, result,
&local_error))
goto out;
out:
if (local_error)
{
data->caught_error = TRUE;
g_propagate_error (data->error, local_error);
}
g_main_loop_quit (data->loop);
}
/**
* ensure_unlinked:
*
* Like gs_file_unlink(), but return successfully if the file doesn't
* exist.
*/
static gboolean
ensure_unlinked (GFile *path,
GCancellable *cancellable,
GError **error)
{
gboolean 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, &current_bootversion, &current_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;

View File

@ -28,7 +28,10 @@
#include <glib/gi18n.h>
static char *opt_osname;
static GOptionEntry options[] = {
{ "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Specify operating system root to use", NULL },
{ NULL }
};
@ -37,57 +40,55 @@ ot_admin_builtin_diff (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GE
{
GOptionContext *context;
gboolean ret = FALSE;
const char *osname;
GFile *ostree_dir = admin_opts->ostree_dir;
gs_free char *booted_osname = NULL;
ot_lobj GFile *repo_path = NULL;
ot_lobj GFile *deployment = NULL;
gs_unref_object OtDeployment *deployment = NULL;
gs_unref_object GFile *deployment_dir = NULL;
ot_lobj GFile *deploy_parent = NULL;
ot_lptrarray GPtrArray *modified = NULL;
ot_lptrarray GPtrArray *removed = NULL;
ot_lptrarray GPtrArray *added = NULL;
gs_unref_ptrarray GPtrArray *deployments = NULL;
ot_lobj GFile *orig_etc_path = NULL;
ot_lobj GFile *new_etc_path = NULL;
__attribute__((unused)) GCancellable *cancellable = NULL;
int bootversion;
context = g_option_context_new ("OSNAME [REVISION] - Diff configuration for OSNAME");
context = g_option_context_new ("Diff current /etc configuration versus default");
g_option_context_add_main_entries (context, options, NULL);
if (!g_option_context_parse (context, &argc, &argv, error))
goto out;
repo_path = g_file_get_child (ostree_dir, "repo");
repo_path = g_file_resolve_relative_path (admin_opts->sysroot, "ostree/repo");
if (argc < 2)
if (!ot_admin_list_deployments (admin_opts->sysroot, &bootversion, &deployments,
cancellable, error))
{
ot_util_usage_error (context, "OSNAME must be specified", error);
g_prefix_error (error, "While listing deployments: ");
goto out;
}
osname = argv[1];
if (argc > 2)
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);

View File

@ -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;

View File

@ -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,

View File

@ -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

View File

@ -41,15 +41,12 @@ ot_admin_builtin_prune (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, G
{
GOptionContext *context;
gboolean ret = FALSE;
guint i;
const char *osname;
GFile *ostree_dir = admin_opts->ostree_dir;
ot_lobj GFile *repo_path = NULL;
ot_lobj GFile *deploy_dir = NULL;
ot_lobj GFile *current_deployment = NULL;
ot_lobj GFile *previous_deployment = NULL;
ot_lobj GFile *active_deployment = NULL;
ot_lptrarray GPtrArray *deployments = NULL;
gs_free char *active_osname = NULL;
__attribute__((unused)) GCancellable *cancellable = NULL;
@ -68,43 +65,10 @@ ot_admin_builtin_prune (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, G
osname = argv[1];
if (!ot_admin_list_deployments (ostree_dir, osname, &deployments,
cancellable, error))
if (!ot_admin_cleanup (admin_opts->sysroot, cancellable, error))
goto out;
if (!ot_admin_get_current_deployment (ostree_dir, osname, &current_deployment,
cancellable, error));
if (!ot_admin_get_previous_deployment (ostree_dir, osname, &previous_deployment,
cancellable, error));
if (!ot_admin_get_active_deployment (ostree_dir, &active_osname, &active_deployment,
cancellable, error));
for (i = 0; i < deployments->len; i++)
{
GFile *deployment = deployments->pdata[i];
ot_lobj GFile *deployment_etc = NULL;
ot_lobj GFile *parent = NULL;
if ((current_deployment && g_file_equal (deployment, current_deployment))
|| (previous_deployment && g_file_equal (deployment, previous_deployment))
|| (active_deployment && g_file_equal (deployment, active_deployment)))
continue;
parent = g_file_get_parent (deployment);
deployment_etc = ot_gfile_get_child_strconcat (parent, gs_file_get_basename_cached (deployment),
"-etc", NULL);
g_print ("Deleting deployment %s\n", gs_file_get_path_cached (deployment));
if (!gs_shutil_rm_rf (deployment, cancellable, error))
goto out;
/* Note - not atomic; we may be leaving the -etc directory around
* if this fails in the middle =/
*/
if (!gs_shutil_rm_rf (deployment_etc, cancellable, error))
goto out;
}
repo_path = g_file_get_child (ostree_dir, "repo");
repo_path = g_file_resolve_relative_path (admin_opts->sysroot, "ostree/repo");
if (!opt_no_repo_prune)
{
@ -112,7 +76,7 @@ ot_admin_builtin_prune (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, G
repo_arg = g_strconcat ("--repo=", gs_file_get_path_cached (repo_path), NULL);
if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
if (!gs_subprocess_simple_run_sync (NULL,
GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
cancellable, error,
"ostree", repo_arg, "prune", "--refs-only",

View File

@ -1,178 +0,0 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2012 Colin Walters <walters@verbum.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Colin Walters <walters@verbum.org>
*/
#include "config.h"
#include "ot-admin-builtins.h"
#include "ot-admin-functions.h"
#include "ostree.h"
#include <glib/gi18n.h>
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, &current_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;
}

View File

@ -0,0 +1,100 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Colin Walters <walters@verbum.org>
*/
#include "config.h"
#include "ot-admin-builtins.h"
#include "ot-admin-functions.h"
#include "ostree.h"
#include <glib/gi18n.h>
static GOptionEntry options[] = {
{ NULL }
};
gboolean
ot_admin_builtin_status (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error)
{
GOptionContext *context;
gboolean ret = FALSE;
int bootversion;
gs_unref_object OtDeployment *booted_deployment = NULL;
gs_unref_ptrarray GPtrArray *deployments = NULL;
__attribute__((unused)) GCancellable *cancellable = NULL;
guint i;
context = g_option_context_new ("List deployments");
g_option_context_add_main_entries (context, options, NULL);
if (!g_option_context_parse (context, &argc, &argv, error))
goto out;
if (!ot_admin_list_deployments (admin_opts->sysroot, &bootversion, &deployments,
cancellable, error))
{
g_prefix_error (error, "While listing deployments: ");
goto out;
}
/* Find the currently booted deployment, if any; we will
* ensure it is present in the new deployment list.
*/
if (!ot_admin_find_booted_deployment (admin_opts->sysroot, deployments,
&booted_deployment,
cancellable, error))
goto out;
if (deployments->len == 0)
{
g_print ("No deployments.\n");
}
else
{
int subbootversion;
if (!ot_admin_read_current_subbootversion (admin_opts->sysroot, bootversion,
&subbootversion,
cancellable, error))
goto out;
g_print ("bootversion: %d.%d\n", bootversion, subbootversion);
for (i = 0; i < deployments->len; i++)
{
OtDeployment *deployment = deployments->pdata[i];
g_print ("%u: %c %s %s.%d\n",
i,
deployment == booted_deployment ? '*' : ' ',
ot_deployment_get_osname (deployment),
ot_deployment_get_csum (deployment),
ot_deployment_get_deployserial (deployment));
}
}
ret = TRUE;
out:
if (context)
g_option_context_free (context);
return ret;
}

View File

@ -1,296 +0,0 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2012 Colin Walters <walters@verbum.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Colin Walters <walters@verbum.org>
*/
#include "config.h"
#include "ot-admin-builtins.h"
#include "ostree.h"
#include <glib/gi18n.h>
#include <sys/utsname.h>
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;
}

View File

@ -24,6 +24,7 @@
#include "ot-admin-builtins.h"
#include "ot-admin-functions.h"
#include "ot-admin-deploy.h"
#include "ostree.h"
#include "otutil.h"
@ -32,8 +33,10 @@
#include <glib/gi18n.h>
static gboolean opt_reboot;
static char *opt_osname;
static GOptionEntry options[] = {
{ "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Specify operating system root to use", NULL },
{ "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Reboot after a successful upgrade", NULL },
{ NULL }
};
@ -41,79 +44,108 @@ static GOptionEntry options[] = {
gboolean
ot_admin_builtin_upgrade (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error)
{
GOptionContext *context;
gboolean ret = FALSE;
GFile *ostree_dir = admin_opts->ostree_dir;
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, &current_bootversion,
&current_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, &current_rev);
ostree_dir_arg = g_strconcat ("--ostree-dir=",
gs_file_get_path_cached (ostree_dir),
NULL);
if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
cancellable, error,
"ostree", "admin", ostree_dir_arg, "pull-deploy", osname, NULL))
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,

View File

@ -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

1245
src/ostree/ot-admin-deploy.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2012 Colin Walters <walters@verbum.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Colin Walters <walters@verbum.org>
*/
#ifndef __OT_ADMIN_DEPLOY__
#define __OT_ADMIN_DEPLOY_
#include <gio/gio.h>
#include "ot-deployment.h"
#include "ot-bootloader.h"
#include "ot-ordered-hash.h"
G_BEGIN_DECLS
gboolean ot_admin_deploy (GFile *sysroot,
int current_bootversion,
GPtrArray *current_deployments,
const char *osname,
const char *revision,
GKeyFile *origin,
char **add_kernel_argv,
gboolean retain,
OtDeployment *booted_deployment,
OtDeployment *merge_deployment,
OtDeployment **out_new_deployment,
int *out_new_bootversion,
GPtrArray **out_new_deployments,
GCancellable *cancellable,
GError **error);
G_END_DECLS
#endif

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,10 @@
#define __OT_ADMIN_FUNCTIONS__
#include <gio/gio.h>
#include "ostree.h"
#include "ot-deployment.h"
#include "ot-bootloader.h"
#include "ot-ordered-hash.h"
G_BEGIN_DECLS
@ -31,50 +35,84 @@ gboolean ot_admin_ensure_initialized (GFile *ostree_dir,
GCancellable *cancellable,
GError **error);
gboolean ot_admin_get_booted_os (char **out_osname,
char **out_tree,
GCancellable *cancellable,
GError **error);
gboolean ot_admin_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

View File

@ -0,0 +1,312 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2013 Colin Walters <walters@verbum.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include "ot-bootloader-syslinux.h"
#include "libgsystem.h"
#include "otutil.h"
#include "ot-admin-functions.h"
#include <string.h>
struct _OtBootloaderSyslinux
{
GObject parent_instance;
GFile *sysroot;
GFile *config_path;
};
typedef GObjectClass OtBootloaderSyslinuxClass;
static void ot_bootloader_syslinux_bootloader_iface_init (OtBootloaderInterface *iface);
G_DEFINE_TYPE_WITH_CODE (OtBootloaderSyslinux, ot_bootloader_syslinux, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (OT_TYPE_BOOTLOADER, ot_bootloader_syslinux_bootloader_iface_init));
static gboolean
ot_bootloader_syslinux_query (OtBootloader *bootloader)
{
OtBootloaderSyslinux *self = OT_BOOTLOADER_SYSLINUX (bootloader);
return g_file_query_file_type (self->config_path, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_SYMBOLIC_LINK;
}
static gboolean
append_config_from_boot_loader_entries (OtBootloaderSyslinux *self,
gboolean regenerate_default,
int bootversion,
GPtrArray *new_lines,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
gs_unref_ptrarray GPtrArray *boot_loader_configs = NULL;
guint i;
if (!ot_admin_read_boot_loader_configs (self->sysroot, bootversion, &boot_loader_configs,
cancellable, error))
goto out;
for (i = 0; i < boot_loader_configs->len; i++)
{
OtConfigParser *config = boot_loader_configs->pdata[i];
const char *val;
val = ot_config_parser_get (config, "title");
if (!val)
val = "(Untitled)";
if (regenerate_default && i == 0)
{
g_ptr_array_add (new_lines, g_strdup_printf ("DEFAULT %s", val));
}
g_ptr_array_add (new_lines, g_strdup_printf ("LABEL %s", val));
val = ot_config_parser_get (config, "linux");
if (!val)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"No \"linux\" key in bootloader config");
goto out;
}
g_ptr_array_add (new_lines, g_strdup_printf ("\tKERNEL %s", val));
val = ot_config_parser_get (config, "initrd");
if (val)
g_ptr_array_add (new_lines, g_strdup_printf ("\tINITRD %s", val));
val = ot_config_parser_get (config, "options");
if (val)
g_ptr_array_add (new_lines, g_strdup_printf ("\tAPPEND %s", val));
}
ret = TRUE;
out:
return ret;
}
static char *
join_lines (GPtrArray *lines)
{
GString *buf = g_string_new ("");
guint i;
gboolean prev_was_empty = FALSE;
for (i = 0; i < lines->len; i++)
{
const char *line = lines->pdata[i];
/* Special bit to remove extraneous empty lines */
if (*line == '\0')
{
if (prev_was_empty || i == 0)
continue;
else
prev_was_empty = TRUE;
}
g_string_append (buf, line);
g_string_append_c (buf, '\n');
}
return g_string_free (buf, FALSE);
}
static gboolean
ot_bootloader_syslinux_write_config (OtBootloader *bootloader,
int bootversion,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
OtBootloaderSyslinux *self = OT_BOOTLOADER_SYSLINUX (bootloader);
gs_unref_object GFile *new_config_path = NULL;
gs_free char *config_contents = NULL;
gs_free char *new_config_contents = NULL;
gs_unref_ptrarray GPtrArray *new_lines = NULL;
gs_unref_ptrarray GPtrArray *tmp_lines = NULL;
gs_free char *kernel_arg = NULL;
gboolean saw_default = FALSE;
gboolean regenerate_default = FALSE;
gboolean parsing_label = FALSE;
char **lines = NULL;
char **iter;
guint i;
new_config_path = ot_gfile_resolve_path_printf (self->sysroot, "boot/loader.%d/syslinux.cfg",
bootversion);
/* This should follow the symbolic link to the current bootversion. */
config_contents = gs_file_load_contents_utf8 (self->config_path, cancellable, error);
if (!config_contents)
goto out;
lines = g_strsplit (config_contents, "\n", -1);
new_lines = g_ptr_array_new_with_free_func (g_free);
tmp_lines = g_ptr_array_new_with_free_func (g_free);
/* Note special iteration condition here; we want to also loop one
* more time at the end where line = NULL to ensure we finish off
* processing the last LABEL.
*/
iter = lines;
while (TRUE)
{
char *line = *iter;
gboolean skip = FALSE;
if (parsing_label &&
(line == NULL || !g_str_has_prefix (line, "\t")))
{
parsing_label = FALSE;
if (kernel_arg == NULL)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"No KERNEL argument found after LABEL");
goto out;
}
/* If this is a non-ostree kernel, just emit the lines
* we saw.
*/
if (!g_str_has_prefix (kernel_arg, "/ostree/"))
{
for (i = 0; i < tmp_lines->len; i++)
{
g_ptr_array_add (new_lines, tmp_lines->pdata[i]);
tmp_lines->pdata[i] = NULL; /* Transfer ownership */
}
}
else
{
/* Otherwise, we drop the config on the floor - it
* will be regenerated.
*/
g_ptr_array_set_size (tmp_lines, 0);
}
}
if (line == NULL)
break;
if (!parsing_label &&
(g_str_has_prefix (line, "LABEL ")))
{
parsing_label = TRUE;
g_ptr_array_set_size (tmp_lines, 0);
}
else if (parsing_label && g_str_has_prefix (line, "\tKERNEL "))
{
g_free (kernel_arg);
kernel_arg = g_strdup (line + strlen ("\tKERNEL "));
}
else if (!parsing_label &&
(g_str_has_prefix (line, "DEFAULT ")))
{
saw_default = TRUE;
if (g_str_has_prefix (line, "DEFAULT ostree:"))
regenerate_default = TRUE;
skip = TRUE;
}
if (skip)
{
g_free (line);
}
else
{
if (parsing_label)
{
g_ptr_array_add (tmp_lines, line);
}
else
{
g_ptr_array_add (new_lines, line);
}
}
/* Transfer ownership */
*iter = NULL;
iter++;
}
if (!saw_default)
regenerate_default = TRUE;
if (!append_config_from_boot_loader_entries (self, regenerate_default,
bootversion, new_lines,
cancellable, error))
goto out;
new_config_contents = join_lines (new_lines);
if (strcmp (new_config_contents, config_contents) != 0)
{
if (!g_file_replace_contents (new_config_path, new_config_contents,
strlen (new_config_contents),
NULL, FALSE, G_FILE_CREATE_NONE,
NULL, cancellable, error))
goto out;
g_print ("Saved new version of %s\n", gs_file_get_path_cached (self->config_path));
}
ret = TRUE;
out:
g_free (lines); /* Note we freed elements individually */
return ret;
}
static void
ot_bootloader_syslinux_finalize (GObject *object)
{
OtBootloaderSyslinux *self = OT_BOOTLOADER_SYSLINUX (object);
g_clear_object (&self->sysroot);
g_clear_object (&self->config_path);
G_OBJECT_CLASS (ot_bootloader_syslinux_parent_class)->finalize (object);
}
void
ot_bootloader_syslinux_init (OtBootloaderSyslinux *self)
{
}
static void
ot_bootloader_syslinux_bootloader_iface_init (OtBootloaderInterface *iface)
{
iface->query = ot_bootloader_syslinux_query;
iface->write_config = ot_bootloader_syslinux_write_config;
}
void
ot_bootloader_syslinux_class_init (OtBootloaderSyslinuxClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = ot_bootloader_syslinux_finalize;
}
OtBootloaderSyslinux *
ot_bootloader_syslinux_new (GFile *sysroot)
{
OtBootloaderSyslinux *self = g_object_new (OT_TYPE_BOOTLOADER_SYSLINUX, NULL);
self->sysroot = g_object_ref (sysroot);
self->config_path = g_file_resolve_relative_path (self->sysroot, "boot/syslinux/syslinux.cfg");
return self;
}

View File

@ -0,0 +1,40 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2013 Colin Walters <walters@verbum.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __OT_BOOTLOADER_SYSLINUX_H__
#define __OT_BOOTLOADER_SYSLINUX_H__
#include "ot-bootloader.h"
G_BEGIN_DECLS
#define OT_TYPE_BOOTLOADER_SYSLINUX (ot_bootloader_syslinux_get_type ())
#define OT_BOOTLOADER_SYSLINUX(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OT_TYPE_BOOTLOADER_SYSLINUX, OtBootloaderSyslinux))
#define OT_IS_BOOTLOADER_SYSLINUX(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OT_TYPE_BOOTLOADER_SYSLINUX))
typedef struct _OtBootloaderSyslinux OtBootloaderSyslinux;
GType ot_bootloader_syslinux_get_type (void) G_GNUC_CONST;
OtBootloaderSyslinux * ot_bootloader_syslinux_new (GFile *sysroot);
G_END_DECLS
#endif /* __OT_BOOTLOADER_SYSLINUX_H__ */

View File

@ -0,0 +1,49 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2013 Colin Walters <walters@verbum.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include "ot-bootloader.h"
G_DEFINE_INTERFACE (OtBootloader, ot_bootloader, G_TYPE_OBJECT)
static void
ot_bootloader_default_init (OtBootloaderInterface *iface)
{
}
gboolean
ot_bootloader_query (OtBootloader *self)
{
g_return_val_if_fail (OT_IS_BOOTLOADER (self), FALSE);
return OT_BOOTLOADER_GET_IFACE (self)->query (self);
}
gboolean
ot_bootloader_write_config (OtBootloader *self,
int bootversion,
GCancellable *cancellable,
GError **error)
{
g_return_val_if_fail (OT_IS_BOOTLOADER (self), FALSE);
return OT_BOOTLOADER_GET_IFACE (self)->write_config (self, bootversion,
cancellable, error);
}

View File

@ -0,0 +1,59 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2013 Colin Walters <walters@verbum.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __OT_BOOTLOADER_H__
#define __OT_BOOTLOADER_H__
#include <gio/gio.h>
G_BEGIN_DECLS
#define OT_TYPE_BOOTLOADER (ot_bootloader_get_type ())
#define OT_BOOTLOADER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OT_TYPE_BOOTLOADER, OtBootloader))
#define OT_IS_BOOTLOADER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OT_TYPE_BOOTLOADER))
#define OT_BOOTLOADER_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), OT_TYPE_BOOTLOADER, OtBootloaderInterface))
typedef struct _OtBootloader OtBootloader;
typedef struct _OtBootloaderInterface OtBootloaderInterface;
struct _OtBootloaderInterface
{
GTypeInterface g_iface;
/* virtual functions */
gboolean (* query) (OtBootloader *self);
gboolean (* write_config) (OtBootloader *self,
int bootversion,
GCancellable *cancellable,
GError **error);
};
GType ot_bootloader_get_type (void) G_GNUC_CONST;
gboolean ot_bootloader_query (OtBootloader *self);
gboolean ot_bootloader_write_config (OtBootloader *self,
int bootversion,
GCancellable *cancellable,
GError **error);
G_END_DECLS
#endif /* __OT_BOOTLOADER_H__ */

View File

@ -31,12 +31,10 @@
#include <glib/gi18n.h>
static char *opt_ostree_dir = NULL;
static char *opt_boot_dir = "/boot";
static char *opt_sysroot = "/";
static GOptionEntry options[] = {
{ "ostree-dir", 0, 0, G_OPTION_ARG_STRING, &opt_ostree_dir, "Path to OSTree root directory (default: /ostree)", NULL },
{ "boot-dir", 0, 0, G_OPTION_ARG_STRING, &opt_boot_dir, "Path to system boot directory (default: /boot)", NULL },
{ "sysroot", 0, 0, G_OPTION_ARG_STRING, &opt_sysroot, "Path to root directory (default: /)", NULL },
{ NULL }
};
@ -51,9 +49,8 @@ static OstreeAdminCommand admin_subcommands[] = {
{ "deploy", ot_admin_builtin_deploy },
{ "install", ot_admin_builtin_install },
{ "upgrade", ot_admin_builtin_upgrade },
{ "pull-deploy", ot_admin_builtin_pull_deploy },
{ "prune", ot_admin_builtin_prune },
{ "update-kernel", ot_admin_builtin_update_kernel },
{ "status", ot_admin_builtin_status },
{ "config-diff", ot_admin_builtin_diff },
{ "run-triggers", ot_admin_builtin_run_triggers },
{ NULL, NULL }
@ -70,8 +67,6 @@ ostree_builtin_admin (int argc, char **argv, GFile *repo_path, GError **error)
int subcmd_argc;
OtAdminBuiltinOpts admin_opts;
char **subcmd_argv = NULL;
ot_lobj GFile *ostree_dir = NULL;
ot_lobj GFile *boot_dir = NULL;
context = g_option_context_new ("[OPTIONS] SUBCOMMAND - Run an administrative subcommand");
@ -117,21 +112,9 @@ ostree_builtin_admin (int argc, char **argv, GFile *repo_path, GError **error)
goto out;
}
if (opt_ostree_dir != NULL)
{
ostree_dir = g_file_new_for_path (opt_ostree_dir);
}
else
{
if (!ot_admin_get_default_ostree_dir (&ostree_dir, cancellable, error))
goto out;
}
boot_dir = g_file_new_for_path (opt_boot_dir);
ostree_prep_builtin_argv (subcommand_name, argc-2, argv+2, &subcmd_argc, &subcmd_argv);
admin_opts.ostree_dir = ostree_dir;
admin_opts.boot_dir = boot_dir;
admin_opts.sysroot = g_file_new_for_path (opt_sysroot);
if (!subcommand->fn (subcmd_argc, subcmd_argv, &admin_opts, error))
goto out;

View File

@ -0,0 +1,230 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2013 Colin Walters <walters@verbum.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include "ot-config-parser.h"
#include "libgsystem.h"
struct _OtConfigParser
{
GObject parent_instance;
gboolean parsed;
char *separators;
GHashTable *options;
GPtrArray *lines;
};
typedef GObjectClass OtConfigParserClass;
G_DEFINE_TYPE (OtConfigParser, ot_config_parser, G_TYPE_OBJECT)
gboolean
ot_config_parser_parse (OtConfigParser *self,
GFile *path,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
gs_free char *contents = NULL;
char **lines = NULL;
char **iter = NULL;
g_return_val_if_fail (!self->parsed, FALSE);
contents = gs_file_load_contents_utf8 (path, cancellable, error);
if (!contents)
goto out;
lines = g_strsplit (contents, "\n", -1);
for (iter = lines; *iter; iter++)
{
const char *line = *iter;
char *keyname = "";
if (g_ascii_isalpha (*line))
{
char **items = NULL;
items = g_strsplit_set (line, self->separators, 2);
if (g_strv_length (items) == 2 && items[0][0] != '\0')
{
keyname = items[0];
g_hash_table_insert (self->options, items[0], items[1]);
g_free (items); /* Transfer ownership */
}
else
{
g_strfreev (items);
}
}
g_ptr_array_add (self->lines, g_variant_new ("(ss)", keyname, line));
}
self->parsed = TRUE;
ret = TRUE;
out:
g_strfreev (lines);
return ret;
}
void
ot_config_parser_set (OtConfigParser *self,
const char *key,
const char *value)
{
g_hash_table_replace (self->options, g_strdup (key), g_strdup (value));
}
const char *
ot_config_parser_get (OtConfigParser *self,
const char *key)
{
return g_hash_table_lookup (self->options, key);
}
static gboolean
write_key (OtConfigParser *self,
GDataOutputStream *out,
const char *key,
const char *value,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
if (!g_data_output_stream_put_string (out, key, cancellable, error))
goto out;
if (!g_data_output_stream_put_byte (out, self->separators[0], cancellable, error))
goto out;
if (!g_data_output_stream_put_string (out, value, cancellable, error))
goto out;
if (!g_data_output_stream_put_byte (out, '\n', cancellable, error))
goto out;
ret = TRUE;
out:
return ret;
}
gboolean
ot_config_parser_write (OtConfigParser *self,
GFile *output,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
GHashTableIter hashiter;
gpointer hashkey, hashvalue;
gs_unref_object GOutputStream *out = NULL;
gs_unref_object GDataOutputStream *dataout = NULL;
guint i;
gs_unref_hashtable GHashTable *written_overrides = NULL;
written_overrides = g_hash_table_new (g_str_hash, g_str_equal);
out = (GOutputStream*)g_file_replace (output, NULL, FALSE, 0, cancellable, error);
if (!out)
goto out;
dataout = g_data_output_stream_new (out);
for (i = 0; i < self->lines->len; i++)
{
GVariant *linedata = self->lines->pdata[i];
const char *key;
const char *value;
const char *line;
g_variant_get (linedata, "(&s&s)", &key, &line);
value = g_hash_table_lookup (self->options, key);
if (value == NULL)
{
if (!g_data_output_stream_put_string (dataout, line, cancellable, error))
goto out;
if (!g_data_output_stream_put_byte (dataout, '\n', cancellable, error))
goto out;
}
else
{
if (!write_key (self, dataout, key, value, cancellable, error))
goto out;
g_hash_table_insert (written_overrides, (gpointer)key, (gpointer)key);
}
}
g_hash_table_iter_init (&hashiter, self->options);
while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue))
{
if (g_hash_table_lookup (written_overrides, hashkey))
continue;
if (!write_key (self, dataout, hashkey, hashvalue, cancellable, error))
goto out;
}
if (!g_output_stream_close ((GOutputStream*)dataout, cancellable, error))
goto out;
ret = TRUE;
out:
return ret;
}
static void
ot_config_parser_finalize (GObject *object)
{
OtConfigParser *self = OT_CONFIG_PARSER (object);
g_hash_table_unref (self->options);
g_ptr_array_unref (self->lines);
g_free (self->separators);
G_OBJECT_CLASS (ot_config_parser_parent_class)->finalize (object);
}
static void
ot_config_parser_init (OtConfigParser *self)
{
self->options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
self->lines = g_ptr_array_new ();
}
void
ot_config_parser_class_init (OtConfigParserClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = ot_config_parser_finalize;
}
OtConfigParser *
ot_config_parser_new (const char *separators)
{
OtConfigParser *self = NULL;
g_return_val_if_fail (separators != NULL && separators[0], NULL);
self = g_object_new (OT_TYPE_CONFIG_PARSER, NULL);
self->separators = g_strdup (separators);
return self;
}

View File

@ -0,0 +1,58 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2013 Colin Walters <walters@verbum.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __OT_CONFIG_PARSER_H__
#define __OT_CONFIG_PARSER_H__
#include <gio/gio.h>
G_BEGIN_DECLS
#define OT_TYPE_CONFIG_PARSER (ot_config_parser_get_type ())
#define OT_CONFIG_PARSER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OT_TYPE_CONFIG_PARSER, OtConfigParser))
#define OT_IS_CONFIG_PARSER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OT_TYPE_CONFIG_PARSER))
typedef struct _OtConfigParser OtConfigParser;
GType ot_config_parser_get_type (void) G_GNUC_CONST;
OtConfigParser * ot_config_parser_new (const char *separator);
gboolean ot_config_parser_parse (OtConfigParser *self,
GFile *path,
GCancellable *cancellable,
GError **error);
gboolean ot_config_parser_write (OtConfigParser *self,
GFile *output,
GCancellable *cancellable,
GError **error);
void ot_config_parser_set (OtConfigParser *self,
const char *key,
const char *value);
const char *ot_config_parser_get (OtConfigParser *self,
const char *key);
G_END_DECLS
#endif /* __OT_CONFIG_PARSER_H__ */

209
src/ostree/ot-deployment.c Normal file
View File

@ -0,0 +1,209 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2013 Colin Walters <walters@verbum.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include "ot-deployment.h"
struct _OtDeployment
{
GObject parent_instance;
int index; /* Global offset */
char *osname; /* osname */
char *csum; /* OSTree checksum of tree */
int deployserial; /* How many times this particular csum appears in deployment list */
char *bootcsum; /* Checksum of kernel+initramfs */
int bootserial; /* An integer assigned to this tree per its ${bootcsum} */
OtConfigParser *bootconfig; /* Bootloader configuration */
GKeyFile *origin; /* How to construct an upgraded version of this tree */
};
typedef GObjectClass OtDeploymentClass;
G_DEFINE_TYPE (OtDeployment, ot_deployment, G_TYPE_OBJECT)
const char *
ot_deployment_get_csum (OtDeployment *self)
{
return self->csum;
}
const char *
ot_deployment_get_bootcsum (OtDeployment *self)
{
return self->bootcsum;
}
const char *
ot_deployment_get_osname (OtDeployment *self)
{
return self->osname;
}
int
ot_deployment_get_deployserial (OtDeployment *self)
{
return self->deployserial;
}
int
ot_deployment_get_bootserial (OtDeployment *self)
{
return self->bootserial;
}
OtConfigParser *
ot_deployment_get_bootconfig (OtDeployment *self)
{
return self->bootconfig;
}
GKeyFile *
ot_deployment_get_origin (OtDeployment *self)
{
return self->origin;
}
int
ot_deployment_get_index (OtDeployment *self)
{
return self->index;
}
void
ot_deployment_set_index (OtDeployment *self, int index)
{
self->index = index;
}
void
ot_deployment_set_bootserial (OtDeployment *self, int index)
{
self->bootserial = index;
}
void
ot_deployment_set_bootconfig (OtDeployment *self, OtConfigParser *bootconfig)
{
g_clear_object (&self->bootconfig);
if (bootconfig)
self->bootconfig = g_object_ref (bootconfig);
}
void
ot_deployment_set_origin (OtDeployment *self, GKeyFile *origin)
{
g_clear_pointer (&self->origin, g_key_file_unref);
if (origin)
self->origin = g_key_file_ref (origin);
}
OtDeployment *
ot_deployment_clone (OtDeployment *self)
{
OtDeployment *ret = ot_deployment_new (self->index, self->osname, self->csum,
self->deployserial,
self->bootcsum, self->bootserial);
ot_deployment_set_bootconfig (ret, self->bootconfig);
ot_deployment_set_origin (ret, self->origin);
return ret;
}
guint
ot_deployment_hash (gconstpointer v)
{
OtDeployment *d = (OtDeployment*)v;
return g_str_hash (ot_deployment_get_osname (d)) +
g_str_hash (ot_deployment_get_csum (d)) +
ot_deployment_get_deployserial (d);
}
gboolean
ot_deployment_equal (gconstpointer ap, gconstpointer bp)
{
OtDeployment *a = (OtDeployment*)ap;
OtDeployment *b = (OtDeployment*)bp;
if (a == NULL && b == NULL)
return TRUE;
else if (a != NULL && b != NULL)
return g_str_equal (ot_deployment_get_osname (a),
ot_deployment_get_osname (b)) &&
g_str_equal (ot_deployment_get_csum (a),
ot_deployment_get_csum (b)) &&
ot_deployment_get_deployserial (a) == ot_deployment_get_deployserial (b);
else
return FALSE;
}
static void
ot_deployment_finalize (GObject *object)
{
OtDeployment *self = OT_DEPLOYMENT (object);
g_free (self->osname);
g_free (self->csum);
g_free (self->bootcsum);
g_clear_object (&self->bootconfig);
g_clear_pointer (&self->origin, g_key_file_unref);
G_OBJECT_CLASS (ot_deployment_parent_class)->finalize (object);
}
void
ot_deployment_init (OtDeployment *self)
{
}
void
ot_deployment_class_init (OtDeploymentClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = ot_deployment_finalize;
}
OtDeployment *
ot_deployment_new (int index,
const char *osname,
const char *csum,
int deployserial,
const char *bootcsum,
int bootserial)
{
OtDeployment *self;
/* index may be -1 */
g_return_val_if_fail (osname != NULL, NULL);
g_return_val_if_fail (csum != NULL, NULL);
g_return_val_if_fail (deployserial >= 0, NULL);
/* We can have "disconnected" deployments that don't have a
bootcsum/serial */
self = g_object_new (OT_TYPE_DEPLOYMENT, NULL);
self->index = index;
self->osname = g_strdup (osname);
self->csum = g_strdup (csum);
self->deployserial = deployserial;
self->bootcsum = g_strdup (bootcsum);
self->bootserial = bootserial;
return self;
}

View File

@ -0,0 +1,66 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2013 Colin Walters <walters@verbum.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __OT_DEPLOYMENT_H__
#define __OT_DEPLOYMENT_H__
#include <gio/gio.h>
#include "ot-config-parser.h"
G_BEGIN_DECLS
#define OT_TYPE_DEPLOYMENT (ot_deployment_get_type ())
#define OT_DEPLOYMENT(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OT_TYPE_DEPLOYMENT, OtDeployment))
#define OT_IS_DEPLOYMENT(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OT_TYPE_DEPLOYMENT))
typedef struct _OtDeployment OtDeployment;
GType ot_deployment_get_type (void) G_GNUC_CONST;
guint ot_deployment_hash (gconstpointer v);
gboolean ot_deployment_equal (gconstpointer a, gconstpointer b);
OtDeployment * ot_deployment_new (int index,
const char *osname,
const char *csum,
int deployserial,
const char *bootcsum,
int bootserial);
int ot_deployment_get_index (OtDeployment *self);
const char *ot_deployment_get_osname (OtDeployment *self);
int ot_deployment_get_deployserial (OtDeployment *self);
const char *ot_deployment_get_csum (OtDeployment *self);
const char *ot_deployment_get_bootcsum (OtDeployment *self);
int ot_deployment_get_bootserial (OtDeployment *self);
OtConfigParser *ot_deployment_get_bootconfig (OtDeployment *self);
GKeyFile *ot_deployment_get_origin (OtDeployment *self);
void ot_deployment_set_index (OtDeployment *self, int index);
void ot_deployment_set_bootserial (OtDeployment *self, int index);
void ot_deployment_set_bootconfig (OtDeployment *self, OtConfigParser *bootconfig);
void ot_deployment_set_origin (OtDeployment *self, GKeyFile *origin);
OtDeployment *ot_deployment_clone (OtDeployment *self);
G_END_DECLS
#endif /* __OT_DEPLOYMENT_H__ */

View File

@ -0,0 +1,82 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2013 Colin Walters <walters@verbum.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include "ot-ordered-hash.h"
OtOrderedHash *
ot_ordered_hash_new (void)
{
OtOrderedHash *ret;
ret = g_new0 (OtOrderedHash, 1);
ret->order = g_ptr_array_new_with_free_func (g_free);
ret->table = g_hash_table_new (g_str_hash, g_str_equal);
return ret;
}
void
ot_ordered_hash_free (OtOrderedHash *ohash)
{
if (!ohash)
return;
g_ptr_array_unref (ohash->order);
g_hash_table_unref (ohash->table);
g_free (ohash);
}
void
ot_ordered_hash_cleanup (void *loc)
{
ot_ordered_hash_free (*((OtOrderedHash**)loc));
}
void
ot_ordered_hash_replace_key_take (OtOrderedHash *ohash,
char *key,
const char *value)
{
gboolean existed;
existed = g_hash_table_remove (ohash->table, key);
if (!existed)
g_ptr_array_add (ohash->order, key);
g_hash_table_insert (ohash->table, key, (char*)value);
}
void
ot_ordered_hash_replace_key (OtOrderedHash *ohash,
const char *key,
const char *val)
{
GString *buf;
gsize keylen;
char *valp;
char *valblock;
buf = g_string_new (key);
keylen = buf->len;
g_string_append_c (buf, '\0');
g_string_append (buf, val);
valblock = g_string_free (buf, FALSE);
valp = valblock + keylen + 1;
ot_ordered_hash_replace_key_take (ohash, valblock, valp);
}

View File

@ -0,0 +1,46 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2013 Colin Walters <walters@verbum.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __OT_ORDERED_HASH_H__
#define __OT_ORDERED_HASH_H__
#include <gio/gio.h>
G_BEGIN_DECLS
typedef struct {
GPtrArray *order;
GHashTable *table;
} OtOrderedHash;
OtOrderedHash *ot_ordered_hash_new (void);
void ot_ordered_hash_free (OtOrderedHash *ohash);
void ot_ordered_hash_cleanup (void *loc);
void ot_ordered_hash_replace_key_take (OtOrderedHash *ohash,
char *key,
const char *value);
void ot_ordered_hash_replace_key (OtOrderedHash *ohash,
const char *key,
const char *val);
G_END_DECLS
#endif /* __OT_ORDERED_HASH_H__ */

View File

@ -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]);

View File

@ -1,337 +0,0 @@
/* -*- c-file-style: "gnu" -*-
* Switch to new root directory and start init.
*
* Copyright 2011,2012 Colin Walters <walters@verbum.org>
*
* Based on code from util-linux/sys-utils/switch_root.c,
* Copyright 2002-2009 Red Hat, Inc. All rights reserved.
* Authors:
* Peter Jones <pjones@redhat.com>
* Jeremy Katz <katzj@redhat.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#define _GNU_SOURCE
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <fcntl.h>
#include <assert.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <dirent.h>
#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);
}

View File

@ -34,10 +34,18 @@ assert_streq () {
test "$1" = "$2" || (echo 1>&2 "$1 != $2"; exit 1)
}
assert_not_streq () {
(! test "$1" = "$2") || (echo 1>&2 "$1 == $2"; exit 1)
}
assert_has_file () {
test -f "$1" || (echo 1>&2 "Couldn't find '$1'"; exit 1)
}
assert_has_dir () {
test -d "$1" || (echo 1>&2 "Couldn't find '$1'"; exit 1)
}
assert_not_has_file () {
if test -f "$1"; then
echo 1>&2 "File '$1' exists"; exit 1
@ -50,6 +58,12 @@ assert_not_file_has_content () {
fi
}
assert_not_has_dir () {
if test -d "$1"; then
echo 1>&2 "Directory '$1' exists"; exit 1
fi
}
assert_file_has_content () {
if ! grep -q -e "$2" "$1"; then
echo 1>&2 "File '$1' doesn't match regexp '$2'"; exit 1
@ -136,3 +150,86 @@ setup_fake_remote_repo1() {
export OSTREE="ostree --repo=repo"
}
setup_os_repository () {
mode=$1
shift
oldpwd=`pwd`
cd ${test_tmpdir}
mkdir testos-repo
if test -n "$mode"; then
ostree --repo=testos-repo init --mode=${mode}
else
ostree --repo=testos-repo init
fi
cd ${test_tmpdir}
mkdir osdata
cd osdata
mkdir -p boot usr/bin usr/lib/modules/3.6.0 usr/share usr/etc
echo "a kernel" > boot/vmlinuz-3.6.0
echo "an initramfs" > boot/initramfs-3.6.0
echo "a kernel module" > usr/lib/modules/3.6.0/foofs.ko
bootcsum=$(cat boot/vmlinuz-3-6.0 boot/initramfs-3.6.0 usr/lib/modules/3.6.0/foofs.ko | sha256sum | cut -f 1 -d ' ')
export bootcsum
mv boot/vmlinuz-3.6.0 boot/vmlinuz-3.6.0-${bootcsum}
mv boot/initramfs-3.6.0 boot/initramfs-3.6.0-${bootcsum}
echo "an executable" > usr/bin/sh
echo "some shared data" > usr/share/langs.txt
echo "a library" > usr/lib/libfoo.so.0
ln -s usr/bin bin
cat > usr/etc/os-release <<EOF
NAME=TestOS
VERSION=42
ID=testos
VERSION_ID=42
PRETTY_NAME="TestOS 42"
EOF
echo "a config file" > usr/etc/aconfigfile
mkdir -p usr/etc/NetworkManager
echo "a default daemon file" > usr/etc/NetworkManager/nm.conf
ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Build"
echo "a new executable" > usr/bin/sh
ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Build"
ostree --repo=${test_tmpdir}/testos-repo fsck -q
cd ${test_tmpdir}
mkdir sysroot
ostree admin --sysroot=sysroot init-fs sysroot
ostree admin --sysroot=sysroot os-init testos
# Stub syslinux configuration
mkdir -p sysroot/boot/loader.0
ln -s loader.0 sysroot/boot/loader
touch sysroot/boot/loader/syslinux.cfg
# And a compatibility symlink
mkdir -p sysroot/boot/syslinux
ln -s ../loader/syslinux.cfg sysroot/boot/syslinux/syslinux.cfg
}
os_repository_new_commit ()
{
cd ${test_tmpdir}/osdata
rm boot/*
echo "new: a kernel" > boot/vmlinuz-3.6.0
echo "new: an initramfs" > boot/initramfs-3.6.0
echo "new: a kernel module" > usr/lib/modules/3.6.0/foofs.ko
echo "new: another kernel module" > usr/lib/modules/3.6.0/othermod.ko
bootcsum=$(cat boot/vmlinuz-3.6.0 boot/initramfs-3.6.0 usr/lib/modules/3.6.0/foofs.ko usr/lib/modules/3.6.0/othermod.ko | sha256sum | cut -f 1 -d ' ')
export bootcsum
mv boot/vmlinuz-3.6.0 boot/vmlinuz-3.6.0-${bootcsum}
mv boot/initramfs-3.6.0 boot/initramfs-3.6.0-${bootcsum}
echo "a new default config file" > usr/etc/a-new-default-config-file
mkdir -p usr/etc/new-default-dir
echo "a new default dir and file" > usr/etc/new-default-dir/moo
ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Build"
cd ${test_tmpdir}
}

134
tests/t0015-admin-deploy.sh Executable file
View File

@ -0,0 +1,134 @@
#!/bin/bash
#
# Copyright (C) 2011 Colin Walters <walters@verbum.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
set -e
. $(dirname $0)/libtest.sh
echo "1..1"
setup_os_repository "archive-z2"
echo "ok setup"
echo "1..7"
ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime
rev=$(ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime)
export rev
# This initial deployment gets kicked off with some kernel arguments
ostree admin --sysroot=sysroot deploy --karg=root=LABEL=MOO --karg=quiet --os=testos testos:testos/buildmaster/x86_64-runtime
echo "ok deploy command"
assert_not_has_dir sysroot/boot/loader.0
assert_has_dir sysroot/boot/loader.1
assert_has_dir sysroot/ostree/boot.1.1
assert_has_file sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf
assert_file_has_content sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf 'options.* root=LABEL=MOO'
assert_file_has_content sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf 'options.* quiet'
assert_file_has_content sysroot/boot/ostree/testos-${bootcsum}/vmlinuz-3.6.0 'a kernel'
assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.0/etc/os-release 'NAME=TestOS'
assert_file_has_content sysroot/ostree/boot.1/testos/${bootcsum}/0/etc/os-release 'NAME=TestOS'
echo "ok layout"
ostree admin --sysroot=sysroot deploy --os=testos testos:testos/buildmaster/x86_64-runtime
# Need a new bootversion, sine we now have two deployments
assert_has_dir sysroot/boot/loader.0
assert_not_has_dir sysroot/boot/loader.1
assert_has_dir sysroot/ostree/boot.0.1
assert_not_has_dir sysroot/ostree/boot.0.0
assert_not_has_dir sysroot/ostree/boot.1.0
assert_not_has_dir sysroot/ostree/boot.1.1
# Ensure we propagated kernel arguments from previous deployment
assert_file_has_content sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf 'options.* root=LABEL=MOO'
assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.1/etc/os-release 'NAME=TestOS'
assert_file_has_content sysroot/ostree/boot.0/testos/${bootcsum}/0/etc/os-release 'NAME=TestOS'
echo "ok second deploy"
ostree admin --sysroot=sysroot deploy --os=testos testos:testos/buildmaster/x86_64-runtime
# Keep the same bootversion
assert_has_dir sysroot/boot/loader.0
assert_not_has_dir sysroot/boot/loader.1
# But swap subbootversion
assert_has_dir sysroot/ostree/boot.0.0
assert_not_has_dir sysroot/ostree/boot.0.1
echo "ok third deploy (swap)"
ostree admin --sysroot=sysroot deploy --os=otheros testos/buildmaster/x86_64-runtime
assert_not_has_dir sysroot/boot/loader.0
assert_has_dir sysroot/boot/loader.1
assert_has_file sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf
assert_has_file sysroot/boot/loader/entries/ostree-otheros-${rev}-0.conf
assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.1/etc/os-release 'NAME=TestOS'
assert_file_has_content sysroot/ostree/deploy/otheros/deploy/${rev}.0/etc/os-release 'NAME=TestOS'
echo "ok independent deploy"
ostree admin --sysroot=sysroot deploy --retain --os=testos testos:testos/buildmaster/x86_64-runtime
assert_has_dir sysroot/boot/loader.0
assert_not_has_dir sysroot/boot/loader.1
assert_has_file sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf
assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.2/etc/os-release 'NAME=TestOS'
assert_has_file sysroot/boot/loader/entries/ostree-testos-${rev}-2.conf
assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/os-release 'NAME=TestOS'
echo "ok fourth deploy (retain)"
echo "a new local config file" > sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/a-new-config-file
rm sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/aconfigfile
ln -s /ENOENT sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/a-new-broken-symlink
ostree admin --sysroot=sysroot deploy --retain --os=testos testos:testos/buildmaster/x86_64-runtime
linktarget=$(readlink sysroot/ostree/deploy/testos/deploy/${rev}.4/etc/a-new-broken-symlink)
test "${linktarget}" = /ENOENT
assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/os-release 'NAME=TestOS'
assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.4/etc/os-release 'NAME=TestOS'
assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.4/etc/a-new-config-file 'a new local config file'
assert_not_has_file sysroot/ostree/deploy/testos/deploy/${rev}.4/etc/aconfigfile
echo "ok deploy with modified /etc"
os_repository_new_commit
ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime
newrev=$(ostree --repo=sysroot/ostree/repo rev-parse testos:testos/buildmaster/x86_64-runtime)
export newrev
assert_not_streq ${rev} ${newrev}
ostree admin --sysroot=sysroot deploy --os=testos testos:testos/buildmaster/x86_64-runtime
assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/os-release 'NAME=TestOS'
# New files in /usr/etc
assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/a-new-default-config-file "a new default config file"
assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/new-default-dir/moo "a new default dir and file"
# And persist /etc changes from before
assert_not_has_file sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/aconfigfile
echo "ok upgrade bare"
os_repository_new_commit
ostree --repo=sysroot/ostree/repo remote add testos file://$(pwd)/testos-repo testos/buildmaster/x86_64-runtime
ostree admin --sysroot=sysroot upgrade --os=testos
rev=${newrev}
newrev=$(ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime)
assert_not_streq ${rev} ${newrev}
assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/os-release 'NAME=TestOS'
echo "ok upgrade"