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