Make /etc a writable mount
First, move deployments to /ostree/deploy. Having them in the toplevel clutters the filesystem layout too much. When we deploy a tree like /ostree/deploy/NAME, there is now also a writable directory /ostree/deploy/NAME-etc. This is mounted as read-write inside the system. On an initial install, that directory is copied from /ostree/deploy/NAME/etc. On subsequent deployments, we find any changes made in the current deployment's /etc, and apply that set of changes to the new deployment's /etc. See https://live.gnome.org/OSTree/EverythingInEtcIsABug
This commit is contained in:
parent
9ba968a5f4
commit
2ddbeb1f71
|
|
@ -22,8 +22,11 @@ endif
|
|||
ostadmin_SOURCES = src/ostadmin/main.c \
|
||||
src/ostadmin/ot-admin-builtins.h \
|
||||
src/ostadmin/ot-admin-builtin-init.c \
|
||||
src/ostadmin/ot-admin-builtin-diff.c \
|
||||
src/ostadmin/ot-admin-builtin-deploy.c \
|
||||
src/ostadmin/ot-admin-builtin-update-kernel.c \
|
||||
src/ostadmin/ot-admin-functions.h \
|
||||
src/ostadmin/ot-admin-functions.c \
|
||||
src/ostadmin/ot-admin-main.h \
|
||||
src/ostadmin/ot-admin-main.c \
|
||||
$(NULL)
|
||||
|
|
|
|||
|
|
@ -97,14 +97,14 @@ diff_item_new (GFile *a,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
diff_files (GFile *a,
|
||||
GFileInfo *a_info,
|
||||
GFile *b,
|
||||
GFileInfo *b_info,
|
||||
OstreeDiffItem **out_item,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
static gboolean
|
||||
diff_files (GFile *a,
|
||||
GFileInfo *a_info,
|
||||
GFile *b,
|
||||
GFileInfo *b_info,
|
||||
OstreeDiffItem **out_item,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
ot_lfree char *checksum_a = NULL;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
static OtAdminBuiltin builtins[] = {
|
||||
{ "deploy", ot_admin_builtin_deploy, 0 },
|
||||
{ "diff", ot_admin_builtin_diff, 0 },
|
||||
{ "init", ot_admin_builtin_init, 0 },
|
||||
{ "update-kernel", ot_admin_builtin_update_kernel, 0 },
|
||||
{ NULL }
|
||||
|
|
|
|||
|
|
@ -23,96 +23,481 @@
|
|||
#include "config.h"
|
||||
|
||||
#include "ot-admin-builtins.h"
|
||||
#include "ot-admin-functions.h"
|
||||
#include "ostree.h"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
typedef struct {
|
||||
OstreeRepo *repo;
|
||||
GFile *ostree_dir;
|
||||
} OtAdminDeploy;
|
||||
|
||||
static gboolean opt_no_kernel;
|
||||
static gboolean opt_force;
|
||||
static char *opt_ostree_dir = "/ostree";
|
||||
|
||||
static GOptionEntry options[] = {
|
||||
{ "ostree-dir", 0, 0, G_OPTION_ARG_STRING, &opt_ostree_dir, "Path to OSTree root directory (default: /ostree)", NULL },
|
||||
{ "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 },
|
||||
{ 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.
|
||||
*
|
||||
* Unless the new-current equals current, in which case, do nothing.
|
||||
*/
|
||||
static gboolean
|
||||
update_current (const char *deploy_target,
|
||||
update_current (OtAdminDeploy *self,
|
||||
GFile *current_deployment,
|
||||
GFile *deploy_target,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
ot_lfree char *tmp_symlink = NULL;
|
||||
ot_lfree char *current_name = NULL;
|
||||
ot_lobj GFile *current_path = NULL;
|
||||
ot_lobj GFile *previous_path = NULL;
|
||||
ot_lobj GFile *tmp_current_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_previous = NULL;
|
||||
|
||||
tmp_symlink = g_build_filename (opt_ostree_dir, "tmp-current", NULL);
|
||||
(void) unlink (tmp_symlink);
|
||||
current_path = g_file_get_child (self->ostree_dir, "current");
|
||||
previous_path = g_file_get_child (self->ostree_dir, "previous");
|
||||
|
||||
if (symlink (deploy_target, tmp_symlink) < 0)
|
||||
relative_current = g_file_get_relative_path (self->ostree_dir, deploy_target);
|
||||
g_assert (relative_current);
|
||||
|
||||
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", ot_gfile_get_path_cached (current_path),
|
||||
relative_current);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
tmp_previous_path = g_file_get_child (self->ostree_dir, "tmp-previous");
|
||||
(void) ot_gfile_unlink (tmp_previous_path, NULL, NULL);
|
||||
|
||||
relative_previous = g_file_get_relative_path (self->ostree_dir, current_deployment);
|
||||
g_assert (relative_previous);
|
||||
if (symlink (relative_previous, ot_gfile_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->ostree_dir, "tmp-current");
|
||||
(void) ot_gfile_unlink (tmp_current_path, NULL, NULL);
|
||||
|
||||
if (symlink (relative_current, ot_gfile_get_path_cached (tmp_current_path)) < 0)
|
||||
{
|
||||
ot_util_set_error_from_errno (error, errno);
|
||||
goto out;
|
||||
}
|
||||
|
||||
current_name = g_build_filename (opt_ostree_dir, "current", NULL);
|
||||
if (rename (tmp_symlink, current_name) < 0)
|
||||
if (!ot_gfile_rename (tmp_current_path, current_path,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (tmp_previous_path)
|
||||
{
|
||||
ot_util_set_error_from_errno (error, errno);
|
||||
goto out;
|
||||
if (!ot_gfile_rename (tmp_previous_path, previous_path,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_print ("%s set to %s\n", current_name, deploy_target);
|
||||
g_print ("ostadmin: %s set to %s\n", ot_gfile_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 ot_gfile_unlink(), but return successfully if the file doesn't
|
||||
* exist.
|
||||
*/
|
||||
static gboolean
|
||||
do_checkout (OtAdminDeploy *self,
|
||||
ensure_unlinked (GFile *path,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
GError *temp_error = NULL;
|
||||
|
||||
if (!ot_gfile_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 *modified_etc,
|
||||
GFile *new_etc,
|
||||
GFile *file,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
ot_lobj GFile *target_file = NULL;
|
||||
ot_lobj GFile *target_parent = NULL;
|
||||
ot_lfree char *path = NULL;
|
||||
|
||||
path = g_file_get_relative_path (modified_etc, file);
|
||||
g_assert (path);
|
||||
target_file = g_file_resolve_relative_path (new_etc, path);
|
||||
target_parent = g_file_get_parent (target_file);
|
||||
|
||||
/* FIXME actually we need to copy permissions and xattrs */
|
||||
if (!ot_gfile_ensure_directory (target_parent, TRUE, error))
|
||||
goto out;
|
||||
|
||||
if (!g_file_copy (file, target_file, 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) g_object_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, modified_etc, new_etc, diff->src,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
for (i = 0; i < added->len; i++)
|
||||
{
|
||||
GFile *file = added->pdata[i];
|
||||
if (!copy_one_config_file (self, 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/DEPLOY_TARGET.
|
||||
*
|
||||
* Merge configuration changes from the old deployment, if any.
|
||||
*
|
||||
* Update the OSTREE_DIR/current and OSTREE_DIR/previous symbolic
|
||||
* links.
|
||||
*/
|
||||
static gboolean
|
||||
deploy_tree (OtAdminDeploy *self,
|
||||
const char *deploy_target,
|
||||
const char *revision,
|
||||
GFile **out_deploy_dir,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
ot_lobj GFile *deploy_path = NULL;
|
||||
ot_lobj GFile *deploy_dir = NULL;
|
||||
ot_lfree char *deploy_target_fullname = NULL;
|
||||
ot_lfree char *deploy_target_fullname_tmp = NULL;
|
||||
ot_lobj GFile *deploy_target_path = 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_lfree char *tree_ref = NULL;
|
||||
ot_lfree char *repo_path = NULL;
|
||||
ot_lfree char *repo_arg = NULL;
|
||||
ot_lptrarray GPtrArray *checkout_args = NULL;
|
||||
ot_lobj GFile *previous_deployment = 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;
|
||||
ot_lfree char *resolved_commit = NULL;
|
||||
GError *temp_error = NULL;
|
||||
gboolean skip_checkout;
|
||||
|
||||
repo_path = g_build_filename (opt_ostree_dir, "repo", NULL);
|
||||
repo_arg = g_strconcat ("--repo=", repo_path, NULL);
|
||||
if (!revision)
|
||||
revision = deploy_target;
|
||||
|
||||
deploy_path = ot_gfile_from_build_path (opt_ostree_dir, deploy_target, NULL);
|
||||
deploy_parent = g_file_get_parent (deploy_path);
|
||||
deploy_dir = g_file_get_child (self->ostree_dir, "deploy");
|
||||
|
||||
if (!ostree_repo_resolve_rev (self->repo, revision, FALSE, &resolved_commit, error))
|
||||
goto out;
|
||||
|
||||
root = (OstreeRepoFile*)ostree_repo_file_new_root (self->repo, 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, "-", resolved_commit, NULL);
|
||||
deploy_target_path = g_file_resolve_relative_path (deploy_dir, deploy_target_fullname);
|
||||
|
||||
deploy_target_fullname_tmp = g_strconcat (deploy_target_fullname, ".tmp", NULL);
|
||||
deploy_target_path_tmp = g_file_resolve_relative_path (deploy_dir, deploy_target_fullname_tmp);
|
||||
|
||||
deploy_parent = g_file_get_parent (deploy_target_path);
|
||||
if (!ot_gfile_ensure_directory (deploy_parent, TRUE, error))
|
||||
goto out;
|
||||
|
||||
checkout_args = g_ptr_array_new ();
|
||||
ot_ptrarray_add_many (checkout_args, "ostree", repo_arg,
|
||||
"checkout", "--atomic-retarget", revision ? revision : deploy_target,
|
||||
ot_gfile_get_path_cached (deploy_path), NULL);
|
||||
g_ptr_array_add (checkout_args, NULL);
|
||||
deploy_target_etc_name = g_strconcat (deploy_target, "-", resolved_commit, "-etc", NULL);
|
||||
deploy_target_etc_path = g_file_resolve_relative_path (deploy_dir, deploy_target_etc_name);
|
||||
|
||||
if (!ot_spawn_sync_checked (opt_ostree_dir, (char**)checkout_args->pdata, NULL, G_SPAWN_SEARCH_PATH,
|
||||
NULL, NULL, NULL, NULL, error))
|
||||
/* Delete any previous temporary data */
|
||||
if (!ot_gio_shutil_rm_rf (deploy_target_path_tmp, cancellable, error))
|
||||
goto out;
|
||||
|
||||
existing_checkout_info = g_file_query_info (deploy_target_path, OSTREE_GIO_FAST_QUERYINFO,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable, &temp_error);
|
||||
if (existing_checkout_info)
|
||||
{
|
||||
if (opt_force)
|
||||
{
|
||||
if (!ot_gio_shutil_rm_rf (deploy_target_path, cancellable, error))
|
||||
goto out;
|
||||
if (!ot_gio_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, &previous_deployment,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
if (previous_deployment)
|
||||
{
|
||||
ot_lfree char *etc_name;
|
||||
ot_lobj GFile *parent;
|
||||
|
||||
etc_name = g_strconcat (ot_gfile_get_basename_cached (previous_deployment), "-etc", NULL);
|
||||
parent = g_file_get_parent (previous_deployment);
|
||||
|
||||
previous_deployment_etc = g_file_get_child (parent, etc_name);
|
||||
|
||||
if (!g_file_query_exists (previous_deployment_etc, cancellable)
|
||||
|| g_file_equal (previous_deployment, deploy_target_path))
|
||||
g_clear_object (&previous_deployment_etc);
|
||||
else
|
||||
previous_deployment_etc_default = g_file_get_child (previous_deployment, "etc");
|
||||
}
|
||||
|
||||
|
||||
if (!skip_checkout)
|
||||
{
|
||||
ProcessOneCheckoutData checkout_data;
|
||||
|
||||
g_print ("ostadmin: Creating deployment %s\n",
|
||||
ot_gfile_get_path_cached (deploy_target_path));
|
||||
|
||||
memset (&checkout_data, 0, sizeof (checkout_data));
|
||||
checkout_data.loop = g_main_loop_new (NULL, TRUE);
|
||||
checkout_data.error = error;
|
||||
|
||||
ostree_repo_checkout_tree_async (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;
|
||||
|
||||
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 (!ot_gio_shutil_rm_rf (deploy_target_etc_path, cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (!ot_gio_shutil_cp_a (deploy_target_default_etc_path, deploy_target_etc_path,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
g_print ("ostadmin: Created %s\n", ot_gfile_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 (!ot_gfile_rename (deploy_target_path_tmp, deploy_target_path,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!update_current (self, previous_deployment, deploy_target_path,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
ret = TRUE;
|
||||
ot_transfer_out_value (out_deploy_dir, &deploy_target_path);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* do_update_kernel:
|
||||
*
|
||||
* Ensure we have a GRUB entry, initramfs set up, etc.
|
||||
*/
|
||||
static gboolean
|
||||
do_update_kernel (OtAdminDeploy *self,
|
||||
const char *deploy_target,
|
||||
GFile *deploy_path,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
|
|
@ -121,11 +506,12 @@ do_update_kernel (OtAdminDeploy *self,
|
|||
|
||||
args = g_ptr_array_new ();
|
||||
ot_ptrarray_add_many (args, "ostadmin", "update-kernel",
|
||||
"--ostree-dir", opt_ostree_dir,
|
||||
deploy_target, NULL);
|
||||
"--ostree-dir", ot_gfile_get_path_cached (self->ostree_dir),
|
||||
ot_gfile_get_path_cached (deploy_path), NULL);
|
||||
g_ptr_array_add (args, NULL);
|
||||
|
||||
if (!ot_spawn_sync_checked (opt_ostree_dir, (char**)args->pdata, NULL, G_SPAWN_SEARCH_PATH,
|
||||
if (!ot_spawn_sync_checked (ot_gfile_get_path_cached (self->ostree_dir),
|
||||
(char**)args->pdata, NULL, G_SPAWN_SEARCH_PATH,
|
||||
NULL, NULL, NULL, NULL, error))
|
||||
goto out;
|
||||
|
||||
|
|
@ -142,6 +528,8 @@ ot_admin_builtin_deploy (int argc, char **argv, GError **error)
|
|||
OtAdminDeploy self_data;
|
||||
OtAdminDeploy *self = &self_data;
|
||||
gboolean ret = FALSE;
|
||||
ot_lobj GFile *repo_path = NULL;
|
||||
ot_lobj GFile *deploy_path = NULL;
|
||||
const char *deploy_target = NULL;
|
||||
const char *revision = NULL;
|
||||
__attribute__((unused)) GCancellable *cancellable = NULL;
|
||||
|
|
@ -149,6 +537,7 @@ ot_admin_builtin_deploy (int argc, char **argv, GError **error)
|
|||
memset (self, 0, sizeof (*self));
|
||||
|
||||
context = g_option_context_new ("NAME [REVISION] - Check out revision NAME (or REVISION as NAME)");
|
||||
|
||||
g_option_context_add_main_entries (context, options, NULL);
|
||||
|
||||
if (!g_option_context_parse (context, &argc, &argv, error))
|
||||
|
|
@ -159,25 +548,35 @@ ot_admin_builtin_deploy (int argc, char **argv, GError **error)
|
|||
ot_util_usage_error (context, "NAME must be specified", error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
||||
self->ostree_dir = g_file_new_for_path (opt_ostree_dir);
|
||||
|
||||
if (!ot_admin_ensure_initialized (self->ostree_dir, cancellable, error))
|
||||
goto out;
|
||||
|
||||
repo_path = g_file_get_child (self->ostree_dir, "repo");
|
||||
self->repo = ostree_repo_new (repo_path);
|
||||
if (!ostree_repo_check (self->repo, error))
|
||||
goto out;
|
||||
|
||||
deploy_target = argv[1];
|
||||
if (argc > 2)
|
||||
revision = argv[2];
|
||||
|
||||
if (!do_checkout (self, deploy_target, revision, cancellable, error))
|
||||
if (!deploy_tree (self, deploy_target, revision, &deploy_path,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (!opt_no_kernel)
|
||||
{
|
||||
if (!do_update_kernel (self, deploy_target, cancellable, error))
|
||||
if (!do_update_kernel (self, deploy_path, cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!update_current (deploy_target, cancellable, error))
|
||||
goto out;
|
||||
|
||||
ret = TRUE;
|
||||
out:
|
||||
g_clear_object (&self->repo);
|
||||
g_clear_object (&self->ostree_dir);
|
||||
if (context)
|
||||
g_option_context_free (context);
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
/* -*- 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 char *opt_ostree_dir = "/ostree";
|
||||
|
||||
static GOptionEntry options[] = {
|
||||
{ "ostree-dir", 0, 0, G_OPTION_ARG_STRING, &opt_ostree_dir, "Path to OSTree root directory (default: /ostree)", NULL },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
gboolean
|
||||
ot_admin_builtin_diff (int argc, char **argv, GError **error)
|
||||
{
|
||||
GOptionContext *context;
|
||||
gboolean ret = FALSE;
|
||||
ot_lobj GFile *ostree_dir = NULL;
|
||||
ot_lobj GFile *repo_path = NULL;
|
||||
ot_lobj GFile *deployment = NULL;
|
||||
ot_lobj GFile *deploy_parent = NULL;
|
||||
ot_lptrarray GPtrArray *modified = NULL;
|
||||
ot_lptrarray GPtrArray *removed = NULL;
|
||||
ot_lptrarray GPtrArray *added = NULL;
|
||||
ot_lobj GFile *orig_etc_path = NULL;
|
||||
ot_lobj GFile *new_etc_path = NULL;
|
||||
__attribute__((unused)) GCancellable *cancellable = NULL;
|
||||
|
||||
context = g_option_context_new ("[NAME] - Diff configuration for revision NAME");
|
||||
|
||||
g_option_context_add_main_entries (context, options, NULL);
|
||||
|
||||
if (!g_option_context_parse (context, &argc, &argv, error))
|
||||
goto out;
|
||||
|
||||
ostree_dir = g_file_new_for_path (opt_ostree_dir);
|
||||
repo_path = g_file_get_child (ostree_dir, "repo");
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
deployment = ot_gfile_get_child_build_path (ostree_dir, "deploy", argv[1], NULL);
|
||||
if (!g_file_query_exists (deployment, NULL))
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Deployment %s doesn't exist", ot_gfile_get_path_cached (deployment));
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ot_admin_get_current_deployment (ostree_dir, &deployment,
|
||||
cancellable, error))
|
||||
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,
|
||||
ot_gfile_get_basename_cached (deployment),
|
||||
"-etc", NULL);
|
||||
|
||||
modified = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_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_path, new_etc_path, modified, removed, added,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
ostree_diff_print (new_etc_path, modified, removed, added);
|
||||
|
||||
ret = TRUE;
|
||||
out:
|
||||
g_clear_object (&ostree_dir);
|
||||
if (context)
|
||||
g_option_context_free (context);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
#include "config.h"
|
||||
|
||||
#include "ot-admin-builtins.h"
|
||||
#include "ot-admin-functions.h"
|
||||
#include "otutil.h"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
|
@ -49,48 +50,11 @@ ot_admin_builtin_init (int argc, char **argv, GError **error)
|
|||
if (!g_option_context_parse (context, &argc, &argv, error))
|
||||
goto out;
|
||||
|
||||
g_clear_object (&dir);
|
||||
dir = ot_gfile_from_build_path (opt_ostree_dir, "repo", NULL);
|
||||
if (!ot_gfile_ensure_directory (dir, TRUE, error))
|
||||
goto out;
|
||||
dir = g_file_new_for_path (opt_ostree_dir);
|
||||
|
||||
/* We presently copy over host kernel modules */
|
||||
g_clear_object (&dir);
|
||||
dir = ot_gfile_from_build_path (opt_ostree_dir, "modules", NULL);
|
||||
if (!ot_gfile_ensure_directory (dir, TRUE, error))
|
||||
if (!ot_admin_ensure_initialized (dir, cancellable, error))
|
||||
goto out;
|
||||
|
||||
g_clear_object (&dir);
|
||||
dir = ot_gfile_from_build_path (opt_ostree_dir, "repo", "objects", NULL);
|
||||
if (!g_file_query_exists (dir, NULL))
|
||||
{
|
||||
ot_lfree char *opt_repo_path = g_strdup_printf ("--repo=%s/repo", opt_ostree_dir);
|
||||
const char *child_argv[] = { "ostree", opt_repo_path, "init", NULL };
|
||||
|
||||
if (!ot_spawn_sync_checked (NULL, (char**)child_argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
|
||||
NULL, NULL, error))
|
||||
{
|
||||
g_prefix_error (error, "Failed to initialize repository: ");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure a few subdirectories of /var exist, since we need them for
|
||||
dracut generation */
|
||||
g_clear_object (&dir);
|
||||
dir = ot_gfile_from_build_path (opt_ostree_dir, "var", "log", NULL);
|
||||
if (!ot_gfile_ensure_directory (dir, TRUE, error))
|
||||
goto out;
|
||||
g_clear_object (&dir);
|
||||
dir = ot_gfile_from_build_path (opt_ostree_dir, "var", "tmp", NULL);
|
||||
if (!ot_gfile_ensure_directory (dir, TRUE, error))
|
||||
goto out;
|
||||
if (chmod (ot_gfile_get_path_cached (dir), 01777) < 0)
|
||||
{
|
||||
ot_util_set_error_from_errno (error, errno);
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_print ("%s initialized as OSTree root\n", opt_ostree_dir);
|
||||
|
||||
ret = TRUE;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ typedef struct {
|
|||
OstreeRepo *repo;
|
||||
} OtAdminUpdateKernel;
|
||||
|
||||
static char *opt_ostree_dir;
|
||||
static char *opt_ostree_dir = "/ostree";
|
||||
|
||||
static GOptionEntry options[] = {
|
||||
{ "ostree-dir", 0, 0, G_OPTION_ARG_STRING, &opt_ostree_dir, "Path to OSTree root directory", NULL },
|
||||
|
|
@ -70,14 +70,13 @@ copy_modules (const char *release,
|
|||
|
||||
static gboolean
|
||||
update_initramfs (const char *release,
|
||||
const char *deploy_target,
|
||||
const char *deploy_path,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
ot_lfree char *initramfs_name = NULL;
|
||||
ot_lobj GFile *initramfs_file = NULL;
|
||||
ot_lfree char *last_deploy_path = NULL;
|
||||
|
||||
initramfs_name = g_strconcat ("initramfs-ostree-", release, ".img", NULL);
|
||||
initramfs_file = ot_gfile_from_build_path ("/boot", initramfs_name, NULL);
|
||||
|
|
@ -98,8 +97,6 @@ update_initramfs (const char *release,
|
|||
ostree_vardir = g_build_filename (opt_ostree_dir, "var", NULL);
|
||||
ostree_moduledir = g_build_filename (opt_ostree_dir, "modules", NULL);
|
||||
|
||||
last_deploy_path = g_build_filename (opt_ostree_dir, deploy_target, NULL);
|
||||
|
||||
mkinitramfs_args = g_ptr_array_new ();
|
||||
/* Note: the hardcoded /tmp path below is not actually a
|
||||
* security flaw, because we've bind-mounted dracut's view
|
||||
|
|
@ -113,12 +110,12 @@ update_initramfs (const char *release,
|
|||
"--mount-bind", ostree_vardir, "/var",
|
||||
"--mount-bind", ot_gfile_get_path_cached (tmpdir), "/tmp",
|
||||
"--mount-bind", ostree_moduledir, "/lib/modules",
|
||||
last_deploy_path,
|
||||
deploy_path,
|
||||
"dracut", "-f", "/tmp/initramfs-ostree.img", release,
|
||||
NULL);
|
||||
g_ptr_array_add (mkinitramfs_args, NULL);
|
||||
|
||||
g_print ("Generating initramfs using %s...\n", last_deploy_path);
|
||||
g_print ("Generating initramfs using %s...\n", deploy_path);
|
||||
if (!ot_spawn_sync_checked (NULL, (char**)mkinitramfs_args->pdata, NULL,
|
||||
G_SPAWN_SEARCH_PATH,
|
||||
NULL, NULL, NULL, NULL, error))
|
||||
|
|
@ -277,14 +274,11 @@ ot_admin_builtin_update_kernel (int argc, char **argv, GError **error)
|
|||
OtAdminUpdateKernel self_data;
|
||||
OtAdminUpdateKernel *self = &self_data;
|
||||
gboolean ret = FALSE;
|
||||
const char *deploy_target = NULL;
|
||||
const char *deploy_path = NULL;
|
||||
struct utsname utsname;
|
||||
const char *release;
|
||||
__attribute__((unused)) GCancellable *cancellable = NULL;
|
||||
|
||||
if (!opt_ostree_dir)
|
||||
opt_ostree_dir = "/ostree";
|
||||
|
||||
memset (self, 0, sizeof (*self));
|
||||
|
||||
context = g_option_context_new ("[OSTREE_REVISION [KERNEL_RELEASE]] - Update kernel and regenerate initial ramfs");
|
||||
|
|
@ -294,9 +288,9 @@ ot_admin_builtin_update_kernel (int argc, char **argv, GError **error)
|
|||
goto out;
|
||||
|
||||
if (argc > 1)
|
||||
deploy_target = argv[1];
|
||||
deploy_path = argv[1];
|
||||
else
|
||||
deploy_target = "current";
|
||||
deploy_path = "current";
|
||||
|
||||
(void) uname (&utsname);
|
||||
|
||||
|
|
@ -314,7 +308,7 @@ ot_admin_builtin_update_kernel (int argc, char **argv, GError **error)
|
|||
if (!copy_modules (release, cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (!update_initramfs (release, deploy_target, cancellable, error))
|
||||
if (!update_initramfs (release, deploy_path, cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (!update_grub (release, cancellable, error))
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ G_BEGIN_DECLS
|
|||
|
||||
gboolean ot_admin_builtin_init (int argc, char **argv, GError **error);
|
||||
gboolean ot_admin_builtin_deploy (int argc, char **argv, GError **error);
|
||||
gboolean ot_admin_builtin_diff (int argc, char **argv, GError **error);
|
||||
gboolean ot_admin_builtin_update_kernel (int argc, char **argv, GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||
*
|
||||
* Copyright (C) 2012 Colin Walters <walters@verbum.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Author: Colin Walters <walters@verbum.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "ot-admin-functions.h"
|
||||
#include "otutil.h"
|
||||
#include "ostree-core.h"
|
||||
|
||||
gboolean
|
||||
ot_admin_ensure_initialized (GFile *ostree_dir,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
ot_lobj GFile *dir = NULL;
|
||||
|
||||
g_clear_object (&dir);
|
||||
dir = g_file_get_child (ostree_dir, "repo");
|
||||
if (!ot_gfile_ensure_directory (dir, TRUE, error))
|
||||
goto out;
|
||||
|
||||
g_clear_object (&dir);
|
||||
dir = g_file_get_child (ostree_dir, "deploy");
|
||||
if (!ot_gfile_ensure_directory (dir, TRUE, error))
|
||||
goto out;
|
||||
|
||||
g_clear_object (&dir);
|
||||
dir = g_file_get_child (ostree_dir, "modules");
|
||||
if (!ot_gfile_ensure_directory (dir, TRUE, error))
|
||||
goto out;
|
||||
|
||||
g_clear_object (&dir);
|
||||
dir = ot_gfile_get_child_build_path (ostree_dir, "repo", "objects", NULL);
|
||||
if (!g_file_query_exists (dir, NULL))
|
||||
{
|
||||
ot_lfree char *opt_repo_arg = g_strdup_printf ("--repo=%s/repo",
|
||||
ot_gfile_get_path_cached (ostree_dir));
|
||||
const char *child_argv[] = { "ostree", opt_repo_arg, "init", NULL };
|
||||
|
||||
if (!ot_spawn_sync_checked (NULL, (char**)child_argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
|
||||
NULL, NULL, error))
|
||||
{
|
||||
g_prefix_error (error, "Failed to initialize repository: ");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure a few subdirectories of /var exist, since we need them for
|
||||
dracut generation */
|
||||
g_clear_object (&dir);
|
||||
dir = ot_gfile_get_child_build_path (ostree_dir, "var", "log", NULL);
|
||||
if (!ot_gfile_ensure_directory (dir, TRUE, error))
|
||||
goto out;
|
||||
g_clear_object (&dir);
|
||||
dir = ot_gfile_get_child_build_path (ostree_dir, "var", "tmp", NULL);
|
||||
if (!ot_gfile_ensure_directory (dir, TRUE, error))
|
||||
goto out;
|
||||
if (chmod (ot_gfile_get_path_cached (dir), 01777) < 0)
|
||||
{
|
||||
ot_util_set_error_from_errno (error, errno);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = TRUE;
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* ot_admin_get_current_deployment:
|
||||
*
|
||||
* Returns in @out_deployment the full file path of the current
|
||||
* deployment that the /ostree/current symbolic link points to, or
|
||||
* %NULL if none.
|
||||
*/
|
||||
gboolean
|
||||
ot_admin_get_current_deployment (GFile *ostree_dir,
|
||||
GFile **out_deployment,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
ot_lobj GFile *current_path = NULL;
|
||||
ot_lobj GFileInfo *file_info = NULL;
|
||||
ot_lobj GFile *ret_deployment = NULL;
|
||||
GError *temp_error = NULL;
|
||||
|
||||
current_path = g_file_get_child (ostree_dir, "current");
|
||||
|
||||
file_info = g_file_query_info (current_path, OSTREE_GIO_FAST_QUERYINFO,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable, &temp_error);
|
||||
if (!file_info)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *target;
|
||||
if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_SYMBOLIC_LINK)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Not a symbolic link");
|
||||
goto out;
|
||||
}
|
||||
target = g_file_info_get_symlink_target (file_info);
|
||||
g_assert (target);
|
||||
ret_deployment = g_file_resolve_relative_path (ostree_dir, target);
|
||||
}
|
||||
|
||||
ret = TRUE;
|
||||
ot_transfer_out_value (out_deployment, &ret_deployment);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/* -*- 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_FUNCTIONS__
|
||||
#define __OT_ADMIN_FUNCTIONS__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
gboolean ot_admin_ensure_initialized (GFile *ostree_dir,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
gboolean ot_admin_get_current_deployment (GFile *ostree_dir,
|
||||
GFile **out_deployment,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
|
@ -30,7 +30,6 @@
|
|||
#include <glib/gi18n.h>
|
||||
|
||||
static gboolean opt_user_mode;
|
||||
static gboolean opt_atomic_retarget;
|
||||
static gboolean opt_no_triggers;
|
||||
static char *opt_subpath;
|
||||
static gboolean opt_union;
|
||||
|
|
@ -41,89 +40,12 @@ static GOptionEntry options[] = {
|
|||
{ "user-mode", 'U', 0, G_OPTION_ARG_NONE, &opt_user_mode, "Do not change file ownership or initialize extended attributes", NULL },
|
||||
{ "subpath", 0, 0, G_OPTION_ARG_STRING, &opt_subpath, "Checkout sub-directory PATH", "PATH" },
|
||||
{ "union", 0, 0, G_OPTION_ARG_NONE, &opt_union, "Keep existing directories, overwrite existing files", NULL },
|
||||
{ "atomic-retarget", 0, 0, G_OPTION_ARG_NONE, &opt_atomic_retarget, "Make a symbolic link for destination, suffix with checksum", NULL },
|
||||
{ "no-triggers", 0, 0, G_OPTION_ARG_NONE, &opt_no_triggers, "Don't run triggers", NULL },
|
||||
{ "from-stdin", 0, 0, G_OPTION_ARG_NONE, &opt_from_stdin, "Process many checkouts from standard input", NULL },
|
||||
{ "from-file", 0, 0, G_OPTION_ARG_STRING, &opt_from_file, "Process many checkouts from input file", NULL },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static gboolean
|
||||
atomic_symlink_swap (GFile *dest,
|
||||
const char *target,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
ot_lobj GFile *parent = NULL;
|
||||
ot_lfree char *tmp_name = NULL;
|
||||
ot_lobj GFile *tmp_link = NULL;
|
||||
|
||||
parent = g_file_get_parent (dest);
|
||||
/* HACK - should use randomly generated temporary target name */
|
||||
tmp_name = g_strconcat (ot_gfile_get_basename_cached (dest),
|
||||
"-tmplink", NULL);
|
||||
tmp_link = g_file_get_child (parent, tmp_name);
|
||||
if (symlink (target, ot_gfile_get_path_cached (tmp_link)) < 0)
|
||||
{
|
||||
ot_util_set_error_from_errno (error, errno);
|
||||
goto out;
|
||||
}
|
||||
if (!ot_gfile_rename (tmp_link, dest, cancellable, error))
|
||||
goto out;
|
||||
|
||||
ret = TRUE;
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_commit_from_symlink (GFile *symlink,
|
||||
char **out_commit,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
const char *target;
|
||||
const char *last_dash;
|
||||
const char *checksum;
|
||||
ot_lobj GFileInfo *file_info = NULL;
|
||||
ot_lfree char *ret_commit = NULL;
|
||||
|
||||
file_info = g_file_query_info (symlink, OSTREE_GIO_FAST_QUERYINFO,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable, error);
|
||||
if (!file_info)
|
||||
goto out;
|
||||
|
||||
target = g_file_info_get_symlink_target (file_info);
|
||||
if (target == NULL)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Not a symbolic link");
|
||||
goto out;
|
||||
}
|
||||
|
||||
last_dash = strrchr (target, '-');
|
||||
if (last_dash == NULL)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Invalid existing symlink target; no trailing dash");
|
||||
goto out;
|
||||
}
|
||||
checksum = last_dash + 1;
|
||||
|
||||
if (!ostree_validate_structureof_checksum_string (checksum, error))
|
||||
goto out;
|
||||
|
||||
ret_commit = g_strdup (checksum);
|
||||
|
||||
ret = TRUE;
|
||||
ot_transfer_out_value (out_commit, &ret_commit);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
gboolean caught_error;
|
||||
GError **error;
|
||||
|
|
@ -263,7 +185,10 @@ process_many_checkouts (OstreeRepo *repo,
|
|||
|
||||
if (!process_one_checkout (repo, resolved_commit, subpath, target,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
{
|
||||
g_prefix_error (error, "Processing tree %s: ", resolved_commit);
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_free (revision);
|
||||
}
|
||||
|
|
@ -286,11 +211,9 @@ ostree_builtin_checkout (int argc, char **argv, GFile *repo_path, GError **error
|
|||
gboolean ret = FALSE;
|
||||
const char *commit;
|
||||
const char *destination;
|
||||
gboolean skip_checkout;
|
||||
ot_lobj OstreeRepo *repo = NULL;
|
||||
ot_lfree char *existing_commit = NULL;
|
||||
ot_lfree char *resolved_commit = NULL;
|
||||
ot_lfree char *suffixed_destination = NULL;
|
||||
ot_lfree char *tmp_destination = NULL;
|
||||
ot_lobj GFileInfo *symlink_file_info = NULL;
|
||||
ot_lobj GFile *checkout_target = NULL;
|
||||
|
|
@ -319,13 +242,6 @@ ostree_builtin_checkout (int argc, char **argv, GFile *repo_path, GError **error
|
|||
|
||||
if (opt_from_stdin || opt_from_file)
|
||||
{
|
||||
if (opt_atomic_retarget)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"--atomic-retarget may not be used with --from-stdin or --from-file");
|
||||
goto out;
|
||||
}
|
||||
|
||||
destination = argv[1];
|
||||
checkout_target = g_file_new_for_path (destination);
|
||||
|
||||
|
|
@ -349,69 +265,18 @@ ostree_builtin_checkout (int argc, char **argv, GFile *repo_path, GError **error
|
|||
if (!ostree_repo_resolve_rev (repo, commit, FALSE, &resolved_commit, error))
|
||||
goto out;
|
||||
|
||||
if (opt_atomic_retarget)
|
||||
{
|
||||
GError *temp_error = NULL;
|
||||
checkout_target = g_file_new_for_path (destination);
|
||||
|
||||
suffixed_destination = g_strconcat (destination, "-", resolved_commit, NULL);
|
||||
checkout_target = g_file_new_for_path (suffixed_destination);
|
||||
tmp_destination = g_strconcat (suffixed_destination, ".tmp", NULL);
|
||||
checkout_target_tmp = g_file_new_for_path (tmp_destination);
|
||||
symlink_target = g_file_new_for_path (destination);
|
||||
if (!process_one_checkout (repo, resolved_commit, opt_subpath,
|
||||
checkout_target_tmp ? checkout_target_tmp : checkout_target,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (!parse_commit_from_symlink (symlink_target, &existing_commit,
|
||||
cancellable, &temp_error))
|
||||
{
|
||||
if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||||
{
|
||||
skip_checkout = FALSE;
|
||||
g_clear_error (&temp_error);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_propagate_error (error, temp_error);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
skip_checkout = strcmp (existing_commit, resolved_commit) == 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!opt_no_triggers)
|
||||
{
|
||||
checkout_target = g_file_new_for_path (destination);
|
||||
skip_checkout = FALSE;
|
||||
}
|
||||
|
||||
if (skip_checkout)
|
||||
{
|
||||
g_print ("ostree-checkout: Rev %s is already checked out as %s\n", commit, resolved_commit);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!process_one_checkout (repo, resolved_commit, opt_subpath,
|
||||
checkout_target_tmp ? checkout_target_tmp : checkout_target,
|
||||
cancellable, error))
|
||||
if (!ostree_run_triggers_in_root (checkout_target_tmp ? checkout_target_tmp : checkout_target,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (!opt_no_triggers)
|
||||
{
|
||||
if (!ostree_run_triggers_in_root (checkout_target_tmp ? checkout_target_tmp : checkout_target,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (opt_atomic_retarget)
|
||||
{
|
||||
if (!ot_gfile_rename (checkout_target_tmp, checkout_target, cancellable, error))
|
||||
goto out;
|
||||
if (!atomic_symlink_swap (symlink_target,
|
||||
ot_gfile_get_basename_cached (checkout_target),
|
||||
cancellable, error))
|
||||
goto out;
|
||||
g_print ("ostree-checkout: Rev %s checked out as %s\n", commit, resolved_commit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ ostree_builtin_diff (int argc, char **argv, GFile *repo_path, GError **error)
|
|||
gboolean ret = FALSE;
|
||||
GOptionContext *context;
|
||||
GCancellable *cancellable = NULL;
|
||||
int i;
|
||||
const char *src;
|
||||
const char *target;
|
||||
ot_lobj OstreeRepo *repo = NULL;
|
||||
|
|
|
|||
|
|
@ -154,11 +154,12 @@ main(int argc, char *argv[])
|
|||
const char *toproot_bind_mounts[] = { "/home", "/root", "/tmp", NULL };
|
||||
const char *ostree_bind_mounts[] = { "/var", NULL };
|
||||
/* ostree_readonly_bind_mounts /lib/modules -> modules */
|
||||
const char *readonly_bind_mounts[] = { "/bin", "/etc", "/lib", "/sbin", "/usr",
|
||||
const char *readonly_bind_mounts[] = { "/bin", "/lib", "/sbin", "/usr",
|
||||
NULL };
|
||||
const char *root_mountpoint = NULL;
|
||||
const char *ostree_target = NULL;
|
||||
const char *ostree_subinit = NULL;
|
||||
char ostree_target_path[PATH_MAX];
|
||||
char srcpath[PATH_MAX];
|
||||
char destpath[PATH_MAX];
|
||||
struct stat stbuf;
|
||||
|
|
@ -199,7 +200,7 @@ main(int argc, char *argv[])
|
|||
perrorv ("Invalid ostree root '%s'", destpath);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
|
||||
for (i = 0; initramfs_move_mounts[i] != NULL; i++)
|
||||
{
|
||||
const char *path = initramfs_move_mounts[i];
|
||||
|
|
@ -245,16 +246,46 @@ main(int argc, char *argv[])
|
|||
/* From this point on we're chrooted into the real root filesystem,
|
||||
* so we no longer refer to root_mountpoint.
|
||||
*/
|
||||
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s", 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))
|
||||
{
|
||||
if (readlink (destpath, ostree_target_path, PATH_MAX) < 0)
|
||||
{
|
||||
perrorv ("readlink(%s) failed: ", destpath);
|
||||
exit (1);
|
||||
}
|
||||
fprintf (stderr, "Resolved OSTree target to: %s\n", ostree_target_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy (ostree_target_path, ostree_target, PATH_MAX);
|
||||
fprintf (stderr, "OSTree target is: %s\n", ostree_target_path);
|
||||
}
|
||||
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s/sysroot", ostree_target);
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s/sysroot", ostree_target_path);
|
||||
if (mount ("/", destpath, NULL, MS_BIND, NULL) < 0)
|
||||
{
|
||||
perrorv ("Failed to bind mount / to '%s'", destpath);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
snprintf (srcpath, sizeof(srcpath), "/ostree/%s-etc", ostree_target_path);
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s/etc", ostree_target_path);
|
||||
if (mount (srcpath, destpath, NULL, MS_BIND, NULL) < 0)
|
||||
{
|
||||
perrorv ("Failed to bind mount '%s' to '%s'", srcpath, destpath);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
snprintf (srcpath, sizeof(srcpath), "%s", "/ostree/var");
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s/var", ostree_target);
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s/var", ostree_target_path);
|
||||
if (mount (srcpath, destpath, NULL, MS_BIND, NULL) < 0)
|
||||
{
|
||||
perrorv ("Failed to bind mount '%s' to '%s'", srcpath, destpath);
|
||||
|
|
@ -274,7 +305,7 @@ main(int argc, char *argv[])
|
|||
for (i = 0; ostree_bind_mounts[i] != NULL; i++)
|
||||
{
|
||||
snprintf (srcpath, sizeof(srcpath), "/ostree/%s", ostree_bind_mounts[i]);
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s%s", ostree_target, ostree_bind_mounts[i]);
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s%s", ostree_target_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);
|
||||
|
|
@ -284,7 +315,7 @@ main(int argc, char *argv[])
|
|||
|
||||
for (i = 0; readonly_bind_mounts[i] != NULL; i++)
|
||||
{
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s%s", ostree_target, readonly_bind_mounts[i]);
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s%s", ostree_target_path, readonly_bind_mounts[i]);
|
||||
if (mount (destpath, destpath, NULL, MS_BIND, NULL) < 0)
|
||||
{
|
||||
perrorv ("failed to bind mount (class:readonly) %s", destpath);
|
||||
|
|
@ -299,14 +330,14 @@ main(int argc, char *argv[])
|
|||
|
||||
/* This should come after we've bind mounted /lib */
|
||||
snprintf (srcpath, sizeof(srcpath), "/ostree/modules");
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s/lib/modules", ostree_target);
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s/lib/modules", ostree_target_path);
|
||||
if (mount (srcpath, destpath, NULL, MS_MGC_VAL|MS_BIND, NULL) < 0)
|
||||
{
|
||||
perrorv ("failed to bind mount %s to %s", srcpath, destpath);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s", ostree_target);
|
||||
snprintf (destpath, sizeof(destpath), "/ostree/%s", ostree_target_path);
|
||||
if (chroot (destpath) < 0)
|
||||
{
|
||||
perrorv ("failed to change root to '%s'", destpath);
|
||||
|
|
|
|||
Loading…
Reference in New Issue