This commit changes the sysroot API so that one can create arbitrary new deployment checkouts, then commit them as one step. This is to enable things like an automatic bisection tool which say create 50 deployments at once, then when done clean them up. This also moves some printfs from the library into src/ostree.
1160 lines
40 KiB
C
1160 lines
40 KiB
C
/* -*- 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 "ostree-sysroot-private.h"
|
|
#include "otutil.h"
|
|
#include "libgsystem.h"
|
|
|
|
/**
|
|
* copy_one_config_file:
|
|
*
|
|
* Copy @file from @modified_etc to @new_etc, overwriting any existing
|
|
* file there.
|
|
*/
|
|
static gboolean
|
|
copy_one_config_file (GFile *orig_etc,
|
|
GFile *modified_etc,
|
|
GFile *new_etc,
|
|
GFile *src,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
gs_unref_object GFileInfo *src_info = NULL;
|
|
gs_unref_object GFile *dest = NULL;
|
|
gs_unref_object GFile *parent = NULL;
|
|
gs_free 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)
|
|
{
|
|
gs_unref_object GFileEnumerator *src_enum = NULL;
|
|
gs_unref_object 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)
|
|
{
|
|
gs_unref_object GFile *child = g_file_get_child (src, g_file_info_get_name (child_info));
|
|
|
|
if (!copy_one_config_file (orig_etc, modified_etc, new_etc, child,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
g_clear_object (&child_info);
|
|
if (temp_error != NULL)
|
|
{
|
|
g_propagate_error (error, temp_error);
|
|
goto out;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
parent = g_file_get_parent (dest);
|
|
|
|
/* FIXME actually we need to copy permissions and xattrs */
|
|
if (!gs_file_ensure_directory (parent, TRUE, cancellable, error))
|
|
goto out;
|
|
|
|
/* We unlink here because otherwise gio throws an error on
|
|
* dangling symlinks.
|
|
*/
|
|
if (!ot_gfile_ensure_unlinked (dest, cancellable, error))
|
|
goto out;
|
|
|
|
if (!g_file_copy (src, dest, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA,
|
|
cancellable, NULL, NULL, error))
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* merge_etc_changes:
|
|
*
|
|
* Compute the difference between @orig_etc and @modified_etc,
|
|
* and apply that to @new_etc.
|
|
*
|
|
* The algorithm for computing the difference is pretty simple; it's
|
|
* approximately equivalent to "diff -unR orig_etc modified_etc",
|
|
* except that rather than attempting a 3-way merge if a file is also
|
|
* changed in @new_etc, the modified version always wins.
|
|
*/
|
|
static gboolean
|
|
merge_etc_changes (GFile *orig_etc,
|
|
GFile *modified_etc,
|
|
GFile *new_etc,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
gs_unref_ptrarray GPtrArray *modified = NULL;
|
|
gs_unref_ptrarray GPtrArray *removed = NULL;
|
|
gs_unref_ptrarray 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];
|
|
gs_unref_object GFile *target_file = NULL;
|
|
gs_free char *path = NULL;
|
|
|
|
path = g_file_get_relative_path (orig_etc, file);
|
|
g_assert (path);
|
|
target_file = g_file_resolve_relative_path (new_etc, path);
|
|
|
|
if (!ot_gfile_ensure_unlinked (target_file, cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < modified->len; i++)
|
|
{
|
|
OstreeDiffItem *diff = modified->pdata[i];
|
|
|
|
if (!copy_one_config_file (orig_etc, modified_etc, new_etc, diff->target,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
for (i = 0; i < added->len; i++)
|
|
{
|
|
GFile *file = added->pdata[i];
|
|
|
|
if (!copy_one_config_file (orig_etc, modified_etc, new_etc, file,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* checkout_deployment_tree:
|
|
*
|
|
* Look up @revision in the repository, and check it out in
|
|
* /ostree/deploy/OS/deploy/${treecsum}.${deployserial}.
|
|
*/
|
|
static gboolean
|
|
checkout_deployment_tree (OstreeSysroot *sysroot,
|
|
OstreeRepo *repo,
|
|
OstreeDeployment *deployment,
|
|
GFile **out_deployment_path,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
const char *csum = ostree_deployment_get_csum (deployment);
|
|
gs_unref_object GFile *root = NULL;
|
|
gs_unref_object GFileInfo *file_info = NULL;
|
|
gs_free char *checkout_target_name = NULL;
|
|
gs_unref_object GFile *osdeploy_path = NULL;
|
|
gs_unref_object GFile *deploy_target_path = NULL;
|
|
gs_unref_object GFile *deploy_parent = NULL;
|
|
|
|
if (!ostree_repo_read_commit (repo, csum, &root, NULL, cancellable, error))
|
|
goto out;
|
|
|
|
file_info = g_file_query_info (root, OSTREE_GIO_FAST_QUERYINFO,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable, error);
|
|
if (!file_info)
|
|
goto out;
|
|
|
|
osdeploy_path = ot_gfile_get_child_build_path (sysroot->path, "ostree", "deploy",
|
|
ostree_deployment_get_osname (deployment),
|
|
"deploy", NULL);
|
|
checkout_target_name = g_strdup_printf ("%s.%d", csum, ostree_deployment_get_deployserial (deployment));
|
|
deploy_target_path = g_file_get_child (osdeploy_path, checkout_target_name);
|
|
|
|
deploy_parent = g_file_get_parent (deploy_target_path);
|
|
if (!gs_file_ensure_directory (deploy_parent, TRUE, cancellable, error))
|
|
goto out;
|
|
|
|
g_print ("ostadmin: Creating deployment %s\n",
|
|
gs_file_get_path_cached (deploy_target_path));
|
|
|
|
if (!ostree_repo_checkout_tree (repo, 0, 0, deploy_target_path, OSTREE_REPO_FILE (root),
|
|
file_info, cancellable, error))
|
|
goto out;
|
|
|
|
ret = TRUE;
|
|
ot_transfer_out_value (out_deployment_path, &deploy_target_path);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
merge_configuration (OstreeSysroot *sysroot,
|
|
OstreeDeployment *previous_deployment,
|
|
OstreeDeployment *deployment,
|
|
GFile *deployment_path,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
gs_unref_object GFile *source_etc_path = NULL;
|
|
gs_unref_object GFile *source_etc_pristine_path = NULL;
|
|
gs_unref_object GFile *deployment_usretc_path = NULL;
|
|
gs_unref_object GFile *deployment_etc_path = NULL;
|
|
gboolean etc_exists;
|
|
gboolean usretc_exists;
|
|
|
|
if (previous_deployment)
|
|
{
|
|
gs_unref_object GFile *previous_path = NULL;
|
|
OstreeBootconfigParser *previous_bootconfig;
|
|
|
|
previous_path = ostree_sysroot_get_deployment_directory (sysroot, previous_deployment);
|
|
source_etc_path = g_file_resolve_relative_path (previous_path, "etc");
|
|
source_etc_pristine_path = g_file_resolve_relative_path (previous_path, "usr/etc");
|
|
|
|
previous_bootconfig = ostree_deployment_get_bootconfig (previous_deployment);
|
|
if (previous_bootconfig)
|
|
{
|
|
const char *previous_options = ostree_bootconfig_parser_get (previous_bootconfig, "options");
|
|
/* Completely overwrite the previous options here; we will extend
|
|
* them later.
|
|
*/
|
|
ostree_bootconfig_parser_set (ostree_deployment_get_bootconfig (deployment), "options",
|
|
previous_options);
|
|
}
|
|
}
|
|
|
|
deployment_etc_path = g_file_get_child (deployment_path, "etc");
|
|
deployment_usretc_path = g_file_resolve_relative_path (deployment_path, "usr/etc");
|
|
|
|
etc_exists = g_file_query_exists (deployment_etc_path, NULL);
|
|
usretc_exists = g_file_query_exists (deployment_usretc_path, NULL);
|
|
|
|
if (etc_exists && usretc_exists)
|
|
{
|
|
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
|
"Tree contains both /etc and /usr/etc");
|
|
goto out;
|
|
}
|
|
else if (etc_exists)
|
|
{
|
|
/* Compatibility hack */
|
|
if (!gs_file_rename (deployment_etc_path, deployment_usretc_path,
|
|
cancellable, error))
|
|
goto out;
|
|
usretc_exists = TRUE;
|
|
etc_exists = FALSE;
|
|
}
|
|
|
|
if (usretc_exists)
|
|
{
|
|
g_assert (!etc_exists);
|
|
if (!gs_shutil_cp_a (deployment_usretc_path, deployment_etc_path,
|
|
cancellable, error))
|
|
goto out;
|
|
g_print ("ostadmin: Created %s\n", gs_file_get_path_cached (deployment_etc_path));
|
|
}
|
|
|
|
if (source_etc_path)
|
|
{
|
|
if (!merge_etc_changes (source_etc_pristine_path, source_etc_path, deployment_etc_path,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
else
|
|
{
|
|
g_print ("ostadmin: No previous configuration changes to merge\n");
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
write_origin_file (OstreeSysroot *sysroot,
|
|
OstreeDeployment *deployment,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
GKeyFile *origin = ostree_deployment_get_origin (deployment);
|
|
|
|
if (origin)
|
|
{
|
|
gs_unref_object GFile *deployment_path = ostree_sysroot_get_deployment_directory (sysroot, deployment);
|
|
gs_unref_object GFile *origin_path = ostree_sysroot_get_deployment_origin_path (deployment_path);
|
|
gs_free char *contents = NULL;
|
|
gsize len;
|
|
|
|
contents = g_key_file_to_data (origin, &len, error);
|
|
if (!contents)
|
|
goto out;
|
|
|
|
if (!g_file_replace_contents (origin_path, contents, len, NULL, FALSE,
|
|
G_FILE_CREATE_REPLACE_DESTINATION, NULL,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
get_kernel_from_tree (GFile *deployroot,
|
|
GFile **out_kernel,
|
|
GFile **out_initramfs,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
gs_unref_object GFile *bootdir = g_file_get_child (deployroot, "boot");
|
|
gs_unref_object GFileEnumerator *dir_enum = NULL;
|
|
gs_unref_object GFile *ret_kernel = NULL;
|
|
gs_unref_object GFile *ret_initramfs = NULL;
|
|
gs_free char *kernel_checksum = NULL;
|
|
gs_free char *initramfs_checksum = NULL;
|
|
|
|
dir_enum = g_file_enumerate_children (bootdir, OSTREE_GIO_FAST_QUERYINFO,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
NULL, error);
|
|
if (!dir_enum)
|
|
goto out;
|
|
|
|
while (TRUE)
|
|
{
|
|
GFileInfo *file_info = NULL;
|
|
const char *name;
|
|
|
|
if (!gs_file_enumerator_iterate (dir_enum, &file_info, NULL,
|
|
cancellable, error))
|
|
goto out;
|
|
if (file_info == NULL)
|
|
break;
|
|
|
|
name = g_file_info_get_name (file_info);
|
|
|
|
if (ret_kernel == NULL && g_str_has_prefix (name, "vmlinuz-"))
|
|
{
|
|
const char *dash = strrchr (name, '-');
|
|
g_assert (dash);
|
|
if (ostree_validate_structureof_checksum_string (dash + 1, NULL))
|
|
{
|
|
kernel_checksum = g_strdup (dash + 1);
|
|
ret_kernel = g_file_get_child (bootdir, name);
|
|
}
|
|
}
|
|
else if (ret_initramfs == NULL && g_str_has_prefix (name, "initramfs-"))
|
|
{
|
|
const char *dash = strrchr (name, '-');
|
|
g_assert (dash);
|
|
if (ostree_validate_structureof_checksum_string (dash + 1, NULL))
|
|
{
|
|
initramfs_checksum = g_strdup (dash + 1);
|
|
ret_initramfs = g_file_get_child (bootdir, name);
|
|
}
|
|
}
|
|
|
|
if (ret_kernel && ret_initramfs)
|
|
break;
|
|
}
|
|
|
|
if (ret_kernel == NULL)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
|
"Failed to find boot/vmlinuz-<CHECKSUM> in %s",
|
|
gs_file_get_path_cached (deployroot));
|
|
goto out;
|
|
}
|
|
|
|
if (ret_initramfs != NULL)
|
|
{
|
|
if (strcmp (kernel_checksum, initramfs_checksum) != 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
|
"Mismatched kernel %s checksum vs initrd %s",
|
|
gs_file_get_basename_cached (ret_initramfs),
|
|
gs_file_get_basename_cached (ret_initramfs));
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
ot_transfer_out_value (out_kernel, &ret_kernel);
|
|
ot_transfer_out_value (out_initramfs, &ret_initramfs);
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
checksum_from_kernel_src (GFile *src,
|
|
char **out_checksum,
|
|
GError **error)
|
|
{
|
|
const char *last_dash = strrchr (gs_file_get_path_cached (src), '-');
|
|
if (!last_dash)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Malformed initramfs name '%s', missing '-'", gs_file_get_basename_cached (src));
|
|
return FALSE;
|
|
}
|
|
*out_checksum = g_strdup (last_dash + 1);
|
|
return TRUE;
|
|
}
|
|
|
|
/* FIXME: We should really do individual fdatasync() on files/dirs,
|
|
* since this causes us to block on unrelated I/O. However, it's just
|
|
* safer for now.
|
|
*/
|
|
static gboolean
|
|
full_system_sync (GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
sync ();
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
swap_bootlinks (OstreeSysroot *self,
|
|
int bootversion,
|
|
GPtrArray *new_deployments,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
guint i;
|
|
int old_subbootversion;
|
|
int new_subbootversion;
|
|
gs_unref_object GFile *ostree_dir = g_file_get_child (self->path, "ostree");
|
|
gs_free char *ostree_bootdir_name = g_strdup_printf ("boot.%d", bootversion);
|
|
gs_unref_object GFile *ostree_bootdir = g_file_resolve_relative_path (ostree_dir, ostree_bootdir_name);
|
|
gs_free char *ostree_subbootdir_name = NULL;
|
|
gs_unref_object GFile *ostree_subbootdir = NULL;
|
|
|
|
if (bootversion != self->bootversion)
|
|
{
|
|
if (!_ostree_sysroot_read_current_subbootversion (self, bootversion, &old_subbootversion,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
else
|
|
old_subbootversion = self->subbootversion;
|
|
|
|
new_subbootversion = old_subbootversion == 0 ? 1 : 0;
|
|
|
|
ostree_subbootdir_name = g_strdup_printf ("boot.%d.%d", bootversion, new_subbootversion);
|
|
ostree_subbootdir = g_file_resolve_relative_path (ostree_dir, ostree_subbootdir_name);
|
|
|
|
if (!gs_file_ensure_directory (ostree_subbootdir, TRUE, cancellable, error))
|
|
goto out;
|
|
|
|
for (i = 0; i < new_deployments->len; i++)
|
|
{
|
|
OstreeDeployment *deployment = new_deployments->pdata[i];
|
|
gs_free char *bootlink_pathname = g_strdup_printf ("%s/%s/%d",
|
|
ostree_deployment_get_osname (deployment),
|
|
ostree_deployment_get_bootcsum (deployment),
|
|
ostree_deployment_get_bootserial (deployment));
|
|
gs_free char *bootlink_target = g_strdup_printf ("../../../deploy/%s/deploy/%s.%d",
|
|
ostree_deployment_get_osname (deployment),
|
|
ostree_deployment_get_csum (deployment),
|
|
ostree_deployment_get_deployserial (deployment));
|
|
gs_unref_object GFile *linkname = g_file_get_child (ostree_subbootdir, bootlink_pathname);
|
|
gs_unref_object GFile *linkname_parent = g_file_get_parent (linkname);
|
|
|
|
if (!gs_file_ensure_directory (linkname_parent, TRUE, cancellable, error))
|
|
goto out;
|
|
|
|
if (!g_file_make_symbolic_link (linkname, bootlink_target, cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
if (!ot_gfile_atomic_symlink_swap (ostree_bootdir, ostree_subbootdir_name,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
remove_checksum_from_kernel_name (const char *name,
|
|
const char *csum)
|
|
{
|
|
const char *p = strrchr (name, '-');
|
|
g_assert_cmpstr (p+1, ==, csum);
|
|
return g_strndup (name, p-name);
|
|
}
|
|
|
|
static GHashTable *
|
|
parse_os_release (const char *contents,
|
|
const char *split)
|
|
{
|
|
char **lines = g_strsplit (contents, split, -1);
|
|
char **iter;
|
|
GHashTable *ret = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
|
|
|
for (iter = lines; *iter; iter++)
|
|
{
|
|
char *line = *iter;
|
|
char *eq;
|
|
const char *quotedval;
|
|
char *val;
|
|
|
|
if (g_str_has_prefix (line, "#"))
|
|
continue;
|
|
|
|
eq = strchr (line, '=');
|
|
if (!eq)
|
|
continue;
|
|
|
|
*eq = '\0';
|
|
quotedval = eq + 1;
|
|
val = g_shell_unquote (quotedval, NULL);
|
|
if (!val)
|
|
continue;
|
|
|
|
g_hash_table_insert (ret, line, val);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* install_deployment_kernel:
|
|
*
|
|
* Write out an entry in /boot/loader/entries for @deployment.
|
|
*/
|
|
static gboolean
|
|
install_deployment_kernel (OstreeSysroot *sysroot,
|
|
int new_bootversion,
|
|
OstreeDeployment *deployment,
|
|
guint n_deployments,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
|
|
{
|
|
gboolean ret = FALSE;
|
|
const char *osname = ostree_deployment_get_osname (deployment);
|
|
const char *bootcsum = ostree_deployment_get_bootcsum (deployment);
|
|
gs_unref_object GFile *bootdir = NULL;
|
|
gs_unref_object GFile *bootcsumdir = NULL;
|
|
gs_unref_object GFile *bootconfpath = NULL;
|
|
gs_unref_object GFile *bootconfpath_parent = NULL;
|
|
gs_free char *dest_kernel_name = NULL;
|
|
gs_unref_object GFile *dest_kernel_path = NULL;
|
|
gs_unref_object GFile *dest_initramfs_path = NULL;
|
|
gs_unref_object GFile *tree_kernel_path = NULL;
|
|
gs_unref_object GFile *tree_initramfs_path = NULL;
|
|
gs_unref_object GFile *etc_os_release = NULL;
|
|
gs_unref_object GFile *deployment_dir = NULL;
|
|
gs_free char *contents = NULL;
|
|
gs_unref_hashtable GHashTable *osrelease_values = NULL;
|
|
gs_free char *linux_relpath = NULL;
|
|
gs_free char *linux_key = NULL;
|
|
gs_free char *initramfs_relpath = NULL;
|
|
gs_free char *title_key = NULL;
|
|
gs_free char *initrd_key = NULL;
|
|
gs_free char *version_key = NULL;
|
|
gs_free char *ostree_kernel_arg = NULL;
|
|
gs_free char *options_key = NULL;
|
|
__attribute__((cleanup(_ostree_ordered_hash_cleanup))) OstreeOrderedHash *ohash = NULL;
|
|
const char *val;
|
|
OstreeBootconfigParser *bootconfig;
|
|
gsize len;
|
|
|
|
bootconfig = ostree_deployment_get_bootconfig (deployment);
|
|
deployment_dir = ostree_sysroot_get_deployment_directory (sysroot, deployment);
|
|
|
|
if (!get_kernel_from_tree (deployment_dir, &tree_kernel_path, &tree_initramfs_path,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
bootdir = g_file_get_child (ostree_sysroot_get_path (sysroot), "boot");
|
|
bootcsumdir = ot_gfile_resolve_path_printf (bootdir, "ostree/%s-%s",
|
|
osname,
|
|
bootcsum);
|
|
bootconfpath = ot_gfile_resolve_path_printf (bootdir, "loader.%d/entries/ostree-%s-%d.conf",
|
|
new_bootversion, osname,
|
|
ostree_deployment_get_index (deployment));
|
|
|
|
if (!gs_file_ensure_directory (bootcsumdir, TRUE, cancellable, error))
|
|
goto out;
|
|
bootconfpath_parent = g_file_get_parent (bootconfpath);
|
|
if (!gs_file_ensure_directory (bootconfpath_parent, TRUE, cancellable, error))
|
|
goto out;
|
|
|
|
dest_kernel_name = remove_checksum_from_kernel_name (gs_file_get_basename_cached (tree_kernel_path),
|
|
bootcsum);
|
|
dest_kernel_path = g_file_get_child (bootcsumdir, dest_kernel_name);
|
|
if (!g_file_query_exists (dest_kernel_path, NULL))
|
|
{
|
|
if (!gs_file_linkcopy_sync_data (tree_kernel_path, dest_kernel_path, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
if (tree_initramfs_path)
|
|
{
|
|
gs_free char *dest_initramfs_name = remove_checksum_from_kernel_name (gs_file_get_basename_cached (tree_initramfs_path),
|
|
bootcsum);
|
|
dest_initramfs_path = g_file_get_child (bootcsumdir, dest_initramfs_name);
|
|
|
|
if (!g_file_query_exists (dest_initramfs_path, NULL))
|
|
{
|
|
if (!gs_file_linkcopy_sync_data (tree_initramfs_path, dest_initramfs_path, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
etc_os_release = g_file_resolve_relative_path (deployment_dir, "etc/os-release");
|
|
|
|
if (!g_file_load_contents (etc_os_release, cancellable,
|
|
&contents, &len, NULL, error))
|
|
{
|
|
g_prefix_error (error, "Reading /etc/os-release: ");
|
|
goto out;
|
|
}
|
|
|
|
osrelease_values = parse_os_release (contents, "\n");
|
|
|
|
/* title */
|
|
val = g_hash_table_lookup (osrelease_values, "PRETTY_NAME");
|
|
if (val == NULL)
|
|
val = g_hash_table_lookup (osrelease_values, "ID");
|
|
if (val == NULL)
|
|
{
|
|
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"No PRETTY_NAME or ID in /etc/os-release");
|
|
goto out;
|
|
}
|
|
|
|
title_key = g_strdup_printf ("ostree:%s:%d %s", ostree_deployment_get_osname (deployment),
|
|
ostree_deployment_get_index (deployment),
|
|
val);
|
|
ostree_bootconfig_parser_set (bootconfig, "title", title_key);
|
|
|
|
version_key = g_strdup_printf ("%d", n_deployments - ostree_deployment_get_index (deployment));
|
|
ostree_bootconfig_parser_set (bootconfig, "version", version_key);
|
|
|
|
linux_relpath = g_file_get_relative_path (bootdir, dest_kernel_path);
|
|
linux_key = g_strconcat ("/", linux_relpath, NULL);
|
|
ostree_bootconfig_parser_set (bootconfig, "linux", linux_key);
|
|
|
|
if (dest_initramfs_path)
|
|
{
|
|
initramfs_relpath = g_file_get_relative_path (bootdir, dest_initramfs_path);
|
|
initrd_key = g_strconcat ("/", initramfs_relpath, NULL);
|
|
ostree_bootconfig_parser_set (bootconfig, "initrd", initrd_key);
|
|
}
|
|
|
|
val = ostree_bootconfig_parser_get (bootconfig, "options");
|
|
ostree_kernel_arg = g_strdup_printf ("/ostree/boot.%d/%s/%s/%d",
|
|
new_bootversion, osname, bootcsum,
|
|
ostree_deployment_get_bootserial (deployment));
|
|
ohash = _ostree_sysroot_parse_kernel_args (val);
|
|
_ostree_ordered_hash_replace_key (ohash, "ostree", ostree_kernel_arg);
|
|
options_key = _ostree_sysroot_kernel_arg_string_serialize (ohash);
|
|
ostree_bootconfig_parser_set (bootconfig, "options", options_key);
|
|
|
|
if (!ostree_bootconfig_parser_write (ostree_deployment_get_bootconfig (deployment), bootconfpath,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
swap_bootloader (OstreeSysroot *sysroot,
|
|
int current_bootversion,
|
|
int new_bootversion,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
gs_unref_object GFile *boot_loader_link = NULL;
|
|
gs_free char *new_target = NULL;
|
|
|
|
g_assert ((current_bootversion == 0 && new_bootversion == 1) ||
|
|
(current_bootversion == 1 && new_bootversion == 0));
|
|
|
|
boot_loader_link = g_file_resolve_relative_path (sysroot->path, "boot/loader");
|
|
new_target = g_strdup_printf ("loader.%d", new_bootversion);
|
|
|
|
if (!ot_gfile_atomic_symlink_swap (boot_loader_link, new_target,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static GHashTable *
|
|
bootcsum_counts_for_deployment_list (GPtrArray *deployments,
|
|
gboolean set_bootserial)
|
|
{
|
|
guint i;
|
|
GHashTable *ret =
|
|
g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
|
|
|
|
for (i = 0; i < deployments->len; i++)
|
|
{
|
|
OstreeDeployment *deployment = deployments->pdata[i];
|
|
const char *bootcsum = ostree_deployment_get_bootcsum (deployment);
|
|
guint count;
|
|
|
|
count = GPOINTER_TO_UINT (g_hash_table_lookup (ret, bootcsum));
|
|
g_hash_table_replace (ret, (char*)bootcsum, GUINT_TO_POINTER (count + 1));
|
|
|
|
if (set_bootserial)
|
|
ostree_deployment_set_bootserial (deployment, count);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* TEMPORARY HACK: Add a "current" symbolic link that's easy to
|
|
* follow inside the gnome-ostree build scripts. This isn't atomic,
|
|
* but that doesn't matter because it's only used by deployments
|
|
* done from the host.
|
|
*/
|
|
static gboolean
|
|
create_current_symlinks (OstreeSysroot *self,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
guint i;
|
|
gs_unref_hashtable GHashTable *created_current_for_osname =
|
|
g_hash_table_new (g_str_hash, g_str_equal);
|
|
|
|
for (i = 0; i < self->deployments->len; i++)
|
|
{
|
|
OstreeDeployment *deployment = self->deployments->pdata[i];
|
|
const char *osname = ostree_deployment_get_osname (deployment);
|
|
|
|
if (!g_hash_table_lookup (created_current_for_osname, osname))
|
|
{
|
|
gs_unref_object GFile *osdir = ot_gfile_resolve_path_printf (self->path, "ostree/deploy/%s", osname);
|
|
gs_unref_object GFile *os_current_path = g_file_get_child (osdir, "current");
|
|
gs_unref_object GFile *deployment_path = ostree_sysroot_get_deployment_directory (self, deployment);
|
|
gs_free char *target = g_file_get_relative_path (osdir, deployment_path);
|
|
|
|
g_assert (target != NULL);
|
|
|
|
if (!ot_gfile_atomic_symlink_swap (os_current_path, target,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
g_hash_table_insert (created_current_for_osname, (char*)osname, GUINT_TO_POINTER (1));
|
|
}
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ostree_sysroot_write_deployments:
|
|
* @self: Sysroot
|
|
* @new_deployments: (element-type OstreeDeployment): List of new deployments
|
|
* @cancellable: Cancellable
|
|
* @error: Error
|
|
*
|
|
* Assuming @new_deployments have already been deployed in place on
|
|
* disk, atomically update bootloader configuration.
|
|
*/
|
|
gboolean
|
|
ostree_sysroot_write_deployments (OstreeSysroot *self,
|
|
GPtrArray *new_deployments,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
guint i;
|
|
gboolean requires_new_bootversion = FALSE;
|
|
gboolean found_booted_deployment = FALSE;
|
|
gs_unref_hashtable GHashTable *new_bootcsum_to_count = NULL;
|
|
|
|
g_assert (self->loaded);
|
|
|
|
/* Calculate the total number of deployments per bootcsums; while we
|
|
* are doing this, assign a bootserial to each new deployment.
|
|
*/
|
|
new_bootcsum_to_count = bootcsum_counts_for_deployment_list (new_deployments, TRUE);
|
|
|
|
/* Determine whether or not we need to touch the bootloader
|
|
* configuration. If we have an equal number of deployments and
|
|
* more strongly an equal number of deployments per bootcsum, then
|
|
* we can just swap the subbootversion bootlinks.
|
|
*/
|
|
if (new_deployments->len != self->deployments->len)
|
|
requires_new_bootversion = TRUE;
|
|
else
|
|
{
|
|
GHashTableIter hashiter;
|
|
gpointer hkey, hvalue;
|
|
gs_unref_hashtable GHashTable *orig_bootcsum_to_count
|
|
= bootcsum_counts_for_deployment_list (self->deployments, FALSE);
|
|
|
|
g_hash_table_iter_init (&hashiter, orig_bootcsum_to_count);
|
|
while (g_hash_table_iter_next (&hashiter, &hkey, &hvalue))
|
|
{
|
|
guint orig_count = GPOINTER_TO_UINT (hvalue);
|
|
gpointer new_countp = g_hash_table_lookup (new_bootcsum_to_count, hkey);
|
|
guint new_count = GPOINTER_TO_UINT (new_countp);
|
|
|
|
if (orig_count != new_count)
|
|
{
|
|
requires_new_bootversion = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < new_deployments->len; i++)
|
|
{
|
|
OstreeDeployment *deployment = new_deployments->pdata[i];
|
|
|
|
if (deployment == self->booted_deployment)
|
|
found_booted_deployment = TRUE;
|
|
|
|
ostree_deployment_set_index (deployment, i);
|
|
}
|
|
|
|
if (self->booted_deployment && !found_booted_deployment)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Attempting to remove booted deployment");
|
|
goto out;
|
|
}
|
|
|
|
if (!requires_new_bootversion)
|
|
{
|
|
if (!full_system_sync (cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Full sync: ");
|
|
goto out;
|
|
}
|
|
|
|
if (!swap_bootlinks (self, self->bootversion,
|
|
new_deployments,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Swapping current bootlinks: ");
|
|
goto out;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int new_bootversion = self->bootversion ? 0 : 1;
|
|
gs_unref_object OstreeBootloader *bootloader = _ostree_sysroot_query_bootloader (self);
|
|
|
|
if (bootloader)
|
|
g_print ("Detected bootloader: %s\n", _ostree_bootloader_get_name (bootloader));
|
|
else
|
|
g_print ("Detected bootloader: (unknown)\n");
|
|
|
|
for (i = 0; i < new_deployments->len; i++)
|
|
{
|
|
OstreeDeployment *deployment = new_deployments->pdata[i];
|
|
if (!install_deployment_kernel (self, new_bootversion,
|
|
deployment, new_deployments->len,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Installing kernel: ");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* Swap bootlinks for *new* version */
|
|
if (!swap_bootlinks (self, new_bootversion, new_deployments,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Generating new bootlinks: ");
|
|
goto out;
|
|
}
|
|
|
|
if (bootloader && !_ostree_bootloader_write_config (bootloader, new_bootversion,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Bootloader write config: ");
|
|
goto out;
|
|
}
|
|
|
|
if (!full_system_sync (cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Full sync: ");
|
|
goto out;
|
|
}
|
|
|
|
if (!swap_bootloader (self, self->bootversion, new_bootversion,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Final bootloader swap: ");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
g_print ("Transaction complete, performing cleanup\n");
|
|
|
|
/* Now reload from disk */
|
|
if (!ostree_sysroot_load (self, cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Reloading deployments after commit: ");
|
|
goto out;
|
|
}
|
|
|
|
if (!create_current_symlinks (self, cancellable, error))
|
|
goto out;
|
|
|
|
/* And finally, cleanup of any leftover data.
|
|
*/
|
|
if (!ostree_sysroot_cleanup (self, cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Performing final cleanup: ");
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
allocate_deployserial (OstreeSysroot *self,
|
|
const char *osname,
|
|
const char *revision,
|
|
int *out_deployserial,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
guint i;
|
|
int new_deployserial = 0;
|
|
gs_unref_object GFile *osdir = NULL;
|
|
gs_unref_ptrarray GPtrArray *tmp_current_deployments =
|
|
g_ptr_array_new_with_free_func (g_object_unref);
|
|
|
|
osdir = ot_gfile_get_child_build_path (self->path, "ostree/deploy", osname, NULL);
|
|
|
|
if (!_ostree_sysroot_list_deployment_dirs_for_os (osdir, tmp_current_deployments,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
for (i = 0; i < tmp_current_deployments->len; i++)
|
|
{
|
|
OstreeDeployment *deployment = tmp_current_deployments->pdata[i];
|
|
|
|
if (strcmp (ostree_deployment_get_osname (deployment), osname) != 0)
|
|
continue;
|
|
if (strcmp (ostree_deployment_get_csum (deployment), revision) != 0)
|
|
continue;
|
|
|
|
new_deployserial = MAX(new_deployserial, ostree_deployment_get_deployserial (deployment)+1);
|
|
}
|
|
|
|
ret = TRUE;
|
|
*out_deployserial = new_deployserial;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ostree_sysroot_deploy_one_tree:
|
|
* @self: Sysroot
|
|
* @osname: (allow-none): osname to use for merge deployment
|
|
* @revision: Checksum to add
|
|
* @origin: (allow-none): Origin to use for upgrades
|
|
* @add_kernel_argv: (allow-none): Append these arguments to kernel configuration
|
|
* @provided_merge_deployment: (allow-none): Use this deployment for merge path
|
|
* @out_new_deployment: (out): The new deployment path
|
|
* @cancellable: Cancellable
|
|
* @error: Error
|
|
*
|
|
* Check out deployment tree with revision @revision, performing a 3
|
|
* way merge with @provided_merge_deployment for configuration.
|
|
*/
|
|
gboolean
|
|
ostree_sysroot_deploy_one_tree (OstreeSysroot *self,
|
|
const char *osname,
|
|
const char *revision,
|
|
GKeyFile *origin,
|
|
char **add_kernel_argv,
|
|
OstreeDeployment *provided_merge_deployment,
|
|
OstreeDeployment **out_new_deployment,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
gint new_deployserial;
|
|
gs_unref_object OstreeDeployment *new_deployment = NULL;
|
|
gs_unref_object OstreeDeployment *merge_deployment = NULL;
|
|
gs_unref_object OstreeRepo *repo = NULL;
|
|
gs_unref_object GFile *commit_root = NULL;
|
|
gs_unref_object GFile *tree_kernel_path = NULL;
|
|
gs_unref_object GFile *tree_initramfs_path = NULL;
|
|
gs_unref_object GFile *new_deployment_path = NULL;
|
|
gs_free char *new_bootcsum = NULL;
|
|
gs_unref_object OstreeBootconfigParser *bootconfig = NULL;
|
|
|
|
g_return_val_if_fail (osname != NULL || self->booted_deployment != NULL, FALSE);
|
|
|
|
if (osname == NULL)
|
|
osname = ostree_deployment_get_osname (self->booted_deployment);
|
|
|
|
if (!ostree_sysroot_get_repo (self, &repo, cancellable, error))
|
|
goto out;
|
|
|
|
if (!ostree_repo_read_commit (repo, revision, &commit_root, NULL, cancellable, error))
|
|
goto out;
|
|
|
|
if (!get_kernel_from_tree (commit_root, &tree_kernel_path, &tree_initramfs_path,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
if (tree_initramfs_path != NULL)
|
|
{
|
|
if (!checksum_from_kernel_src (tree_initramfs_path, &new_bootcsum, error))
|
|
goto out;
|
|
}
|
|
else
|
|
{
|
|
if (!checksum_from_kernel_src (tree_kernel_path, &new_bootcsum, error))
|
|
goto out;
|
|
}
|
|
|
|
if (provided_merge_deployment != NULL)
|
|
merge_deployment = g_object_ref (provided_merge_deployment);
|
|
|
|
if (!allocate_deployserial (self, osname, revision, &new_deployserial,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
new_deployment = ostree_deployment_new (0, osname, revision, new_deployserial,
|
|
new_bootcsum, -1);
|
|
ostree_deployment_set_origin (new_deployment, origin);
|
|
|
|
/* Check out the userspace tree onto the filesystem */
|
|
if (!checkout_deployment_tree (self, repo, new_deployment, &new_deployment_path,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Checking out tree: ");
|
|
goto out;
|
|
}
|
|
|
|
if (!write_origin_file (self, new_deployment, cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Writing out origin file: ");
|
|
goto out;
|
|
}
|
|
|
|
/* Create an empty boot configuration; we will merge things into
|
|
* it as we go.
|
|
*/
|
|
bootconfig = ostree_bootconfig_parser_new ();
|
|
ostree_deployment_set_bootconfig (new_deployment, bootconfig);
|
|
|
|
if (!merge_configuration (self, merge_deployment, new_deployment,
|
|
new_deployment_path,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "During /etc merge: ");
|
|
goto out;
|
|
}
|
|
|
|
/* We have inherited kernel arguments from the previous deployment;
|
|
* now, override/extend that with arguments provided by the command
|
|
* line.
|
|
*
|
|
* After this, install_deployment_kernel() will set the other boot
|
|
* options and write it out to disk.
|
|
*/
|
|
if (add_kernel_argv)
|
|
{
|
|
char **strviter;
|
|
__attribute__((cleanup(_ostree_ordered_hash_cleanup))) OstreeOrderedHash *ohash = NULL;
|
|
gs_free char *new_options = NULL;
|
|
|
|
ohash = _ostree_sysroot_parse_kernel_args (ostree_bootconfig_parser_get (bootconfig, "options"));
|
|
|
|
for (strviter = add_kernel_argv; *strviter; strviter++)
|
|
{
|
|
char *karg = g_strdup (*strviter);
|
|
const char *val = _ostree_sysroot_split_keyeq (karg);
|
|
|
|
_ostree_ordered_hash_replace_key_take (ohash, karg, val);
|
|
}
|
|
|
|
new_options = _ostree_sysroot_kernel_arg_string_serialize (ohash);
|
|
ostree_bootconfig_parser_set (bootconfig, "options", new_options);
|
|
}
|
|
|
|
ret = TRUE;
|
|
ot_transfer_out_value (out_new_deployment, &new_deployment);
|
|
out:
|
|
return ret;
|
|
}
|
|
|