ryantimwilson / rpms / systemd

Forked from rpms/systemd a month ago
Clone
c2dfb7
From 2808e53f785e9ca7fdab286678e784b661b4c185 Mon Sep 17 00:00:00 2001
c2dfb7
From: Zsolt Dollenstein <zsol.zsol@gmail.com>
c2dfb7
Date: Tue, 3 Jul 2018 12:22:29 -0700
c2dfb7
Subject: [PATCH] Add support for opening files for appending
c2dfb7
c2dfb7
Addresses part of #8983
c2dfb7
c2dfb7
(cherry picked from commit 566b7d23eb747e9c5a74e5647693077b52395fc5)
c2dfb7
c2dfb7
Resolves: #1809175
c2dfb7
---
c2dfb7
 man/systemd.exec.xml                          | 16 ++++++----
c2dfb7
 src/core/dbus-execute.c                       | 30 ++++++++++++++-----
c2dfb7
 src/core/execute.c                            | 20 ++++++++++---
c2dfb7
 src/core/execute.h                            |  1 +
c2dfb7
 src/core/load-fragment.c                      | 11 +++++++
c2dfb7
 src/core/main.c                               |  4 +--
c2dfb7
 src/test/test-execute.c                       | 10 +++++++
c2dfb7
 test/meson.build                              |  2 ++
c2dfb7
 .../exec-standardoutput-append.service        | 13 ++++++++
c2dfb7
 .../exec-standardoutput-file.service          | 13 ++++++++
c2dfb7
 10 files changed, 101 insertions(+), 19 deletions(-)
c2dfb7
 create mode 100644 test/test-execute/exec-standardoutput-append.service
c2dfb7
 create mode 100644 test/test-execute/exec-standardoutput-file.service
c2dfb7
c2dfb7
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
c2dfb7
index bdaed68162..e2a5ede968 100644
c2dfb7
--- a/man/systemd.exec.xml
c2dfb7
+++ b/man/systemd.exec.xml
c2dfb7
@@ -1792,8 +1792,8 @@ SystemCallErrorNumber=EPERM</programlisting>
c2dfb7
         of <option>inherit</option>, <option>null</option>, <option>tty</option>, <option>journal</option>,
c2dfb7
         <option>syslog</option>, <option>kmsg</option>, <option>journal+console</option>,
c2dfb7
         <option>syslog+console</option>, <option>kmsg+console</option>,
c2dfb7
-        <option>file:<replaceable>path</replaceable></option>, <option>socket</option> or
c2dfb7
-        <option>fd:<replaceable>name</replaceable></option>.</para>
c2dfb7
+        <option>file:<replaceable>path</replaceable></option>, <option>append:<replaceable>path</replaceable></option>,
c2dfb7
+        <option>socket</option> or<option>fd:<replaceable>name</replaceable></option>.</para>
c2dfb7
 
c2dfb7
         <para><option>inherit</option> duplicates the file descriptor of standard input for standard output.</para>
c2dfb7
 
c2dfb7
@@ -1824,11 +1824,17 @@ SystemCallErrorNumber=EPERM</programlisting>
c2dfb7
 
c2dfb7
         <para>The <option>file:<replaceable>path</replaceable></option> option may be used to connect a specific file
c2dfb7
         system object to standard output. The semantics are similar to the same option of
