summaryrefslogtreecommitdiff
path: root/src/test/test-exec-util.c
diff options
context:
space:
mode:
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2017-01-22 15:22:37 -0500
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2017-02-20 18:49:13 -0500
commitc6e47247a745f9245eb3dbfb29fc066ef72d3886 (patch)
tree4bc9451abde3c562a24ae18c2bfcb2dee4f618c2 /src/test/test-exec-util.c
parent504afd7c34e00eb84589e94e59cd14f2fffa2807 (diff)
downloadsystemd-c6e47247a745f9245eb3dbfb29fc066ef72d3886.tar.gz
basic/exec-util: add support for synchronous (ordered) execution
The output of processes can be gathered, and passed back to the callee. (This commit just implements the basic functionality and tests.) After the preparation in previous commits, the change in functionality is relatively simple. For coding convenience, alarm is prepared *before* any children are executed, and not before. This shouldn't matter usually, since just forking of the children should be pretty quick. One could also argue that this is more correct, because we will also catch the case when (for whatever reason), forking itself is slow. Three callback functions and three levels of serialization are used: - from individual generator processes to the generator forker - from the forker back to the main process - deserialization in the main process v2: - replace an structure with an indexed array of callbacks
Diffstat (limited to 'src/test/test-exec-util.c')
-rw-r--r--src/test/test-exec-util.c220
1 files changed, 209 insertions, 11 deletions
diff --git a/src/test/test-exec-util.c b/src/test/test-exec-util.c
index 26533f0bf6..41cbef74b1 100644
--- a/src/test/test-exec-util.c
+++ b/src/test/test-exec-util.c
@@ -24,21 +24,59 @@
#include <sys/wait.h>
#include <unistd.h>
+#include "alloc-util.h"
+#include "copy.h"
#include "def.h"
#include "exec-util.h"
+#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "log.h"
#include "macro.h"
#include "rm-rf.h"
#include "string-util.h"
+#include "strv.h"
-static void test_execute_directory(void) {
- char template_lo[] = "/tmp/test-readlink_and_make_absolute-lo.XXXXXXX";
- char template_hi[] = "/tmp/test-readlink_and_make_absolute-hi.XXXXXXX";
+static int here = 0, here2 = 0, here3 = 0;
+void *ignore_stdout_args[] = {&here, &here2, &here3};
+
+/* noop handlers, just check that arguments are passed correctly */
+static int ignore_stdout_func(int fd, void *arg) {
+ assert(fd >= 0);
+ assert(arg == &here);
+ safe_close(fd);
+
+ return 0;
+}
+static int ignore_stdout_func2(int fd, void *arg) {
+ assert(fd >= 0);
+ assert(arg == &here2);
+ safe_close(fd);
+
+ return 0;
+}
+static int ignore_stdout_func3(int fd, void *arg) {
+ assert(fd >= 0);
+ assert(arg == &here3);
+ safe_close(fd);
+
+ return 0;
+}
+
+static const gather_stdout_callback_t ignore_stdout[] = {
+ ignore_stdout_func,
+ ignore_stdout_func2,
+ ignore_stdout_func3,
+};
+
+static void test_execute_directory(bool gather_stdout) {
+ char template_lo[] = "/tmp/test-exec-util.XXXXXXX";
+ char template_hi[] = "/tmp/test-exec-util.XXXXXXX";
const char * dirs[] = {template_hi, template_lo, NULL};
const char *name, *name2, *name3, *overridden, *override, *masked, *mask;
+ log_info("/* %s (%s) */", __func__, gather_stdout ? "gathering stdout" : "asynchronous");
+
assert_se(mkdtemp(template_lo));
assert_se(mkdtemp(template_hi));
@@ -50,20 +88,34 @@ static void test_execute_directory(void) {
masked = strjoina(template_lo, "/masked");
mask = strjoina(template_hi, "/masked");
- assert_se(write_string_file(name, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works", WRITE_STRING_FILE_CREATE) == 0);
- assert_se(write_string_file(name2, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works2", WRITE_STRING_FILE_CREATE) == 0);
- assert_se(write_string_file(overridden, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed", WRITE_STRING_FILE_CREATE) == 0);
- assert_se(write_string_file(override, "#!/bin/sh\necho 'Executing '$0", WRITE_STRING_FILE_CREATE) == 0);
- assert_se(write_string_file(masked, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed", WRITE_STRING_FILE_CREATE) == 0);
+ assert_se(write_string_file(name,
+ "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works",
+ WRITE_STRING_FILE_CREATE) == 0);
+ assert_se(write_string_file(name2,
+ "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works2",
+ WRITE_STRING_FILE_CREATE) == 0);
+ assert_se(write_string_file(overridden,
+ "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
+ WRITE_STRING_FILE_CREATE) == 0);
+ assert_se(write_string_file(override,
+ "#!/bin/sh\necho 'Executing '$0",
+ WRITE_STRING_FILE_CREATE) == 0);
+ assert_se(write_string_file(masked,
+ "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
+ WRITE_STRING_FILE_CREATE) == 0);
assert_se(symlink("/dev/null", mask) == 0);
+ assert_se(touch(name3) >= 0);
+
assert_se(chmod(name, 0755) == 0);
assert_se(chmod(name2, 0755) == 0);
assert_se(chmod(overridden, 0755) == 0);
assert_se(chmod(override, 0755) == 0);
assert_se(chmod(masked, 0755) == 0);
- assert_se(touch(name3) >= 0);
- execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL);
+ if (gather_stdout)
+ execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL);
+ else
+ execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, NULL);
assert_se(chdir(template_lo) == 0);
assert_se(access("it_works", F_OK) >= 0);
@@ -77,11 +129,157 @@ static void test_execute_directory(void) {
(void) rm_rf(template_hi, REMOVE_ROOT|REMOVE_PHYSICAL);
}
+static void test_execution_order(void) {
+ char template_lo[] = "/tmp/test-exec-util-lo.XXXXXXX";
+ char template_hi[] = "/tmp/test-exec-util-hi.XXXXXXX";
+ const char *dirs[] = {template_hi, template_lo, NULL};
+ const char *name, *name2, *name3, *overridden, *override, *masked, *mask;
+ const char *output, *t;
+ _cleanup_free_ char *contents = NULL;
+
+ assert_se(mkdtemp(template_lo));
+ assert_se(mkdtemp(template_hi));
+
+ output = strjoina(template_hi, "/output");
+
+ log_info("/* %s >>%s */", __func__, output);
+
+ /* write files in "random" order */
+ name2 = strjoina(template_lo, "/90-bar");
+ name = strjoina(template_hi, "/80-foo");
+ name3 = strjoina(template_lo, "/last");
+ overridden = strjoina(template_lo, "/30-override");
+ override = strjoina(template_hi, "/30-override");
+ masked = strjoina(template_lo, "/10-masked");
+ mask = strjoina(template_hi, "/10-masked");
+
+ t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
+ assert_se(write_string_file(name, t, WRITE_STRING_FILE_CREATE) == 0);
+
+ t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
+ assert_se(write_string_file(name2, t, WRITE_STRING_FILE_CREATE) == 0);
+
+ t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
+ assert_se(write_string_file(name3, t, WRITE_STRING_FILE_CREATE) == 0);
+
+ t = strjoina("#!/bin/sh\necho OVERRIDDEN >>", output);
+ assert_se(write_string_file(overridden, t, WRITE_STRING_FILE_CREATE) == 0);
+
+ t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
+ assert_se(write_string_file(override, t, WRITE_STRING_FILE_CREATE) == 0);
+
+ t = strjoina("#!/bin/sh\necho MASKED >>", output);
+ assert_se(write_string_file(masked, t, WRITE_STRING_FILE_CREATE) == 0);
+
+ assert_se(symlink("/dev/null", mask) == 0);
+
+ assert_se(chmod(name, 0755) == 0);
+ assert_se(chmod(name2, 0755) == 0);
+ assert_se(chmod(name3, 0755) == 0);
+ assert_se(chmod(overridden, 0755) == 0);
+ assert_se(chmod(override, 0755) == 0);
+ assert_se(chmod(masked, 0755) == 0);
+
+ execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL);
+
+ assert_se(read_full_file(output, &contents, NULL) >= 0);
+ assert_se(streq(contents, "30-override\n80-foo\n90-bar\nlast\n"));
+
+ (void) rm_rf(template_lo, REMOVE_ROOT|REMOVE_PHYSICAL);
+ (void) rm_rf(template_hi, REMOVE_ROOT|REMOVE_PHYSICAL);
+}
+
+static int gather_stdout_one(int fd, void *arg) {
+ char ***s = arg, *t;
+ char buf[128] = {};
+
+ assert_se(s);
+ assert_se(read(fd, buf, sizeof buf) >= 0);
+ safe_close(fd);
+
+ assert_se(t = strndup(buf, sizeof buf));
+ assert_se(strv_push(s, t) >= 0);
+
+ return 0;
+}
+static int gather_stdout_two(int fd, void *arg) {
+ char ***s = arg, **t;
+
+ STRV_FOREACH(t, *s)
+ assert_se(write(fd, *t, strlen(*t)) == (ssize_t) strlen(*t));
+ safe_close(fd);
+
+ return 0;
+}
+static int gather_stdout_three(int fd, void *arg) {
+ char **s = arg;
+ char buf[128] = {};
+
+ assert_se(read(fd, buf, sizeof buf - 1) > 0);
+ safe_close(fd);
+ assert_se(*s = strndup(buf, sizeof buf));
+
+ return 0;
+}
+
+const gather_stdout_callback_t const gather_stdout[] = {
+ gather_stdout_one,
+ gather_stdout_two,
+ gather_stdout_three,
+};
+
+
+static void test_stdout_gathering(void) {
+ char template[] = "/tmp/test-exec-util.XXXXXXX";
+ const char *dirs[] = {template, NULL};
+ const char *name, *name2, *name3;
+ int r;
+
+ char **tmp = NULL; /* this is only used in the forked process, no cleanup here */
+ _cleanup_free_ char *output = NULL;
+
+ void* args[] = {&tmp, &tmp, &output};
+
+ assert_se(mkdtemp(template));
+
+ log_info("/* %s */", __func__);
+
+ /* write files */
+ name = strjoina(template, "/10-foo");
+ name2 = strjoina(template, "/20-bar");
+ name3 = strjoina(template, "/30-last");
+
+ assert_se(write_string_file(name,
+ "#!/bin/sh\necho a\necho b\necho c\n",
+ WRITE_STRING_FILE_CREATE) == 0);
+ assert_se(write_string_file(name2,
+ "#!/bin/sh\necho d\n",
+ WRITE_STRING_FILE_CREATE) == 0);
+ assert_se(write_string_file(name3,
+ "#!/bin/sh\nsleep 1",
+ WRITE_STRING_FILE_CREATE) == 0);
+
+ assert_se(chmod(name, 0755) == 0);
+ assert_se(chmod(name2, 0755) == 0);
+ assert_se(chmod(name3, 0755) == 0);
+
+ r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_stdout, args, NULL);
+ assert_se(r >= 0);
+
+ log_info("got: %s", output);
+
+ assert_se(streq(output, "a\nb\nc\nd\n"));
+}
+
int main(int argc, char *argv[]) {
+ log_set_max_level(LOG_DEBUG);
log_parse_environment();
log_open();
- test_execute_directory();
+ test_execute_directory(true);
+ test_execute_directory(false);
+ test_execution_order();
+ test_stdout_gathering();
return 0;
}