core: add support for setting stdin/stdout/stderr for transient services

When starting a transient service, allow setting stdin/stdout/stderr fds
for it, by passing them in via the bus.

This also simplifies some of the serialization code for units.
This commit is contained in:
Lennart Poettering 2015-10-07 23:07:39 +02:00
parent 66cb2fde7b
commit a34ceba66f
12 changed files with 278 additions and 96 deletions

View file

@ -774,8 +774,9 @@ static int automount_stop(Unit *u) {
static int automount_serialize(Unit *u, FILE *f, FDSet *fds) {
Automount *a = AUTOMOUNT(u);
void *p;
Iterator i;
void *p;
int r;
assert(a);
assert(f);
@ -790,15 +791,9 @@ static int automount_serialize(Unit *u, FILE *f, FDSet *fds) {
SET_FOREACH(p, a->expire_tokens, i)
unit_serialize_item_format(u, f, "expire-token", "%u", PTR_TO_UINT(p));
if (a->pipe_fd >= 0) {
int copy;
copy = fdset_put_dup(fds, a->pipe_fd);
if (copy < 0)
return copy;
unit_serialize_item_format(u, f, "pipe-fd", "%i", copy);
}
r = unit_serialize_item_fd(u, f, fds, "pipe-fd", a->pipe_fd);
if (r < 0)
return r;
return 0;
}

View file

@ -656,6 +656,7 @@ static int busname_stop(Unit *u) {
static int busname_serialize(Unit *u, FILE *f, FDSet *fds) {
BusName *n = BUSNAME(u);
int r;
assert(n);
assert(f);
@ -667,15 +668,9 @@ static int busname_serialize(Unit *u, FILE *f, FDSet *fds) {
if (n->control_pid > 0)
unit_serialize_item_format(u, f, "control-pid", PID_FMT, n->control_pid);
if (n->starter_fd >= 0) {
int copy;
copy = fdset_put_dup(fds, n->starter_fd);
if (copy < 0)
return copy;
unit_serialize_item_format(u, f, "starter-fd", "%i", copy);
}
r = unit_serialize_item_fd(u, f, fds, "starter-fd", n->starter_fd);
if (r < 0)
return r;
return 0;
}

View file

@ -19,6 +19,7 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "async.h"
#include "strv.h"
#include "path-util.h"
#include "unit.h"
@ -120,6 +121,37 @@ static int bus_service_set_transient_property(
return 1;
} else if (STR_IN_SET(name,
"StandardInputFileDescriptor",
"StandardOutputFileDescriptor",
"StandardErrorFileDescriptor")) {
int fd;
r = sd_bus_message_read(message, "h", &fd);
if (r < 0)
return r;
if (mode != UNIT_CHECK) {
int copy;
copy = fcntl(fd, F_DUPFD_CLOEXEC, 3);
if (copy < 0)
return -errno;
if (streq(name, "StandardInputFileDescriptor")) {
asynchronous_close(s->stdin_fd);
s->stdin_fd = copy;
} else if (streq(name, "StandardOutputFileDescriptor")) {
asynchronous_close(s->stdout_fd);
s->stdout_fd = copy;
} else {
asynchronous_close(s->stderr_fd);
s->stderr_fd = copy;
}
}
return 1;
} else if (streq(name, "ExecStart")) {
unsigned n = 0;

View file

@ -359,12 +359,28 @@ static int fixup_output(ExecOutput std_output, int socket_fd) {
return std_output;
}
static int setup_input(const ExecContext *context, int socket_fd, bool apply_tty_stdin) {
static int setup_input(
const ExecContext *context,
const ExecParameters *params,
int socket_fd) {
ExecInput i;
assert(context);
assert(params);
i = fixup_input(context->std_input, socket_fd, apply_tty_stdin);
if (params->stdin_fd >= 0) {
if (dup2(params->stdin_fd, STDIN_FILENO) < 0)
return -errno;
/* Try to make this the controlling tty, if it is a tty, and reset it */
(void) ioctl(STDIN_FILENO, TIOCSCTTY, context->std_input == EXEC_INPUT_TTY_FORCE);
(void) reset_terminal_fd(STDIN_FILENO, true);
return STDIN_FILENO;
}
i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin);
switch (i) {
@ -401,16 +417,40 @@ static int setup_input(const ExecContext *context, int socket_fd, bool apply_tty
}
}
static int setup_output(Unit *unit, const ExecContext *context, int fileno, int socket_fd, const char *ident, bool apply_tty_stdin, uid_t uid, gid_t gid) {
static int setup_output(
Unit *unit,
const ExecContext *context,
const ExecParameters *params,
int fileno,
int socket_fd,
const char *ident,
uid_t uid, gid_t gid) {
ExecOutput o;
ExecInput i;
int r;
assert(unit);
assert(context);
assert(params);
assert(ident);
i = fixup_input(context->std_input, socket_fd, apply_tty_stdin);
if (fileno == STDOUT_FILENO && params->stdout_fd >= 0) {
if (dup2(params->stdout_fd, STDOUT_FILENO) < 0)
return -errno;
return STDOUT_FILENO;
}
if (fileno == STDERR_FILENO && params->stderr_fd >= 0) {
if (dup2(params->stderr_fd, STDERR_FILENO) < 0)
return -errno;
return STDERR_FILENO;
}
i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin);
o = fixup_output(context->std_output, socket_fd);
if (fileno == STDERR_FILENO) {
@ -1324,6 +1364,44 @@ static bool exec_needs_mount_namespace(
return false;
}
static int close_remaining_fds(
const ExecParameters *params,
ExecRuntime *runtime,
int socket_fd,
int *fds, unsigned n_fds) {
unsigned n_dont_close = 0;
int dont_close[n_fds + 7];
assert(params);
if (params->stdin_fd >= 0)
dont_close[n_dont_close++] = params->stdin_fd;
if (params->stdout_fd >= 0)
dont_close[n_dont_close++] = params->stdout_fd;
if (params->stderr_fd >= 0)
dont_close[n_dont_close++] = params->stderr_fd;
if (socket_fd >= 0)
dont_close[n_dont_close++] = socket_fd;
if (n_fds > 0) {
memcpy(dont_close + n_dont_close, fds, sizeof(int) * n_fds);
n_dont_close += n_fds;
}
if (params->bus_endpoint_fd >= 0)
dont_close[n_dont_close++] = params->bus_endpoint_fd;
if (runtime) {
if (runtime->netns_storage_socket[0] >= 0)
dont_close[n_dont_close++] = runtime->netns_storage_socket[0];
if (runtime->netns_storage_socket[1] >= 0)
dont_close[n_dont_close++] = runtime->netns_storage_socket[1];
}
return close_all_fds(dont_close, n_dont_close);
}
static int exec_child(
Unit *unit,
ExecCommand *command,
@ -1339,8 +1417,6 @@ static int exec_child(
_cleanup_strv_free_ char **our_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL;
_cleanup_free_ char *mac_selinux_context_net = NULL;
const char *username = NULL, *home = NULL, *shell = NULL, *wd;
unsigned n_dont_close = 0;
int dont_close[n_fds + 4];
uid_t uid = UID_INVALID;
gid_t gid = GID_INVALID;
int i, r;
@ -1380,22 +1456,7 @@ static int exec_child(
log_forget_fds();
if (socket_fd >= 0)
dont_close[n_dont_close++] = socket_fd;
if (n_fds > 0) {
memcpy(dont_close + n_dont_close, fds, sizeof(int) * n_fds);
n_dont_close += n_fds;
}
if (params->bus_endpoint_fd >= 0)
dont_close[n_dont_close++] = params->bus_endpoint_fd;
if (runtime) {
if (runtime->netns_storage_socket[0] >= 0)
dont_close[n_dont_close++] = runtime->netns_storage_socket[0];
if (runtime->netns_storage_socket[1] >= 0)
dont_close[n_dont_close++] = runtime->netns_storage_socket[1];
}
r = close_all_fds(dont_close, n_dont_close);
r = close_remaining_fds(params, runtime, socket_fd, fds, n_fds);
if (r < 0) {
*exit_status = EXIT_FDS;
return r;
@ -1451,21 +1512,21 @@ static int exec_child(
/* If a socket is connected to STDIN/STDOUT/STDERR, we
* must sure to drop O_NONBLOCK */
if (socket_fd >= 0)
fd_nonblock(socket_fd, false);
(void) fd_nonblock(socket_fd, false);
r = setup_input(context, socket_fd, params->apply_tty_stdin);
r = setup_input(context, params, socket_fd);
if (r < 0) {
*exit_status = EXIT_STDIN;
return r;
}
r = setup_output(unit, context, STDOUT_FILENO, socket_fd, basename(command->path), params->apply_tty_stdin, uid, gid);
r = setup_output(unit, context, params, STDOUT_FILENO, socket_fd, basename(command->path), uid, gid);
if (r < 0) {
*exit_status = EXIT_STDOUT;
return r;
}
r = setup_output(unit, context, STDERR_FILENO, socket_fd, basename(command->path), params->apply_tty_stdin, uid, gid);
r = setup_output(unit, context, params, STDERR_FILENO, socket_fd, basename(command->path), uid, gid);
if (r < 0) {
*exit_status = EXIT_STDERR;
return r;

View file

@ -208,23 +208,22 @@ struct ExecContext {
struct ExecParameters {
char **argv;
char **environment;
int *fds;
char **fd_names;
unsigned n_fds;
char **environment;
bool apply_permissions:1;
bool apply_chroot:1;
bool apply_tty_stdin:1;
bool apply_permissions;
bool apply_chroot;
bool apply_tty_stdin;
bool confirm_spawn;
bool selinux_context_net;
bool confirm_spawn:1;
bool selinux_context_net:1;
bool cgroup_delegate:1;
CGroupMask cgroup_supported;
const char *cgroup_path;
bool cgroup_delegate;
const char *runtime_prefix;
@ -234,6 +233,10 @@ struct ExecParameters {
char *bus_endpoint_path;
int bus_endpoint_fd;
int stdin_fd;
int stdout_fd;
int stderr_fd;
};
int exec_spawn(Unit *unit,

View file

@ -694,6 +694,9 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) {
.apply_chroot = true,
.apply_tty_stdin = true,
.bus_endpoint_fd = -1,
.stdin_fd = -1,
.stdout_fd = -1,
.stderr_fd = -1,
};
assert(m);

View file

@ -108,6 +108,7 @@ static void service_init(Unit *u) {
s->type = _SERVICE_TYPE_INVALID;
s->socket_fd = -1;
s->bus_endpoint_fd = -1;
s->stdin_fd = s->stdout_fd = s->stderr_fd = -1;
s->guess_main_pid = true;
RATELIMIT_INIT(s->start_limit, u->manager->default_start_limit_interval, u->manager->default_start_limit_burst);
@ -271,11 +272,15 @@ static void service_release_resources(Unit *u) {
assert(s);
if (!s->fd_store)
if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0)
return;
log_unit_debug(u, "Releasing all resources.");
s->stdin_fd = safe_close(s->stdin_fd);
s->stdout_fd = safe_close(s->stdout_fd);
s->stderr_fd = safe_close(s->stderr_fd);
while (s->fd_store)
service_fd_store_unlink(s->fd_store);
@ -1090,11 +1095,13 @@ static int service_spawn(
pid_t pid;
ExecParameters exec_params = {
.apply_permissions = apply_permissions,
.apply_chroot = apply_chroot,
.apply_tty_stdin = apply_tty_stdin,
.bus_endpoint_fd = -1,
.selinux_context_net = s->socket_fd_selinux_context_net
.apply_permissions = apply_permissions,
.apply_chroot = apply_chroot,
.apply_tty_stdin = apply_tty_stdin,
.bus_endpoint_fd = -1,
.stdin_fd = -1,
.stdout_fd = -1,
.stderr_fd = -1,
};
int r;
@ -1236,8 +1243,12 @@ static int service_spawn(
exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(s)->manager);
exec_params.watchdog_usec = s->watchdog_usec;
exec_params.bus_endpoint_path = bus_endpoint_path;
exec_params.selinux_context_net = s->socket_fd_selinux_context_net;
if (s->type == SERVICE_IDLE)
exec_params.idle_pipe = UNIT(s)->manager->idle_pipe;
exec_params.stdin_fd = s->stdin_fd;
exec_params.stdout_fd = s->stdout_fd;
exec_params.stderr_fd = s->stderr_fd;
r = exec_spawn(UNIT(s),
c,
@ -2038,6 +2049,7 @@ _pure_ static bool service_can_reload(Unit *u) {
static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
Service *s = SERVICE(u);
ServiceFDStore *fs;
int r;
assert(u);
assert(f);
@ -2056,12 +2068,9 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
unit_serialize_item(u, f, "main-pid-known", yes_no(s->main_pid_known));
unit_serialize_item(u, f, "bus-name-good", yes_no(s->bus_name_good));
if (s->status_text) {
_cleanup_free_ char *c = NULL;
c = cescape(s->status_text);
unit_serialize_item(u, f, "status-text", strempty(c));
}
r = unit_serialize_item_escaped(u, f, "status-text", s->status_text);
if (r < 0)
return r;
/* FIXME: There's a minor uncleanliness here: if there are
* multiple commands attached here, we will start from the
@ -2069,25 +2078,22 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
if (s->control_command_id >= 0)
unit_serialize_item(u, f, "control-command", service_exec_command_to_string(s->control_command_id));
if (s->socket_fd >= 0) {
int copy;
r = unit_serialize_item_fd(u, f, fds, "stdin-fd", s->stdin_fd);
if (r < 0)
return r;
r = unit_serialize_item_fd(u, f, fds, "stdout-fd", s->stdout_fd);
if (r < 0)
return r;
r = unit_serialize_item_fd(u, f, fds, "stderr-fd", s->stderr_fd);
if (r < 0)
return r;
copy = fdset_put_dup(fds, s->socket_fd);
if (copy < 0)
return copy;
unit_serialize_item_format(u, f, "socket-fd", "%i", copy);
}
if (s->bus_endpoint_fd >= 0) {
int copy;
copy = fdset_put_dup(fds, s->bus_endpoint_fd);
if (copy < 0)
return copy;
unit_serialize_item_format(u, f, "endpoint-fd", "%i", copy);
}
r = unit_serialize_item_fd(u, f, fds, "socket-fd", s->socket_fd);
if (r < 0)
return r;
r = unit_serialize_item_fd(u, f, fds, "endpoint-fd", s->bus_endpoint_fd);
if (r < 0)
return r;
LIST_FOREACH(fd_store, fs, s->fd_store) {
_cleanup_free_ char *c = NULL;
@ -2116,8 +2122,7 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
if (dual_timestamp_is_set(&s->watchdog_timestamp))
dual_timestamp_serialize(f, "watchdog-timestamp", &s->watchdog_timestamp);
if (s->forbid_restart)
unit_serialize_item(u, f, "forbid-restart", yes_no(s->forbid_restart));
unit_serialize_item(u, f, "forbid-restart", yes_no(s->forbid_restart));
return 0;
}
@ -2288,6 +2293,33 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
log_unit_debug(u, "Failed to parse forbid-restart value: %s", value);
else
s->forbid_restart = b;
} else if (streq(key, "stdin-fd")) {
int fd;
if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
log_unit_debug(u, "Failed to parse stdin-fd value: %s", value);
else {
asynchronous_close(s->stdin_fd);
s->stdin_fd = fdset_remove(fds, fd);
}
} else if (streq(key, "stdout-fd")) {
int fd;
if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
log_unit_debug(u, "Failed to parse stdout-fd value: %s", value);
else {
asynchronous_close(s->stdout_fd);
s->stdout_fd = fdset_remove(fds, fd);
}
} else if (streq(key, "stderr-fd")) {
int fd;
if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
log_unit_debug(u, "Failed to parse stderr-fd value: %s", value);
else {
asynchronous_close(s->stderr_fd);
s->stderr_fd = fdset_remove(fds, fd);
}
} else
log_unit_debug(u, "Unknown serialization key: %s", key);

View file

@ -195,6 +195,10 @@ struct Service {
char *usb_function_descriptors;
char *usb_function_strings;
int stdin_fd;
int stdout_fd;
int stderr_fd;
};
extern const UnitVTable service_vtable;

View file

@ -1505,6 +1505,9 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
.apply_chroot = true,
.apply_tty_stdin = true,
.bus_endpoint_fd = -1,
.stdin_fd = -1,
.stdout_fd = -1,
.stderr_fd = -1,
};
assert(s);

View file

@ -597,6 +597,9 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) {
.apply_chroot = true,
.apply_tty_stdin = true,
.bus_endpoint_fd = -1,
.stdin_fd = -1,
.stdout_fd = -1,
.stderr_fd = -1,
};
assert(s);

View file

@ -2624,6 +2624,62 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
return 0;
}
int unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) {
assert(u);
assert(f);
assert(key);
if (!value)
return 0;
fputs(key, f);
fputc('=', f);
fputs(value, f);
fputc('\n', f);
return 1;
}
int unit_serialize_item_escaped(Unit *u, FILE *f, const char *key, const char *value) {
_cleanup_free_ char *c = NULL;
assert(u);
assert(f);
assert(key);
if (!value)
return 0;
c = cescape(value);
if (!c)
return -ENOMEM;
fputs(key, f);
fputc('=', f);
fputs(c, f);
fputc('\n', f);
return 1;
}
int unit_serialize_item_fd(Unit *u, FILE *f, FDSet *fds, const char *key, int fd) {
int copy;
assert(u);
assert(f);
assert(key);
if (fd < 0)
return 0;
copy = fdset_put_dup(fds, fd);
if (copy < 0)
return copy;
fprintf(f, "%s=%i\n", key, copy);
return 1;
}
void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *format, ...) {
va_list ap;
@ -2642,15 +2698,6 @@ void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *f
fputc('\n', f);
}
void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) {
assert(u);
assert(f);
assert(key);
assert(value);
fprintf(f, "%s=%s\n", key, value);
}
int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
ExecRuntime **rt = NULL;
size_t offset;

View file

@ -533,11 +533,15 @@ char *unit_dbus_path(Unit *u);
int unit_load_related_unit(Unit *u, const char *type, Unit **_found);
bool unit_can_serialize(Unit *u) _pure_;
int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs);
void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *value, ...) _printf_(4,5);
void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value);
int unit_deserialize(Unit *u, FILE *f, FDSet *fds);
int unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value);
int unit_serialize_item_escaped(Unit *u, FILE *f, const char *key, const char *value);
int unit_serialize_item_fd(Unit *u, FILE *f, FDSet *fds, const char *key, int fd);
void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *value, ...) _printf_(4,5);
int unit_add_node_link(Unit *u, const char *what, bool wants);
int unit_coldplug(Unit *u);