c2dfb7
-        <varname>StandardInput=</varname>, see above. If standard input and output are directed to the same file path,
c2dfb7
-        it is opened only once, for reading as well as writing and duplicated. This is particular useful when the
c2dfb7
-        specified path refers to an <constant>AF_UNIX</constant> socket in the file system, as in that case only a
c2dfb7
+        <varname>StandardInput=</varname>, see above. If <replaceable>path</replaceable> refers to a regular file
c2dfb7
+        on the filesystem, it is opened (created if it doesn't exist yet) for writing at the beginning of the file,
c2dfb7
+        but without truncating it.
c2dfb7
+        If standard input and output are directed to the same file path, it is opened only once, for reading as well
c2dfb7
+        as writing and duplicated. This is particularly useful when the specified path refers to an
c2dfb7
+        <constant>AF_UNIX</constant> socket in the file system, as in that case only a
c2dfb7
         single stream connection is created for both input and output.</para>
c2dfb7
 
c2dfb7
+        <para><option>append:<replaceable>path</replaceable></option> is similar to <option>file:<replaceable>path
c2dfb7
+        </replaceable></option> above, but it opens the file in append mode.</para>
c2dfb7
+
c2dfb7
         <para><option>socket</option> connects standard output to a socket acquired via socket activation. The
c2dfb7
         semantics are similar to the same option of <varname>StandardInput=</varname>, see above.</para>
c2dfb7
 
c2dfb7
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
c2dfb7
index e7c0b893d1..f9527e56b2 100644
c2dfb7
--- a/src/core/dbus-execute.c
c2dfb7
+++ b/src/core/dbus-execute.c
c2dfb7
@@ -1772,7 +1772,10 @@ int bus_exec_context_set_transient_property(
c2dfb7
 
c2dfb7
                 return 1;
c2dfb7
 
c2dfb7
-        } else if (STR_IN_SET(name, "StandardInputFile", "StandardOutputFile", "StandardErrorFile")) {
c2dfb7
+        } else if (STR_IN_SET(name,
c2dfb7
+                              "StandardInputFile",
c2dfb7
+                              "StandardOutputFile", "StandardOutputFileToCreate", "StandardOutputFileToAppend",
c2dfb7
+                              "StandardErrorFile", "StandardErrorFileToCreate", "StandardErrorFileToAppend")) {
c2dfb7
                 const char *s;
c2dfb7
 
c2dfb7
                 r = sd_bus_message_read(message, "s", &s);
c2dfb7
@@ -1796,23 +1799,34 @@ int bus_exec_context_set_transient_property(
c2dfb7
                                 c->std_input = EXEC_INPUT_FILE;
c2dfb7
                                 unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardInput=file:%s", s);
c2dfb7
 
c2dfb7
-                        } else if (streq(name, "StandardOutputFile")) {
c2dfb7
+                        } else if (STR_IN_SET(name, "StandardOutputFile", "StandardOutputFileToAppend")) {
c2dfb7
                                 r = free_and_strdup(&c->stdio_file[STDOUT_FILENO], empty_to_null(s));
c2dfb7
                                 if (r < 0)
c2dfb7
                                         return r;
c2dfb7
 
c2dfb7
-                                c->std_output = EXEC_OUTPUT_FILE;
c2dfb7
-                                unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardOutput=file:%s", s);
c2dfb7
-
c2dfb7
+                                if (streq(name, "StandardOutputFile")) {
c2dfb7
+                                        c->std_output = EXEC_OUTPUT_FILE;
c2dfb7
+                                        unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardOutput=file:%s", s);
c2dfb7
+                                } else {
c2dfb7
+                                        assert(streq(name, "StandardOutputFileToAppend"));
c2dfb7
+                                        c->std_output = EXEC_OUTPUT_FILE_APPEND;
c2dfb7
+                                        unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardOutput=append:%s", s);
c2dfb7
+                                }
c2dfb7
                         } else {
c2dfb7
-                                assert(streq(name, "StandardErrorFile"));
c2dfb7
+                                assert(STR_IN_SET(name, "StandardErrorFile", "StandardErrorFileToAppend"));
c2dfb7
 
c2dfb7
                                 r = free_and_strdup(&c->stdio_file[STDERR_FILENO], empty_to_null(s));
c2dfb7
                                 if (r < 0)
c2dfb7
                                         return r;
c2dfb7
 
c2dfb7
-                                c->std_error = EXEC_OUTPUT_FILE;
c2dfb7
-                                unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardError=file:%s", s);
c2dfb7
+                                if (streq(name, "StandardErrorFile")) {
c2dfb7
+                                        c->std_error = EXEC_OUTPUT_FILE;
c2dfb7
+                                        unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardOutput=file:%s", s);
c2dfb7
+                                } else {
c2dfb7
+                                      assert(streq(name, "StandardErrorFileToAppend"));
c2dfb7
+                                      c->std_error = EXEC_OUTPUT_FILE_APPEND;
c2dfb7
+                                      unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardOutput=append:%s", s);
c2dfb7
+                                }
c2dfb7
                         }
c2dfb7
                 }
c2dfb7
 
c2dfb7
diff --git a/src/core/execute.c b/src/core/execute.c
c2dfb7
index f012023224..3c54ac1110 100644
c2dfb7
--- a/src/core/execute.c
c2dfb7
+++ b/src/core/execute.c
c2dfb7
@@ -89,6 +89,7 @@
c2dfb7
 #include "strv.h"
c2dfb7
 #include "syslog-util.h"
c2dfb7
 #include "terminal-util.h"
c2dfb7
+#include "umask-util.h"
c2dfb7
 #include "unit.h"
c2dfb7
 #include "user-util.h"
c2dfb7
 #include "util.h"
c2dfb7
@@ -675,9 +676,10 @@ static int setup_output(
c2dfb7
                 (void) fd_nonblock(named_iofds[fileno], false);
c2dfb7
                 return dup2(named_iofds[fileno], fileno) < 0 ? -errno : fileno;
c2dfb7
 
c2dfb7
-        case EXEC_OUTPUT_FILE: {
c2dfb7
+        case EXEC_OUTPUT_FILE:
c2dfb7
+        case EXEC_OUTPUT_FILE_APPEND: {
c2dfb7
                 bool rw;
c2dfb7
-                int fd;
c2dfb7
+                int fd, flags;
c2dfb7
 
c2dfb7
                 assert(context->stdio_file[fileno]);
c2dfb7
 
c2dfb7
@@ -687,11 +689,16 @@ static int setup_output(
c2dfb7
                 if (rw)
c2dfb7
                         return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno;
c2dfb7
 
c2dfb7
-                fd = acquire_path(context->stdio_file[fileno], O_WRONLY, 0666 & ~context->umask);
c2dfb7
+                flags = O_WRONLY;
c2dfb7
+                if (o == EXEC_OUTPUT_FILE_APPEND)
c2dfb7
+                        flags |= O_APPEND;
c2dfb7
+
c2dfb7
+                fd = acquire_path(context->stdio_file[fileno], flags, 0666 & ~context->umask);
c2dfb7
+
c2dfb7
                 if (fd < 0)
c2dfb7
                         return fd;
c2dfb7
 
c2dfb7
-                return move_fd(fd, fileno, false);
c2dfb7
+                return move_fd(fd, fileno, 0);
c2dfb7
         }
c2dfb7
 
c2dfb7
         default:
c2dfb7
@@ -4168,8 +4175,12 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
c2dfb7
                 fprintf(f, "%sStandardInputFile: %s\n", prefix, c->stdio_file[STDIN_FILENO]);
c2dfb7
         if (c->std_output == EXEC_OUTPUT_FILE)
c2dfb7
                 fprintf(f, "%sStandardOutputFile: %s\n", prefix, c->stdio_file[STDOUT_FILENO]);
c2dfb7
+        if (c->std_output == EXEC_OUTPUT_FILE_APPEND)
c2dfb7
+                fprintf(f, "%sStandardOutputFileToAppend: %s\n", prefix, c->stdio_file[STDOUT_FILENO]);
c2dfb7
         if (c->std_error == EXEC_OUTPUT_FILE)
c2dfb7
                 fprintf(f, "%sStandardErrorFile: %s\n", prefix, c->stdio_file[STDERR_FILENO]);
c2dfb7
+        if (c->std_error == EXEC_OUTPUT_FILE_APPEND)
c2dfb7
+                fprintf(f, "%sStandardErrorFileToAppend: %s\n", prefix, c->stdio_file[STDERR_FILENO]);
c2dfb7
 
c2dfb7
         if (c->tty_path)
c2dfb7
                 fprintf(f,
c2dfb7
@@ -5111,6 +5122,7 @@ static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = {
c2dfb7
         [EXEC_OUTPUT_SOCKET] = "socket",
c2dfb7
         [EXEC_OUTPUT_NAMED_FD] = "fd",
c2dfb7
         [EXEC_OUTPUT_FILE] = "file",
c2dfb7
+        [EXEC_OUTPUT_FILE_APPEND] = "append",
c2dfb7
 };
c2dfb7
 
c2dfb7
 DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput);
c2dfb7
diff --git a/src/core/execute.h b/src/core/execute.h
c2dfb7
index 2266355962..86c1cee84c 100644
c2dfb7
--- a/src/core/execute.h
c2dfb7
+++ b/src/core/execute.h
c2dfb7
@@ -57,6 +57,7 @@ typedef enum ExecOutput {
c2dfb7
         EXEC_OUTPUT_SOCKET,
c2dfb7
         EXEC_OUTPUT_NAMED_FD,
c2dfb7
         EXEC_OUTPUT_FILE,
c2dfb7
+        EXEC_OUTPUT_FILE_APPEND,
c2dfb7
         _EXEC_OUTPUT_MAX,
c2dfb7
         _EXEC_OUTPUT_INVALID = -1
c2dfb7
 } ExecOutput;
c2dfb7
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
c2dfb7
index 2082166afb..9b2724307d 100644
c2dfb7
--- a/src/core/load-fragment.c
c2dfb7
+++ b/src/core/load-fragment.c
c2dfb7
@@ -1016,6 +1016,17 @@ int config_parse_exec_output(
c2dfb7
 
c2dfb7
                 eo = EXEC_OUTPUT_FILE;
c2dfb7
 
c2dfb7
+        } else if ((n = startswith(rvalue, "append:"))) {
c2dfb7
+
c2dfb7
+                r = unit_full_printf(u, n, &resolved);
c2dfb7
+                if (r < 0)
c2dfb7
+                        return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s: %m", n);
c2dfb7
+
c2dfb7
+                r = path_simplify_and_warn(resolved, PATH_CHECK_ABSOLUTE | PATH_CHECK_FATAL, unit, filename, line, lvalue);
c2dfb7
+                if (r < 0)
c2dfb7
+                        return -ENOEXEC;
c2dfb7
+
c2dfb7
+                eo = EXEC_OUTPUT_FILE_APPEND;
c2dfb7
         } else {
c2dfb7
                 eo = exec_output_from_string(rvalue);
c2dfb7
                 if (eo < 0) {
c2dfb7
diff --git a/src/core/main.c b/src/core/main.c
c2dfb7
index 9f238a8430..25536054b3 100644
c2dfb7
--- a/src/core/main.c
c2dfb7
+++ b/src/core/main.c
c2dfb7
@@ -620,8 +620,8 @@ static int config_parse_output_restricted(
c2dfb7
                 return 0;
c2dfb7
         }
c2dfb7
 
c2dfb7
-        if (IN_SET(t, EXEC_OUTPUT_SOCKET, EXEC_OUTPUT_NAMED_FD, EXEC_OUTPUT_FILE)) {
c2dfb7
-                log_syntax(unit, LOG_ERR, filename, line, 0, "Standard output types socket, fd:, file: are not supported as defaults, ignoring: %s", rvalue);
c2dfb7
+        if (IN_SET(t, EXEC_OUTPUT_SOCKET, EXEC_OUTPUT_NAMED_FD, EXEC_OUTPUT_FILE, EXEC_OUTPUT_FILE_APPEND)) {
c2dfb7
+                log_syntax(unit, LOG_ERR, filename, line, 0, "Standard output types socket, fd:, file:, append: are not supported as defaults, ignoring: %s", rvalue);
c2dfb7
                 return 0;
c2dfb7
         }
c2dfb7
 
c2dfb7
diff --git a/src/test/test-execute.c b/src/test/test-execute.c
c2dfb7
index 637ffe96bb..0f8dc883b1 100644
c2dfb7
--- a/src/test/test-execute.c
c2dfb7
+++ b/src/test/test-execute.c
c2dfb7
@@ -651,6 +651,14 @@ static void test_exec_standardinput(Manager *m) {
c2dfb7
         test(m, "exec-standardinput-file.service", 0, CLD_EXITED);
c2dfb7
 }
c2dfb7
 
c2dfb7
+static void test_exec_standardoutput(Manager *m) {
c2dfb7
+        test(m, "exec-standardoutput-file.service", 0, CLD_EXITED);
c2dfb7
+}
c2dfb7
+
c2dfb7
+static void test_exec_standardoutput_append(Manager *m) {
c2dfb7
+        test(m, "exec-standardoutput-append.service", 0, CLD_EXITED);
c2dfb7
+}
c2dfb7
+
c2dfb7
 static int run_tests(UnitFileScope scope, const test_function_t *tests) {
c2dfb7
         const test_function_t *test = NULL;
c2dfb7
         _cleanup_(manager_freep) Manager *m = NULL;
c2dfb7
@@ -698,6 +706,8 @@ int main(int argc, char *argv[]) {
c2dfb7
                 test_exec_restrictnamespaces,
c2dfb7
                 test_exec_runtimedirectory,
c2dfb7
                 test_exec_standardinput,
c2dfb7
+                test_exec_standardoutput,
c2dfb7
+                test_exec_standardoutput_append,
c2dfb7
                 test_exec_supplementarygroups,
c2dfb7
                 test_exec_systemcallerrornumber,
c2dfb7
                 test_exec_systemcallfilter,
c2dfb7
diff --git a/test/meson.build b/test/meson.build
c2dfb7
index fb9f2cdb9b..4d1c51048c 100644
c2dfb7
--- a/test/meson.build
c2dfb7
+++ b/test/meson.build
c2dfb7
@@ -115,6 +115,8 @@ test_data_files = '''
c2dfb7
         test-execute/exec-specifier@.service
c2dfb7
         test-execute/exec-standardinput-data.service
c2dfb7
         test-execute/exec-standardinput-file.service
c2dfb7
+        test-execute/exec-standardoutput-file.service
c2dfb7
+        test-execute/exec-standardoutput-append.service
c2dfb7
         test-execute/exec-supplementarygroups-multiple-groups-default-group-user.service
c2dfb7
         test-execute/exec-supplementarygroups-multiple-groups-withgid.service
c2dfb7
         test-execute/exec-supplementarygroups-multiple-groups-withuid.service
c2dfb7
diff --git a/test/test-execute/exec-standardoutput-append.service b/test/test-execute/exec-standardoutput-append.service
c2dfb7
new file mode 100644
c2dfb7
index 0000000000..8983bb056b
c2dfb7
--- /dev/null
c2dfb7
+++ b/test/test-execute/exec-standardoutput-append.service
c2dfb7
@@ -0,0 +1,13 @@
c2dfb7
+[Unit]
c2dfb7
+Description=Test for StandardOutput=append:
c2dfb7
+
c2dfb7
+[Service]
c2dfb7
+ExecStartPre=sh -c 'printf "hello\n" > /tmp/test-exec-standardoutput-output'
c2dfb7
+ExecStartPre=sh -c 'printf "hello\nhello\n" > /tmp/test-exec-standardoutput-expected'
c2dfb7
+StandardInput=data
c2dfb7
+StandardInputText=hello
c2dfb7
+StandardOutput=append:/tmp/test-exec-standardoutput-output
c2dfb7
+StandardError=null
c2dfb7
+ExecStart=cat
c2dfb7
+ExecStart=cmp /tmp/test-exec-standardoutput-output /tmp/test-exec-standardoutput-expected
c2dfb7
+Type=oneshot
c2dfb7
diff --git a/test/test-execute/exec-standardoutput-file.service b/test/test-execute/exec-standardoutput-file.service
c2dfb7
new file mode 100644
c2dfb7
index 0000000000..71e2604b94
c2dfb7
--- /dev/null
c2dfb7
+++ b/test/test-execute/exec-standardoutput-file.service
c2dfb7
@@ -0,0 +1,13 @@
c2dfb7
+[Unit]
c2dfb7
+Description=Test for StandardOutput=file:
c2dfb7
+
c2dfb7
+[Service]
c2dfb7
+ExecStartPre=sh -c 'printf "nooo\nhello\n" > /tmp/test-exec-standardoutput-output'
c2dfb7
+ExecStartPre=sh -c 'printf "hello\nello\n" > /tmp/test-exec-standardoutput-expected'
c2dfb7
+StandardInput=data
c2dfb7
+StandardInputText=hello
c2dfb7
+StandardOutput=file:/tmp/test-exec-standardoutput-output
c2dfb7
+StandardError=null
c2dfb7
+ExecStart=cat
c2dfb7
+ExecStart=cmp /tmp/test-exec-standardoutput-expected /tmp/test-exec-standardoutput-output
c2dfb7
+Type=oneshot