core: add ability to define arbitrary bind mounts for services

This adds two new settings BindPaths= and BindReadOnlyPaths=. They allow
defining arbitrary bind mounts specific to particular services. This is
particularly useful for services with RootDirectory= set as this permits making
specific bits of the host directory available to chrooted services.

The two new settings follow the concepts nspawn already possess in --bind= and
--bind-ro=, as well as the .nspawn settings Bind= and BindReadOnly= (and these
latter options should probably be renamed to BindPaths= and BindReadOnlyPaths=
too).

Fixes: #3439
This commit is contained in:
Lennart Poettering 2016-11-23 22:21:40 +01:00
parent 8fceda937f
commit d2d6c096f6
11 changed files with 502 additions and 26 deletions

View file

@ -967,6 +967,31 @@
<varname>SystemCallFilter=~@mount</varname>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>BindPaths=</varname></term>
<term><varname>BindReadOnlyPaths=</varname></term>
<listitem><para>Configures unit-specific bind mounts. A bind mount makes a particular file or directory
available at an additional place in the unit's view of the file system. Any bind mounts created with this
option are specific to the unit, and are not visible in the host's mount table. This option expects a
whitespace separated list of bind mount definitions. Each definition consists of a colon-separated triple of
source path, destination path and option string, where the latter two are optional. If only a source path is
specified the source and destination is taken to be the same. The option string may be either
<literal>rbind</literal> or <literal>norbind</literal> for configuring a recursive or non-recursive bind
mount. If the destination parth is omitted, the option string must be omitted too.</para>
<para><varname>BindPaths=</varname> creates regular writable bind mounts (unless the source file system mount
is already marked read-only), while <varname>BindReadOnlyPaths=</varname> creates read-only bind mounts. These
settings may be used more than once, each usage appends to the unit's list of bind mounts. If the empty string
is assigned to either of these two options the entire list of bind mounts defined prior to this is reset. Note
that in this case both read-only and regular bind mounts are reset, regardless which of the two settings is
used.</para>
<para>This option is particularly useful when <varname>RootDirectory=</varname> is used. In this case the
source path refers to a path on the host file system, while the destination path referes to a path below the
root directory of the unit.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>PrivateTmp=</varname></term>

View file

