diff --git a/Makefile-man.am b/Makefile-man.am
index 615bf0f0..ce7e93cd 100644
--- a/Makefile-man.am
+++ b/Makefile-man.am
@@ -19,7 +19,17 @@
if ENABLE_MAN
-man1_files = ostree.1 ostree-admin-cleanup.1 ostree-admin-config-diff.1 ostree-admin-deploy.1 ostree-admin-init-fs.1 ostree-admin-instutil.1 ostree-admin-os-init.1 ostree-admin-status.1 ostree-admin-set-origin.1 ostree-admin-switch.1 ostree-admin-undeploy.1 ostree-admin-upgrade.1 ostree-admin.1 ostree-cat.1 ostree-checkout.1 ostree-checksum.1 ostree-commit.1 ostree-export.1 ostree-gpg-sign.1 ostree-config.1 ostree-diff.1 ostree-fsck.1 ostree-init.1 ostree-log.1 ostree-ls.1 ostree-prune.1 ostree-pull-local.1 ostree-pull.1 ostree-refs.1 ostree-remote.1 ostree-reset.1 ostree-rev-parse.1 ostree-show.1 ostree-summary.1 ostree-static-delta.1 ostree-trivial-httpd.1
+man1_files = ostree.1 ostree-admin-cleanup.1 \
+ostree-admin-config-diff.1 ostree-admin-deploy.1 \
+ostree-admin-init-fs.1 ostree-admin-instutil.1 ostree-admin-os-init.1 \
+ostree-admin-status.1 ostree-admin-set-origin.1 ostree-admin-switch.1 \
+ostree-admin-undeploy.1 ostree-admin-upgrade.1 ostree-admin-unlock.1 \
+ostree-admin.1 ostree-cat.1 ostree-checkout.1 ostree-checksum.1 \
+ostree-commit.1 ostree-export.1 ostree-gpg-sign.1 ostree-config.1 \
+ostree-diff.1 ostree-fsck.1 ostree-init.1 ostree-log.1 ostree-ls.1 \
+ostree-prune.1 ostree-pull-local.1 ostree-pull.1 ostree-refs.1 \
+ostree-remote.1 ostree-reset.1 ostree-rev-parse.1 ostree-show.1 \
+ostree-summary.1 ostree-static-delta.1 ostree-trivial-httpd.1
if BUILDOPT_FUSE
man1_files += rofiles-fuse.1
diff --git a/Makefile-ostree.am b/Makefile-ostree.am
index ff7e372b..0ef5c4ee 100644
--- a/Makefile-ostree.am
+++ b/Makefile-ostree.am
@@ -66,6 +66,7 @@ ostree_SOURCES += \
src/ostree/ot-admin-builtin-status.c \
src/ostree/ot-admin-builtin-switch.c \
src/ostree/ot-admin-builtin-upgrade.c \
+ src/ostree/ot-admin-builtin-unlock.c \
src/ostree/ot-admin-builtins.h \
src/ostree/ot-admin-instutil-builtin-selinux-ensure-labeled.c \
src/ostree/ot-admin-instutil-builtin-set-kargs.c \
diff --git a/buildutil/tap-test b/buildutil/tap-test
index 38080bb3..e7914541 100755
--- a/buildutil/tap-test
+++ b/buildutil/tap-test
@@ -12,7 +12,7 @@ tempdir=$(mktemp -d /var/tmp/tap-test.XXXXXX)
touch ${tempdir}/.testtmp
function cleanup () {
if test -n "${TEST_SKIP_CLEANUP:-}"; then
- echo "Skipping cleanup of ${test_tmpdir}"
+ echo "Skipping cleanup of ${tempdir}"
else if test -f ${tempdir}/.test; then
rm "${tempdir}" -rf
fi
diff --git a/man/ostree-admin-unlock.xml b/man/ostree-admin-unlock.xml
new file mode 100644
index 00000000..ca02bbde
--- /dev/null
+++ b/man/ostree-admin-unlock.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+ ostree admin unlock
+ OSTree
+
+
+
+ Developer
+ Colin
+ Walters
+ walters@verbum.org
+
+
+
+
+
+ ostree admin unlock
+ 1
+
+
+
+ ostree-admin-unlock
+ Prepare the current deployment for hotfix or development
+
+
+
+
+ ostree admin unlock OPTIONS
+
+
+
+
+ Description
+
+
+ Remove the read-only bind mount on /usr
+ and replace it with a writable overlay filesystem. This
+ default invocation of "unlock" is intended for
+ development/testing purposes. All changes in the overlay
+ are lost on reboot. However, this command also supports
+ "hotfixes", see below.
+
+
+
+
+ Options
+
+
+
+
+
+ If this option is provided, the
+ current deployment will be cloned as a rollback
+ target. This option is intended for things like
+ emergency security updates to userspace components
+ such as sshd. The semantics here
+ differ from the default "development" unlock mode
+ in that reboots will retain any changes (which is what
+ you likely want for security hotfixes).
+
+
+
+
+
diff --git a/src/libostree/libostree.sym b/src/libostree/libostree.sym
index a85f0dbb..c78986e5 100644
--- a/src/libostree/libostree.sym
+++ b/src/libostree/libostree.sym
@@ -318,4 +318,7 @@ global:
ostree_repo_list_refs_ext;
ostree_sysroot_init_osname;
ostree_sysroot_load_if_changed;
+ ostree_sysroot_deployment_unlock;
+ ostree_deployment_get_unlocked;
+ ostree_deployment_unlocked_state_to_string;
} LIBOSTREE_2016.3;
diff --git a/src/libostree/ostree-deployment-private.h b/src/libostree/ostree-deployment-private.h
index b5ebb957..856a3987 100644
--- a/src/libostree/ostree-deployment-private.h
+++ b/src/libostree/ostree-deployment-private.h
@@ -24,6 +24,21 @@
G_BEGIN_DECLS
+struct _OstreeDeployment
+{
+ 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} */
+ OstreeBootconfigParser *bootconfig; /* Bootloader configuration */
+ GKeyFile *origin; /* How to construct an upgraded version of this tree */
+ OstreeDeploymentUnlockedState unlocked; /* The unlocked state */
+};
+
void _ostree_deployment_set_bootcsum (OstreeDeployment *self, const char *bootcsum);
G_END_DECLS
diff --git a/src/libostree/ostree-deployment.c b/src/libostree/ostree-deployment.c
index 3a80474e..7b93e6cc 100644
--- a/src/libostree/ostree-deployment.c
+++ b/src/libostree/ostree-deployment.c
@@ -23,20 +23,6 @@
#include "ostree-deployment-private.h"
#include "libglnx.h"
-struct _OstreeDeployment
-{
- 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} */
- OstreeBootconfigParser *bootconfig; /* Bootloader configuration */
- GKeyFile *origin; /* How to construct an upgraded version of this tree */
-};
-
typedef GObjectClass OstreeDeploymentClass;
G_DEFINE_TYPE (OstreeDeployment, ostree_deployment, G_TYPE_OBJECT)
@@ -258,6 +244,7 @@ ostree_deployment_new (int index,
self->deployserial = deployserial;
self->bootcsum = g_strdup (bootcsum);
self->bootserial = bootserial;
+ self->unlocked = OSTREE_DEPLOYMENT_UNLOCKED_NONE;
return self;
}
@@ -279,3 +266,24 @@ ostree_deployment_get_origin_relpath (OstreeDeployment *self)
ostree_deployment_get_csum (self),
ostree_deployment_get_deployserial (self));
}
+
+const char *
+ostree_deployment_unlocked_state_to_string (OstreeDeploymentUnlockedState state)
+{
+ switch (state)
+ {
+ case OSTREE_DEPLOYMENT_UNLOCKED_NONE:
+ return "none";
+ case OSTREE_DEPLOYMENT_UNLOCKED_HOTFIX:
+ return "hotfix";
+ case OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT:
+ return "development";
+ }
+ g_assert_not_reached ();
+}
+
+OstreeDeploymentUnlockedState
+ostree_deployment_get_unlocked (OstreeDeployment *self)
+{
+ return self->unlocked;
+}
diff --git a/src/libostree/ostree-deployment.h b/src/libostree/ostree-deployment.h
index a474b350..bde0cf37 100644
--- a/src/libostree/ostree-deployment.h
+++ b/src/libostree/ostree-deployment.h
@@ -78,4 +78,16 @@ OstreeDeployment *ostree_deployment_clone (OstreeDeployment *self);
_OSTREE_PUBLIC
char *ostree_deployment_get_origin_relpath (OstreeDeployment *self);
+typedef enum {
+ OSTREE_DEPLOYMENT_UNLOCKED_NONE,
+ OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT,
+ OSTREE_DEPLOYMENT_UNLOCKED_HOTFIX
+} OstreeDeploymentUnlockedState;
+
+_OSTREE_PUBLIC
+const char *ostree_deployment_unlocked_state_to_string (OstreeDeploymentUnlockedState state);
+
+_OSTREE_PUBLIC
+OstreeDeploymentUnlockedState ostree_deployment_get_unlocked (OstreeDeployment *self);
+
G_END_DECLS
diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h
index 229893d2..d210a36f 100644
--- a/src/libostree/ostree-sysroot-private.h
+++ b/src/libostree/ostree-sysroot-private.h
@@ -58,6 +58,9 @@ struct OstreeSysroot {
};
#define OSTREE_SYSROOT_LOCKFILE "ostree/lock"
+/* We keep some transient state in /run */
+#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_DIR "/run/ostree/deployment-state/"
+#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT "unlocked-development"
gboolean
_ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self,
diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c
index aa08838a..6ee3dff9 100644
--- a/src/libostree/ostree-sysroot.c
+++ b/src/libostree/ostree-sysroot.c
@@ -24,6 +24,7 @@
#include "ostree-core-private.h"
#include "ostree-sysroot-private.h"
+#include "ostree-deployment-private.h"
#include "ostree-bootloader-uboot.h"
#include "ostree-bootloader-syslinux.h"
#include "ostree-bootloader-grub2.h"
@@ -646,6 +647,16 @@ parse_bootlink (const char *bootlink,
return ret;
}
+static char *
+get_unlocked_development_path (OstreeDeployment *deployment)
+{
+ return g_strdup_printf ("%s%s.%d/%s",
+ _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_DIR,
+ ostree_deployment_get_csum (deployment),
+ ostree_deployment_get_deployserial (deployment),
+ _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT);
+}
+
static gboolean
parse_deployment (OstreeSysroot *self,
const char *boot_link,
@@ -667,6 +678,8 @@ parse_deployment (OstreeSysroot *self,
g_autofree char *treebootserial_target = NULL;
g_autofree char *deploy_dir = NULL;
GKeyFile *origin = NULL;
+ g_autofree char *unlocked_development_path = NULL;
+ struct stat stbuf;
if (!ensure_sysroot_fd (self, error))
goto out;
@@ -704,6 +717,24 @@ parse_deployment (OstreeSysroot *self,
if (origin)
ostree_deployment_set_origin (ret_deployment, origin);
+ ret_deployment->unlocked = OSTREE_DEPLOYMENT_UNLOCKED_NONE;
+ unlocked_development_path = get_unlocked_development_path (ret_deployment);
+ if (lstat (unlocked_development_path, &stbuf) == 0)
+ ret_deployment->unlocked = OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT;
+ else
+ {
+ g_autofree char *existing_unlocked_state =
+ g_key_file_get_string (origin, "origin", "unlocked", NULL);
+
+ if (g_strcmp0 (existing_unlocked_state, "hotfix") == 0)
+ {
+ ret_deployment->unlocked = OSTREE_DEPLOYMENT_UNLOCKED_HOTFIX;
+ }
+ /* TODO: warn on unknown unlock types? */
+ }
+
+ g_debug ("Deployment %s.%d unlocked=%d", treecsum, deployserial, ret_deployment->unlocked);
+
ret = TRUE;
if (out_deployment)
*out_deployment = g_steal_pointer (&ret_deployment);
@@ -1481,6 +1512,10 @@ ostree_sysroot_init_osname (OstreeSysroot *self,
*
* If %OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN is
* specified, then all current deployments will be kept.
+ *
+ * If %OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NOT_DEFAULT is
+ * specified, then instead of prepending, the new deployment will be
+ * added right after the booted or merge deployment, instead of first.
*/
gboolean
ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot,
@@ -1497,6 +1532,8 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot,
g_autoptr(GPtrArray) deployments = NULL;
g_autoptr(GPtrArray) new_deployments = g_ptr_array_new_with_free_func (g_object_unref);
gboolean retain = (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN) > 0;
+ const gboolean make_default = !((flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NOT_DEFAULT) > 0);
+ gboolean added_new = FALSE;
deployments = ostree_sysroot_get_deployments (sysroot);
booted_deployment = ostree_sysroot_get_booted_deployment (sysroot);
@@ -1504,23 +1541,44 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot,
if (osname == NULL && booted_deployment)
osname = ostree_deployment_get_osname (booted_deployment);
- g_ptr_array_add (new_deployments, g_object_ref (new_deployment));
+ if (make_default)
+ {
+ g_ptr_array_add (new_deployments, g_object_ref (new_deployment));
+ added_new = TRUE;
+ }
for (i = 0; i < deployments->len; i++)
{
OstreeDeployment *deployment = deployments->pdata[i];
+ const gboolean is_merge_or_booted =
+ ostree_deployment_equal (deployment, booted_deployment) ||
+ ostree_deployment_equal (deployment, merge_deployment);
/* Keep deployments with different osnames, as well as the
* booted and merge deployments
*/
if (retain ||
- (osname != NULL &&
- strcmp (ostree_deployment_get_osname (deployment), osname) != 0) ||
- ostree_deployment_equal (deployment, booted_deployment) ||
- ostree_deployment_equal (deployment, merge_deployment))
+ (osname != NULL && strcmp (ostree_deployment_get_osname (deployment), osname) != 0) ||
+ is_merge_or_booted)
{
g_ptr_array_add (new_deployments, g_object_ref (deployment));
}
+
+ if (!added_new)
+ {
+ g_ptr_array_add (new_deployments, g_object_ref (new_deployment));
+ added_new = TRUE;
+ }
+ }
+
+ /* In this non-default case , an improvement in the future would be
+ * to put the new deployment right after the current default in the
+ * order.
+ */
+ if (!added_new)
+ {
+ g_ptr_array_add (new_deployments, g_object_ref (new_deployment));
+ added_new = TRUE;
}
if (!ostree_sysroot_write_deployments (sysroot, new_deployments, cancellable, error))
@@ -1533,3 +1591,263 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot,
out:
return ret;
}
+
+static gboolean
+clone_deployment (OstreeSysroot *sysroot,
+ OstreeDeployment *target_deployment,
+ OstreeDeployment *merge_deployment,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ __attribute__((cleanup(_ostree_kernel_args_cleanup))) OstreeKernelArgs *kargs = NULL;
+ glnx_unref_object OstreeDeployment *new_deployment = NULL;
+
+ /* Ensure we have a clean slate */
+ if (!ostree_sysroot_prepare_cleanup (sysroot, cancellable, error))
+ {
+ g_prefix_error (error, "Performing initial cleanup: ");
+ goto out;
+ }
+
+ kargs = _ostree_kernel_args_new ();
+
+ { OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (merge_deployment);
+ g_auto(GStrv) previous_args = g_strsplit (ostree_bootconfig_parser_get (bootconfig, "options"), " ", -1);
+
+ _ostree_kernel_args_append_argv (kargs, previous_args);
+ }
+
+ {
+ g_auto(GStrv) kargs_strv = _ostree_kernel_args_to_strv (kargs);
+
+ if (!ostree_sysroot_deploy_tree (sysroot,
+ ostree_deployment_get_osname (target_deployment),
+ ostree_deployment_get_csum (target_deployment),
+ ostree_deployment_get_origin (target_deployment),
+ merge_deployment,
+ kargs_strv,
+ &new_deployment,
+ cancellable, error))
+ goto out;
+ }
+
+ /* Hotfixes push the deployment as rollback target, so it shouldn't
+ * be the default.
+ */
+ if (!ostree_sysroot_simple_write_deployment (sysroot, ostree_deployment_get_osname (target_deployment),
+ new_deployment, merge_deployment,
+ OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NOT_DEFAULT,
+ cancellable, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/**
+ * ostree_sysroot_deployment_unlock:
+ * @self: Sysroot
+ * @deployment: Deployment
+ * @unlocked_state: Transition to this unlocked state
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Configure the target deployment @deployment such that it
+ * is writable. There are multiple modes, essentially differing
+ * in whether or not any changes persist across reboot.
+ *
+ * The `OSTREE_DEPLOYMENT_UNLOCKED_HOTFIX` state is persistent
+ * across reboots.
+ */
+gboolean
+ostree_sysroot_deployment_unlock (OstreeSysroot *self,
+ OstreeDeployment *deployment,
+ OstreeDeploymentUnlockedState unlocked_state,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ OstreeDeploymentUnlockedState current_unlocked =
+ ostree_deployment_get_unlocked (deployment);
+ glnx_unref_object OstreeDeployment *deployment_clone =
+ ostree_deployment_clone (deployment);
+ glnx_unref_object OstreeDeployment *merge_deployment = NULL;
+ GKeyFile *origin_clone = ostree_deployment_get_origin (deployment_clone);
+ const char hotfix_ovl_options[] = "lowerdir=usr,upperdir=.usr-ovl-upper,workdir=.usr-ovl-work";
+ const char *ovl_options = NULL;
+ g_autofree char *deployment_path = NULL;
+ glnx_fd_close int deployment_dfd = -1;
+ pid_t mount_child;
+
+ /* This function cannot re-lock */
+ g_return_val_if_fail (unlocked_state != OSTREE_DEPLOYMENT_UNLOCKED_NONE, FALSE);
+
+ if (current_unlocked != OSTREE_DEPLOYMENT_UNLOCKED_NONE)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Deployment is already in unlocked state: %s",
+ ostree_deployment_unlocked_state_to_string (current_unlocked));
+ goto out;
+ }
+
+ merge_deployment = ostree_sysroot_get_merge_deployment (self, ostree_deployment_get_osname (deployment));
+ if (!merge_deployment)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No previous deployment to duplicate");
+ goto out;
+ }
+
+ /* For hotfixes, we push a rollback target */
+ if (unlocked_state == OSTREE_DEPLOYMENT_UNLOCKED_HOTFIX)
+ {
+ if (!clone_deployment (self, deployment, merge_deployment, cancellable, error))
+ goto out;
+ }
+
+ /* Crack it open */
+ if (!ostree_sysroot_deployment_set_mutable (self, deployment, TRUE,
+ cancellable, error))
+ goto out;
+
+ deployment_path = ostree_sysroot_get_deployment_dirpath (self, deployment);
+
+ if (!glnx_opendirat (self->sysroot_fd, deployment_path, TRUE, &deployment_dfd, error))
+ goto out;
+
+ switch (unlocked_state)
+ {
+ case OSTREE_DEPLOYMENT_UNLOCKED_NONE:
+ g_assert_not_reached ();
+ break;
+ case OSTREE_DEPLOYMENT_UNLOCKED_HOTFIX:
+ {
+ /* Create the overlayfs directories in the deployment root
+ * directly for hotfixes. The ostree-prepare-root.c helper
+ * is also set up to detect and mount these.
+ */
+ if (!glnx_shutil_mkdir_p_at (deployment_dfd, ".usr-ovl-upper", 0755, cancellable, error))
+ goto out;
+ if (!glnx_shutil_mkdir_p_at (deployment_dfd, ".usr-ovl-work", 0755, cancellable, error))
+ goto out;
+ ovl_options = hotfix_ovl_options;
+ }
+ break;
+ case OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT:
+ {
+ /* We're just doing transient development/hacking? Okay,
+ * stick the overlayfs bits in /var/tmp.
+ */
+ char *development_ovldir = strdupa ("/var/tmp/ostree-unlock-ovl.XXXXXX");
+ const char *development_ovl_upper;
+ const char *development_ovl_work;
+
+ if (!glnx_mkdtempat (AT_FDCWD, development_ovldir, 0700, error))
+ goto out;
+
+ development_ovl_upper = glnx_strjoina (development_ovldir, "/upper");
+ if (!glnx_shutil_mkdir_p_at (AT_FDCWD, development_ovl_upper, 0755, cancellable, error))
+ goto out;
+ development_ovl_work = glnx_strjoina (development_ovldir, "/work");
+ if (!glnx_shutil_mkdir_p_at (AT_FDCWD, development_ovl_work, 0755, cancellable, error))
+ goto out;
+ ovl_options = glnx_strjoina ("lowerdir=usr,upperdir=", development_ovl_upper,
+ ",workdir=", development_ovl_work);
+ }
+ }
+
+ g_assert (ovl_options != NULL);
+
+ /* Here we run `mount()` in a fork()ed child because we need to use
+ * `chdir()` in order to have the mount path options to overlayfs not
+ * look ugly.
+ *
+ * We can't `chdir()` inside a shared library since there may be
+ * threads, etc.
+ */
+ {
+ /* Make a copy of the fd that's *not* FD_CLOEXEC so that we pass
+ * it to the child.
+ */
+ glnx_fd_close int child_deployment_dfd = dup (deployment_dfd);
+
+ if (child_deployment_dfd < 0)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ mount_child = fork ();
+ if (mount_child < 0)
+ {
+ glnx_set_prefix_error_from_errno (error, "%s", "fork");
+ goto out;
+ }
+ else if (mount_child == 0)
+ {
+ /* Child process. Do NOT use any GLib API here. */
+ if (fchdir (child_deployment_dfd) < 0)
+ exit (EXIT_FAILURE);
+ (void) close (child_deployment_dfd);
+ if (mount ("overlay", "/usr", "overlay", 0, ovl_options) < 0)
+ exit (EXIT_FAILURE);
+ exit (EXIT_SUCCESS);
+ }
+ else
+ {
+ /* Parent */
+ int estatus;
+
+ if (TEMP_FAILURE_RETRY (waitpid (mount_child, &estatus, 0)) < 0)
+ {
+ glnx_set_prefix_error_from_errno (error, "%s", "waitpid() on mount helper");
+ goto out;
+ }
+ if (!g_spawn_check_exit_status (estatus, error))
+ {
+ g_prefix_error (error, "overlayfs mount helper: ");
+ goto out;
+ }
+ }
+ }
+
+ /* Now, write out the flag saying what we did */
+ switch (unlocked_state)
+ {
+ case OSTREE_DEPLOYMENT_UNLOCKED_NONE:
+ g_assert_not_reached ();
+ break;
+ case OSTREE_DEPLOYMENT_UNLOCKED_HOTFIX:
+ g_key_file_set_string (origin_clone, "origin", "unlocked",
+ ostree_deployment_unlocked_state_to_string (unlocked_state));
+ if (!ostree_sysroot_write_origin_file (self, deployment, origin_clone,
+ cancellable, error))
+ goto out;
+ break;
+ case OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT:
+ {
+ g_autofree char *devpath = get_unlocked_development_path (deployment);
+ g_autofree char *devpath_parent = dirname (g_strdup (devpath));
+
+ if (!glnx_shutil_mkdir_p_at (AT_FDCWD, devpath_parent, 0755, cancellable, error))
+ goto out;
+
+ if (!g_file_set_contents (devpath, "", 0, error))
+ goto out;
+ }
+ }
+
+ /* For hotfixes we already pushed a rollback which will bump the
+ * mtime, but we need to bump it again so that clients get the state
+ * change for this deployment. For development we need to do this
+ * regardless.
+ */
+ if (!_ostree_sysroot_bump_mtime (self, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
diff --git a/src/libostree/ostree-sysroot.h b/src/libostree/ostree-sysroot.h
index 077862aa..1e60ddbe 100644
--- a/src/libostree/ostree-sysroot.h
+++ b/src/libostree/ostree-sysroot.h
@@ -163,6 +163,13 @@ gboolean ostree_sysroot_deployment_set_mutable (OstreeSysroot *self,
GCancellable *cancellable,
GError **error);
+_OSTREE_PUBLIC
+gboolean ostree_sysroot_deployment_unlock (OstreeSysroot *self,
+ OstreeDeployment *deployment,
+ OstreeDeploymentUnlockedState unlocked_state,
+ GCancellable *cancellable,
+ GError **error);
+
_OSTREE_PUBLIC
OstreeDeployment *ostree_sysroot_get_merge_deployment (OstreeSysroot *self,
const char *osname);
@@ -174,7 +181,8 @@ GKeyFile *ostree_sysroot_origin_new_from_refspec (OstreeSysroot *self,
typedef enum {
OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NONE = 0,
- OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN = (1 << 0)
+ OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN = (1 << 0),
+ OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NOT_DEFAULT = (1 << 1)
} OstreeSysrootSimpleWriteDeploymentFlags;
_OSTREE_PUBLIC
diff --git a/src/ostree/ot-admin-builtin-status.c b/src/ostree/ot-admin-builtin-status.c
index cd275cc6..df4d0745 100644
--- a/src/ostree/ot-admin-builtin-status.c
+++ b/src/ostree/ot-admin-builtin-status.c
@@ -89,6 +89,9 @@ ot_admin_builtin_status (int argc, char **argv, GCancellable *cancellable, GErro
glnx_unref_object OstreeRepo *repo = NULL;
OstreeDeployment *booted_deployment = NULL;
g_autoptr(GPtrArray) deployments = NULL;
+ const int is_tty = isatty (1);
+ const char *red_bold_prefix = is_tty ? "\x1b[31m\x1b[1m" : "";
+ const char *red_bold_suffix = is_tty ? "\x1b[22m\x1b[0m" : "";
guint i;
context = g_option_context_new ("List deployments");
@@ -118,12 +121,15 @@ ot_admin_builtin_status (int argc, char **argv, GCancellable *cancellable, GErro
OstreeDeployment *deployment = deployments->pdata[i];
GKeyFile *origin;
const char *ref = ostree_deployment_get_csum (deployment);
+ OstreeDeploymentUnlockedState unlocked = ostree_deployment_get_unlocked (deployment);
g_autofree char *version = version_of_commit (repo, ref);
glnx_unref_object OstreeGpgVerifyResult *result = NULL;
GString *output_buffer;
guint jj, n_signatures;
GError *local_error = NULL;
+ origin = ostree_deployment_get_origin (deployment);
+
g_print ("%c %s %s.%d\n",
deployment == booted_deployment ? '*' : ' ',
ostree_deployment_get_osname (deployment),
@@ -131,7 +137,15 @@ ot_admin_builtin_status (int argc, char **argv, GCancellable *cancellable, GErro
ostree_deployment_get_deployserial (deployment));
if (version)
g_print (" Version: %s\n", version);
- origin = ostree_deployment_get_origin (deployment);
+ switch (unlocked)
+ {
+ case OSTREE_DEPLOYMENT_UNLOCKED_NONE:
+ break;
+ default:
+ g_print (" %sUnlocked: %s%s\n", red_bold_prefix,
+ ostree_deployment_unlocked_state_to_string (unlocked),
+ red_bold_suffix);
+ }
if (!origin)
g_print (" origin: none\n");
else
diff --git a/src/ostree/ot-admin-builtin-unlock.c b/src/ostree/ot-admin-builtin-unlock.c
new file mode 100644
index 00000000..9d265325
--- /dev/null
+++ b/src/ostree/ot-admin-builtin-unlock.c
@@ -0,0 +1,104 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2016 Colin Walters
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "ot-main.h"
+#include "ot-admin-builtins.h"
+#include "ot-admin-functions.h"
+#include "ostree.h"
+#include "otutil.h"
+
+#include "../libostree/ostree-kernel-args.h"
+
+#include
+#include
+
+static gboolean opt_hotfix;
+
+static GOptionEntry options[] = {
+ { "hotfix", 0, 0, G_OPTION_ARG_NONE, &opt_hotfix, "Keep the current deployment as default", NULL },
+ { NULL }
+};
+
+gboolean
+ot_admin_builtin_unlock (int argc, char **argv, GCancellable *cancellable, GError **error)
+{
+ gboolean ret = FALSE;
+ GOptionContext *context;
+ glnx_unref_object OstreeSysroot *sysroot = NULL;
+ glnx_unref_object OstreeRepo *repo = NULL;
+ g_autoptr(GPtrArray) new_deployments = NULL;
+ glnx_unref_object OstreeDeployment *merge_deployment = NULL;
+ OstreeDeployment *booted_deployment = NULL;
+ OstreeDeploymentUnlockedState target_state;
+
+ context = g_option_context_new ("Make the current deployment mutable (as a hotfix or development)");
+
+ if (!ostree_admin_option_context_parse (context, options, &argc, &argv,
+ OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER,
+ &sysroot, cancellable, error))
+ goto out;
+
+ if (argc > 1)
+ {
+ ot_util_usage_error (context, "This command takes no extra arguments", error);
+ goto out;
+ }
+
+ if (!ostree_sysroot_load (sysroot, cancellable, error))
+ goto out;
+
+ booted_deployment = ostree_sysroot_get_booted_deployment (sysroot);
+ if (!booted_deployment)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Not currently booted into an OSTree system");
+ goto out;
+ }
+
+ target_state = opt_hotfix ? OSTREE_DEPLOYMENT_UNLOCKED_HOTFIX : OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT;
+
+ if (!ostree_sysroot_deployment_unlock (sysroot, booted_deployment,
+ target_state, cancellable, error))
+ goto out;
+
+ switch (target_state)
+ {
+ case OSTREE_DEPLOYMENT_UNLOCKED_NONE:
+ g_assert_not_reached ();
+ break;
+ case OSTREE_DEPLOYMENT_UNLOCKED_HOTFIX:
+ g_print ("Hotfix mode enabled. A writable overlayfs is now mounted on /usr\n"
+ "for this booted deployment. A non-hotfixed clone has been created\n"
+ "as the non-default rollback target.\n");
+ break;
+ case OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT:
+ g_print ("Development mode enabled. A writable overlayfs is now mounted on /usr.\n"
+ "All changes there will be discarded on reboot.\n");
+ break;
+ }
+
+ ret = TRUE;
+ out:
+ if (context)
+ g_option_context_free (context);
+ return ret;
+}
diff --git a/src/ostree/ot-admin-builtin-upgrade.c b/src/ostree/ot-admin-builtin-upgrade.c
index 5d4796ac..b3b531c0 100644
--- a/src/ostree/ot-admin-builtin-upgrade.c
+++ b/src/ostree/ot-admin-builtin-upgrade.c
@@ -97,6 +97,9 @@ ot_admin_builtin_upgrade (int argc, char **argv, GCancellable *cancellable, GErr
"override-commit", NULL);
}
+ /* Should we consider requiring --discard-hotfix here? */
+ origin_changed |= g_key_file_remove_key (origin, "origin", "unlocked", NULL);
+
if (origin_changed)
{
/* XXX GCancellable parameter is not used. */
diff --git a/src/ostree/ot-admin-builtins.h b/src/ostree/ot-admin-builtins.h
index 1a3c1264..f9425119 100644
--- a/src/ostree/ot-admin-builtins.h
+++ b/src/ostree/ot-admin-builtins.h
@@ -34,6 +34,7 @@ gboolean ot_admin_builtin_init_fs (int argc, char **argv, GCancellable *cancella
gboolean ot_admin_builtin_undeploy (int argc, char **argv, GCancellable *cancellable, GError **error);
gboolean ot_admin_builtin_deploy (int argc, char **argv, GCancellable *cancellable, GError **error);
gboolean ot_admin_builtin_cleanup (int argc, char **argv, GCancellable *cancellable, GError **error);
+gboolean ot_admin_builtin_unlock (int argc, char **argv, GCancellable *cancellable, GError **error);
gboolean ot_admin_builtin_status (int argc, char **argv, GCancellable *cancellable, GError **error);
gboolean ot_admin_builtin_set_origin (int argc, char **argv, GCancellable *cancellable, GError **error);
gboolean ot_admin_builtin_diff (int argc, char **argv, GCancellable *cancellable, GError **error);
diff --git a/src/ostree/ot-builtin-admin.c b/src/ostree/ot-builtin-admin.c
index a4cb0dde..8b866170 100644
--- a/src/ostree/ot-builtin-admin.c
+++ b/src/ostree/ot-builtin-admin.c
@@ -47,6 +47,7 @@ static OstreeAdminCommand admin_subcommands[] = {
{ "status", ot_admin_builtin_status },
{ "switch", ot_admin_builtin_switch },
{ "undeploy", ot_admin_builtin_undeploy },
+ { "unlock", ot_admin_builtin_unlock },
{ "upgrade", ot_admin_builtin_upgrade },
{ NULL, NULL }
};
diff --git a/src/switchroot/ostree-mount-util.c b/src/switchroot/ostree-mount-util.c
index c6df559c..daec66c5 100644
--- a/src/switchroot/ostree-mount-util.c
+++ b/src/switchroot/ostree-mount-util.c
@@ -26,6 +26,10 @@
#include
#include
#include
+#include
+#include
+#include
+#include
#include "ostree-mount-util.h"
@@ -48,3 +52,18 @@ perrorv (const char *format, ...)
return 0;
}
+
+int
+path_is_on_readonly_fs (char *path)
+{
+ struct statvfs stvfsbuf;
+
+ if (statvfs (path, &stvfsbuf) == -1)
+ {
+ perrorv ("statvfs(%s): ", path);
+ exit (EXIT_FAILURE);
+ }
+
+ return (stvfsbuf.f_flag & ST_RDONLY) != 0;
+}
+
diff --git a/src/switchroot/ostree-mount-util.h b/src/switchroot/ostree-mount-util.h
index 63e90c67..475b2cab 100644
--- a/src/switchroot/ostree-mount-util.h
+++ b/src/switchroot/ostree-mount-util.h
@@ -22,3 +22,5 @@
#pragma once
int perrorv (const char *format, ...) __attribute__ ((format (printf, 1, 2)));
+
+int path_is_on_readonly_fs (char *path);
diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c
index 3de137bb..375867b1 100644
--- a/src/switchroot/ostree-prepare-root.c
+++ b/src/switchroot/ostree-prepare-root.c
@@ -111,7 +111,6 @@ touch_run_ostree (void)
int
main(int argc, char *argv[])
{
- const char *readonly_bind_mounts[] = { "/usr", NULL };
const char *root_mountpoint = NULL;
char *ostree_target = NULL;
char *deploy_path = NULL;
@@ -119,7 +118,7 @@ main(int argc, char *argv[])
char destpath[PATH_MAX];
char newroot[PATH_MAX];
struct stat stbuf;
- int i;
+ int orig_cwd_dfd;
if (argc < 2)
{
@@ -211,21 +210,70 @@ main(int argc, char *argv[])
}
}
- /* Set up any read-only bind mounts (notably /usr) */
- for (i = 0; readonly_bind_mounts[i] != NULL; i++)
+ /* Here we do a dance to chdir to the newroot so that we can have
+ * the potential overlayfs mount points not look ugly. However...I
+ * think we could do this a lot earlier and make all of the mounts
+ * here just be relative.
+ */
+ orig_cwd_dfd = openat (AT_FDCWD, ".", O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY);
+ if (orig_cwd_dfd < 0)
{
- snprintf (destpath, sizeof(destpath), "%s%s", newroot, readonly_bind_mounts[i]);
- if (mount (destpath, destpath, NULL, MS_BIND, NULL) < 0)
+ perrorv ("failed to open .");
+ exit (EXIT_FAILURE);
+ }
+
+ if (chdir (newroot) < 0)
+ {
+ perrorv ("failed to chdir to newroot");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Do we have a persistent overlayfs for /usr? If so, mount it now. */
+ if (lstat (".usr-ovl-work", &stbuf) == 0)
+ {
+ const char usr_ovl_options[] = "lowerdir=usr,upperdir=.usr-ovl-upper,workdir=.usr-ovl-work";
+
+ /* Except overlayfs barfs if we try to mount it on a read-only
+ * filesystem. For this use case I think admins are going to be
+ * okay if we remount the rootfs here, rather than waiting until
+ * later boot and `systemd-remount-fs.service`.
+ */
+ if (path_is_on_readonly_fs ("."))
{
- perrorv ("failed to bind mount (class:readonly) %s", destpath);
- exit (EXIT_FAILURE);
+ if (mount (".", ".", NULL, MS_REMOUNT | MS_SILENT, NULL) < 0)
+ {
+ perrorv ("Failed to remount rootfs writable (for overlayfs)");
+ exit (EXIT_FAILURE);
+ }
}
- if (mount (destpath, destpath, NULL, MS_BIND | MS_REMOUNT | MS_RDONLY, NULL) < 0)
+
+ if (mount ("overlay", "usr", "overlay", 0, usr_ovl_options) < 0)
{
- perrorv ("failed to bind mount (class:readonly) %s", destpath);
+ perrorv ("failed to mount /usr overlayfs");
exit (EXIT_FAILURE);
}
}
+ else
+ {
+ /* Otherwise, a read-only bind mount for /usr */
+ if (mount ("usr", "usr", NULL, MS_BIND, NULL) < 0)
+ {
+ perrorv ("failed to bind mount (class:readonly) /usr");
+ exit (EXIT_FAILURE);
+ }
+ if (mount ("usr", "usr", NULL, MS_BIND | MS_REMOUNT | MS_RDONLY, NULL) < 0)
+ {
+ perrorv ("failed to bind mount (class:readonly) /usr");
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ if (fchdir (orig_cwd_dfd) < 0)
+ {
+ perrorv ("failed to chdir to orig root");
+ exit (EXIT_FAILURE);
+ }
+ (void) close (orig_cwd_dfd);
touch_run_ostree ();
diff --git a/src/switchroot/ostree-remount.c b/src/switchroot/ostree-remount.c
index b8d3a963..aecaf9a8 100644
--- a/src/switchroot/ostree-remount.c
+++ b/src/switchroot/ostree-remount.c
@@ -37,20 +37,6 @@
#include "ostree-mount-util.h"
-static int
-path_is_on_readonly_fs (char *path)
-{
- struct statvfs stvfsbuf;
-
- if (statvfs (path, &stvfsbuf) == -1)
- {
- perrorv ("statvfs(%s): ", path);
- exit (EXIT_FAILURE);
- }
-
- return (stvfsbuf.f_flag & ST_RDONLY) != 0;
-}
-
/* Having a writeable /var is necessary for full system functioning.
* If /var isn't writeable, we mount tmpfs over it. While this is
* somewhat outside of ostree's scope, having all /var twiddling