Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * Copyright (c) 2022 Egor Tensin <egor@tensin.name> | ||
3 | * This file is part of the "cimple" project. | ||
4 | * For details, see https://github.com/egor-tensin/cimple. | ||
5 | * Distributed under the MIT License. | ||
6 | */ | ||
7 | |||
8 | #include "process.h" | ||
9 | #include "file.h" | ||
10 | #include "log.h" | ||
11 | |||
12 | #include <fcntl.h> | ||
13 | #include <stdlib.h> | ||
14 | #include <sys/wait.h> | ||
15 | #include <unistd.h> | ||
16 | |||
17 | ✗ | static int exec_child(const char *args[], const char *envp[]) | |
18 | { | ||
19 | static const char *default_envp[] = {NULL}; | ||
20 | |||
21 | ✗ | if (!envp) | |
22 | ✗ | envp = default_envp; | |
23 | |||
24 | ✗ | int ret = execvpe(args[0], (char *const *)args, (char *const *)envp); | |
25 | ✗ | if (ret < 0) { | |
26 | ✗ | log_errno("execvpe"); | |
27 | ✗ | return ret; | |
28 | } | ||
29 | |||
30 | ✗ | return ret; | |
31 | } | ||
32 | |||
33 | 9180 | static int wait_for_child(pid_t pid, int *ec) | |
34 | { | ||
35 | int status; | ||
36 | |||
37 | 9180 | pid_t ret = waitpid(pid, &status, __WNOTHREAD); | |
38 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
|
9180 | if (ret < 0) { |
39 | ✗ | log_errno("waitpid"); | |
40 | ✗ | return ret; | |
41 | } | ||
42 | |||
43 | /* The child process reports the lowest 8 bits of its exit code, which | ||
44 | * are treated as an unsigned integer on Linux. | ||
45 | * | ||
46 | * If it was killed by a signal, indicate that by negating the signal | ||
47 | * number. */ | ||
48 | |||
49 |
2/2✓ Branch 0 taken 9144 times.
✓ Branch 1 taken 36 times.
|
9180 | if (WIFEXITED(status)) { |
50 | 9144 | *ec = WEXITSTATUS(status); | |
51 |
1/2✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
|
36 | } else if (WIFSIGNALED(status)) { |
52 | 36 | *ec = -WTERMSIG(status); | |
53 | } else { | ||
54 | ✗ | log_err("This shouldn't happen: %d\n", status); | |
55 | ✗ | *ec = 1; | |
56 | } | ||
57 | |||
58 | 9180 | return 0; | |
59 | } | ||
60 | |||
61 | ✗ | int process_execute(const char *args[], const char *envp[], int *ec) | |
62 | { | ||
63 | ✗ | pid_t child_pid = fork(); | |
64 | ✗ | if (child_pid < 0) { | |
65 | ✗ | log_errno("fork"); | |
66 | ✗ | return child_pid; | |
67 | } | ||
68 | |||
69 | ✗ | if (!child_pid) | |
70 | ✗ | exit(exec_child(args, envp)); | |
71 | |||
72 | ✗ | return wait_for_child(child_pid, ec); | |
73 | } | ||
74 | |||
75 | ✗ | static int redirect_and_exec_child(int pipe_fds[2], const char *args[], const char *envp[]) | |
76 | { | ||
77 | ✗ | int ret = 0; | |
78 | |||
79 | ✗ | file_close(pipe_fds[0]); | |
80 | |||
81 | ✗ | ret = dup2(pipe_fds[1], STDOUT_FILENO); | |
82 | ✗ | if (ret < 0) { | |
83 | ✗ | log_errno("dup2"); | |
84 | ✗ | return ret; | |
85 | } | ||
86 | |||
87 | ✗ | ret = dup2(pipe_fds[1], STDERR_FILENO); | |
88 | ✗ | if (ret < 0) { | |
89 | ✗ | log_errno("dup2"); | |
90 | ✗ | return ret; | |
91 | } | ||
92 | |||
93 | ✗ | return exec_child(args, envp); | |
94 | } | ||
95 | |||
96 | 9180 | int process_execute_and_capture(const char *args[], const char *envp[], | |
97 | struct process_output *result) | ||
98 | { | ||
99 | static const int flags = O_CLOEXEC; | ||
100 | int pipe_fds[2]; | ||
101 | 9180 | int ret = 0; | |
102 | |||
103 | 9180 | ret = pipe2(pipe_fds, flags); | |
104 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
|
9180 | if (ret < 0) { |
105 | ✗ | log_errno("pipe2"); | |
106 | ✗ | return -1; | |
107 | } | ||
108 | |||
109 | 9180 | pid_t child_pid = fork(); | |
110 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
|
9180 | if (child_pid < 0) { |
111 | ✗ | log_errno("fork"); | |
112 | ✗ | goto close_pipe; | |
113 | } | ||
114 | |||
115 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
|
9180 | if (!child_pid) |
116 | ✗ | exit(redirect_and_exec_child(pipe_fds, args, envp)); | |
117 | |||
118 | 9180 | file_close(pipe_fds[1]); | |
119 | |||
120 | 9180 | ret = file_read(pipe_fds[0], &result->data, &result->data_size); | |
121 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
|
9180 | if (ret < 0) |
122 | ✗ | goto close_pipe; | |
123 | |||
124 | 9180 | ret = wait_for_child(child_pid, &result->ec); | |
125 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
|
9180 | if (ret < 0) |
126 | ✗ | goto free_data; | |
127 | |||
128 | 9180 | goto close_pipe; | |
129 | |||
130 | ✗ | free_data: | |
131 | ✗ | free(result->data); | |
132 | |||
133 | 9180 | close_pipe: | |
134 | 9180 | file_close(pipe_fds[0]); | |
135 | /* No errno checking here, we might've already closed the write end. */ | ||
136 | 9180 | close(pipe_fds[1]); | |
137 | |||
138 | 9180 | return ret; | |
139 | } | ||
140 | |||
141 | 18360 | int process_output_create(struct process_output **_output) | |
142 | { | ||
143 | 18360 | struct process_output *output = calloc(1, sizeof(struct process_output)); | |
144 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 18360 times.
|
18360 | if (!output) { |
145 | ✗ | log_errno("calloc"); | |
146 | ✗ | return -1; | |
147 | } | ||
148 | |||
149 | 18360 | output->ec = 0; | |
150 | 18360 | output->data = NULL; | |
151 | 18360 | output->data_size = 0; | |
152 | |||
153 | 18360 | *_output = output; | |
154 | 18360 | return 0; | |
155 | } | ||
156 | |||
157 | 18360 | void process_output_destroy(struct process_output *output) | |
158 | { | ||
159 | 18360 | free(output->data); | |
160 | 18360 | free(output); | |
161 | 18360 | } | |
162 | |||
163 | 9180 | void process_output_dump(const struct process_output *output) | |
164 | { | ||
165 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 9180 times.
|
9180 | log("Process exit code: %d\n", output->ec); |
166 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 9180 times.
|
9180 | log("Process output: %zu bytes\n", output->data_size); |
167 | 9180 | } | |
168 |