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