core: add OpenFile setting

This commit is contained in:
Richard Phibel 2022-11-07 17:13:15 +01:00 committed by Lennart Poettering
parent 81315baa68
commit cd48e23f6a
27 changed files with 914 additions and 6 deletions

View file

@ -2576,6 +2576,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
readonly u NRestarts = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s OOMPolicy = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(sst) OpenFile = [...];
readonly t ExecMainStartTimestamp = ...;
readonly t ExecMainStartTimestampMonotonic = ...;
readonly t ExecMainExitTimestamp = ...;
@ -3173,6 +3175,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property OOMPolicy is not documented!-->
<!--property OpenFile is not documented!-->
<!--property ExecCondition is not documented!-->
<!--property ExecConditionEx is not documented!-->
@ -3729,6 +3733,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="OOMPolicy"/>
<variablelist class="dbus-property" generated="True" extra-ref="OpenFile"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExecMainStartTimestamp"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExecMainStartTimestampMonotonic"/>

View file

@ -1156,6 +1156,37 @@
kills, this setting determines the state of the unit after <command>systemd-oomd</command> kills a
cgroup associated with it.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>OpenFile=</varname></term>
<listitem><para>Takes an argument of the form <literal>path<optional><replaceable>:fd-name:options</replaceable></optional></literal>,
where:
<itemizedlist>
<listitem><simpara><literal>path</literal> is a path to a file or an <constant>AF_UNIX</constant> socket in the file system;</simpara></listitem>
<listitem><simpara><literal>fd-name</literal> is a name that will be associated with the file descriptor;
the name may contain any ASCII character, but must exclude control characters and ":", and must be at most 255 characters in length;
it is optional and, if not provided, defaults to the file name;</simpara></listitem>
<listitem><simpara><literal>options</literal> is a comma-separated list of access options;
possible values are
<literal>read-only</literal>,
<literal>append</literal>,
<literal>truncate</literal>,
<literal>graceful</literal>;
if not specified, files will be opened in <constant>rw</constant> mode;
if <literal>graceful</literal> is specified, errors during file/socket opening are ignored.
Specifying the same option several times is treated as an error.</simpara></listitem>
</itemizedlist>
The file or socket is opened by the service manager and the file descriptor is passed to the service.
If the path is a socket, we call <function>connect()</function> on it.
See <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>
for more details on how to retrieve these file descriptors.</para>
<para>This setting is useful to allow services to access files/sockets that they can't access themselves
(due to running in a separate mount namespace, not having privileges, ...).</para>
<para>This setting can be specified multiple times, in which case all the specified paths are opened and the file descriptors passed to the service.
If the empty string is assigned, the entire list of open files defined prior to this is reset.</para></listitem>
</varlistentry>
</variablelist>
<para id='shared-unit-options'>Check

View file

