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
32static 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
56static 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
101void 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
111int 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
175void cleanup_process(child_process_t *proc)
176{
177    /* nothing */
178    (void)proc;
179}
180
181#else
182/** Windows */
183static 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
230void 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
238int 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
269void 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
281int 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