@ -675,6 +675,49 @@ static int property_get_output_fdname(
return sd_bus_message_append(reply, "s", name);
}
static int property_get_bind_paths(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
ExecContext *c = userdata;
unsigned i;
bool ro;
int r;
assert(bus);
assert(c);
assert(property);
assert(reply);
ro = !!strstr(property, "ReadOnly");
r = sd_bus_message_open_container(reply, 'a', "(ssbt)");
if (r < 0)
return r;
for (i = 0; i < c->n_bind_mounts; i++) {
if (ro != c->bind_mounts[i].read_only)
continue;
r = sd_bus_message_append(
reply, "(ssbt)",
c->bind_mounts[i].source,
c->bind_mounts[i].destination,
c->bind_mounts[i].ignore_enoent,
c->bind_mounts[i].recursive ? MS_REC : 0);
if (r < 0)
return r;
}
return sd_bus_message_close_container(reply);
}
const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
@ -783,6 +826,8 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("MemoryDenyWriteExecute", "b", bus_property_get_bool, offsetof(ExecContext, memory_deny_write_execute), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestrictRealtime", "b", bus_property_get_bool, offsetof(ExecContext, restrict_realtime), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestrictNamespaces", "t", bus_property_get_ulong, offsetof(ExecContext, restrict_namespaces), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("BindPaths", "a(ssbt)", property_get_bind_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("BindReadOnlyPaths", "a(ssbt)", property_get_bind_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_VTABLE_END
};
@ -1364,8 +1409,8 @@ int bus_exec_context_set_transient_property(
if (r < 0)
return r;
if (!isempty(path) && !path_is_absolute(path))
return sd_bus_error_set_errnof(error, EINVAL, "Path %s is not absolute.", path);
if (!path_is_absolute(path))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not absolute.", path);
if (mode != UNIT_CHECK) {
char *buf = NULL;
@ -1629,8 +1674,74 @@ int bus_exec_context_set_transient_property(
unit_write_drop_in_private_format(u, mode, name, "%s=%s", name, strempty(mount_propagation_flags_to_string(flags)));
}
return 1;
} else if (STR_IN_SET(name, "BindPaths", "BindReadOnlyPaths")) {
unsigned empty = true;
r = sd_bus_message_enter_container(message, 'a', "(ssbt)");
if (r < 0)
return r;
while ((r = sd_bus_message_enter_container(message, 'r', "ssbt")) > 0) {
const char *source, *destination;
int ignore_enoent;
uint64_t mount_flags;
r = sd_bus_message_read(message, "ssbt", &source, &destination, &ignore_enoent, &mount_flags);
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (!path_is_absolute(source))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not absolute.", source);
if (!path_is_absolute(destination))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not absolute.", source);
if (!IN_SET(mount_flags, 0, MS_REC))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown mount flags.");
if (mode != UNIT_CHECK) {
r = bind_mount_add(&c->bind_mounts, &c->n_bind_mounts,
&(BindMount) {
.source = strdup(source),
.destination = strdup(destination),
.read_only = !!strstr(name, "ReadOnly"),
.recursive = !!(mount_flags & MS_REC),
.ignore_enoent = ignore_enoent,
});
if (r < 0)
return r;
unit_write_drop_in_private_format(
u, mode, name,
"%s=%s%s:%s:%s",
name,
ignore_enoent ? "-" : "",
source,
destination,
(mount_flags & MS_REC) ? "rbind" : "norbind");
}
empty = false;
}
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (empty) {
bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
c->bind_mounts = NULL;
c->n_bind_mounts = 0;
}
return 1;
}
ri = rlimit_from_string(name);
if (ri < 0) {
soft = endswith(name, "Soft");

View file

@ -1826,6 +1826,9 @@ static bool exec_needs_mount_namespace(
!strv_isempty(context->inaccessible_paths))
return true;
if (context->n_bind_mounts > 0)
return true;
if (context->mount_flags != 0)
return true;
@ -2147,6 +2150,8 @@ static int apply_mount_namespace(Unit *u, const ExecContext *context,
r = setup_namespace(root_dir, &ns_info, rw,
context->read_only_paths,
context->inaccessible_paths,
context->bind_mounts,
context->n_bind_mounts,
tmp,
var,
context->protect_home,
@ -3086,6 +3091,8 @@ void exec_context_done(ExecContext *c) {
c->read_write_paths = strv_free(c->read_write_paths);
c->inaccessible_paths = strv_free(c->inaccessible_paths);
bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
if (c->cpuset)
CPU_FREE(c->cpuset);
@ -3569,6 +3576,15 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
fputs("\n", f);
}
if (c->n_bind_mounts > 0)
for (i = 0; i < c->n_bind_mounts; i++) {
fprintf(f, "%s%s: %s:%s:%s\n", prefix,
c->bind_mounts[i].read_only ? "BindReadOnlyPaths" : "BindPaths",
c->bind_mounts[i].source,
c->bind_mounts[i].destination,
c->bind_mounts[i].recursive ? "rbind" : "norbind");
}
if (c->utmp_id)
fprintf(f,
"%sUtmpIdentifier: %s\n",

View file

@ -161,6 +161,8 @@ struct ExecContext {
char **read_write_paths, **read_only_paths, **inaccessible_paths;
unsigned long mount_flags;
BindMount *bind_mounts;
unsigned n_bind_mounts;
uint64_t capability_bounding_set;
uint64_t capability_ambient_set;

View file

@ -89,6 +89,8 @@ $1.InaccessibleDirectories, config_parse_namespace_path_strv, 0,
$1.ReadWritePaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_write_paths)
$1.ReadOnlyPaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_paths)
$1.InaccessiblePaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.inaccessible_paths)
$1.BindPaths, config_parse_bind_paths, 0, offsetof($1, exec_context)
$1.BindReadOnlyPaths, config_parse_bind_paths, 0, offsetof($1, exec_context)
$1.PrivateTmp, config_parse_bool, 0, offsetof($1, exec_context.private_tmp)
$1.PrivateDevices, config_parse_bool, 0, offsetof($1, exec_context.private_devices)
$1.ProtectKernelTunables, config_parse_bool, 0, offsetof($1, exec_context.protect_kernel_tunables)

View file

@ -3891,6 +3891,132 @@ int config_parse_namespace_path_strv(
return 0;
}
int config_parse_bind_paths(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
ExecContext *c = data;
const char *p;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if (isempty(rvalue)) {
/* Empty assignment resets the list */
bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
c->bind_mounts = NULL;
c->n_bind_mounts = 0;
return 0;
}
p = rvalue;
for (;;) {
_cleanup_free_ char *source = NULL, *destination = NULL;
char *s = NULL, *d = NULL;
bool rbind = true, ignore_enoent = false;
r = extract_first_word(&p, &source, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
if (r == 0)
break;
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue);
return 0;
}
s = source;
if (s[0] == '-') {
ignore_enoent = true;
s++;
}
if (!utf8_is_valid(s)) {
log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, s);
return 0;
}
if (!path_is_absolute(s)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute source path, ignoring: %s", s);
return 0;
}
path_kill_slashes(s);
/* Optionally, the destination is specified. */
if (p && p[-1] == ':') {
r = extract_first_word(&p, &destination, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue);
return 0;
}
if (r == 0) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Missing argument after ':': %s", rvalue);
return 0;
}
if (!utf8_is_valid(destination)) {
log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, destination);
return 0;
}
if (!path_is_absolute(destination)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute destination path, ignoring: %s", destination);
return 0;
}
d = path_kill_slashes(destination);
/* Optionally, there's also a short option string specified */
if (p && p[-1] == ':') {
_cleanup_free_ char *options = NULL;
r = extract_first_word(&p, &options, NULL, EXTRACT_QUOTES);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue);
return 0;
}
if (isempty(options) || streq(options, "rbind"))
rbind = true;
else if (streq(options, "norbind"))
rbind = false;
else {
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid option string, ignoring setting: %s", options);
return 0;
}
}
} else
d = s;
r = bind_mount_add(&c->bind_mounts, &c->n_bind_mounts,
&(BindMount) {
.source = s,
.destination = d,
.read_only = !!strstr(lvalue, "ReadOnly"),
.recursive = rbind,
.ignore_enoent = ignore_enoent,
});
if (r < 0)
return log_oom();
}
return 0;
}
int config_parse_no_new_privileges(
const char* unit,
const char *filename,
@ -4388,6 +4514,7 @@ void unit_dump_config_items(FILE *f) {
{ config_parse_sec, "SECONDS" },
{ config_parse_nsec, "NANOSECONDS" },
{ config_parse_namespace_path_strv, "PATH [...]" },
{ config_parse_bind_paths, "PATH[:PATH[:OPTIONS]] [...]" },
{ config_parse_unit_requires_mounts_for, "PATH [...]" },
{ config_parse_exec_mount_flags, "MOUNTFLAG [...]" },
{ config_parse_unit_string_printf, "STRING" },

View file

@ -117,6 +117,7 @@ int config_parse_sec_fix_0(const char *unit, const char *filename, unsigned line
int config_parse_user_group(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_user_group_strv(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_restrict_namespaces(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_bind_paths(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length);

View file

@ -50,6 +50,8 @@
typedef enum MountMode {
/* This is ordered by priority! */
INACCESSIBLE,
BIND_MOUNT,
BIND_MOUNT_RECURSIVE,
READONLY,
PRIVATE_TMP,
PRIVATE_VAR_TMP,
@ -64,6 +66,8 @@ typedef struct MountEntry {
bool has_prefix:1; /* Already is prefixed by the root dir? */
bool read_only:1; /* Shall this mount point be read-only? */
char *path_malloc; /* Use this instead of 'path' if we had to allocate memory */
const char *source_const; /* The source path, for bind mounts */
char *source_malloc;
} MountEntry;
/*
@ -166,6 +170,12 @@ static bool mount_entry_read_only(const MountEntry *p) {
return p->read_only || IN_SET(p->mode, READONLY, INACCESSIBLE);
}
static const char *mount_entry_source(const MountEntry *p) {
assert(p);
return p->source_malloc ?: p->source_const;
}
static int append_access_mounts(MountEntry **p, char **strv, MountMode mode) {
char **i;
@ -201,6 +211,25 @@ static int append_access_mounts(MountEntry **p, char **strv, MountMode mode) {
return 0;
}
static int append_bind_mounts(MountEntry **p, const BindMount *binds, unsigned n) {
unsigned i;
assert(p);
for (i = 0; i < n; i++) {
const BindMount *b = binds + i;
*((*p)++) = (MountEntry) {
.path_const = b->destination,
.mode = b->recursive ? BIND_MOUNT_RECURSIVE : BIND_MOUNT,
.read_only = b->read_only,
.source_const = b->source,
};
}
return 0;
}
static int append_static_mounts(MountEntry **p, const MountEntry *mounts, unsigned n, bool ignore_protect) {
unsigned i;
@ -568,27 +597,33 @@ fail:
return r;
}
static int mount_entry_chase(MountEntry *m, const char *root_directory) {
static int mount_entry_chase(
const char *root_directory,
MountEntry *m,
const char *path,
char **location) {
char *chased;
int r;
assert(m);
/* Since mount() will always follow symlinks and we need to take the different root directory into account we
* chase the symlinks on our own first. */
* chase the symlinks on our own first. This is called for the destination path, as well as the source path (if
* that applies). The result is stored in "location". */
r = chase_symlinks(mount_entry_path(m), root_directory, 0, &chased);
r = chase_symlinks(path, root_directory, 0, &chased);
if (r == -ENOENT && m->ignore) {
log_debug_errno(r, "Path %s does not exist, ignoring.", mount_entry_path(m));
log_debug_errno(r, "Path %s does not exist, ignoring.", path);
return 0;
}
if (r < 0)
return log_debug_errno(r, "Failed to follow symlinks on %s: %m", mount_entry_path(m));
return log_debug_errno(r, "Failed to follow symlinks on %s: %m", path);
log_debug("Followed symlinks %s → %s.", mount_entry_path(m), chased);
log_debug("Followed symlinks %s → %s.", path, chased);
free(m->path_malloc);
m->path_malloc = chased;
free(*location);
*location = chased;
return 1;
}
@ -600,11 +635,12 @@ static int apply_mount(
const char *var_tmp_dir) {
const char *what;
bool rbind = true;
int r;
assert(m);
r = mount_entry_chase(m, root_directory);
r = mount_entry_chase(root_directory, m, mount_entry_path(m), &m->path_malloc);
if (r <= 0)
return r;
@ -633,7 +669,6 @@ static int apply_mount(
case READONLY:
case READWRITE:
r = path_is_mount_point(mount_entry_path(m), root_directory, 0);
if (r < 0)
return log_debug_errno(r, "Failed to determine whether %s is already a mount point: %m", mount_entry_path(m));
@ -643,6 +678,19 @@ static int apply_mount(
what = mount_entry_path(m);
break;
case BIND_MOUNT:
rbind = false;
/* fallthrough */
case BIND_MOUNT_RECURSIVE:
/* Also chase the source mount */
r = mount_entry_chase(root_directory, m, mount_entry_source(m), &m->source_malloc);
if (r <= 0)
return r;
what = mount_entry_source(m);
break;
case PRIVATE_TMP:
what = tmp_dir;
break;
@ -660,7 +708,7 @@ static int apply_mount(
assert(what);
if (mount(what, mount_entry_path(m), NULL, MS_BIND|MS_REC, NULL) < 0)
if (mount(what, mount_entry_path(m), NULL, MS_BIND|(rbind ? MS_REC : 0), NULL) < 0)
return log_debug_errno(errno, "Failed to mount %s to %s: %m", what, mount_entry_path(m));
log_debug("Successfully mounted %s to %s", what, mount_entry_path(m));
@ -695,6 +743,8 @@ static unsigned namespace_calculate_mounts(
char** read_write_paths,
char** read_only_paths,
char** inaccessible_paths,
const BindMount *bind_mounts,
unsigned n_bind_mounts,
const char* tmp_dir,
const char* var_tmp_dir,
ProtectHome protect_home,
@ -719,6 +769,7 @@ static unsigned namespace_calculate_mounts(
strv_length(read_write_paths) +
strv_length(read_only_paths) +
strv_length(inaccessible_paths) +
n_bind_mounts +
ns_info->private_dev +
(ns_info->protect_kernel_tunables ? ELEMENTSOF(protect_kernel_tunables_table) : 0) +
(ns_info->protect_control_groups ? 1 : 0) +
@ -732,6 +783,8 @@ int setup_namespace(
char** read_write_paths,
char** read_only_paths,
char** inaccessible_paths,
const BindMount *bind_mounts,
unsigned n_bind_mounts,
const char* tmp_dir,
const char* var_tmp_dir,
ProtectHome protect_home,
@ -751,6 +804,7 @@ int setup_namespace(
read_write_paths,
read_only_paths,
inaccessible_paths,
bind_mounts, n_bind_mounts,
tmp_dir, var_tmp_dir,
protect_home, protect_system);
@ -772,6 +826,10 @@ int setup_namespace(
if (r < 0)
goto finish;
r = append_bind_mounts(&m, bind_mounts, n_bind_mounts);
if (r < 0)
goto finish;
if (tmp_dir) {
*(m++) = (MountEntry) {
.path_const = "/tmp",
@ -911,6 +969,53 @@ finish:
return r;
}
void bind_mount_free_many(BindMount *b, unsigned n) {
unsigned i;
assert(b || n == 0);
for (i = 0; i < n; i++) {
free(b[i].source);
free(b[i].destination);
}
free(b);
}
int bind_mount_add(BindMount **b, unsigned *n, const BindMount *item) {
_cleanup_free_ char *s = NULL, *d = NULL;
BindMount *c;
assert(b);
assert(n);
assert(item);
s = strdup(item->source);
if (!s)
return -ENOMEM;
d = strdup(item->destination);
if (!d)
return -ENOMEM;
c = realloc_multiply(*b, sizeof(BindMount), *n + 1);
if (!c)
return -ENOMEM;
*b = c;
c[(*n) ++] = (BindMount) {
.source = s,
.destination = d,
.read_only = item->read_only,
.recursive = item->recursive,
.ignore_enoent = item->ignore_enoent,
};
s = d = NULL;
return 0;
}
static int setup_one_tmp_dir(const char *id, const char *prefix, char **path) {
_cleanup_free_ char *x = NULL;
char bid[SD_ID128_STRING_MAX];

View file

@ -21,6 +21,7 @@
***/
typedef struct NameSpaceInfo NameSpaceInfo;
typedef struct BindMount BindMount;
#include <stdbool.h>
@ -51,20 +52,32 @@ struct NameSpaceInfo {
bool protect_kernel_modules:1;
};
int setup_namespace(const char *chroot,
const NameSpaceInfo *ns_info,
char **read_write_paths,
char **read_only_paths,
char **inaccessible_paths,
const char *tmp_dir,
const char *var_tmp_dir,
ProtectHome protect_home,
ProtectSystem protect_system,
unsigned long mount_flags);
struct BindMount {
char *source;
char *destination;
bool read_only:1;
bool recursive:1;
bool ignore_enoent:1;
};
int setup_tmp_dirs(const char *id,
char **tmp_dir,
char **var_tmp_dir);
int setup_namespace(
const char *root_directory,
const NameSpaceInfo *ns_info,
char **read_write_paths,
char **read_only_paths,
char **inaccessible_paths,
const BindMount *bind_mounts,
unsigned n_bind_mounts,
const char *tmp_dir,
const char *var_tmp_dir,
ProtectHome protect_home,
ProtectSystem protect_system,
unsigned long mount_flags);
int setup_tmp_dirs(
const char *id,
char **tmp_dir,
char **var_tmp_dir);
int setup_netns(int netns_storage_socket[2]);
@ -73,3 +86,6 @@ ProtectHome protect_home_from_string(const char *s) _pure_;
const char* protect_system_to_string(ProtectSystem p) _const_;
ProtectSystem protect_system_from_string(const char *s) _pure_;
void bind_mount_free_many(BindMount *b, unsigned n);
int bind_mount_add(BindMount **b, unsigned *n, const BindMount *item);

View file

@ -590,6 +590,76 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
}
r = sd_bus_message_append(m, "v", "t", f);
} else if (STR_IN_SET(field, "BindPaths", "BindReadOnlyPaths")) {
const char *p = eq;
r = sd_bus_message_open_container(m, 'v', "a(ssbt)");
if (r < 0)
return r;
r = sd_bus_message_open_container(m, 'a', "(ssbt)");
if (r < 0)
return r;
for (;;) {
_cleanup_free_ char *source = NULL, *destination = NULL;
char *s = NULL, *d = NULL;
bool ignore_enoent = false;
uint64_t flags = MS_REC;
r = extract_first_word(&p, &source, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return log_error_errno(r, "Failed to parse argument: %m");
if (r == 0)
break;
s = source;
if (s[0] == '-') {
ignore_enoent = true;
s++;
}
if (p && p[-1] == ':') {
r = extract_first_word(&p, &destination, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return log_error_errno(r, "Failed to parse argument: %m");
if (r == 0) {
log_error("Missing argument after ':': %s", eq);
return -EINVAL;
}
d = destination;
if (p && p[-1] == ':') {
_cleanup_free_ char *options = NULL;
r = extract_first_word(&p, &options, NULL, EXTRACT_QUOTES);
if (r < 0)
return log_error_errno(r, "Failed to parse argument: %m");
if (isempty(options) || streq(options, "rbind"))
flags = MS_REC;
else if (streq(options, "norbind"))
flags = 0;
else {
log_error("Unknown options: %s", eq);
return -EINVAL;
}
}
} else
d = s;
r = sd_bus_message_append(m, "(ssbt)", s, d, ignore_enoent, flags);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(m);
if (r < 0)
return r;
r = sd_bus_message_close_container(m);
} else {
log_error("Unknown assignment %s.", assignment);
return -EINVAL;

View file

@ -81,6 +81,7 @@ int main(int argc, char *argv[]) {
(char **) writable,
(char **) readonly,
(char **) inaccessible,
&(BindMount) { .source = (char*) "/usr/bin", .destination = (char*) "/etc/systemd", .read_only = true }, 1,
tmp_dir,
var_tmp_dir,
PROTECT_HOME_NO,