Line data Source code
1 : #include "testitall.h"
2 : #include <sysexits.h>
3 :
4 : // Global buffers for captured output streams
5 : memory _STDOUT = {sizeof(char),0,0,NULL};
6 : memory *STDOUT = &_STDOUT;
7 : memory _STDERR = {sizeof(char),0,0,NULL};
8 : memory *STDERR = &_STDERR;
9 : memory _EXTEND = {sizeof(char),0,0,NULL};
10 : memory *EXTEND = &_EXTEND;
11 :
12 : extern char **environ; // Environment variables used by posix_spawnp
13 :
14 : /**
15 : * Executes an external command, capturing stdout/stderr into shared buffers.
16 : * @param command Shell command to execute.
17 : * @param expected_return_code Exit code the command must produce for SUCCESS.
18 : * @param buffer_policy Bitmask controlling stdout/stderr handling.
19 : * Use STDOUT_SUPPRESS/STDERR_SUPPRESS to drop buffers,
20 : * STDOUT_ENABLE/STDERR_ENABLE to document enabled streams,
21 : * and their combinations like ALLOW_BOTH.
22 : * @return SUCCESS if execution, capture, and exit code checks succeed; FAILURE otherwise.
23 : *
24 : * Side effects:
25 : * - Clears global STDOUT/STDERR buffers before running.
26 : * - Formats diagnostics into STDERR on unexpected exit codes or unsuppressed stderr.
27 : */
28 285 : Return external_call(
29 : const char *command,
30 : const int expected_return_code,
31 : unsigned int buffer_policy)
32 : {
33 : /// The status that will be passed to return() before exiting.
34 : /// By default, the function worked without errors.
35 285 : Return status = SUCCESS;
36 285 : const bool suppress_stdout = (buffer_policy & STDOUT_SUPPRESS) != 0U;
37 285 : const bool suppress_stderr = (buffer_policy & STDERR_SUPPRESS) != 0U;
38 :
39 : // Create pipes to capture stdout and stderr
40 : int stdout_pipe[2],stderr_pipe[2];
41 :
42 285 : if(pipe(stdout_pipe) == -1 || pipe(stderr_pipe) == -1)
43 : {
44 0 : serp("Error creating pipe");
45 0 : return(FAILURE);
46 : }
47 :
48 : // Clear data from previous usage
49 285 : del(STDERR);
50 : // Clear data from previous usage
51 285 : del(STDOUT);
52 :
53 : // Initialize spawn file actions and attributes
54 : posix_spawn_file_actions_t actions;
55 285 : posix_spawn_file_actions_init(&actions);
56 :
57 : // Redirect child process stdout and stderr to the pipes
58 285 : posix_spawn_file_actions_adddup2(&actions,stdout_pipe[1],STDOUT_FILENO);
59 285 : posix_spawn_file_actions_adddup2(&actions,stderr_pipe[1],STDERR_FILENO);
60 :
61 : // Close unused ends of the pipes in the child process
62 285 : posix_spawn_file_actions_addclose(&actions,stdout_pipe[0]);
63 285 : posix_spawn_file_actions_addclose(&actions,stderr_pipe[0]);
64 285 : posix_spawn_file_actions_addclose(&actions,stdout_pipe[1]);
65 285 : posix_spawn_file_actions_addclose(&actions,stderr_pipe[1]);
66 :
67 : pid_t pid;
68 :
69 : // Prepare command arguments
70 285 : char *const arguments[] = {
71 : (char *)(uintptr_t)"sh",
72 : (char *)(uintptr_t)"-c",
73 : (char *)(uintptr_t)command,
74 : NULL
75 : };
76 :
77 : // Execute the command while inheriting current environment variables
78 285 : if(posix_spawnp(&pid,(char *)(uintptr_t)"sh",&actions,NULL,arguments,environ) != 0)
79 : {
80 0 : serp("Error executing posix_spawnp"); // Handle command execution error
81 0 : posix_spawn_file_actions_destroy(&actions);
82 0 : return(FAILURE);
83 : }
84 :
85 : // Clean up spawn resources
86 285 : posix_spawn_file_actions_destroy(&actions);
87 :
88 : // Close the write ends of the pipes
89 285 : close(stdout_pipe[1]);
90 285 : close(stderr_pipe[1]);
91 :
92 : // Variables for reading from the pipe
93 285 : char *tmp_stdout_buffer = NULL; // Pointer to the buffer
94 285 : size_t total_read = 0; // Total bytes read so far
95 285 : ssize_t count = 0; // Bytes read during each iteration
96 :
97 : // Read data from the pipe (chunk size matches libmem's MEMORY_BLOCK_BYTES)
98 : char temp_buffer[MEMORY_BLOCK_BYTES];
99 :
100 389 : while((count = read(stdout_pipe[0],temp_buffer,MEMORY_BLOCK_BYTES)) > 0)
101 : {
102 104 : if(count == -1)
103 : {
104 0 : serp("Error reading from pipe"); // Handle read error
105 0 : free(tmp_stdout_buffer);
106 0 : return(FAILURE);
107 : }
108 :
109 : // Reallocate memory to accommodate new data
110 104 : char *new_buffer = realloc(tmp_stdout_buffer,total_read + (size_t)count + 1); // +1 for null terminator
111 :
112 104 : if(!new_buffer)
113 : {
114 0 : report("Memory allocation failed, requested size: %zu bytes",total_read + (size_t)count + 1);
115 0 : free(tmp_stdout_buffer);
116 0 : return(FAILURE);
117 : }
118 104 : tmp_stdout_buffer = new_buffer;
119 :
120 : // Copy the read data into the buffer
121 104 : memcpy(tmp_stdout_buffer + total_read,temp_buffer,(size_t)count);
122 104 : total_read += (size_t)count;
123 : }
124 :
125 285 : if(total_read > 0)
126 : {
127 94 : if(resize(STDOUT,total_read + 1) != SUCCESS)
128 : {
129 0 : free(tmp_stdout_buffer);
130 0 : return(FAILURE);
131 : }
132 :
133 94 : char *stdout_mem = data(char,STDOUT);
134 :
135 94 : if(stdout_mem == NULL)
136 : {
137 0 : free(tmp_stdout_buffer);
138 0 : return(FAILURE);
139 : }
140 :
141 94 : memcpy(stdout_mem,tmp_stdout_buffer,(size_t)total_read);
142 94 : stdout_mem[STDOUT->length - 1] = '\0';
143 : }
144 :
145 285 : free(tmp_stdout_buffer); // Free allocated memory
146 :
147 : // Variables for reading stderr
148 285 : char *tmp_stderr_buffer = NULL; // Pointer to the buffer
149 285 : total_read = 0; // Total bytes read so far
150 :
151 : /* Read data from the pipe */
152 :
153 : // Clear the temporary buffer
154 285 : memset(temp_buffer,0,MEMORY_BLOCK_BYTES);
155 :
156 287 : while((count = read(stderr_pipe[0],temp_buffer,MEMORY_BLOCK_BYTES)) > 0)
157 : {
158 2 : if(count == -1)
159 : {
160 0 : serp("Error reading from pipe"); // Handle read error
161 0 : free(tmp_stderr_buffer);
162 0 : return(FAILURE);
163 : }
164 :
165 : // Reallocate memory to accommodate new data
166 2 : char *new_buffer = realloc(tmp_stderr_buffer,total_read + (size_t)count + 1); // +1 for null terminator
167 :
168 2 : if(!new_buffer)
169 : {
170 0 : report("Memory allocation failed, requested size: %zu bytes",total_read + (size_t)count + 1);
171 0 : free(tmp_stderr_buffer);
172 0 : return(FAILURE);
173 : }
174 2 : tmp_stderr_buffer = new_buffer;
175 :
176 : // Copy the read data into the buffer
177 2 : memcpy(tmp_stderr_buffer + total_read,temp_buffer,(size_t)count);
178 2 : total_read += (size_t)count;
179 : }
180 :
181 285 : if(total_read > 0)
182 : {
183 2 : if(resize(STDERR,total_read + 1) != SUCCESS)
184 : {
185 0 : free(tmp_stderr_buffer);
186 0 : return(FAILURE);
187 : }
188 :
189 2 : char *stderr_mem = data(char,STDERR);
190 :
191 2 : if(stderr_mem == NULL)
192 : {
193 0 : free(tmp_stderr_buffer);
194 0 : return(FAILURE);
195 : }
196 :
197 2 : memcpy(stderr_mem,tmp_stderr_buffer,(size_t)total_read);
198 2 : stderr_mem[STDERR->length - 1] = '\0';
199 : }
200 :
201 285 : free(tmp_stderr_buffer);
202 :
203 : // Wait for the child process to finish and capture its exit status
204 : int return_code;
205 285 : bool allow_stderr = (expected_return_code == EX_USAGE);
206 :
207 285 : if(waitpid(pid,&return_code,0) == -1)
208 : {
209 0 : serp("Error waiting for child process");
210 0 : return(FAILURE);
211 : }
212 285 : int exit_code = WEXITSTATUS(return_code);
213 :
214 285 : close(stdout_pipe[0]); // Close the read end of the pipe
215 285 : close(stderr_pipe[0]);
216 :
217 285 : if(STDERR->length > 0)
218 : {
219 2 : if(allow_stderr == true)
220 : {
221 1 : if(STDOUT->length == 0 && STDOUT->data == NULL)
222 : {
223 0 : run(copy(STDOUT,STDERR));
224 :
225 : } else {
226 1 : run(concat_strings(STDOUT,STDERR));
227 : }
228 :
229 : // Clear STDERR to avoid warning reports
230 1 : del(STDERR);
231 :
232 : } else {
233 : // Suppress the output from the STDERR buffer if needed
234 1 : if(suppress_stderr == true)
235 : {
236 : // Suppress the output from the STDERR buffer
237 1 : del(STDERR);
238 :
239 : } else {
240 : // Format stderr output
241 : char *str;
242 0 : const char *stderr_view = getcstring(STDERR);
243 0 : int rt = asprintf(&str, \
244 : YELLOW "Warning! STDERR buffer is not empty!\n"
245 : "External command call:\n" YELLOW ">>" RESET "%s" YELLOW "<<" RESET "\n"
246 : "Stderr output:\n" YELLOW ">>" RESET "%s" YELLOW "<<" RESET "\n",
247 : command,stderr_view);
248 :
249 0 : if(rt > -1)
250 : {
251 : // Copy str into STDERR buffer
252 0 : run(resize(STDERR,(size_t)rt + 1));
253 :
254 0 : run(copy_literal(STDERR,str));
255 :
256 : } else {
257 0 : report("Memory allocation failed, requested size: %zu bytes",(size_t)rt + 1);
258 : }
259 :
260 0 : free(str);
261 :
262 0 : return(FAILURE);
263 : }
264 : }
265 : }
266 :
267 285 : if(STDOUT->length > 0)
268 : {
269 : // Suppress the output from the STDOUT buffer if needed
270 94 : if(suppress_stdout == true)
271 : {
272 : // Suppress the output from the STDOUT buffer
273 0 : del(STDOUT);
274 :
275 : }
276 : }
277 :
278 : // Check the exit status of the child process
279 285 : if(expected_return_code != exit_code)
280 : {
281 : // Format stderr output
282 : char *str;
283 0 : const char *stderr_view = getcstring(STDERR);
284 0 : const char *stdout_view = getcstring(STDOUT);
285 0 : int rt = asprintf(&str,YELLOW "ERROR: Unexpected exit code!" RESET "\n"
286 : YELLOW "External command call:\n" YELLOW ">>" RESET "%s" YELLOW "<<" RESET "\n"
287 : YELLOW "Exited with code " RESET "%d " YELLOW "but expected " RESET "%d\n"
288 : YELLOW "Process terminated signal" RESET " %d\n"
289 : YELLOW "Stderr output:\n>>" RESET "%s" YELLOW "<<" RESET "\n"
290 : YELLOW "Stdout output:\n>>" RESET "%s" YELLOW "<<" RESET "\n",
291 : command,
292 : exit_code,
293 : expected_return_code,
294 : WTERMSIG(return_code),
295 : stderr_view,
296 : stdout_view);
297 :
298 0 : if(rt > -1)
299 : {
300 : // Copy str into STDERR buffer
301 0 : run(resize(STDERR,(size_t)rt + 1));
302 :
303 0 : run(copy_literal(STDERR,str));
304 :
305 : } else {
306 0 : report("Memory allocation failed, requested size: %zu bytes",(size_t)rt + 1);
307 : }
308 :
309 0 : free(str);
310 :
311 0 : return(FAILURE);
312 : }
313 :
314 285 : return(SUCCESS);
315 : }
|