mirror of
https://github.com/systemd/systemd.git
synced 2024-11-23 18:23:32 +08:00
Merge pull request #31000 from flatcar-hub/krnowak/mutable-overlays
systemd-sysext: Implement optional mutability for extensions
This commit is contained in:
commit
e5191faf44
@ -69,8 +69,10 @@
|
||||
<filename>/var/</filename> included in a system extension image will <emphasis>not</emphasis> appear in
|
||||
the respective hierarchies after activation.</para>
|
||||
|
||||
<para>System extension images are strictly read-only, and the host <filename>/usr/</filename> and
|
||||
<filename>/opt/</filename> hierarchies become read-only too while they are activated.</para>
|
||||
<para>System extension images are strictly read-only by default. On mutable host file systems,
|
||||
<filename>/usr/</filename> and <filename>/opt/</filename> hierarchies become read-only while extensions
|
||||
are merged, unless mutability is enabled. Mutability may be enabled via the <option>--mutable=</option>
|
||||
option; see "Mutability" below for more information.</para>
|
||||
|
||||
<para>System extensions are supposed to be purely additive, i.e. they are supposed to include only files
|
||||
that do not exist in the underlying basic OS image. However, the underlying mechanism (overlayfs) also
|
||||
@ -158,6 +160,11 @@
|
||||
same as sysext images. The merged hierarchy will be mounted with <literal>nosuid</literal> and
|
||||
(if not disabled via <option>--noexec=false</option>) <literal>noexec</literal>.</para>
|
||||
|
||||
<para>Just like sysexts, confexts are strictly read-only by default. Merging confexts on mutable host
|
||||
file systems will result in <filename>/etc/</filename> becoming read-only. As with sysexts, mutability
|
||||
can be enabled via the <option>--mutable=</option> option. Refer to "Mutability" below for more
|
||||
information.</para>
|
||||
|
||||
<para>Confexts are looked for in the directories <filename>/run/confexts/</filename>,
|
||||
<filename>/var/lib/confexts/</filename>, <filename>/usr/lib/confexts/</filename> and
|
||||
<filename>/usr/local/lib/confexts/</filename>. The first listed directory is not suitable for
|
||||
@ -205,6 +212,55 @@
|
||||
to tie the most frequently configured options to runtime updateable flags that can be changed without a
|
||||
system reboot. This will help reduce servicing times when there is a need for changing the OS configuration.</para></refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Mutability</title>
|
||||
<para>By default, merging system extensions on mutable host file systems will render <filename>/usr/</filename>
|
||||
and <filename>/opt/</filename> hierarchies read-only. Merging configuration extensions will have the same
|
||||
effect on <filename>/etc/</filename>. Mutable mode allows writes to these locations when extensions are
|
||||
merged.</para>
|
||||
|
||||
<para>The following modes are supported:
|
||||
<orderedlist>
|
||||
<listitem><para><option>disabled</option>: Force immutable mode even if write routing
|
||||
directories exist below <filename>/var/lib/extensions.mutable/</filename>.
|
||||
This is the default.</para></listitem>
|
||||
<listitem><para><option>auto</option>: Automatic mode. Mutability is disabled by default
|
||||
and only enabled if a corresponding write routing directory exists below
|
||||
<filename>/var/lib/extensions.mutable/</filename>.</para></listitem>
|
||||
<listitem><para><option>enabled</option>: Force mutable mode and automatically create write routing
|
||||
directories below <filename>/var/lib/extensions.mutable/</filename> when required.</para></listitem>
|
||||
<listitem><para><option>import</option>: Force immutable mode like <option>disabled</option> above, but
|
||||
merge the contents of directories below <filename>/var/lib/extensions.mutable/</filename> into the host
|
||||
file system.</para></listitem>
|
||||
</orderedlist>
|
||||
See "Options" below on specifying modes using the <option>--mutable=</option> command line option.</para>
|
||||
|
||||
<para>Mutable mode routes writes to subdirectories in <filename>/var/lib/extensions.mutable/</filename>.
|
||||
<simplelist type="horiz">
|
||||
<member>Writes to <filename>/usr/</filename> are directed to <filename>/var/lib/extensions.mutable/usr/</filename></member>,
|
||||
<member>writes to <filename>/opt/</filename> are directed to <filename>/var/lib/extensions.mutable/opt/</filename>, and</member>
|
||||
<member>writes to <filename>/etc/</filename> land in <filename>/var/lib/extensions.mutable/etc/</filename>.</member>
|
||||
</simplelist></para>
|
||||
|
||||
<para>If <filename>usr/</filename>, <filename>opt/</filename>, or <filename>etc/</filename>
|
||||
in <filename>/var/lib/extensions.mutable/</filename> are symlinks, then writes are directed to the
|
||||
symlinks' targets.
|
||||
Consequently, to retain mutability of a host file system, create symlinks
|
||||
<simplelist type="horiz">
|
||||
<member><filename>/var/lib/extensions.mutable/etc/</filename> → <filename>/etc/</filename></member>
|
||||
<member><filename>/var/lib/extensions.mutable/usr/</filename> → <filename>/usr/</filename></member>
|
||||
<member><filename>/var/lib/extensions.mutable/opt/</filename> → <filename>/opt/</filename></member>
|
||||
</simplelist>
|
||||
to route writes back to the original base directory hierarchy.</para>
|
||||
|
||||
<para> Alternatively, a temporary file system may be mounted to
|
||||
<filename>/var/lib/extensions.mutable/</filename>, or symlinks in
|
||||
<filename>/var/lib/extensions.mutable/</filename> may point to sub-directories on a temporary
|
||||
file system (e.g. below <filename>/tmp/</filename>) to only allow ephemeral changes.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Commands</title>
|
||||
|
||||
@ -313,6 +369,45 @@
|
||||
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--mutable=</option><replaceable>BOOL</replaceable>|<replaceable>auto</replaceable>|<replaceable>import</replaceable></term>
|
||||
<listitem><para>Set mutable mode.</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>no</option></term>
|
||||
<listitem><para>force immutable mode even with write routing directories present.
|
||||
This is the default.</para>
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>auto</option></term>
|
||||
<listitem><para>enable mutable mode individually for <filename>/usr/</filename>,
|
||||
<filename>/opt/</filename>, and <filename>/etc/</filename> if write routing sub-directories
|
||||
or symlinks are present in <filename>/var/lib/extensions.mutable/</filename>; disable otherwise.
|
||||
See "Mutability" above for more information on write routing.</para>
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>yes</option></term>
|
||||
<listitem><para>force mutable mode. Write routing directories will be created in
|
||||
<filename>/var/lib/extensions.mutable/</filename> if not present.</para>
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>import</option></term>
|
||||
<listitem><para>immutable mode, but with contents of write routing directories in
|
||||
<filename>/var/lib/extensions.mutable/</filename> also merged into the host file system.</para>
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--noexec=</option><replaceable>BOOL</replaceable></term>
|
||||
|
||||
|
@ -453,6 +453,16 @@ int bind_remount_one_with_mountinfo(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bind_remount_one(const char *path, unsigned long new_flags, unsigned long flags_mask) {
|
||||
_cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
|
||||
|
||||
proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
|
||||
if (!proc_self_mountinfo)
|
||||
return log_debug_errno(errno, "Failed to open /proc/self/mountinfo: %m");
|
||||
|
||||
return bind_remount_one_with_mountinfo(path, new_flags, flags_mask, proc_self_mountinfo);
|
||||
}
|
||||
|
||||
static int mount_switch_root_pivot(int fd_newroot, const char *path) {
|
||||
assert(fd_newroot >= 0);
|
||||
assert(path);
|
||||
|
@ -26,6 +26,7 @@ static inline int bind_remount_recursive(const char *prefix, unsigned long new_f
|
||||
}
|
||||
|
||||
int bind_remount_one_with_mountinfo(const char *path, unsigned long new_flags, unsigned long flags_mask, FILE *proc_self_mountinfo);
|
||||
int bind_remount_one(const char *path, unsigned long new_flags, unsigned long flags_mask);
|
||||
|
||||
int mount_switch_root_full(const char *path, unsigned long mount_propagation_flag, bool force_ms_move);
|
||||
static inline int mount_switch_root(const char *path, unsigned long mount_propagation_flag) {
|
||||
|
@ -39,15 +39,27 @@
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "process-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "sort-util.h"
|
||||
#include "string-util.h"
|
||||
#include "terminal-util.h"
|
||||
#include "user-util.h"
|
||||
#include "varlink.h"
|
||||
#include "varlink-io.systemd.sysext.h"
|
||||
#include "verbs.h"
|
||||
|
||||
typedef enum MutableMode {
|
||||
MUTABLE_YES,
|
||||
MUTABLE_NO,
|
||||
MUTABLE_AUTO,
|
||||
MUTABLE_IMPORT,
|
||||
_MUTABLE_MAX,
|
||||
_MUTABLE_INVALID = -EINVAL,
|
||||
} MutableMode;
|
||||
|
||||
static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */
|
||||
static char *arg_root = NULL;
|
||||
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
|
||||
@ -58,6 +70,7 @@ static bool arg_no_reload = false;
|
||||
static int arg_noexec = -1;
|
||||
static ImagePolicy *arg_image_policy = NULL;
|
||||
static bool arg_varlink = false;
|
||||
static MutableMode arg_mutable = MUTABLE_NO;
|
||||
|
||||
/* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
|
||||
static ImageClass arg_image_class = IMAGE_SYSEXT;
|
||||
@ -252,11 +265,22 @@ static int unmerge_hierarchy(
|
||||
ImageClass image_class,
|
||||
const char *p) {
|
||||
|
||||
_cleanup_free_ char *dot_dir = NULL, *work_dir_info_file = NULL;
|
||||
int r;
|
||||
|
||||
assert(p);
|
||||
|
||||
dot_dir = path_join(p, image_class_info[image_class].dot_directory_name);
|
||||
if (!dot_dir)
|
||||
return log_oom();
|
||||
|
||||
work_dir_info_file = path_join(dot_dir, "work_dir");
|
||||
if (!work_dir_info_file)
|
||||
return log_oom();
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *escaped_work_dir_in_root = NULL, *work_dir = NULL;
|
||||
|
||||
/* We only unmount /usr/ if it is a mount point and really one of ours, in order not to break
|
||||
* systems where /usr/ is a mount point of its own already. */
|
||||
|
||||
@ -266,9 +290,40 @@ static int unmerge_hierarchy(
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
r = read_one_line_file(work_dir_info_file, &escaped_work_dir_in_root);
|
||||
if (r < 0) {
|
||||
if (r != -ENOENT)
|
||||
return log_error_errno(r, "Failed to read '%s': %m", work_dir_info_file);
|
||||
} else {
|
||||
_cleanup_free_ char *work_dir_in_root = NULL;
|
||||
ssize_t l;
|
||||
|
||||
l = cunescape_length(escaped_work_dir_in_root, r, 0, &work_dir_in_root);
|
||||
if (l < 0)
|
||||
return log_error_errno(l, "Failed to unescape work directory path: %m");
|
||||
work_dir = path_join(arg_root, work_dir_in_root);
|
||||
if (!work_dir)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = umount_verbose(LOG_DEBUG, dot_dir, MNT_DETACH|UMOUNT_NOFOLLOW);
|
||||
if (r < 0) {
|
||||
/* EINVAL is possibly "not a mount point". Let it slide as it's expected to occur if
|
||||
* the whole hierarchy was read-only, so the dot directory inside it was not
|
||||
* bind-mounted as read-only. */
|
||||
if (r != -EINVAL)
|
||||
return log_error_errno(r, "Failed to unmount '%s': %m", dot_dir);
|
||||
}
|
||||
|
||||
r = umount_verbose(LOG_ERR, p, MNT_DETACH|UMOUNT_NOFOLLOW);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to unmount file system '%s': %m", p);
|
||||
return r;
|
||||
|
||||
if (work_dir) {
|
||||
r = rm_rf(work_dir, REMOVE_ROOT | REMOVE_MISSING_OK | REMOVE_PHYSICAL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to remove '%s': %m", work_dir);
|
||||
}
|
||||
|
||||
log_info("Unmerged '%s'.", p);
|
||||
}
|
||||
@ -478,11 +533,38 @@ static int verb_status(int argc, char **argv, void *userdata) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int append_overlayfs_path_option(
|
||||
char **options,
|
||||
const char *separator,
|
||||
const char *option,
|
||||
const char *path) {
|
||||
|
||||
_cleanup_free_ char *escaped = NULL;
|
||||
|
||||
assert(options);
|
||||
assert(separator);
|
||||
assert(path);
|
||||
|
||||
escaped = shell_escape(path, ",:");
|
||||
if (!escaped)
|
||||
return log_oom();
|
||||
|
||||
if (option) {
|
||||
if (!strextend(options, separator, option, "=", escaped))
|
||||
return log_oom();
|
||||
} else if (!strextend(options, separator, escaped))
|
||||
return log_oom();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mount_overlayfs(
|
||||
ImageClass image_class,
|
||||
int noexec,
|
||||
const char *where,
|
||||
char **layers) {
|
||||
char **layers,
|
||||
const char *upper_dir,
|
||||
const char *work_dir) {
|
||||
|
||||
_cleanup_free_ char *options = NULL;
|
||||
bool separator = false;
|
||||
@ -490,20 +572,16 @@ static int mount_overlayfs(
|
||||
int r;
|
||||
|
||||
assert(where);
|
||||
assert((upper_dir && work_dir) || (!upper_dir && !work_dir));
|
||||
|
||||
options = strdup("lowerdir=");
|
||||
if (!options)
|
||||
return log_oom();
|
||||
|
||||
STRV_FOREACH(l, layers) {
|
||||
_cleanup_free_ char *escaped = NULL;
|
||||
|
||||
escaped = shell_escape(*l, ",:");
|
||||
if (!escaped)
|
||||
return log_oom();
|
||||
|
||||
if (!strextend(&options, separator ? ":" : "", escaped))
|
||||
return log_oom();
|
||||
r = append_overlayfs_path_option(&options, separator ? ":" : "", NULL, *l);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
separator = true;
|
||||
}
|
||||
@ -512,6 +590,22 @@ static int mount_overlayfs(
|
||||
if (noexec >= 0)
|
||||
SET_FLAG(flags, MS_NOEXEC, noexec);
|
||||
|
||||
if (upper_dir && work_dir) {
|
||||
r = append_overlayfs_path_option(&options, ",", "upperdir", upper_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
flags &= ~MS_RDONLY;
|
||||
|
||||
r = append_overlayfs_path_option(&options, ",", "workdir", work_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
/* redirect_dir=on and noatime prevent unnecessary upcopies, metacopy=off prevents broken
|
||||
* files from partial upcopies after umount. */
|
||||
if (!strextend(&options, ",redirect_dir=on,noatime,metacopy=off"))
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
/* Now mount the actual overlayfs */
|
||||
r = mount_nofollow_verbose(LOG_ERR, image_class_info[image_class].short_identifier, where, "overlay", flags, options);
|
||||
if (r < 0)
|
||||
@ -520,41 +614,451 @@ static int mount_overlayfs(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int merge_hierarchy(
|
||||
ImageClass image_class,
|
||||
const char *hierarchy,
|
||||
int noexec,
|
||||
char **extensions,
|
||||
char **paths,
|
||||
const char *meta_path,
|
||||
const char *overlay_path) {
|
||||
static char *hierarchy_as_single_path_component(const char *hierarchy) {
|
||||
/* We normally expect hierarchy to be /usr, /opt or /etc, but for debugging purposes the hierarchy
|
||||
* could very well be like /foo/bar/baz/. So for a given hierarchy we generate a directory name by
|
||||
* stripping the leading and trailing separators and replacing the rest of separators with dots. This
|
||||
* makes the generated name to be the same for /foo/bar/baz and for /foo/bar.baz, but, again,
|
||||
* speciyfing a different hierarchy is a debugging feature, so non-unique mapping should not be an
|
||||
* issue in general case. */
|
||||
const char *stripped = hierarchy;
|
||||
_cleanup_free_ char *dir_name = NULL;
|
||||
|
||||
_cleanup_free_ char *resolved_hierarchy = NULL, *f = NULL, *buf = NULL;
|
||||
_cleanup_strv_free_ char **layers = NULL;
|
||||
struct stat st;
|
||||
assert(hierarchy);
|
||||
|
||||
stripped += strspn(stripped, "/");
|
||||
|
||||
dir_name = strdup(stripped);
|
||||
if (!dir_name)
|
||||
return NULL;
|
||||
delete_trailing_chars(dir_name, "/");
|
||||
string_replace_char(dir_name, '/', '.');
|
||||
return TAKE_PTR(dir_name);
|
||||
}
|
||||
|
||||
static char *determine_mutable_directory_path_for_hierarchy(const char *hierarchy) {
|
||||
_cleanup_free_ char *dir_name = NULL;
|
||||
|
||||
assert(hierarchy);
|
||||
dir_name = hierarchy_as_single_path_component(hierarchy);
|
||||
if (!dir_name)
|
||||
return NULL;
|
||||
|
||||
return path_join("/var/lib/extensions.mutable", dir_name);
|
||||
}
|
||||
|
||||
static int paths_on_same_fs(const char *path1, const char *path2) {
|
||||
struct stat st1, st2;
|
||||
|
||||
assert(path1);
|
||||
assert(path2);
|
||||
|
||||
if (stat(path1, &st1))
|
||||
return log_error_errno(errno, "Failed to stat '%s': %m", path1);
|
||||
|
||||
if (stat(path2, &st2))
|
||||
return log_error_errno(errno, "Failed to stat '%s': %m", path2);
|
||||
|
||||
return st1.st_dev == st2.st_dev;
|
||||
}
|
||||
|
||||
static int work_dir_for_hierarchy(
|
||||
const char *hierarchy,
|
||||
const char *resolved_upper_dir,
|
||||
char **ret_work_dir) {
|
||||
|
||||
_cleanup_free_ char *parent = NULL;
|
||||
int r;
|
||||
|
||||
assert(hierarchy);
|
||||
assert(resolved_upper_dir);
|
||||
assert(ret_work_dir);
|
||||
|
||||
r = path_extract_directory(resolved_upper_dir, &parent);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get parent directory of upperdir '%s': %m", resolved_upper_dir);
|
||||
|
||||
/* TODO: paths_in_same_superblock? partition? device? */
|
||||
r = paths_on_same_fs(resolved_upper_dir, parent);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!r)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EXDEV), "Unable to find a suitable workdir location for upperdir '%s' for host hierarchy '%s' - parent directory of the upperdir is in a different filesystem", resolved_upper_dir, hierarchy);
|
||||
|
||||
_cleanup_free_ char *f = NULL, *dir_name = NULL;
|
||||
|
||||
f = hierarchy_as_single_path_component(hierarchy);
|
||||
if (!f)
|
||||
return log_oom();
|
||||
dir_name = strjoin(".systemd-", f, "-workdir");
|
||||
if (!dir_name)
|
||||
return log_oom();
|
||||
|
||||
free(f);
|
||||
f = path_join(parent, dir_name);
|
||||
if (!f)
|
||||
return log_oom();
|
||||
|
||||
*ret_work_dir = TAKE_PTR(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct OverlayFSPaths {
|
||||
char *hierarchy;
|
||||
char *resolved_hierarchy;
|
||||
char *resolved_mutable_directory;
|
||||
|
||||
/* NULL if merged fs is read-only */
|
||||
char *upper_dir;
|
||||
/* NULL if merged fs is read-only */
|
||||
char *work_dir;
|
||||
/* lowest index is top lowerdir, highest index is bottom lowerdir */
|
||||
char **lower_dirs;
|
||||
} OverlayFSPaths;
|
||||
|
||||
static OverlayFSPaths *overlayfs_paths_free(OverlayFSPaths *op) {
|
||||
if (!op)
|
||||
return NULL;
|
||||
|
||||
free(op->hierarchy);
|
||||
free(op->resolved_hierarchy);
|
||||
free(op->resolved_mutable_directory);
|
||||
|
||||
free(op->upper_dir);
|
||||
free(op->work_dir);
|
||||
strv_free(op->lower_dirs);
|
||||
|
||||
free(op);
|
||||
return NULL;
|
||||
}
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(OverlayFSPaths *, overlayfs_paths_free);
|
||||
|
||||
static int resolve_hierarchy(const char *hierarchy, char **ret_resolved_hierarchy) {
|
||||
_cleanup_free_ char *resolved_path = NULL;
|
||||
int r;
|
||||
|
||||
assert(hierarchy);
|
||||
assert(ret_resolved_hierarchy);
|
||||
|
||||
r = chase(hierarchy, arg_root, CHASE_PREFIX_ROOT, &resolved_path, NULL);
|
||||
if (r < 0 && r != -ENOENT)
|
||||
return log_error_errno(r, "Failed to resolve hierarchy '%s': %m", hierarchy);
|
||||
|
||||
*ret_resolved_hierarchy = TAKE_PTR(resolved_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int resolve_mutable_directory(const char *hierarchy, char **ret_resolved_mutable_directory) {
|
||||
_cleanup_free_ char *path = NULL, *resolved_path = NULL;
|
||||
int r;
|
||||
|
||||
assert(hierarchy);
|
||||
assert(ret_resolved_mutable_directory);
|
||||
|
||||
if (arg_mutable == MUTABLE_NO) {
|
||||
log_debug("Mutability for hierarchy '%s' is disabled, not resolving mutable directory.", hierarchy);
|
||||
*ret_resolved_mutable_directory = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
path = determine_mutable_directory_path_for_hierarchy(hierarchy);
|
||||
if (!path)
|
||||
return log_oom();
|
||||
|
||||
if (arg_mutable == MUTABLE_YES) {
|
||||
_cleanup_free_ char *path_in_root = NULL;
|
||||
|
||||
path_in_root = path_join(arg_root, path);
|
||||
if (!path_in_root)
|
||||
return log_oom();
|
||||
|
||||
r = mkdir_p(path_in_root, 0700);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create a directory '%s': %m", path_in_root);
|
||||
}
|
||||
|
||||
r = chase(path, arg_root, CHASE_PREFIX_ROOT, &resolved_path, NULL);
|
||||
if (r < 0 && r != -ENOENT)
|
||||
return log_error_errno(r, "Failed to resolve mutable directory '%s': %m", path);
|
||||
|
||||
*ret_resolved_mutable_directory = TAKE_PTR(resolved_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int overlayfs_paths_new(const char *hierarchy, OverlayFSPaths **ret_op) {
|
||||
_cleanup_free_ char *hierarchy_copy = NULL, *resolved_hierarchy = NULL, *resolved_mutable_directory = NULL;
|
||||
int r;
|
||||
|
||||
assert (hierarchy);
|
||||
assert (ret_op);
|
||||
|
||||
hierarchy_copy = strdup(hierarchy);
|
||||
if (!hierarchy_copy)
|
||||
return log_oom();
|
||||
|
||||
r = resolve_hierarchy(hierarchy, &resolved_hierarchy);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = resolve_mutable_directory(hierarchy, &resolved_mutable_directory);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
OverlayFSPaths *op;
|
||||
op = new(OverlayFSPaths, 1);
|
||||
if (!op)
|
||||
return log_oom();
|
||||
|
||||
*op = (OverlayFSPaths) {
|
||||
.hierarchy = TAKE_PTR(hierarchy_copy),
|
||||
.resolved_hierarchy = TAKE_PTR(resolved_hierarchy),
|
||||
.resolved_mutable_directory = TAKE_PTR(resolved_mutable_directory),
|
||||
};
|
||||
|
||||
*ret_op = TAKE_PTR(op);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int determine_top_lower_dirs(OverlayFSPaths *op, const char *meta_path) {
|
||||
int r;
|
||||
|
||||
assert(op);
|
||||
assert(meta_path);
|
||||
|
||||
/* Put the meta path (i.e. our synthesized stuff) at the top of the layer stack */
|
||||
r = strv_extend(&op->lower_dirs, meta_path);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
/* If importing mutable layer and it actually exists, add it just below the meta path */
|
||||
if (arg_mutable == MUTABLE_IMPORT && op->resolved_mutable_directory) {
|
||||
r = strv_extend(&op->lower_dirs, op->resolved_mutable_directory);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int determine_middle_lower_dirs(OverlayFSPaths *op, char **paths, size_t *ret_extensions_used) {
|
||||
size_t n = 0;
|
||||
int r;
|
||||
|
||||
assert(op);
|
||||
assert(paths);
|
||||
assert(ret_extensions_used);
|
||||
|
||||
/* Put the extensions in the middle */
|
||||
STRV_FOREACH(p, paths) {
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
|
||||
r = chase(op->hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL);
|
||||
if (r == -ENOENT) {
|
||||
log_debug_errno(r, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", op->hierarchy, *p);
|
||||
continue;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to resolve hierarchy '%s' in extension '%s': %m", op->hierarchy, *p);
|
||||
|
||||
r = dir_is_empty(resolved, /* ignore_hidden_or_backup= */ false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check if hierarchy '%s' in extension '%s' is empty: %m", resolved, *p);
|
||||
if (r > 0) {
|
||||
log_debug("Hierarchy '%s' in extension '%s' is empty, not merging.", op->hierarchy, *p);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = strv_consume(&op->lower_dirs, TAKE_PTR(resolved));
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
++n;
|
||||
}
|
||||
|
||||
*ret_extensions_used = n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hierarchy_as_lower_dir(OverlayFSPaths *op) {
|
||||
int r;
|
||||
|
||||
/* return 0 if hierarchy should be used as lower dir, >0, if not */
|
||||
|
||||
assert(op);
|
||||
|
||||
if (!op->resolved_hierarchy) {
|
||||
log_debug("Host hierarchy '%s' does not exist, will not be used as lowerdir", op->hierarchy);
|
||||
return 1;
|
||||
}
|
||||
|
||||
r = dir_is_empty(op->resolved_hierarchy, /* ignore_hidden_or_backup= */ false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check if host hierarchy '%s' is empty: %m", op->resolved_hierarchy);
|
||||
if (r > 0) {
|
||||
log_debug("Host hierarchy '%s' is empty, will not be used as lower dir.", op->resolved_hierarchy);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (arg_mutable == MUTABLE_IMPORT) {
|
||||
log_debug("Mutability for host hierarchy '%s' is disabled, so it will be a lowerdir", op->resolved_hierarchy);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!op->resolved_mutable_directory) {
|
||||
log_debug("No mutable directory found, so host hierarchy '%s' will be used as lowerdir", op->resolved_hierarchy);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (path_equal(op->resolved_hierarchy, op->resolved_mutable_directory)) {
|
||||
log_debug("Host hierarchy '%s' will serve as upperdir.", op->resolved_hierarchy);
|
||||
return 1;
|
||||
}
|
||||
r = inode_same(op->resolved_hierarchy, op->resolved_mutable_directory, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check inode equality of hierarchy %s and its mutable directory %s: %m", op->resolved_hierarchy, op->resolved_mutable_directory);
|
||||
if (r > 0) {
|
||||
log_debug("Host hierarchy '%s' will serve as upperdir.", op->resolved_hierarchy);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int determine_bottom_lower_dirs(OverlayFSPaths *op) {
|
||||
int r;
|
||||
|
||||
assert(op);
|
||||
|
||||
r = hierarchy_as_lower_dir(op);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!r) {
|
||||
r = strv_extend(&op->lower_dirs, op->resolved_hierarchy);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int determine_lower_dirs(
|
||||
OverlayFSPaths *op,
|
||||
char **paths,
|
||||
const char *meta_path,
|
||||
size_t *ret_extensions_used) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(op);
|
||||
assert(paths);
|
||||
assert(meta_path);
|
||||
assert(ret_extensions_used);
|
||||
|
||||
r = determine_top_lower_dirs(op, meta_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = determine_middle_lower_dirs(op, paths, ret_extensions_used);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = determine_bottom_lower_dirs(op);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int determine_upper_dir(OverlayFSPaths *op) {
|
||||
int r;
|
||||
|
||||
assert(op);
|
||||
assert(!op->upper_dir);
|
||||
|
||||
if (arg_mutable == MUTABLE_IMPORT) {
|
||||
log_debug("Mutability is disabled, there will be no upperdir for host hierarchy '%s'", op->hierarchy);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!op->resolved_mutable_directory) {
|
||||
log_debug("No mutable directory found for host hierarchy '%s', there will be no upperdir", op->hierarchy);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Require upper dir to be on writable filesystem if it's going to be used as an actual overlayfs
|
||||
* upperdir, instead of a lowerdir as an imported path. */
|
||||
r = path_is_read_only_fs(op->resolved_mutable_directory);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine if mutable directory '%s' is on read-only filesystem: %m", op->resolved_mutable_directory);
|
||||
if (r > 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EROFS), "Can't use '%s' as an upperdir as it is read-only.", op->resolved_mutable_directory);
|
||||
|
||||
op->upper_dir = strdup(op->resolved_mutable_directory);
|
||||
if (!op->upper_dir)
|
||||
return log_oom();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int determine_work_dir(OverlayFSPaths *op) {
|
||||
_cleanup_free_ char *work_dir = NULL;
|
||||
int r;
|
||||
|
||||
assert(op);
|
||||
assert(!op->work_dir);
|
||||
|
||||
if (!op->upper_dir)
|
||||
return 0;
|
||||
|
||||
if (arg_mutable == MUTABLE_IMPORT)
|
||||
return 0;
|
||||
|
||||
r = work_dir_for_hierarchy(op->hierarchy, op->upper_dir, &work_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
op->work_dir = TAKE_PTR(work_dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mount_overlayfs_with_op(
|
||||
OverlayFSPaths *op,
|
||||
ImageClass image_class,
|
||||
int noexec,
|
||||
const char *overlay_path,
|
||||
const char *meta_path) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(op);
|
||||
assert(overlay_path);
|
||||
|
||||
/* Resolve the path of the host's version of the hierarchy, i.e. what we want to use as lowest layer
|
||||
* in the overlayfs stack. */
|
||||
r = chase(hierarchy, arg_root, CHASE_PREFIX_ROOT, &resolved_hierarchy, NULL);
|
||||
if (r == -ENOENT)
|
||||
log_debug_errno(r, "Hierarchy '%s' on host doesn't exist, not merging.", hierarchy);
|
||||
else if (r < 0)
|
||||
return log_error_errno(r, "Failed to resolve host hierarchy '%s': %m", hierarchy);
|
||||
else {
|
||||
r = dir_is_empty(resolved_hierarchy, /* ignore_hidden_or_backup= */ false);
|
||||
r = mkdir_p(overlay_path, 0700);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to make directory '%s': %m", overlay_path);
|
||||
|
||||
r = mkdir_p(meta_path, 0700);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to make directory '%s': %m", meta_path);
|
||||
|
||||
if (op->upper_dir && op->work_dir) {
|
||||
r = mkdir_p(op->work_dir, 0700);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check if host hierarchy '%s' is empty: %m", resolved_hierarchy);
|
||||
if (r > 0) {
|
||||
log_debug("Host hierarchy '%s' is empty, not merging.", resolved_hierarchy);
|
||||
resolved_hierarchy = mfree(resolved_hierarchy);
|
||||
}
|
||||
return log_error_errno(r, "Failed to make directory '%s': %m", op->work_dir);
|
||||
}
|
||||
|
||||
r = mount_overlayfs(image_class, noexec, overlay_path, op->lower_dirs, op->upper_dir, op->work_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_extensions_file(ImageClass image_class, char **extensions, const char *meta_path) {
|
||||
_cleanup_free_ char *f = NULL, *buf = NULL;
|
||||
int r;
|
||||
|
||||
assert(extensions);
|
||||
assert(meta_path);
|
||||
|
||||
/* Let's generate a metadata file that lists all extensions we took into account for this
|
||||
* hierarchy. We include this in the final fs, to make things nicely discoverable and
|
||||
* recognizable. */
|
||||
@ -570,79 +1074,184 @@ static int merge_hierarchy(
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write extension meta file '%s': %m", f);
|
||||
|
||||
/* Put the meta path (i.e. our synthesized stuff) at the top of the layer stack */
|
||||
layers = strv_new(meta_path);
|
||||
if (!layers)
|
||||
return log_oom();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Put the extensions in the middle */
|
||||
STRV_FOREACH(p, paths) {
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
static int write_dev_file(ImageClass image_class, const char *meta_path, const char *overlay_path) {
|
||||
_cleanup_free_ char *f = NULL;
|
||||
struct stat st;
|
||||
int r;
|
||||
|
||||
r = chase(hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL);
|
||||
if (r == -ENOENT) {
|
||||
log_debug_errno(r, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", hierarchy, *p);
|
||||
continue;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to resolve hierarchy '%s' in extension '%s': %m", hierarchy, *p);
|
||||
|
||||
r = dir_is_empty(resolved, /* ignore_hidden_or_backup= */ false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check if hierarchy '%s' in extension '%s' is empty: %m", resolved, *p);
|
||||
if (r > 0) {
|
||||
log_debug("Hierarchy '%s' in extension '%s' is empty, not merging.", hierarchy, *p);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = strv_consume(&layers, TAKE_PTR(resolved));
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
if (!layers[1]) /* No extension with files in this hierarchy? Then don't do anything. */
|
||||
return 0;
|
||||
|
||||
if (resolved_hierarchy) {
|
||||
/* Add the host hierarchy as last (lowest) layer in the stack */
|
||||
r = strv_consume(&layers, TAKE_PTR(resolved_hierarchy));
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = mkdir_p(overlay_path, 0700);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to make directory '%s': %m", overlay_path);
|
||||
|
||||
r = mount_overlayfs(image_class, noexec, overlay_path, layers);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* The overlayfs superblock is read-only. Let's also mark the bind mount read-only. Extra turbo safety 😎 */
|
||||
r = bind_remount_recursive(overlay_path, MS_RDONLY, MS_RDONLY, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to make bind mount '%s' read-only: %m", overlay_path);
|
||||
assert(meta_path);
|
||||
assert(overlay_path);
|
||||
|
||||
/* Now we have mounted the new file system. Let's now figure out its .st_dev field, and make that
|
||||
* available in the metadata directory. This is useful to detect whether the metadata dir actually
|
||||
* belongs to the fs it is found on: if .st_dev of the top-level mount matches it, it's pretty likely
|
||||
* we are looking at a live tree, and not an unpacked tar or so of one. */
|
||||
if (stat(overlay_path, &st) < 0)
|
||||
return log_error_errno(r, "Failed to stat mount '%s': %m", overlay_path);
|
||||
return log_error_errno(errno, "Failed to stat mount '%s': %m", overlay_path);
|
||||
|
||||
free(f);
|
||||
f = path_join(meta_path, image_class_info[image_class].dot_directory_name, "dev");
|
||||
if (!f)
|
||||
return log_oom();
|
||||
|
||||
/* Modifying the underlying layers while the overlayfs is mounted is technically undefined, but at
|
||||
* least it won't crash or deadlock, as per the kernel docs about overlayfs:
|
||||
* https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html#changes-to-underlying-filesystems */
|
||||
r = write_string_file(f, FORMAT_DEVNUM(st.st_dev), WRITE_STRING_FILE_CREATE);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write '%s': %m", f);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_work_dir_file(ImageClass image_class, const char *meta_path, const char *work_dir) {
|
||||
_cleanup_free_ char *escaped_work_dir_in_root = NULL, *f = NULL;
|
||||
char *work_dir_in_root = NULL;
|
||||
int r;
|
||||
|
||||
assert(meta_path);
|
||||
|
||||
if (!work_dir)
|
||||
return 0;
|
||||
|
||||
work_dir_in_root = path_startswith(work_dir, empty_to_root(arg_root));
|
||||
if (!work_dir_in_root)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Workdir '%s' must not be outside root '%s'", work_dir, empty_to_root(arg_root));
|
||||
|
||||
f = path_join(meta_path, image_class_info[image_class].dot_directory_name, "work_dir");
|
||||
if (!f)
|
||||
return log_oom();
|
||||
|
||||
/* Paths can have newlines for whatever reason, so better escape them to really get a single
|
||||
* line file. */
|
||||
escaped_work_dir_in_root = cescape(work_dir_in_root);
|
||||
if (!escaped_work_dir_in_root)
|
||||
return log_oom();
|
||||
r = write_string_file(f, escaped_work_dir_in_root, WRITE_STRING_FILE_CREATE);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write '%s': %m", f);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int store_info_in_meta(
|
||||
ImageClass image_class,
|
||||
char **extensions,
|
||||
const char *meta_path,
|
||||
const char *overlay_path,
|
||||
const char *work_dir) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(extensions);
|
||||
assert(meta_path);
|
||||
assert(overlay_path);
|
||||
/* work_dir may be NULL */
|
||||
|
||||
r = write_extensions_file(image_class, extensions, meta_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = write_dev_file(image_class, meta_path, overlay_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = write_work_dir_file(image_class, meta_path, work_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Make sure the top-level dir has an mtime marking the point we established the merge */
|
||||
if (utimensat(AT_FDCWD, meta_path, NULL, AT_SYMLINK_NOFOLLOW) < 0)
|
||||
return log_error_errno(r, "Failed fix mtime of '%s': %m", meta_path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int make_mounts_read_only(ImageClass image_class, const char *overlay_path, bool mutable) {
|
||||
int r;
|
||||
|
||||
assert(overlay_path);
|
||||
|
||||
if (mutable) {
|
||||
/* Bind mount the meta path as read-only on mutable overlays to avoid accidental
|
||||
* modifications of the contents of meta directory, which could lead to systemd thinking that
|
||||
* this hierarchy is not our mount. */
|
||||
_cleanup_free_ char *f = NULL;
|
||||
|
||||
f = path_join(overlay_path, image_class_info[image_class].dot_directory_name);
|
||||
if (!f)
|
||||
return log_oom();
|
||||
|
||||
r = mount_nofollow_verbose(LOG_ERR, f, f, NULL, MS_BIND, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = bind_remount_one(f, MS_RDONLY, MS_RDONLY);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to remount '%s' as read-only: %m", f);
|
||||
} else {
|
||||
/* The overlayfs superblock is read-only. Let's also mark the bind mount read-only. Extra
|
||||
* turbo safety 😎 */
|
||||
r = bind_remount_recursive(overlay_path, MS_RDONLY, MS_RDONLY, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to make bind mount '%s' read-only: %m", overlay_path);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int merge_hierarchy(
|
||||
ImageClass image_class,
|
||||
const char *hierarchy,
|
||||
int noexec,
|
||||
char **extensions,
|
||||
char **paths,
|
||||
const char *meta_path,
|
||||
const char *overlay_path) {
|
||||
|
||||
_cleanup_(overlayfs_paths_freep) OverlayFSPaths *op = NULL;
|
||||
size_t extensions_used = 0;
|
||||
int r;
|
||||
|
||||
assert(hierarchy);
|
||||
assert(extensions);
|
||||
assert(paths);
|
||||
assert(meta_path);
|
||||
assert(overlay_path);
|
||||
|
||||
r = overlayfs_paths_new(hierarchy, &op);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = determine_lower_dirs(op, paths, meta_path, &extensions_used);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (extensions_used == 0) /* No extension with files in this hierarchy? Then don't do anything. */
|
||||
return 0;
|
||||
|
||||
r = determine_upper_dir(op);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = determine_work_dir(op);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = mount_overlayfs_with_op(op, image_class, noexec, overlay_path, meta_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = store_info_in_meta(image_class, extensions, meta_path, overlay_path, op->work_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = make_mounts_read_only(image_class, overlay_path, op->upper_dir && op->work_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -966,7 +1575,8 @@ static int merge_subprocess(
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create hierarchy mount point '%s': %m", resolved);
|
||||
|
||||
r = mount_nofollow_verbose(LOG_ERR, p, resolved, NULL, MS_BIND, NULL);
|
||||
/* Using MS_REC to potentially bring in our read-only bind mount of metadata. */
|
||||
r = mount_nofollow_verbose(LOG_ERR, p, resolved, NULL, MS_BIND|MS_REC, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -1419,6 +2029,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_IMAGE_POLICY,
|
||||
ARG_NOEXEC,
|
||||
ARG_NO_RELOAD,
|
||||
ARG_MUTABLE,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@ -1432,6 +2043,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
|
||||
{ "noexec", required_argument, NULL, ARG_NOEXEC },
|
||||
{ "no-reload", no_argument, NULL, ARG_NO_RELOAD },
|
||||
{ "mutable", required_argument, NULL, ARG_MUTABLE },
|
||||
{}
|
||||
};
|
||||
|
||||
@ -1495,6 +2107,19 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
arg_no_reload = true;
|
||||
break;
|
||||
|
||||
case ARG_MUTABLE:
|
||||
if (streq(optarg, "auto"))
|
||||
arg_mutable = MUTABLE_AUTO;
|
||||
else if (streq(optarg, "import"))
|
||||
arg_mutable = MUTABLE_IMPORT;
|
||||
else {
|
||||
r = parse_boolean(optarg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse argument to --mutable=: %s", optarg);
|
||||
arg_mutable = r ? MUTABLE_YES : MUTABLE_NO;
|
||||
}
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
|
@ -213,6 +213,25 @@ TEST(bind_remount_one) {
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
assert_se(wait_for_terminate_and_check("test-remount-one-with-mountinfo", pid, WAIT_LOG) == EXIT_SUCCESS);
|
||||
|
||||
pid = fork();
|
||||
assert_se(pid >= 0);
|
||||
|
||||
if (pid == 0) {
|
||||
/* child */
|
||||
|
||||
assert_se(detach_mount_namespace() >= 0);
|
||||
|
||||
assert_se(bind_remount_one("/run", MS_RDONLY, MS_RDONLY) >= 0);
|
||||
assert_se(bind_remount_one("/run", MS_NOEXEC, MS_RDONLY|MS_NOEXEC) >= 0);
|
||||
assert_se(bind_remount_one("/proc/idontexist", MS_RDONLY, MS_RDONLY) == -ENOENT);
|
||||
assert_se(bind_remount_one("/proc/self", MS_RDONLY, MS_RDONLY) == -EINVAL);
|
||||
assert_se(bind_remount_one("/", MS_RDONLY, MS_RDONLY) >= 0);
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
assert_se(wait_for_terminate_and_check("test-remount-one", pid, WAIT_LOG) == EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
|
@ -8,10 +8,7 @@ set -o pipefail
|
||||
|
||||
export SYSTEMD_LOG_LEVEL=debug
|
||||
|
||||
# shellcheck disable=SC2317
|
||||
cleanup() {(
|
||||
set +ex
|
||||
|
||||
cleanup_image_dir() {
|
||||
if [ -z "${image_dir}" ]; then
|
||||
return
|
||||
fi
|
||||
@ -20,6 +17,39 @@ cleanup() {(
|
||||
umount "${image_dir}/app-nodistro"
|
||||
umount "${image_dir}/service-scoped-test"
|
||||
rm -rf "${image_dir}"
|
||||
}
|
||||
|
||||
fake_roots_dir=/fake-roots
|
||||
|
||||
cleanup_fake_rootfses() {
|
||||
local tries=10 e
|
||||
local -a lines fake_roots_mounts
|
||||
|
||||
while [[ ${tries} -gt 0 ]]; do
|
||||
tries=$((tries - 1))
|
||||
mapfile -t lines < <(mount | awk '{ print $3 }')
|
||||
fake_roots_mounts=()
|
||||
for e in "${lines[@]}"; do
|
||||
if [[ ${e} = "${fake_roots_dir}"/* ]]; then
|
||||
fake_roots_mounts+=( "${e}" )
|
||||
fi
|
||||
done
|
||||
if [[ ${#fake_roots_mounts[@]} -eq 0 ]]; then
|
||||
break
|
||||
fi
|
||||
for e in "${fake_roots_mounts[@]}"; do
|
||||
umount "${e}"
|
||||
done
|
||||
done
|
||||
rm -rf "${fake_roots_dir}"
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2317
|
||||
cleanup() {(
|
||||
set +ex
|
||||
|
||||
cleanup_image_dir
|
||||
cleanup_fake_rootfses
|
||||
)}
|
||||
|
||||
udevadm control --log-level=debug
|
||||
@ -765,4 +795,690 @@ fi
|
||||
(! systemd-run -P -p RootImage="/this/should/definitely/not/exist.img" false)
|
||||
(! systemd-run -P -p ExtensionDirectories="/foo/bar /foo/baz" false)
|
||||
|
||||
# general systemd-sysext tests
|
||||
|
||||
shopt -s extglob
|
||||
|
||||
die() {
|
||||
echo "${*}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
prep_root() {
|
||||
local r=${1}; shift
|
||||
local h=${1}; shift
|
||||
|
||||
mkdir -p "${r}${h}" "${r}/usr/lib" "${r}/var/lib/extensions" "${r}/var/lib/extensions.mutable"
|
||||
}
|
||||
|
||||
gen_os_release() {
|
||||
local r=${1}; shift
|
||||
|
||||
{
|
||||
echo "ID=testtest"
|
||||
echo "VERSION=1.2.3"
|
||||
} >"${r}/usr/lib/os-release"
|
||||
}
|
||||
|
||||
gen_test_ext_image() {
|
||||
local r=${1}; shift
|
||||
local h=${1}; shift
|
||||
|
||||
local n d f
|
||||
|
||||
n='test-extension'
|
||||
d="${r}/var/lib/extensions/${n}"
|
||||
f="${d}/usr/lib/extension-release.d/extension-release.${n}"
|
||||
mkdir -p "$(dirname "${f}")"
|
||||
echo "ID=_any" >"${f}"
|
||||
mkdir -p "${d}/${h}"
|
||||
touch "${d}${h}/preexisting-file-in-extension-image"
|
||||
}
|
||||
|
||||
hierarchy_ext_mut_path() {
|
||||
local r=${1}; shift
|
||||
local h=${1}; shift
|
||||
|
||||
# /a/b/c -> a.b.c
|
||||
local n=${h}
|
||||
n="${n##+(/)}"
|
||||
n="${n%%+(/)}"
|
||||
n="${n//\//.}"
|
||||
|
||||
printf '%s' "${r}/var/lib/extensions.mutable/${n}"
|
||||
}
|
||||
|
||||
prep_ext_mut() {
|
||||
local p=${1}; shift
|
||||
|
||||
mkdir -p "${p}"
|
||||
touch "${p}/preexisting-file-in-extensions-mutable"
|
||||
}
|
||||
|
||||
make_ro() {
|
||||
local r=${1}; shift
|
||||
local h=${1}; shift
|
||||
|
||||
mount -o bind "${r}${h}" "${r}${h}"
|
||||
mount -o bind,remount,ro "${r}${h}"
|
||||
}
|
||||
|
||||
prep_hierarchy() {
|
||||
local r=${1}; shift
|
||||
local h=${1}; shift
|
||||
|
||||
touch "${r}${h}/preexisting-file-in-hierarchy"
|
||||
}
|
||||
|
||||
prep_ro_hierarchy() {
|
||||
local r=${1}; shift
|
||||
local h=${1}; shift
|
||||
|
||||
prep_hierarchy "${r}" "${h}"
|
||||
make_ro "${r}" "${h}"
|
||||
}
|
||||
|
||||
# extra args:
|
||||
# "e" for checking for the preexisting file in extension
|
||||
# "h" for checking for the preexisting file in hierarchy
|
||||
# "u" for checking for the preexisting file in upperdir
|
||||
check_usual_suspects() {
|
||||
local root=${1}; shift
|
||||
local hierarchy=${1}; shift
|
||||
local message=${1}; shift
|
||||
|
||||
local arg
|
||||
# shellcheck disable=SC2034 # the variables below are used indirectly
|
||||
local e='' h='' u=''
|
||||
|
||||
for arg; do
|
||||
case ${arg} in
|
||||
e|h|u)
|
||||
local -n v=${arg}
|
||||
v=x
|
||||
unset -n v
|
||||
;;
|
||||
*)
|
||||
die "invalid arg to ${0}: ${arg@Q}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# var name, file name
|
||||
local pairs=(
|
||||
e:preexisting-file-in-extension-image
|
||||
h:preexisting-file-in-hierarchy
|
||||
u:preexisting-file-in-extensions-mutable
|
||||
)
|
||||
local pair name file desc full_path
|
||||
for pair in "${pairs[@]}"; do
|
||||
name=${pair%%:*}
|
||||
file=${pair#*:}
|
||||
desc=${file//-/ }
|
||||
full_path="${root}${hierarchy}/${file}"
|
||||
local -n v=${name}
|
||||
if [[ -n ${v} ]]; then
|
||||
test -f "${full_path}" || {
|
||||
ls -la "$(dirname "${full_path}")"
|
||||
die "${desc} is missing ${message}"
|
||||
}
|
||||
else
|
||||
test ! -f "${full_path}" || {
|
||||
ls -la "$(dirname "${full_path}")"
|
||||
die "${desc} unexpectedly exists ${message}"
|
||||
}
|
||||
fi
|
||||
unset -n v
|
||||
done
|
||||
}
|
||||
|
||||
check_usual_suspects_after_merge() {
|
||||
local r=${1}; shift
|
||||
local h=${1}; shift
|
||||
|
||||
check_usual_suspects "${r}" "${h}" "after merge" "${@}"
|
||||
}
|
||||
|
||||
check_usual_suspects_after_unmerge() {
|
||||
local r=${1}; shift
|
||||
local h=${1}; shift
|
||||
|
||||
check_usual_suspects "${r}" "${h}" "after unmerge" "${@}"
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# no extension data in /var/lib/extensions.mutable/…, read-only hierarchy,
|
||||
# mutability disabled by default
|
||||
#
|
||||
# read-only merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/simple-read-only-with-read-only-hierarchy
|
||||
hierarchy=/usr
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-still-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-still-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only after unmerge"
|
||||
|
||||
#
|
||||
# no extension data in /var/lib/extensions.mutable/…, mutable hierarchy,
|
||||
# mutability disabled by default
|
||||
#
|
||||
# read-only merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/simple-read-only-with-mutable-hierarchy
|
||||
hierarchy=/usr
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
prep_hierarchy "${fake_root}" "${hierarchy}"
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-succeed-on-mutable-fs" || die "${fake_root}${hierarchy} is not mutable"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-succeed-on-mutable-fs-again" || die "${fake_root}${hierarchy} is not mutable after unmerge"
|
||||
|
||||
|
||||
#
|
||||
# no extension data in /var/lib/extensions.mutable/…, no hierarchy either,
|
||||
# mutability disabled by default
|
||||
#
|
||||
# read-only merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/simple-read-only-with-missing-hierarchy
|
||||
hierarchy=/opt
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
rmdir "${fake_root}/${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-still-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}"
|
||||
|
||||
|
||||
#
|
||||
# no extension data in /var/lib/extensions.mutable/…, an empty hierarchy,
|
||||
# mutability disabled by default
|
||||
#
|
||||
# read-only merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/simple-read-only-with-empty-hierarchy
|
||||
hierarchy=/opt
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
make_ro "${fake_root}" "${hierarchy}"
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-still-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}"
|
||||
|
||||
|
||||
#
|
||||
# extension data in /var/lib/extensions.mutable/…, read-only hierarchy, mutability disabled-by-default
|
||||
#
|
||||
# read-only merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/simple-mutable-with-read-only-hierarchy-disabled
|
||||
hierarchy=/usr
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
|
||||
prep_ext_mut "${ext_data_path}"
|
||||
|
||||
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-be-read-only" && die "${fake_root}${hierarchy} is not read-only"
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
|
||||
|
||||
|
||||
#
|
||||
# extension data in /var/lib/extensions.mutable/…, read-only hierarchy, auto-mutability
|
||||
#
|
||||
# mutable merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/simple-mutable-with-read-only-hierarchy
|
||||
hierarchy=/usr
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
|
||||
prep_ext_mut "${ext_data_path}"
|
||||
|
||||
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=auto merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/now-is-mutable" || die "${fake_root}${hierarchy} is not mutable"
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h u
|
||||
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable is not stored in expected location"
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
|
||||
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable disappeared from writable storage after unmerge"
|
||||
test ! -f "${fake_root}${hierarchy}/now-is-mutable" || die "now-is-mutable did not disappear from hierarchy after unmerge"
|
||||
|
||||
|
||||
#
|
||||
# extension data in /var/lib/extensions.mutable/…, missing hierarchy,
|
||||
# auto-mutability
|
||||
#
|
||||
# mutable merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/simple-mutable-with-missing-hierarchy
|
||||
hierarchy=/opt
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
rmdir "${fake_root}/${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
|
||||
prep_ext_mut "${ext_data_path}"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=auto merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/now-is-mutable" || die "${fake_root}${hierarchy} is not mutable"
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e u
|
||||
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable is not stored in expected location"
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}"
|
||||
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable disappeared from writable storage after unmerge"
|
||||
test ! -f "${fake_root}${hierarchy}/now-is-mutable" || die "now-is-mutable did not disappear from hierarchy after unmerge"
|
||||
|
||||
|
||||
#
|
||||
# extension data in /var/lib/extensions.mutable/…, empty hierarchy, auto-mutability
|
||||
#
|
||||
# mutable merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/simple-mutable-with-empty-hierarchy
|
||||
hierarchy=/opt
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
|
||||
prep_ext_mut "${ext_data_path}"
|
||||
|
||||
make_ro "${fake_root}" "${hierarchy}"
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=auto merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/now-is-mutable" || die "${fake_root}${hierarchy} is not mutable"
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e u
|
||||
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable is not stored in expected location"
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}"
|
||||
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable disappeared from writable storage after unmerge"
|
||||
test ! -f "${fake_root}${hierarchy}/now-is-mutable" || die "now-is-mutable did not disappear from hierarchy after unmerge"
|
||||
|
||||
|
||||
#
|
||||
# /var/lib/extensions.mutable/… is a symlink to /some/other/dir, read-only
|
||||
# hierarchy, auto-mutability
|
||||
#
|
||||
# mutable merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/mutable-symlink-with-read-only-hierarchy
|
||||
hierarchy=/usr
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
# generate extension writable data
|
||||
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
|
||||
real_ext_dir="${fake_root}/upperdir"
|
||||
prep_ext_mut "${real_ext_dir}"
|
||||
ln -sfTr "${real_ext_dir}" "${ext_data_path}"
|
||||
|
||||
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=auto merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/now-is-mutable" || die "${fake_root}${hierarchy} is not mutable"
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h u
|
||||
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable is not stored in expected location"
|
||||
test -f "${real_ext_dir}/now-is-mutable" || die "now-is-mutable is not stored in expected location"
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
|
||||
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable disappeared from writable storage after unmerge"
|
||||
test -f "${real_ext_dir}/now-is-mutable" || die "now-is-mutable disappeared from writable storage after unmerge"
|
||||
test ! -f "${fake_root}${hierarchy}/now-is-mutable" || die "now-is-mutable did not disappear from hierarchy after unmerge"
|
||||
|
||||
|
||||
#
|
||||
# /var/lib/extensions.mutable/… is a symlink to the hierarchy itself, auto-mutability
|
||||
#
|
||||
# for this to work, hierarchy must be mutable
|
||||
#
|
||||
# mutable merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/mutable-self-upper
|
||||
hierarchy=/usr
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
# generate extension writable data
|
||||
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
|
||||
real_ext_dir="${fake_root}${hierarchy}"
|
||||
prep_ext_mut "${real_ext_dir}"
|
||||
ln -sfTr "${real_ext_dir}" "${ext_data_path}"
|
||||
|
||||
# prepare writable hierarchy
|
||||
touch "${fake_root}${hierarchy}/preexisting-file-in-hierarchy"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=auto merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/now-is-mutable" || die "${fake_root}${hierarchy} is not mutable"
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h u
|
||||
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable is not stored in expected location"
|
||||
test -f "${real_ext_dir}/now-is-mutable" || die "now-is-mutable is not stored in expected location"
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h u
|
||||
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable disappeared from writable storage after unmerge"
|
||||
test -f "${real_ext_dir}/now-is-mutable" || die "now-is-mutable disappeared from writable storage after unmerge"
|
||||
|
||||
|
||||
#
|
||||
# /var/lib/extensions.mutable/… is a symlink to the hierarchy itself, which is
|
||||
# read-only, auto-mutability
|
||||
#
|
||||
# expecting a failure here
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/failure-self-upper-ro
|
||||
hierarchy=/usr
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
# generate extension writable data
|
||||
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
|
||||
real_ext_dir="${fake_root}${hierarchy}"
|
||||
prep_ext_mut "${real_ext_dir}"
|
||||
ln -sfTr "${real_ext_dir}" "${ext_data_path}"
|
||||
|
||||
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=auto merge || die "expected merge to fail"
|
||||
|
||||
|
||||
#
|
||||
# /var/lib/extensions.mutable/… is a dangling symlink, auto-mutability
|
||||
#
|
||||
# read-only merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/read-only-mutable-dangling-symlink
|
||||
hierarchy=/usr
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
|
||||
ln -sfTr "/should/not/exist/" "${ext_data_path}"
|
||||
|
||||
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=auto merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-still-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
|
||||
|
||||
|
||||
#
|
||||
# /var/lib/extensions.mutable/… exists, but it's ignored, mutability disabled explicitly
|
||||
#
|
||||
# read-only merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/disabled
|
||||
hierarchy=/usr
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
|
||||
prep_ext_mut "${ext_data_path}"
|
||||
|
||||
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=no merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-still-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
|
||||
|
||||
|
||||
#
|
||||
# /var/lib/extensions.mutable/… exists, but it's imported instead
|
||||
#
|
||||
# read-only merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/imported
|
||||
hierarchy=/usr
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
|
||||
prep_ext_mut "${ext_data_path}"
|
||||
|
||||
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=import merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-still-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h u
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
|
||||
|
||||
|
||||
#
|
||||
# /var/lib/extensions.mutable/… does not exist, but mutability is enabled
|
||||
# explicitly
|
||||
#
|
||||
# mutable merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/enabled
|
||||
hierarchy=/usr
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
|
||||
|
||||
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
test ! -d "${ext_data_path}" || die "extensions.mutable should not exist"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=yes merge
|
||||
|
||||
test -d "${ext_data_path}" || die "extensions.mutable should exist now"
|
||||
touch "${fake_root}${hierarchy}/now-is-mutable" || die "${fake_root}${hierarchy} is not mutable"
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h
|
||||
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable is not stored in expected location"
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
|
||||
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable disappeared from writable storage after unmerge"
|
||||
test ! -f "${fake_root}${hierarchy}/now-is-mutable" || die "now-is-mutable did not disappear from hierarchy after unmerge"
|
||||
|
||||
|
||||
#
|
||||
# /var/lib/extensions.mutable/… does not exist, auto-mutability
|
||||
#
|
||||
# read-only merged
|
||||
#
|
||||
|
||||
|
||||
fake_root=${fake_roots_dir}/simple-read-only-explicit
|
||||
hierarchy=/usr
|
||||
|
||||
prep_root "${fake_root}" "${hierarchy}"
|
||||
gen_os_release "${fake_root}"
|
||||
gen_test_ext_image "${fake_root}" "${hierarchy}"
|
||||
|
||||
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
|
||||
# run systemd-sysext
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=auto merge
|
||||
|
||||
touch "${fake_root}${hierarchy}/should-still-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
|
||||
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h
|
||||
|
||||
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
|
||||
|
||||
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
|
||||
|
||||
|
||||
#
|
||||
# done
|
||||
#
|
||||
|
||||
|
||||
touch /testok
|
||||
|
Loading…
Reference in New Issue
Block a user