1 /***********************************************************************************************************************************
2 Execute Process
3 ***********************************************************************************************************************************/
4 #include "build.auto.h"
5
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <sys/types.h>
10 #include <sys/wait.h>
11 #include <unistd.h>
12
13 #include "common/debug.h"
14 #include "common/log.h"
15 #include "common/exec.h"
16 #include "common/fork.h"
17 #include "common/io/fdRead.h"
18 #include "common/io/fdWrite.h"
19 #include "common/io/io.h"
20 #include "common/io/read.h"
21 #include "common/io/write.h"
22 #include "common/wait.h"
23
24 /***********************************************************************************************************************************
25 Object type
26 ***********************************************************************************************************************************/
27 struct Exec
28 {
29 ExecPub pub; // Publicly accessible variables
30 String *command; // Command to execute
31 StringList *param; // List of parameters to pass to command
32 const String *name; // Name to display in log/error messages
33 TimeMSec timeout; // Timeout for any i/o operation (read, write, etc.)
34
35 pid_t processId; // Process id of the child process
36
37 int fdRead; // Read file descriptor
38 int fdWrite; // Write file descriptor
39 int fdError; // Error file descriptor
40
41 IoRead *ioReadFd; // File descriptor read interface
42 IoWrite *ioWriteFd; // File descriptor write interface
43 };
44
45 /***********************************************************************************************************************************
46 Macro to close file descriptors after dup2() in the child process
47
48 If the parent process is daemomized and has closed stdout, stdin, and stderr or some combination of them, then the newly created
49 descriptors might overlap stdout, stdin, or stderr. In that case we don't want to accidentally close the descriptor that we have
50 just copied.
51
52 Note that this is pretty specific to the way that file descriptors are handled in this module and may not be generally applicable in
53 other code.
54 ***********************************************************************************************************************************/
55 #define PIPE_DUP2(pipe, pipeIdx, fd) \
56 do \
57 { \
58 dup2(pipe[pipeIdx], fd); \
59 \
60 if (pipe[0] != fd) \
61 close(pipe[0]); \
62 \
63 if (pipe[1] != fd) \
64 close(pipe[1]); \
65 } \
66 while (0);
67
68 /***********************************************************************************************************************************
69 Free exec file descriptors and ensure process is shut down
70 ***********************************************************************************************************************************/
71 static void
execFreeResource(THIS_VOID)72 execFreeResource(THIS_VOID)
73 {
74 THIS(Exec);
75
76 FUNCTION_LOG_BEGIN(logLevelTrace);
77 FUNCTION_LOG_PARAM(EXEC, this);
78 FUNCTION_LOG_END();
79
80 ASSERT(this != NULL);
81
82 // Close file descriptors
83 close(this->fdRead);
84 close(this->fdWrite);
85 close(this->fdError);
86
87 // Wait for the child to exit. We don't really care how it exits as long as it does.
88 if (this->processId != 0)
89 {
90 MEM_CONTEXT_TEMP_BEGIN()
91 {
92 int processResult = 0;
93 Wait *wait = waitNew(this->timeout);
94
95 do
96 {
97 THROW_ON_SYS_ERROR(
98 (processResult = waitpid(this->processId, NULL, WNOHANG)) == -1, ExecuteError,
99 "unable to wait on child process");
100 }
101 while (processResult == 0 && waitMore(wait));
102
103 // If the process did not exit then error -- else we may end up with a collection of zombie processes
104 if (processResult == 0)
105 THROW_FMT(ExecuteError, "%s did not exit when expected", strZ(this->name));
106 }
107 MEM_CONTEXT_TEMP_END();
108 }
109
110 FUNCTION_LOG_RETURN_VOID();
111 }
112
113 /**********************************************************************************************************************************/
114 Exec *
execNew(const String * command,const StringList * param,const String * name,TimeMSec timeout)115 execNew(const String *command, const StringList *param, const String *name, TimeMSec timeout)
116 {
117 FUNCTION_LOG_BEGIN(logLevelDebug)
118 FUNCTION_LOG_PARAM(STRING, command);
119 FUNCTION_LOG_PARAM(STRING_LIST, param);
120 FUNCTION_LOG_PARAM(STRING, name);
121 FUNCTION_LOG_PARAM(TIME_MSEC, timeout);
122 FUNCTION_LOG_END();
123
124 ASSERT(command != NULL);
125 ASSERT(name != NULL);
126 ASSERT(timeout > 0);
127
128 Exec *this = NULL;
129
130 MEM_CONTEXT_NEW_BEGIN("Exec")
131 {
132 this = memNew(sizeof(Exec));
133
134 *this = (Exec)
135 {
136 .pub =
137 {
138 .memContext = MEM_CONTEXT_NEW(),
139 },
140 .command = strDup(command),
141 .name = strDup(name),
142 .timeout = timeout,
143
144 // Parameter list is optional but if not specified we need to build one with the command
145 .param = param == NULL ? strLstNew() : strLstDup(param),
146 };
147
148 // The first parameter must be the command
149 strLstInsert(this->param, 0, this->command);
150 }
151 MEM_CONTEXT_NEW_END();
152
153 FUNCTION_LOG_RETURN(EXEC, this);
154 }
155
156 /***********************************************************************************************************************************
157 Check if the process is still running
158
159 This should be called when anything unexpected happens while reading or writing, including errors and eof. If this function returns
160 then the original error should be rethrown.
161 ***********************************************************************************************************************************/
162 static void
execCheck(Exec * this)163 execCheck(Exec *this)
164 {
165 FUNCTION_LOG_BEGIN(logLevelTrace);
166 FUNCTION_LOG_PARAM(EXEC, this);
167 FUNCTION_LOG_END();
168
169 ASSERT(this != NULL);
170
171 int processStatus;
172 int processResult;
173
174 THROW_ON_SYS_ERROR(
175 (processResult = waitpid(this->processId, &processStatus, WNOHANG)) == -1, ExecuteError, "unable to wait on child process");
176
177 if (processResult != 0)
178 {
179 // Clear the process id so we don't try to wait for this process on free
180 this->processId = 0;
181
182 // If the process exited normally
183 if (WIFEXITED(processStatus))
184 {
185 // Get data from stderr to help diagnose the problem
186 String *errorStr = strTrim(
187 strNewBuf(ioReadBuf(ioFdReadNewOpen(strNewFmt("%s error", strZ(this->name)), this->fdError, 0))));
188
189 // Throw the error with as much information as is available
190 THROWP_FMT(
191 errorTypeFromCode(WEXITSTATUS(processStatus)), "%s terminated unexpectedly [%d]%s%s", strZ(this->name),
192 WEXITSTATUS(processStatus), strSize(errorStr) > 0 ? ": " : "", strSize(errorStr) > 0 ? strZ(errorStr) : "");
193 }
194
195 // If the process did not exit normally then it must have been a signal
196 THROW_FMT(ExecuteError, "%s terminated unexpectedly on signal %d", strZ(this->name), WTERMSIG(processStatus));
197 }
198
199 FUNCTION_LOG_RETURN_VOID();
200 }
201
202 /***********************************************************************************************************************************
203 Read from the process
204 ***********************************************************************************************************************************/
205 static size_t
execRead(THIS_VOID,Buffer * buffer,bool block)206 execRead(THIS_VOID, Buffer *buffer, bool block)
207 {
208 THIS(Exec);
209
210 FUNCTION_LOG_BEGIN(logLevelTrace);
211 FUNCTION_LOG_PARAM(EXEC, this);
212 FUNCTION_LOG_PARAM(BUFFER, buffer);
213 FUNCTION_LOG_PARAM(BOOL, block);
214 FUNCTION_LOG_END();
215
216 ASSERT(this != NULL);
217 ASSERT(buffer != NULL);
218
219 size_t result = 0;
220
221 TRY_BEGIN()
222 {
223 result = ioReadInterface(this->ioReadFd)->read(ioReadDriver(this->ioReadFd), buffer, block);
224 }
225 CATCH_ANY()
226 {
227 execCheck(this);
228 RETHROW();
229 }
230 TRY_END();
231
232 FUNCTION_LOG_RETURN(SIZE, result);
233 }
234
235 /***********************************************************************************************************************************
236 Write to the process
237 ***********************************************************************************************************************************/
238 static void
execWrite(THIS_VOID,const Buffer * buffer)239 execWrite(THIS_VOID, const Buffer *buffer)
240 {
241 THIS(Exec);
242
243 FUNCTION_LOG_BEGIN(logLevelTrace);
244 FUNCTION_LOG_PARAM(EXEC, this);
245 FUNCTION_LOG_PARAM(BUFFER, buffer);
246 FUNCTION_LOG_END();
247
248 ASSERT(this != NULL);
249 ASSERT(buffer != NULL);
250
251 TRY_BEGIN()
252 {
253 ioWrite(this->ioWriteFd, buffer);
254 ioWriteFlush(this->ioWriteFd);
255 }
256 CATCH_ANY()
257 {
258 execCheck(this);
259 RETHROW();
260 }
261 TRY_END();
262
263 FUNCTION_LOG_RETURN_VOID();
264 }
265
266 /***********************************************************************************************************************************
267 Is the process eof?
268 ***********************************************************************************************************************************/
269 static bool
execEof(THIS_VOID)270 execEof(THIS_VOID)
271 {
272 THIS(Exec);
273
274 FUNCTION_LOG_BEGIN(logLevelTrace);
275 FUNCTION_LOG_PARAM(EXEC, this);
276 FUNCTION_LOG_END();
277
278 ASSERT(this != NULL);
279
280 // Check if the process is still running on eof
281 if (ioReadInterface(this->ioReadFd)->eof(ioReadDriver(this->ioReadFd)))
282 execCheck(this);
283
284 FUNCTION_LOG_RETURN(BOOL, false);
285 }
286
287 /***********************************************************************************************************************************
288 Get the read file descriptor
289 ***********************************************************************************************************************************/
290 static int
execFdRead(const THIS_VOID)291 execFdRead(const THIS_VOID)
292 {
293 THIS(const Exec);
294
295 FUNCTION_TEST_BEGIN();
296 FUNCTION_TEST_PARAM(EXEC, this);
297 FUNCTION_TEST_END();
298
299 ASSERT(this != NULL);
300
301 FUNCTION_TEST_RETURN(this->fdRead);
302 }
303
304 /**********************************************************************************************************************************/
305 void
execOpen(Exec * this)306 execOpen(Exec *this)
307 {
308 FUNCTION_LOG_BEGIN(logLevelDebug)
309 FUNCTION_LOG_PARAM(EXEC, this);
310 FUNCTION_LOG_END();
311
312 ASSERT(this != NULL);
313
314 // Create pipes to communicate with the subprocess. The names of the pipes are from the perspective of the parent process since
315 // the child process will use them only briefly before exec'ing.
316 int pipeRead[2];
317 int pipeWrite[2];
318 int pipeError[2];
319
320 THROW_ON_SYS_ERROR(pipe(pipeRead) == -1, KernelError, "unable to create read pipe");
321 THROW_ON_SYS_ERROR(pipe(pipeWrite) == -1, KernelError, "unable to create write pipe");
322 THROW_ON_SYS_ERROR(pipe(pipeError) == -1, KernelError, "unable to create error pipe");
323
324 // Fork the subprocess
325 this->processId = forkSafe();
326
327 // Exec command in the child process
328 if (this->processId == 0)
329 {
330 // Disable logging and close log file
331 logClose();
332
333 // Assign stdout to the input side of the read pipe
334 PIPE_DUP2(pipeRead, 1, STDOUT_FILENO);
335
336 // Assign stdin to the output side of the write pipe
337 PIPE_DUP2(pipeWrite, 0, STDIN_FILENO);
338
339 // Assign stderr to the input side of the error pipe
340 PIPE_DUP2(pipeError, 1, STDERR_FILENO);
341
342 // Execute the binary. This statement will not return if it is successful
343 execvp(strZ(this->command), (char ** const)strLstPtr(this->param));
344
345 // If we got here then there was an error. We can't use a throw as we normally would because we have already shutdown
346 // logging and we don't want to execute exit paths that might free parent resources which we still have references to.
347 fprintf(stderr, "unable to execute '%s': [%d] %s\n", strZ(this->command), errno, strerror(errno));
348 exit(errorTypeCode(&ExecuteError));
349 }
350
351 // Close the unused file descriptors
352 close(pipeRead[1]);
353 close(pipeWrite[0]);
354 close(pipeError[1]);
355
356 // Store the file descriptors we'll use and need to close when the process terminates
357 this->fdRead = pipeRead[0];
358 this->fdWrite = pipeWrite[1];
359 this->fdError = pipeError[0];
360
361 // Assign file descriptors to io interfaces
362 this->ioReadFd = ioFdReadNew(strNewFmt("%s read", strZ(this->name)), this->fdRead, this->timeout);
363 this->ioWriteFd = ioFdWriteNewOpen(strNewFmt("%s write", strZ(this->name)), this->fdWrite, this->timeout);
364
365 // Create wrapper interfaces that check process state
366 this->pub.ioReadExec = ioReadNewP(this, .block = true, .read = execRead, .eof = execEof, .fd = execFdRead);
367 ioReadOpen(execIoRead(this));
368 this->pub.ioWriteExec = ioWriteNewP(this, .write = execWrite);
369 ioWriteOpen(execIoWrite(this));
370
371 // Set a callback so the file descriptors will get freed
372 memContextCallbackSet(execMemContext(this), execFreeResource, this);
373
374 FUNCTION_LOG_RETURN_VOID();
375 }
376