@ -17,6 +17,7 @@
#include "fileio.h"
#include "locale-util.h"
#include "mount-util.h"
#include "open-file.h"
#include "parse-util.h"
#include "path-util.h"
#include "selinux-access.h"
@ -36,6 +37,34 @@ static BUS_DEFINE_PROPERTY_GET(property_get_timeout_abort_usec, "t", Service, se
static BUS_DEFINE_PROPERTY_GET(property_get_watchdog_usec, "t", Service, service_get_watchdog_usec);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_timeout_failure_mode, service_timeout_failure_mode, ServiceTimeoutFailureMode);
static int property_get_open_files(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
OpenFile **open_files = ASSERT_PTR(userdata);
int r;
assert(bus);
assert(reply);
r = sd_bus_message_open_container(reply, 'a', "(sst)");
if (r < 0)
return r;
LIST_FOREACH(open_files, of, *open_files) {
r = sd_bus_message_append(reply, "(sst)", of->path, of->fdname, of->flags);
if (r < 0)
return r;
}
return sd_bus_message_close_container(reply);
}
static int property_get_exit_status_set(
sd_bus *bus,
const char *path,
@ -228,6 +257,7 @@ const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("NRestarts", "u", bus_property_get_unsigned, offsetof(Service, n_restarts), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("OOMPolicy", "s", bus_property_get_oom_policy, offsetof(Service, oom_policy), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OpenFile", "a(sst)", property_get_open_files, offsetof(Service, open_files), SD_BUS_VTABLE_PROPERTY_CONST),
BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecCondition", offsetof(Service, exec_command[SERVICE_EXEC_CONDITION]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
@ -532,6 +562,56 @@ static int bus_service_set_transient_property(
if (streq(name, "StandardErrorFileDescriptor"))
return bus_set_transient_std_fd(u, name, &s->stderr_fd, &s->exec_context.stdio_as_fds, message, flags, error);
if (streq(name, "OpenFile")) {
const char *path, *fdname;
uint64_t offlags;
r = sd_bus_message_enter_container(message, 'a', "(sst)");
if (r < 0)
return r;
while ((r = sd_bus_message_read(message, "(sst)", &path, &fdname, &offlags)) > 0) {
_cleanup_(open_file_freep) OpenFile *of = NULL;
_cleanup_free_ char *ofs = NULL;
of = new(OpenFile, 1);
if (!of)
return -ENOMEM;
*of = (OpenFile) {
.path = strdup(path),
.fdname = strdup(fdname),
.flags = offlags,
};
if (!of->path || !of->fdname)
return -ENOMEM;
r = open_file_validate(of);
if (r < 0)
return r;
if (UNIT_WRITE_FLAGS_NOOP(flags))
continue;
r = open_file_to_string(of, &ofs);
if (r < 0)
return sd_bus_error_set_errnof(
error, r, "Failed to convert OpenFile= value to string: %m");
LIST_APPEND(open_files, s->open_files, TAKE_PTR(of));
unit_write_settingf(u, flags | UNIT_ESCAPE_SPECIFIERS, name, "OpenFile=%s", ofs);
}
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
return 1;
}
return 0;
}

View file

@ -151,11 +151,14 @@ static int shift_fds(int fds[], size_t n_fds) {
return 0;
}
static int flags_fds(const int fds[], size_t n_socket_fds, size_t n_storage_fds, bool nonblock) {
size_t n_fds;
static int flags_fds(
const int fds[],
size_t n_socket_fds,
size_t n_fds,
bool nonblock) {
int r;
n_fds = n_socket_fds + n_storage_fds;
if (n_fds <= 0)
return 0;
@ -1805,6 +1808,7 @@ static int build_environment(
const ExecContext *c,
const ExecParameters *p,
size_t n_fds,
char **fdnames,
const char *home,
const char *username,
const char *shell,
@ -1837,7 +1841,7 @@ static int build_environment(
return -ENOMEM;
our_env[n_env++] = x;
joined = strv_join(p->fd_names, ":");
joined = strv_join(fdnames, ":");
if (!joined)
return -ENOMEM;
@ -4105,6 +4109,123 @@ static int add_shifted_fd(int *fds, size_t fds_size, size_t *n_fds, int fd, int
return 1;
}
static int connect_unix_harder(Unit *u, const OpenFile *of, int ofd) {
union sockaddr_union addr = {
.un.sun_family = AF_UNIX,
};
socklen_t sa_len;
static const int socket_types[] = { SOCK_DGRAM, SOCK_STREAM, SOCK_SEQPACKET };
int r;
assert(u);
assert(of);
assert(ofd >= 0);
r = sockaddr_un_set_path(&addr.un, FORMAT_PROC_FD_PATH(ofd));
if (r < 0)
return log_unit_error_errno(u, r, "Failed to set sockaddr for %s: %m", of->path);
sa_len = r;
for (size_t i = 0; i < ELEMENTSOF(socket_types); i++) {
_cleanup_close_ int fd = -EBADF;
fd = socket(AF_UNIX, socket_types[i] | SOCK_CLOEXEC, 0);
if (fd < 0)
return log_unit_error_errno(u, errno, "Failed to create socket for %s: %m", of->path);
r = RET_NERRNO(connect(fd, &addr.sa, sa_len));
if (r == -EPROTOTYPE)
continue;
if (r < 0)
return log_unit_error_errno(u, r, "Failed to connect socket for %s: %m", of->path);
return TAKE_FD(fd);
}
return log_unit_error_errno(u, SYNTHETIC_ERRNO(EPROTOTYPE), "Failed to connect socket for \"%s\".", of->path);
}
static int get_open_file_fd(Unit *u, const OpenFile *of) {
struct stat st;
_cleanup_close_ int fd = -EBADF, ofd = -EBADF;
assert(u);
assert(of);
ofd = open(of->path, O_PATH | O_CLOEXEC);
if (ofd < 0)
return log_error_errno(errno, "Could not open \"%s\": %m", of->path);
if (fstat(ofd, &st) < 0)
return log_error_errno(errno, "Failed to stat %s: %m", of->path);
if (S_ISSOCK(st.st_mode)) {
fd = connect_unix_harder(u, of, ofd);
if (fd < 0)
return fd;
if (FLAGS_SET(of->flags, OPENFILE_READ_ONLY) && shutdown(fd, SHUT_WR) < 0)
return log_error_errno(errno, "Failed to shutdown send for socket %s: %m", of->path);
log_unit_debug(u, "socket %s opened (fd=%d)", of->path, fd);
} else {
int flags = FLAGS_SET(of->flags, OPENFILE_READ_ONLY) ? O_RDONLY : O_RDWR;
if (FLAGS_SET(of->flags, OPENFILE_APPEND))
flags |= O_APPEND;
else if (FLAGS_SET(of->flags, OPENFILE_TRUNCATE))
flags |= O_TRUNC;
fd = fd_reopen(ofd, flags | O_CLOEXEC);
if (fd < 0)
return log_unit_error_errno(u, fd, "Failed to open file %s: %m", of->path);
log_unit_debug(u, "file %s opened (fd=%d)", of->path, fd);
}
return TAKE_FD(fd);
}
static int collect_open_file_fds(
Unit *u,
OpenFile* open_files,
int **fds,
char ***fdnames,
size_t *n_fds) {
int r;
assert(u);
assert(fds);
assert(fdnames);
assert(n_fds);
LIST_FOREACH(open_files, of, open_files) {
_cleanup_close_ int fd = -EBADF;
fd = get_open_file_fd(u, of);
if (fd < 0) {
if (FLAGS_SET(of->flags, OPENFILE_GRACEFUL)) {
log_unit_debug_errno(u, fd, "Failed to get OpenFile= file descriptor for %s, ignoring: %m", of->path);
continue;
}
return fd;
}
if (!GREEDY_REALLOC(*fds, *n_fds + 1))
return -ENOMEM;
r = strv_extend(fdnames, of->fdname);
if (r < 0)
return r;
(*fds)[*n_fds] = TAKE_FD(fd);
(*n_fds)++;
}
return 0;
}
static int exec_child(
Unit *unit,
const ExecCommand *command,
@ -4114,7 +4235,7 @@ static int exec_child(
DynamicCreds *dcreds,
int socket_fd,
const int named_iofds[static 3],
int *fds,
int *params_fds,
size_t n_socket_fds,
size_t n_storage_fds,
char **files_env,
@ -4154,6 +4275,8 @@ static int exec_child(
int secure_bits;
_cleanup_free_ gid_t *gids_after_pam = NULL;
int ngids_after_pam = 0;
_cleanup_free_ int *fds = NULL;
_cleanup_strv_free_ char **fdnames = NULL;
assert(unit);
assert(command);
@ -4196,6 +4319,24 @@ static int exec_child(
/* In case anything used libc syslog(), close this here, too */
closelog();
fds = newdup(int, params_fds, n_fds);
if (!fds) {
*exit_status = EXIT_MEMORY;
return log_oom();
}
fdnames = strv_copy((char**) params->fd_names);
if (!fdnames) {
*exit_status = EXIT_MEMORY;
return log_oom();
}
r = collect_open_file_fds(unit, params->open_files, &fds, &fdnames, &n_fds);
if (r < 0) {
*exit_status = EXIT_FDS;
return log_unit_error_errno(unit, r, "Failed to get OpenFile= file descriptors: %m");
}
int keep_fds[n_fds + 3];
memcpy_safe(keep_fds, fds, n_fds * sizeof(int));
n_keep_fds = n_fds;
@ -4551,6 +4692,7 @@ static int exec_child(
context,
params,
n_fds,
fdnames,
home,
username,
shell,
@ -4843,7 +4985,7 @@ static int exec_child(
if (r >= 0)
r = shift_fds(fds, n_fds);
if (r >= 0)
r = flags_fds(fds, n_socket_fds, n_storage_fds, context->non_blocking);
r = flags_fds(fds, n_socket_fds, n_fds, context->non_blocking);
if (r < 0) {
*exit_status = EXIT_FDS;
return log_unit_error_errno(unit, r, "Failed to adjust passed file descriptors: %m");

View file

@ -23,6 +23,7 @@ typedef struct Manager Manager;
#include "namespace.h"
#include "nsflags.h"
#include "numa-util.h"
#include "open-file.h"
#include "path-util.h"
#include "set.h"
#include "time-util.h"
@ -427,6 +428,8 @@ struct ExecParameters {
int exec_fd;
const char *notify_socket;
LIST_HEAD(OpenFile, open_files);
};
#include "unit.h"

View file

@ -426,6 +426,7 @@ Service.BusPolicy, config_parse_warn_compat,
Service.USBFunctionDescriptors, config_parse_unit_path_printf, 0, offsetof(Service, usb_function_descriptors)
Service.USBFunctionStrings, config_parse_unit_path_printf, 0, offsetof(Service, usb_function_strings)
Service.OOMPolicy, config_parse_oom_policy, 0, offsetof(Service, oom_policy)
Service.OpenFile, config_parse_open_file, 0, offsetof(Service, open_files)
{{ EXEC_CONTEXT_CONFIG_ITEMS('Service') }}
{{ CGROUP_CONTEXT_CONFIG_ITEMS('Service') }}
{{ KILL_CONTEXT_CONFIG_ITEMS('Service') }}

View file

@ -49,6 +49,7 @@
#include "missing_ioprio.h"
#include "mountpoint-util.h"
#include "nulstr-util.h"
#include "open-file.h"
#include "parse-helpers.h"
#include "parse-util.h"
#include "path-util.h"
@ -6532,3 +6533,39 @@ int config_parse_log_filter_patterns(
return 0;
}
int config_parse_open_file(
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) {
_cleanup_(open_file_freep) OpenFile *of = NULL;
OpenFile **head = ASSERT_PTR(data);
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
if (isempty(rvalue)) {
open_file_free_many(head);
return 0;
}
r = open_file_parse(rvalue, &of);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse OpenFile= setting, ignoring: %s", rvalue);
return 0;
}
LIST_APPEND(open_files, *head, TAKE_PTR(of));
return 0;
}

View file

@ -151,6 +151,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_restrict_network_interfaces);
CONFIG_PARSER_PROTOTYPE(config_parse_watchdog_sec);
CONFIG_PARSER_PROTOTYPE(config_parse_tty_size);
CONFIG_PARSER_PROTOTYPE(config_parse_log_filter_patterns);
CONFIG_PARSER_PROTOTYPE(config_parse_open_file);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);

View file

@ -26,6 +26,7 @@
#include "load-fragment.h"
#include "log.h"
#include "manager.h"
#include "open-file.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
@ -360,6 +361,8 @@ static void service_done(Unit *u) {
assert(s);
open_file_free_many(&s->open_files);
s->pid_file = mfree(s->pid_file);
s->status_text = mfree(s->status_text);
@ -925,6 +928,21 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
prefix, s->n_fd_store_max,
prefix, s->n_fd_store);
if (s->open_files)
LIST_FOREACH(open_files, of, s->open_files) {
_cleanup_free_ char *ofs = NULL;
int r;
r = open_file_to_string(of, &ofs);
if (r < 0) {
log_debug_errno(r,
"Failed to convert OpenFile= setting to string, ignoring: %m");
continue;
}
fprintf(f, "%sOpen File: %s\n", prefix, ofs);
}
cgroup_context_dump(UNIT(s), f, prefix);
}
@ -1528,6 +1546,8 @@ static int service_spawn_internal(
if (r < 0)
return r;
exec_params.open_files = s->open_files;
log_unit_debug(UNIT(s), "Passing %zu fds to service", exec_params.n_socket_fds + exec_params.n_storage_fds);
}

View file

@ -6,6 +6,7 @@ typedef struct ServiceFDStore ServiceFDStore;
#include "exit-status.h"
#include "kill.h"
#include "open-file.h"
#include "path.h"
#include "ratelimit.h"
#include "socket.h"
@ -215,6 +216,8 @@ struct Service {
bool flush_n_restarts;
OOMPolicy oom_policy;
LIST_HEAD(OpenFile, open_files);
};
static inline usec_t service_timeout_abort_usec(Service *s) {

View file

@ -29,6 +29,7 @@
#include "mountpoint-util.h"
#include "nsflags.h"
#include "numa-util.h"
#include "open-file.h"
#include "parse-helpers.h"
#include "parse-util.h"
#include "path-util.h"
@ -410,6 +411,23 @@ static int bus_append_exec_command(sd_bus_message *m, const char *field, const c
return 1;
}
static int bus_append_open_file(sd_bus_message *m, const char *field, const char *eq) {
_cleanup_(open_file_freep) OpenFile *of = NULL;
int r;
assert(m);
r = open_file_parse(eq, &of);
if (r < 0)
return log_error_errno(r, "Failed to parse OpenFile= setting: %m");
r = sd_bus_message_append(m, "(sv)", field, "a(sst)", (size_t) 1, of->path, of->fdname, of->flags);
if (r < 0)
return bus_log_create_error(r);
return 1;
}
static int bus_append_ip_address_access(sd_bus_message *m, int family, const union in_addr_union *prefix, unsigned char prefixlen) {
int r;
@ -2300,6 +2318,9 @@ static int bus_append_service_property(sd_bus_message *m, const char *field, con
return 1;
}
if (streq(field, "OpenFile"))
return bus_append_open_file(m, field, eq);
return 0;
}

View file

@ -243,6 +243,8 @@ shared_sources = files(
'nsflags.h',
'numa-util.c',
'numa-util.h',
'open-file.c',
'open-file.h',
'openssl-util.c',
'openssl-util.h',
'output-mode.c',

150
src/shared/open-file.c Normal file
View file

@ -0,0 +1,150 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include "escape.h"
#include "extract-word.h"
#include "fd-util.h"
#include "open-file.h"
#include "path-util.h"
#include "string-table.h"
#include "string-util.h"
int open_file_parse(const char *v, OpenFile **ret) {
_cleanup_free_ char *options = NULL;
_cleanup_(open_file_freep) OpenFile *of = NULL;
int r;
assert(v);
assert(ret);
of = new0(OpenFile, 1);
if (!of)
return -ENOMEM;
r = extract_many_words(&v, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_CUNESCAPE, &of->path, &of->fdname, &options, NULL);
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
/* Enforce that at most 3 colon-separated words are present */
if (!isempty(v))
return -EINVAL;
for (const char *p = options;;) {
OpenFileFlag flag;
_cleanup_free_ char *word = NULL;
r = extract_first_word(&p, &word, ",", 0);
if (r < 0)
return r;
if (r == 0)
break;
flag = open_file_flags_from_string(word);
if (flag < 0)
return flag;
if ((flag & of->flags) != 0)
return -EINVAL;
of->flags |= flag;
}
if (isempty(of->fdname)) {
free(of->fdname);
r = path_extract_filename(of->path, &of->fdname);
if (r < 0)
return r;
}
r = open_file_validate(of);
if (r < 0)
return r;
*ret = TAKE_PTR(of);
return 0;
}
int open_file_validate(const OpenFile *of) {
assert(of);
if (!path_is_valid(of->path) || !path_is_absolute(of->path))
return -EINVAL;
if (!fdname_is_valid(of->fdname))
return -EINVAL;
if ((FLAGS_SET(of->flags, OPENFILE_READ_ONLY) + FLAGS_SET(of->flags, OPENFILE_APPEND) +
FLAGS_SET(of->flags, OPENFILE_TRUNCATE)) > 1)
return -EINVAL;
if ((of->flags & ~_OPENFILE_MASK_PUBLIC) != 0)
return -EINVAL;
return 0;
}
int open_file_to_string(const OpenFile *of, char **ret) {
_cleanup_free_ char *options = NULL, *fname = NULL, *s = NULL;
bool has_fdname = false;
int r;
assert(of);
assert(ret);
s = shell_escape(of->path, ":");
if (!s)
return -ENOMEM;
r = path_extract_filename(of->path, &fname);
if (r < 0)
return r;
has_fdname = !streq(fname, of->fdname);
if (has_fdname)
if (!strextend(&s, ":", of->fdname))
return -ENOMEM;
for (OpenFileFlag flag = OPENFILE_READ_ONLY; flag < _OPENFILE_MAX; flag <<= 1)
if (FLAGS_SET(of->flags, flag) && !strextend_with_separator(&options, ",", open_file_flags_to_string(flag)))
return -ENOMEM;
if (options)
if (!(has_fdname ? strextend(&s, ":", options) : strextend(&s, "::", options)))
return -ENOMEM;
*ret = TAKE_PTR(s);
return 0;
}
OpenFile *open_file_free(OpenFile *of) {
if (!of)
return NULL;
free(of->path);
free(of->fdname);
return mfree(of);
}
void open_file_free_many(OpenFile **head) {
OpenFile *of;
while ((of = *head)) {
LIST_REMOVE(open_files, *head, of);
of = open_file_free(of);
}
}
static const char * const open_file_flags_table[_OPENFILE_MAX] = {
[OPENFILE_READ_ONLY] = "read-only",
[OPENFILE_APPEND] = "append",
[OPENFILE_TRUNCATE] = "truncate",
[OPENFILE_GRACEFUL] = "graceful",
};
DEFINE_STRING_TABLE_LOOKUP(open_file_flags, OpenFileFlag);

36
src/shared/open-file.h Normal file
View file

@ -0,0 +1,36 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "list.h"
typedef enum OpenFileFlag {
OPENFILE_READ_ONLY = 1 << 0,
OPENFILE_APPEND = 1 << 1,
OPENFILE_TRUNCATE = 1 << 2,
OPENFILE_GRACEFUL = 1 << 3,
_OPENFILE_MAX,
_OPENFILE_INVALID = -EINVAL,
_OPENFILE_MASK_PUBLIC = OPENFILE_READ_ONLY | OPENFILE_APPEND | OPENFILE_TRUNCATE | OPENFILE_GRACEFUL,
} OpenFileFlag;
typedef struct OpenFile {
char *path;
char *fdname;
OpenFileFlag flags;
LIST_FIELDS(struct OpenFile, open_files);
} OpenFile;
int open_file_parse(const char *v, OpenFile **ret);
int open_file_validate(const OpenFile *of);
int open_file_to_string(const OpenFile *of, char **ret);
OpenFile *open_file_free(OpenFile *of);
DEFINE_TRIVIAL_CLEANUP_FUNC(OpenFile*, open_file_free);
void open_file_free_many(OpenFile **head);
const char *open_file_flags_to_string(OpenFileFlag t) _const_;
OpenFileFlag open_file_flags_from_string(const char *t) _pure_;

View file

@ -23,6 +23,7 @@
#include "locale-util.h"
#include "memory-util.h"
#include "numa-util.h"
#include "open-file.h"
#include "parse-util.h"
#include "path-util.h"
#include "pretty-print.h"
@ -1857,6 +1858,38 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
if (r < 0)
return bus_log_parse_error(r);
return 1;
} else if (contents[0] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "OpenFile")) {
char *path, *fdname;
uint64_t offlags;
r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sst)");
if (r < 0)
return bus_log_parse_error(r);
while ((r = sd_bus_message_read(m, "(sst)", &path, &fdname, &offlags)) > 0) {
_cleanup_free_ char *ofs = NULL;
r = open_file_to_string(
&(OpenFile){
.path = path,
.fdname = fdname,
.flags = offlags,
},
&ofs);
if (r < 0)
return log_error_errno(
r, "Failed to convert OpenFile= value to string: %m");
bus_print_property_value(name, expected_value, flags, ofs);
}
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_exit_container(m);
if (r < 0)
return bus_log_parse_error(r);
return 1;
}

View file

@ -695,6 +695,8 @@ tests += [
[files('test-hmac.c')],
[files('test-sha256.c')],
[files('test-open-file.c')],
]
############################################################

View file

@ -22,6 +22,7 @@
#include "load-fragment.h"
#include "macro.h"
#include "memory-util.h"
#include "open-file.h"
#include "pcre2-util.h"
#include "rm-rf.h"
#include "specifier.h"
@ -1048,6 +1049,50 @@ TEST(config_parse_log_filter_patterns) {
}
}
TEST(config_parse_open_file) {
_cleanup_(manager_freep) Manager *m = NULL;
_cleanup_(unit_freep) Unit *u = NULL;
_cleanup_(open_file_freep) OpenFile *of = NULL;
int r;
r = manager_new(LOOKUP_SCOPE_USER, MANAGER_TEST_RUN_MINIMAL, &m);
if (manager_errno_skip_test(r)) {
log_notice_errno(r, "Skipping test: manager_new: %m");
return;
}
assert_se(r >= 0);
assert_se(manager_startup(m, NULL, NULL, NULL) >= 0);
assert_se(u = unit_new(m, sizeof(Service)));
assert_se(unit_add_name(u, "foobar.service") == 0);
r = config_parse_open_file(NULL, "fake", 1, "section", 1,
"OpenFile", 0, "/proc/1/ns/mnt:host-mount-namespace:read-only",
&of, u);
assert_se(r >= 0);
assert_se(of);
assert_se(streq(of->path, "/proc/1/ns/mnt"));
assert_se(streq(of->fdname, "host-mount-namespace"));
assert_se(of->flags == OPENFILE_READ_ONLY);
of = open_file_free(of);
r = config_parse_open_file(NULL, "fake", 1, "section", 1,
"OpenFile", 0, "/proc/1/ns/mnt::read-only",
&of, u);
assert_se(r >= 0);
assert_se(of);
assert_se(streq(of->path, "/proc/1/ns/mnt"));
assert_se(streq(of->fdname, "mnt"));
assert_se(of->flags == OPENFILE_READ_ONLY);
r = config_parse_open_file(NULL, "fake", 1, "section", 1,
"OpenFile", 0, "",
&of, u);
assert_se(r >= 0);
assert_se(!of);
}
static int intro(void) {
if (enter_cgroup_subroot(NULL) == -ENOMEDIUM)
return log_tests_skipped("cgroupfs not available");

185
src/test/test-open-file.c Normal file
View file

@ -0,0 +1,185 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "open-file.h"
#include "string-util.h"
#include "tests.h"
TEST(open_file_parse) {
_cleanup_(open_file_freep) OpenFile *of = NULL;
int r;
r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only", &of);
assert_se(r >= 0);
assert_se(streq(of->path, "/proc/1/ns/mnt"));
assert_se(streq(of->fdname, "host-mount-namespace"));
assert_se(of->flags == OPENFILE_READ_ONLY);
of = open_file_free(of);
r = open_file_parse("/proc/1/ns/mnt", &of);
assert_se(r >= 0);
assert_se(streq(of->path, "/proc/1/ns/mnt"));
assert_se(streq(of->fdname, "mnt"));
assert_se(of->flags == 0);
of = open_file_free(of);
r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace", &of);
assert_se(r >= 0);
assert_se(streq(of->path, "/proc/1/ns/mnt"));
assert_se(streq(of->fdname, "host-mount-namespace"));
assert_se(of->flags == 0);
of = open_file_free(of);
r = open_file_parse("/proc/1/ns/mnt::read-only", &of);
assert_se(r >= 0);
assert_se(streq(of->path, "/proc/1/ns/mnt"));
assert_se(streq(of->fdname, "mnt"));
assert_se(of->flags == OPENFILE_READ_ONLY);
of = open_file_free(of);
r = open_file_parse("../file.dat:file:read-only", &of);
assert_se(r == -EINVAL);
of = open_file_free(of);
r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:rw", &of);
assert_se(r == -EINVAL);
of = open_file_free(of);
r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:append", &of);
assert_se(r >= 0);
assert_se(streq(of->path, "/proc/1/ns/mnt"));
assert_se(streq(of->fdname, "host-mount-namespace"));
assert_se(of->flags == OPENFILE_APPEND);
of = open_file_free(of);
r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:truncate", &of);
assert_se(r >= 0);
assert_se(streq(of->path, "/proc/1/ns/mnt"));
assert_se(streq(of->fdname, "host-mount-namespace"));
assert_se(of->flags == OPENFILE_TRUNCATE);
of = open_file_free(of);
r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only,append", &of);
assert_se(r == -EINVAL);
of = open_file_free(of);
r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only,truncate", &of);
assert_se(r == -EINVAL);
of = open_file_free(of);
r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:append,truncate", &of);
assert_se(r == -EINVAL);
of = open_file_free(of);
r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only,read-only", &of);
assert_se(r == -EINVAL);
of = open_file_free(of);
r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:graceful", &of);
assert_se(r >= 0);
assert_se(streq(of->path, "/proc/1/ns/mnt"));
assert_se(streq(of->fdname, "host-mount-namespace"));
assert_se(of->flags == OPENFILE_GRACEFUL);
of = open_file_free(of);
r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only,graceful", &of);
assert_se(r >= 0);
assert_se(streq(of->path, "/proc/1/ns/mnt"));
assert_se(streq(of->fdname, "host-mount-namespace"));
assert_se(of->flags == (OPENFILE_READ_ONLY | OPENFILE_GRACEFUL));
of = open_file_free(of);
r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only:other", &of);
assert_se(r == -EINVAL);
}
TEST(open_file_to_string) {
_cleanup_free_ char *s = NULL;
_cleanup_(open_file_freep) OpenFile *of = NULL;
int r;
assert_se(of = new (OpenFile, 1));
*of = (OpenFile){ .path = strdup("/proc/1/ns/mnt"),
.fdname = strdup("host-mount-namespace"),
.flags = OPENFILE_READ_ONLY };
r = open_file_to_string(of, &s);
assert_se(r >= 0);
assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:read-only"));
s = mfree(s);
of->flags = OPENFILE_APPEND;
r = open_file_to_string(of, &s);
assert_se(r >= 0);
assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:append"));
s = mfree(s);
of->flags = OPENFILE_TRUNCATE;
r = open_file_to_string(of, &s);
assert_se(r >= 0);
assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:truncate"));
s = mfree(s);
of->flags = OPENFILE_GRACEFUL;
r = open_file_to_string(of, &s);
assert_se(r >= 0);
assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:graceful"));
s = mfree(s);
of->flags = OPENFILE_READ_ONLY | OPENFILE_GRACEFUL;
r = open_file_to_string(of, &s);
assert_se(r >= 0);
assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:read-only,graceful"));
s = mfree(s);
of->flags = 0;
r = open_file_to_string(of, &s);
assert_se(r >= 0);
assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace"));
s = mfree(s);
assert_se(free_and_strdup(&of->fdname, "mnt"));
of->flags = OPENFILE_READ_ONLY;
r = open_file_to_string(of, &s);
assert_se(r >= 0);
assert_se(streq(s, "/proc/1/ns/mnt::read-only"));
s = mfree(s);
assert_se(free_and_strdup(&of->path, "/path:with:colon"));
assert_se(free_and_strdup(&of->fdname, "path:with:colon"));
of->flags = 0;
r = open_file_to_string(of, &s);
assert_se(r >= 0);
assert_se(streq(s, "/path\\:with\\:colon"));
}
DEFINE_TEST_MAIN(LOG_INFO);

View file

@ -0,0 +1 @@
../TEST-01-BASIC/Makefile

16
test/TEST-77-OPENFILE/test.sh Executable file
View file

@ -0,0 +1,16 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -e
TEST_DESCRIPTION="Openfile tests"
# shellcheck source=test/test-functions
. "${TEST_BASE_DIR:?}/test-functions"
test_append_files() {
local workspace="${1:?}"
echo "Open" > "$workspace/test-77-open.dat"
echo "File" > "$workspace/test-77-file.dat"
}
do_test "$@"

View file

@ -0,0 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=TEST-77-OPENFILE
[Service]
ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
Type=simple

View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
echo "Socket" | nc -lkU /tmp/test.sock

14
test/units/testsuite-77-run.sh Executable file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
# shellcheck source=test/units/assert.sh
. "$(dirname "$0")"/assert.sh
export SYSTEMD_LOG_LEVEL=debug
assert_eq "$LISTEN_FDS" "1"
assert_eq "$LISTEN_FDNAMES" "new-file"
read -r -u 3 text
assert_eq "$text" "New"

View file

@ -0,0 +1,9 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=TEST-77-OPENFILE
[Service]
OpenFile=/tmp/test.sock:socket:read-only
ExecStartPre=rm -f /failed /testok
ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
Type=oneshot

View file

@ -0,0 +1,14 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
# shellcheck source=test/units/assert.sh
. "$(dirname "$0")"/assert.sh
export SYSTEMD_LOG_LEVEL=debug
assert_eq "$LISTEN_FDS" "1"
assert_eq "$LISTEN_FDNAMES" "socket"
read -r -u 3 text
assert_eq "$text" "Socket"

View file

@ -0,0 +1,10 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=TEST-77-OPENFILE
[Service]
OpenFile=/test-77-open.dat:open:read-only
OpenFile=/test-77-file.dat
ExecStartPre=rm -f /failed /testok
ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
Type=oneshot

35
test/units/testsuite-77.sh Executable file
View file

@ -0,0 +1,35 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
# shellcheck source=test/units/assert.sh
. "$(dirname "$0")"/assert.sh
export SYSTEMD_LOG_LEVEL=debug
assert_eq "$LISTEN_FDS" "2"
assert_eq "$LISTEN_FDNAMES" "open:test-77-file.dat"
read -r -u 3 text
assert_eq "$text" "Open"
read -r -u 4 text
assert_eq "$text" "File"
# Test for socket
systemctl start testsuite-77-netcat.service
systemctl start testsuite-77-socket.service
# Tests for D-Bus
diff <(systemctl show -p OpenFile testsuite-77) - <<EOF
OpenFile=/test-77-open.dat:open:read-only
OpenFile=/test-77-file.dat
EOF
echo "New" > /test-77-new-file.dat
systemd-run --wait -p OpenFile=/test-77-new-file.dat:new-file:read-only "$(dirname "$0")"/testsuite-77-run.sh
assert_rc 202 systemd-run --wait -p OpenFile=/test-77-new-file.dat:new-file:read-only -p OpenFile=/test-77-mssing-file.dat:missing-file:read-only "$(dirname "$0")"/testsuite-77-run.sh
assert_rc 0 systemd-run --wait -p OpenFile=/test-77-new-file.dat:new-file:read-only -p OpenFile=/test-77-mssing-file.dat:missing-file:read-only,graceful "$(dirname "$0")"/testsuite-77-run.sh
# End
touch /testok