diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index ed8ab6205c..7328f9b965 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -2383,8 +2383,8 @@ SystemCallErrorNumber=EPERM
to. Takes one of , , ,
, , ,
, ,
- , or
- .
+ , ,
+ or .
duplicates the file descriptor of standard input for standard output.
@@ -2424,6 +2424,10 @@ SystemCallErrorNumber=EPERM
above, but it opens the file in append mode.
+ is similar to
+ above, but it truncates the file when opening it.
+
+
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 04735354a3..4ad9181993 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -2727,8 +2727,8 @@ int bus_exec_context_set_transient_property(
} else if (STR_IN_SET(name,
"StandardInputFile",
- "StandardOutputFile", "StandardOutputFileToAppend",
- "StandardErrorFile", "StandardErrorFileToAppend")) {
+ "StandardOutputFile", "StandardOutputFileToAppend", "StandardOutputFileToTruncate",
+ "StandardErrorFile", "StandardErrorFileToAppend", "StandardErrorFileToTruncate")) {
const char *s;
r = sd_bus_message_read(message, "s", &s);
@@ -2752,7 +2752,7 @@ 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 (STR_IN_SET(name, "StandardOutputFile", "StandardOutputFileToAppend")) {
+ } else if (STR_IN_SET(name, "StandardOutputFile", "StandardOutputFileToAppend", "StandardOutputFileToTruncate")) {
r = free_and_strdup(&c->stdio_file[STDOUT_FILENO], empty_to_null(s));
if (r < 0)
return r;
@@ -2760,13 +2760,16 @@ int bus_exec_context_set_transient_property(
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"));
+ } else if (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, "StandardOutputFileToTruncate"));
+ c->std_output = EXEC_OUTPUT_FILE_TRUNCATE;
+ unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardOutput=truncate:%s", s);
}
} else {
- assert(STR_IN_SET(name, "StandardErrorFile", "StandardErrorFileToAppend"));
+ assert(STR_IN_SET(name, "StandardErrorFile", "StandardErrorFileToAppend", "StandardErrorFileToTruncate"));
r = free_and_strdup(&c->stdio_file[STDERR_FILENO], empty_to_null(s));
if (r < 0)
@@ -2775,10 +2778,13 @@ int bus_exec_context_set_transient_property(
if (streq(name, "StandardErrorFile")) {
c->std_error = EXEC_OUTPUT_FILE;
unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardError=file:%s", s);
- } else {
- assert(streq(name, "StandardErrorFileToAppend"));
+ } else if (streq(name, "StandardErrorFileToAppend")) {
c->std_error = EXEC_OUTPUT_FILE_APPEND;
unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardError=append:%s", s);
+ } else {
+ assert(streq(name, "StandardErrorFileToTruncate"));
+ c->std_error = EXEC_OUTPUT_FILE_TRUNCATE;
+ unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardError=truncate:%s", s);
}
}
}
diff --git a/src/core/execute.c b/src/core/execute.c
index 8f901fa715..ee5f082783 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -562,7 +562,7 @@ static bool can_inherit_stderr_from_stdout(
if (e == EXEC_OUTPUT_NAMED_FD)
return streq_ptr(context->stdio_fdname[STDOUT_FILENO], context->stdio_fdname[STDERR_FILENO]);
- if (IN_SET(e, EXEC_OUTPUT_FILE, EXEC_OUTPUT_FILE_APPEND))
+ if (IN_SET(e, EXEC_OUTPUT_FILE, EXEC_OUTPUT_FILE_APPEND, EXEC_OUTPUT_FILE_TRUNCATE))
return streq_ptr(context->stdio_file[STDOUT_FILENO], context->stdio_file[STDERR_FILENO]);
return true;
@@ -698,7 +698,8 @@ static int setup_output(
return dup2(named_iofds[fileno], fileno) < 0 ? -errno : fileno;
case EXEC_OUTPUT_FILE:
- case EXEC_OUTPUT_FILE_APPEND: {
+ case EXEC_OUTPUT_FILE_APPEND:
+ case EXEC_OUTPUT_FILE_TRUNCATE: {
bool rw;
int fd, flags;
@@ -713,6 +714,8 @@ static int setup_output(
flags = O_WRONLY;
if (o == EXEC_OUTPUT_FILE_APPEND)
flags |= O_APPEND;
+ else if (o == EXEC_OUTPUT_FILE_TRUNCATE)
+ flags |= O_TRUNC;
fd = acquire_path(context->stdio_file[fileno], flags, 0666 & ~context->umask);
if (fd < 0)
@@ -5357,10 +5360,14 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
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_output == EXEC_OUTPUT_FILE_TRUNCATE)
+ fprintf(f, "%sStandardOutputFileToTruncate: %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->std_error == EXEC_OUTPUT_FILE_TRUNCATE)
+ fprintf(f, "%sStandardErrorFileToTruncate: %s\n", prefix, c->stdio_file[STDERR_FILENO]);
if (c->tty_path)
fprintf(f,
@@ -6449,6 +6456,7 @@ static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = {
[EXEC_OUTPUT_NAMED_FD] = "fd",
[EXEC_OUTPUT_FILE] = "file",
[EXEC_OUTPUT_FILE_APPEND] = "append",
+ [EXEC_OUTPUT_FILE_TRUNCATE] = "truncate",
};
DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput);
diff --git a/src/core/execute.h b/src/core/execute.h
index 33d7e1693d..da8d6ae272 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -61,6 +61,7 @@ typedef enum ExecOutput {
EXEC_OUTPUT_NAMED_FD,
EXEC_OUTPUT_FILE,
EXEC_OUTPUT_FILE_APPEND,
+ EXEC_OUTPUT_FILE_TRUNCATE,
_EXEC_OUTPUT_MAX,
_EXEC_OUTPUT_INVALID = -1
} ExecOutput;
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 4964249bf2..4401e598b5 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -1202,6 +1202,20 @@ int config_parse_exec_output(
return 0;
eo = EXEC_OUTPUT_FILE_APPEND;
+
+ } else if ((n = startswith(rvalue, "truncate:"))) {
+
+ r = unit_full_printf(u, n, &resolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", n);
+ return 0;
+ }
+
+ r = path_simplify_and_warn(resolved, PATH_CHECK_ABSOLUTE | PATH_CHECK_FATAL, unit, filename, line, lvalue);
+ if (r < 0)
+ return 0;
+
+ eo = EXEC_OUTPUT_FILE_TRUNCATE;
} else {
eo = exec_output_from_string(rvalue);
if (eo < 0) {
@@ -5761,8 +5775,8 @@ int config_parse_output_restricted(
return 0;
}
- if (IN_SET(t, EXEC_OUTPUT_SOCKET, EXEC_OUTPUT_NAMED_FD, EXEC_OUTPUT_FILE, EXEC_OUTPUT_FILE_APPEND)) {
- log_syntax(unit, LOG_WARNING, filename, line, 0, "Standard output types socket, fd:, file:, append: 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, EXEC_OUTPUT_FILE_TRUNCATE)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Standard output types socket, fd:, file:, append:, truncate: are not supported as defaults, ignoring: %s", rvalue);
return 0;
}
}
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index bc17b6e1fb..07f936dc6c 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -1144,6 +1144,9 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
} else if ((n = startswith(eq, "append:"))) {
appended = strjoina(field, "FileToAppend");
r = sd_bus_message_append(m, "(sv)", appended, "s", n);
+ } else if ((n = startswith(eq, "truncate:"))) {
+ appended = strjoina(field, "FileToTruncate");
+ r = sd_bus_message_append(m, "(sv)", appended, "s", n);
} else
r = sd_bus_message_append(m, "(sv)", field, "s", eq);
if (r < 0)
diff --git a/src/test/test-execute.c b/src/test/test-execute.c
index 83816f474c..01e2443777 100644
--- a/src/test/test-execute.c
+++ b/src/test/test-execute.c
@@ -811,6 +811,10 @@ static void test_exec_standardoutput_append(Manager *m) {
test(m, "exec-standardoutput-append.service", 0, CLD_EXITED);
}
+static void test_exec_standardoutput_truncate(Manager *m) {
+ test(m, "exec-standardoutput-truncate.service", 0, CLD_EXITED);
+}
+
static void test_exec_condition(Manager *m) {
test_service(m, "exec-condition-failed.service", SERVICE_FAILURE_EXIT_CODE);
test_service(m, "exec-condition-skip.service", SERVICE_SKIP_CONDITION);
@@ -876,6 +880,7 @@ int main(int argc, char *argv[]) {
entry(test_exec_standardinput),
entry(test_exec_standardoutput),
entry(test_exec_standardoutput_append),
+ entry(test_exec_standardoutput_truncate),
entry(test_exec_supplementarygroups),
entry(test_exec_systemcallerrornumber),
entry(test_exec_systemcallfilter),
diff --git a/test/test-execute/exec-standardoutput-truncate.service b/test/test-execute/exec-standardoutput-truncate.service
new file mode 100644
index 0000000000..7a12a06923
--- /dev/null
+++ b/test/test-execute/exec-standardoutput-truncate.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Test for StandardOutput=truncate:
+
+[Service]
+ExecStartPre=sh -c 'printf "hello\n" > /tmp/test-exec-standardoutput-output'
+ExecStartPre=sh -c 'printf "hi\n" > /tmp/test-exec-standardoutput-expected'
+StandardInput=data
+StandardInputText=hi
+StandardOutput=truncate:/tmp/test-exec-standardoutput-output
+StandardError=null
+ExecStart=cat
+ExecStartPost=cmp /tmp/test-exec-standardoutput-output /tmp/test-exec-standardoutput-expected
+Type=oneshot
diff --git a/test/units/testsuite-27.sh b/test/units/testsuite-27.sh
index 9d92e6e574..0e9ffe1189 100755
--- a/test/units/testsuite-27.sh
+++ b/test/units/testsuite-27.sh
@@ -43,6 +43,18 @@ a
c
EOF
+systemd-run --wait --unit=test27-four \
+ -p StandardOutput=truncate:/tmp/stdout \
+ -p StandardError=truncate:/tmp/stderr \
+ -p Type=exec \
+ sh -c 'echo a ; echo b >&2'
+cmp /tmp/stdout </testok