1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2013-2020 Couchbase, Inc.
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 
18 #include "procutil.h"
19 #include <stdlib.h>
20 #include <stdio.h>
21 
22 #ifndef _WIN32
23 #include <string.h>
24 #include <wordexp.h>
25 #include <fcntl.h>  /* O_* */
26 #include <unistd.h> /* usleep */
27 #include <signal.h> /* kill */
28 #include <time.h>
29 #include <errno.h>    /* ESRCH */
30 #include <sys/wait.h> /* waitpid */
31 
clisplit(const char *s)32 static char **clisplit(const char *s)
33 {
34     wordexp_t p;
35     int rv;
36     char **ret;
37     unsigned int ii;
38 
39     memset(&p, 0, sizeof(p));
40     rv = wordexp(s, &p, WRDE_NOCMD | WRDE_SHOWERR);
41     if (rv != 0) {
42         return NULL;
43     }
44     ret = malloc(sizeof(char *) * (p.we_wordc + 1));
45     if (!ret) {
46         return NULL;
47     }
48     for (ii = 0; ii < p.we_wordc; ii++) {
49         ret[ii] = strdup(p.we_wordv[ii]);
50     }
51     ret[ii] = NULL;
52     wordfree(&p);
53     return ret;
54 }
55 
spawn_process_impl(child_process_t *proc)56 static int spawn_process_impl(child_process_t *proc)
57 {
58     int rv;
59     char **argv;
60 
61     argv = clisplit(proc->name);
62     if (argv == NULL) {
63         fprintf(stderr, "Couldn't split arguments: %s\n", proc->name);
64         return -1;
65     }
66     proc->pid = vfork();
67     if (proc->pid < 0) {
68         for (int i = 0; argv[i] != NULL; ++i) {
69             free(argv[i]);
70         }
71         free(argv);
72         return -1;
73     }
74     if (proc->pid == 0) {
75         /** In Child */
76         if (proc->redirect) {
77             int fd = open(proc->redirect, O_RDWR | O_CREAT | O_APPEND, 0644);
78             if (fd < 0) {
79                 perror(proc->redirect);
80                 exit(EXIT_FAILURE);
81             }
82             if (dup2(fd, fileno(stderr)) < 0 || dup2(fd, fileno(stdout)) < 0) {
83                 perror("dup2");
84                 exit(EXIT_FAILURE);
85             }
86             setvbuf(stderr, NULL, _IOLBF, 0);
87         }
88         rv = execvp(argv[0], argv);
89         if (rv < 0) {
90             perror(argv[0]);
91             return -1;
92         }
93     }
94     for (int i = 0; argv[i] != NULL; ++i) {
95         free(argv[i]);
96     }
97     free(argv);
98     return 0;
99 }
100 
kill_process(child_process_t *process, int force)101 void kill_process(child_process_t *process, int force)
102 {
103     int signum = SIGTERM;
104     if (-1 == kill(process->pid, signum)) {
105         if (errno != ESRCH && force) {
106             kill(process->pid, SIGKILL);
107         }
108     }
109 }
110 
wait_process(child_process_t *process, int tmosec)111 int wait_process(child_process_t *process, int tmosec)
112 {
113     int ec, flags = 0;
114     time_t now, tmostamp;
115 
116     if (process->exited) {
117         return 0;
118     }
119     if (tmosec < 0 || tmosec > 0) {
120         flags |= WNOHANG;
121     }
122     now = time(NULL);
123 
124     /** Probably better logic for this */
125     if (tmosec <= 0) {
126         tmostamp = 0;
127     } else {
128         tmostamp = now + tmosec;
129     }
130 
131     do {
132         pid_t pidrv = waitpid(process->pid, &ec, flags);
133 
134         if (pidrv > 0) {
135             if (WIFEXITED(ec)) {
136                 process->status = WEXITSTATUS(ec);
137                 process->exited = 1;
138 
139             } else if (WIFSIGNALED(ec)) {
140                 process->status = WTERMSIG(ec);
141                 process->exited = 1;
142 
143             } else if (WIFSTOPPED(ec) || WIFCONTINUED(ec)) {
144                 continue;
145 
146             } else {
147                 fprintf(stderr,
148                         "Waitpid returned pid with neither EXITED or "
149                         "SIGNALLED true. Assuming something else (0x%x)\n",
150                         ec);
151                 process->status = -1;
152                 process->exited = 1;
153             }
154 
155         } else if (pidrv == -1 && errno == ESRCH) {
156             fprintf(stderr, "Process has already terminated. waitpid(%d) == ESRCH\n", process->pid);
157 
158             process->exited = 1;
159         }
160 
161         if (process->exited) {
162             return 0;
163         }
164 
165         if (!tmostamp) {
166             break;
167         }
168         usleep(500);
169         now = time(NULL);
170     } while (now < tmostamp);
171 
172     return -1;
173 }
174 
cleanup_process(child_process_t *proc)175 void cleanup_process(child_process_t *proc)
176 {
177     /* nothing */
178     (void)proc;
179 }
180 
181 #else
182 /** Windows */
spawn_process_impl(child_process_t *proc)183 static int spawn_process_impl(child_process_t *proc)
184 {
185     BOOL success;
186 
187     if (proc->redirect) {
188         HANDLE out = NULL;
189         HANDLE err = NULL;
190         SECURITY_ATTRIBUTES attrs;
191 
192         memset(&attrs, 0, sizeof(attrs));
193         attrs.nLength = sizeof(attrs);
194         attrs.bInheritHandle = TRUE;
195         out = CreateFile(proc->redirect, FILE_APPEND_DATA, FILE_SHARE_WRITE | FILE_SHARE_READ, &attrs, OPEN_ALWAYS,
196                          FILE_ATTRIBUTE_NORMAL, NULL);
197         if (out == INVALID_HANDLE_VALUE) {
198             fprintf(stderr, "Couldn't open '%s'. %d\n", proc->redirect, (int)GetLastError());
199             return -1;
200         }
201         if (!DuplicateHandle(GetCurrentProcess(), out, GetCurrentProcess(), &err, 0, TRUE, DUPLICATE_SAME_ACCESS)) {
202             fprintf(stderr, "Couldn't DuplicateHandle. %d\n", (int)GetLastError());
203             return -1;
204         }
205         proc->si.cb = sizeof(proc->si);
206         proc->si.hStdError = err;
207         proc->si.hStdOutput = out;
208         proc->si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
209         proc->si.dwFlags = STARTF_USESTDHANDLES;
210     }
211     success = CreateProcess(NULL,               /* name */
212                             (char *)proc->name, /* commandline */
213                             NULL,               /* process attributes */
214                             NULL,               /* security attributes */
215                             TRUE,               /* inherit handles */
216                             0,                  /* creation flags */
217                             NULL,               /* environment */
218                             NULL,               /* current directory */
219                             &proc->si,          /* STARTUPINFO */
220                             &proc->pi /* PROCESS_INFORMATION */);
221 
222     if (!success) {
223         fprintf(stderr, "Couldn't spawn '%s'. [%d]\n", proc->name, (int)GetLastError());
224         return -1;
225     }
226 
227     return 0;
228 }
229 
kill_process(child_process_t *process, int force)230 void kill_process(child_process_t *process, int force)
231 {
232     if (!force) {
233         return; /* nothing we can do here */
234     }
235     TerminateProcess(process->pi.hProcess, 0);
236 }
237 
wait_process(child_process_t *process, int tmosec)238 int wait_process(child_process_t *process, int tmosec)
239 {
240     DWORD millis, result;
241 
242     if (process->exited) {
243         return 0;
244     }
245     if (tmosec < 0) {
246         millis = 0;
247     } else if (tmosec == 0) {
248         millis = INFINITE;
249     } else {
250         millis = tmosec * 1000;
251     }
252     result = WaitForSingleObject(process->pi.hProcess, millis);
253     if (result != WAIT_OBJECT_0) {
254         if (result == WAIT_FAILED) {
255             fprintf(stderr, "Wait failed with code [%d]\n", (int)GetLastError());
256         }
257         return -1;
258     }
259     process->exited = 1;
260     if (!GetExitCodeProcess(process->pi.hProcess, &result)) {
261         fprintf(stderr, "GetExitCodeProcess: %d\n", (int)GetLastError());
262 
263     } else {
264         process->status = result;
265     }
266     return 0;
267 }
268 
cleanup_process(child_process_t *process)269 void cleanup_process(child_process_t *process)
270 {
271     CloseHandle(process->pi.hProcess);
272     CloseHandle(process->pi.hThread);
273     if (process->redirect) {
274         CloseHandle(process->si.hStdOutput);
275         CloseHandle(process->si.hStdError);
276     }
277 }
278 
279 #endif
280 
create_process(child_process_t *proc)281 int create_process(child_process_t *proc)
282 {
283     int rv;
284 
285     if (proc->interactive) {
286         rv = system(proc->name);
287         proc->status = rv;
288         proc->exited = 1;
289         return 0;
290     }
291     proc->status = -1;
292     return spawn_process_impl(proc);
293 }
294