]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add support for setting stdin/stdout/stderr for transient services
authorLennart Poettering <lennart@poettering.net>
Wed, 7 Oct 2015 21:07:39 +0000 (23:07 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 8 Oct 2015 10:55:15 +0000 (12:55 +0200)
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.

12 files changed:
src/core/automount.c
src/core/busname.c
src/core/dbus-service.c
src/core/execute.c
src/core/execute.h
src/core/mount.c
src/core/service.c
src/core/service.h
src/core/socket.c
src/core/swap.c
src/core/unit.c
src/core/unit.h

index 8173a6cbe85a81d4d5f84ec8f753ffabc80df003..e0535ec20177ed10a8ee37d0d89766d39b97c6a0 100644 (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;
 }
index ba353ab660eace4f01889186643283010dd1cf93..38becfc119526041a4c371fcc2c90e558cfd69b5 100644 (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;
 }
index 3436342befa5eee82e95cf49bfeba231637d074a..b636f8ba6a467e1383736eede865ea955b2cba2a 100644 (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;
 
index f5baad05f3038f167a22f047a35705b50b338b19..80ad87d4e4749cbba2417d6f2a5aab47acd49761 100644 (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);
+
+        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, apply_tty_stdin);
+        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;
index f1c37116fd19728035b277b8b6a903b137bcc79e..f8995a4203e355dcda7e4f149be069aacd6eba4a 100644 (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;
-        bool apply_chroot;
-        bool apply_tty_stdin;
+        bool apply_permissions:1;
+        bool apply_chroot:1;
+        bool apply_tty_stdin:1;
 
-        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,
index a74f116657ae6d3ebb7268f71a8a9f3a2a2d3094..861112945353b6daf59aff46142187539b33174b 100644 (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);
index ce3b81398d618045794eb6f5bcf379ef150dece4..1e4f707bf4fef56c6327f87ff54f2948f93d4a7e 100644 (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;
-
-                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;
+        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;
 
-                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);
 
index 61bb44fbcf206760748d54a018986d4cd46c550c..e7656682476c803a198809a9df2179276ddc8bec 100644 (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;
index 6300b20f3e99b9d4f06071dc870fa58f87d8daa8..e42ed62ef1a88e23e83d758867b8d6431fb261c1 100644 (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);
index 1f94d323182b84a5cee2ce96cda160bf18ba7267..f42d151075788d98920ed76b68efb87b39afaf21 100644 (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);
index 33c0317ce0dd16f4e1de5f23761aaceae609bcce..423caa95620f9698601aa676e4199367e252aebd 100644 (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;
index e8d5f86eca03d5ed7bdb8de05bc01f96a6824b58..a4a1b011fcbac8c19e1a32b4ce9beff85e79ccb3 100644 (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);