diff --git a/man/ostree-prepare-root.xml b/man/ostree-prepare-root.xml
index 820e6a278e..c6581cfced 100644
--- a/man/ostree-prepare-root.xml
+++ b/man/ostree-prepare-root.xml
@@ -113,6 +113,10 @@ License along with this library. If not, see .
sysroot.readonly
A boolean value; the default is false. If this is set to true, then the /sysroot mount point is mounted read-only.
+
+ sysroot.etc
+ A string value; the default is persitent. If this is set to transient, then the /sysroot mount point is mounted transiently i.e. a non-persistent location.
+
composefs.enabled
This can be yes, no. maybe or
diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c
index db76de1221..c9f3282c47 100644
--- a/src/switchroot/ostree-prepare-root.c
+++ b/src/switchroot/ostree-prepare-root.c
@@ -84,6 +84,7 @@ const char *config_roots[] = { "/usr/lib", "/etc" };
#define SYSROOT_KEY "sysroot"
#define READONLY_KEY "readonly"
+#define ETC_KEY "etc" // Possible values = "persistent" "transient"
#define COMPOSEFS_KEY "composefs"
#define ENABLED_KEY "enabled"
@@ -349,6 +350,29 @@ load_composefs_config (GKeyFile *config, GError **error)
return g_steal_pointer (&ret);
}
+static gboolean
+copy_selinux_context (const char *src_path, const char *dst_path, GError **error)
+{
+ ssize_t bytes_read, real_size;
+
+ if (TEMP_FAILURE_RETRY (bytes_read = lgetxattr (src_path, "security.selinux", NULL, 0)) < 0)
+ {
+ if (errno == ENODATA || errno == ENOTSUP)
+ return TRUE; /* no selinux context, we're done */
+ return glnx_throw_errno_prefix (error, "lgetxattr(security.selinux)");
+ }
+
+ g_autofree guint8 *buf = g_malloc (bytes_read);
+ if (TEMP_FAILURE_RETRY (real_size = lgetxattr (src_path, "security.selinux", buf, bytes_read))
+ < 0)
+ return glnx_throw_errno_prefix (error, "lgetxattr(security.selinux)");
+
+ if (lsetxattr (dst_path, "security.selinux", buf, real_size, 0) < 0)
+ return glnx_throw_errno_prefix (error, "lsetxattr(security.selinux)");
+
+ return TRUE;
+}
+
int
main (int argc, char *argv[])
{
@@ -582,18 +606,71 @@ main (int argc, char *argv[])
}
}
+ g_autofree char *etc_config = NULL;
+ if (!ot_keyfile_get_value_with_default (config, SYSROOT_KEY, ETC_KEY, "persistent", &etc_config,
+ &error))
+ errx (EXIT_FAILURE, "failed to parse %s.%s: %s", SYSROOT_KEY, ETC_KEY, error->message);
+ bool etc_transient = false;
+ if (g_str_equal (etc_config, "persistent"))
+ etc_transient = false;
+ else if (g_str_equal (etc_config, "transient"))
+ etc_transient = true;
+ else
+ errx (EXIT_FAILURE, "Invalid %s.%s: %s", SYSROOT_KEY, ETC_KEY, etc_config);
+
+ // In theory these could be distinct, but no reason to try to support it.
+ if (etc_transient && !sysroot_readonly)
+ errx (EXIT_FAILURE, "Must specify %s.%s for %s.%s=transient", SYSROOT_KEY, READONLY_KEY,
+ SYSROOT_KEY, ETC_KEY);
+
/* Prepare /etc.
* No action required if sysroot is writable. Otherwise, a bind-mount for
* the deployment needs to be created and remounted as read/write. */
if (sysroot_readonly || using_composefs)
{
- /* Bind-mount /etc (at deploy path), and remount as writable. */
- if (mount ("etc", TMP_SYSROOT "/etc", NULL, MS_BIND | MS_SILENT, NULL) < 0)
- err (EXIT_FAILURE, "failed to prepare /etc bind-mount at /sysroot.tmp/etc");
- if (mount (TMP_SYSROOT "/etc", TMP_SYSROOT "/etc", NULL, MS_BIND | MS_REMOUNT | MS_SILENT,
- NULL)
- < 0)
- err (EXIT_FAILURE, "failed to make writable /etc bind-mount at /sysroot.tmp/etc");
+ if (etc_transient)
+ {
+ /* Do we have a persistent overlayfs for /usr? If so, mount it now. */
+ g_autofree char *etc_ovldir
+ = g_build_filename (OTCORE_RUN_OSTREE_PRIVATE, "etc-transient", NULL);
+ g_autofree char *upper = g_build_filename (etc_ovldir, "upper", NULL);
+ g_autofree char *work = g_build_filename (etc_ovldir, "work", NULL);
+
+ if (mkdirat (AT_FDCWD, etc_ovldir, 0700) < 0)
+ err (EXIT_FAILURE, "Failed to create %s", etc_ovldir);
+ if (mkdirat (AT_FDCWD, upper, 0755) < 0)
+ err (EXIT_FAILURE, "Failed to create %s", upper);
+ if (mkdirat (AT_FDCWD, work, 0755) < 0)
+ err (EXIT_FAILURE, "Failed to create %s", work);
+
+ g_autofree char *etc_ovl_options = g_strdup_printf ("lowerdir=%s,upperdir=%s,workdir=%s",
+ TMP_SYSROOT "/usr/etc", upper, work);
+ if (mount ("overlay", TMP_SYSROOT "/etc", "overlay", MS_SILENT, etc_ovl_options) < 0)
+ err (EXIT_FAILURE, "failed to mount transient etc overlayfs");
+
+ g_autoptr (GError) local_error = NULL;
+ if (!copy_selinux_context (TMP_SYSROOT "/usr/etc", TMP_SYSROOT "/etc", &local_error))
+ err (EXIT_FAILURE, "failed to copy /usr/etc selinux label: %s", local_error->message);
+
+ /* We make ovldir read-only to avoid it being relabeled to
+ * var_run_t when /run is relabeled */
+ if (mount (etc_ovldir, etc_ovldir, NULL, MS_BIND | MS_SILENT, NULL) < 0)
+ err (EXIT_FAILURE, "failed to bind mount (class:readonly) %s", etc_ovldir);
+ if (mount (etc_ovldir, etc_ovldir, NULL, MS_BIND | MS_REMOUNT | MS_RDONLY | MS_SILENT,
+ NULL)
+ < 0)
+ err (EXIT_FAILURE, "failed to bind mount (class:readonly) %s", etc_ovldir);
+ }
+ else
+ {
+ /* Bind-mount /etc (at deploy path), and remount as writable. */
+ if (mount ("etc", TMP_SYSROOT "/etc", NULL, MS_BIND | MS_SILENT, NULL) < 0)
+ err (EXIT_FAILURE, "failed to prepare /etc bind-mount at /sysroot.tmp/etc");
+ if (mount (TMP_SYSROOT "/etc", TMP_SYSROOT "/etc", NULL, MS_BIND | MS_REMOUNT | MS_SILENT,
+ NULL)
+ < 0)
+ err (EXIT_FAILURE, "failed to make writable /etc bind-mount at /sysroot.tmp/etc");
+ }
}
/* Prepare /usr.