]> git.proxmox.com Git - ceph.git/blame - ceph/src/common/SubProcess.h
update sources to v12.1.0
[ceph.git] / ceph / src / common / SubProcess.h
CommitLineData
7c673cae
FG
1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2// vim: ts=8 sw=2 smarttab
3/*
4 * Ceph distributed storage system
5 *
6 * Copyright (C) 2015 Mirantis Inc
7 *
8 * Author: Mykola Golub <mgolub@mirantis.com>
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14 *
15 */
16
17#ifndef SUB_PROCESS_H
18#define SUB_PROCESS_H
19
7c673cae 20#include <sys/wait.h>
7c673cae 21#include <stdarg.h>
7c673cae
FG
22#include <sstream>
23#include <vector>
24#include <iostream>
7c673cae
FG
25#include <include/assert.h>
26#include <common/errno.h>
31f18b77
FG
27#if defined(__FreeBSD__)
28#include <sys/types.h>
29#include <signal.h>
30#endif
7c673cae
FG
31
32/**
33 * SubProcess:
34 * A helper class to spawn a subprocess.
35 *
36 * Example:
37 *
38 * SubProcess cat("cat", SubProcess::PIPE, SubProcess::PIPE);
39 * if (cat.spawn() != 0) {
40 * std::cerr << "cat failed: " << cat.err() << std::endl;
41 * return false;
42 * }
43 * write_to_fd(cat.get_stdout(), "hello world!\n");
44 * cat.close_stdout();
45 * read_from_fd(cat.get_stdin(), buf);
46 * if (cat.join() != 0) {
47 * std::cerr << cat.err() << std::endl;
48 * return false;
49 * }
50 */
51
52class SubProcess {
53public:
54 enum std_fd_op{
55 KEEP,
56 CLOSE,
57 PIPE
58 };
59public:
60 SubProcess(const char *cmd,
61 std_fd_op stdin_op = CLOSE,
62 std_fd_op stdout_op = CLOSE,
63 std_fd_op stderr_op = CLOSE);
64 virtual ~SubProcess();
65
66 void add_cmd_args(const char *arg, ...);
67 void add_cmd_arg(const char *arg);
68
69 virtual int spawn(); // Returns 0 on success or -errno on failure.
70 virtual int join(); // Returns exit code (0 on success).
71
72 bool is_spawned() const { return pid > 0; }
73
74 int get_stdin() const;
75 int get_stdout() const;
76 int get_stderr() const;
77
78 void close_stdin();
79 void close_stdout();
80 void close_stderr();
81
82 void kill(int signo = SIGTERM) const;
83
84 const std::string err() const;
85
86protected:
87 bool is_child() const { return pid == 0; }
88 virtual void exec();
89
90private:
91 void close(int &fd);
92
93protected:
94 std::string cmd;
95 std::vector<std::string> cmd_args;
96 std_fd_op stdin_op;
97 std_fd_op stdout_op;
98 std_fd_op stderr_op;
99 int stdin_pipe_out_fd;
100 int stdout_pipe_in_fd;
101 int stderr_pipe_in_fd;
102 int pid;
103 std::ostringstream errstr;
104};
105
106class SubProcessTimed : public SubProcess {
107public:
108 SubProcessTimed(const char *cmd, std_fd_op stdin_op = CLOSE,
109 std_fd_op stdout_op = CLOSE, std_fd_op stderr_op = CLOSE,
110 int timeout = 0, int sigkill = SIGKILL);
111
112protected:
113 void exec() override;
114
115private:
116 int timeout;
117 int sigkill;
118};
119
120inline SubProcess::SubProcess(const char *cmd_, std_fd_op stdin_op_, std_fd_op stdout_op_, std_fd_op stderr_op_) :
121 cmd(cmd_),
122 cmd_args(),
123 stdin_op(stdin_op_),
124 stdout_op(stdout_op_),
125 stderr_op(stderr_op_),
126 stdin_pipe_out_fd(-1),
127 stdout_pipe_in_fd(-1),
128 stderr_pipe_in_fd(-1),
129 pid(-1),
130 errstr() {
131}
132
133inline SubProcess::~SubProcess() {
134 assert(!is_spawned());
135 assert(stdin_pipe_out_fd == -1);
136 assert(stdout_pipe_in_fd == -1);
137 assert(stderr_pipe_in_fd == -1);
138}
139
140inline void SubProcess::add_cmd_args(const char *arg, ...) {
141 assert(!is_spawned());
142
143 va_list ap;
144 va_start(ap, arg);
145 const char *p = arg;
146 do {
147 add_cmd_arg(p);
148 p = va_arg(ap, const char*);
149 } while (p != NULL);
150 va_end(ap);
151}
152
153inline void SubProcess::add_cmd_arg(const char *arg) {
154 assert(!is_spawned());
155
156 cmd_args.push_back(arg);
157}
158
159inline int SubProcess::get_stdin() const {
160 assert(is_spawned());
161 assert(stdin_op == PIPE);
162
163 return stdin_pipe_out_fd;
164}
165
166inline int SubProcess::get_stdout() const {
167 assert(is_spawned());
168 assert(stdout_op == PIPE);
169
170 return stdout_pipe_in_fd;
171}
172
173inline int SubProcess::get_stderr() const {
174 assert(is_spawned());
175 assert(stderr_op == PIPE);
176
177 return stderr_pipe_in_fd;
178}
179
180inline void SubProcess::close(int &fd) {
181 if (fd == -1)
182 return;
183
184 ::close(fd);
185 fd = -1;
186}
187
188inline void SubProcess::close_stdin() {
189 assert(is_spawned());
190 assert(stdin_op == PIPE);
191
192 close(stdin_pipe_out_fd);
193}
194
195inline void SubProcess::close_stdout() {
196 assert(is_spawned());
197 assert(stdout_op == PIPE);
198
199 close(stdout_pipe_in_fd);
200}
201
202inline void SubProcess::close_stderr() {
203 assert(is_spawned());
204 assert(stderr_op == PIPE);
205
206 close(stderr_pipe_in_fd);
207}
208
209inline void SubProcess::kill(int signo) const {
210 assert(is_spawned());
211
212 int ret = ::kill(pid, signo);
213 assert(ret == 0);
214}
215
216inline const std::string SubProcess::err() const {
217 return errstr.str();
218}
219
220class fd_buf : public std::streambuf {
221 int fd;
222public:
223 fd_buf (int fd) : fd(fd)
224 {}
225protected:
226 int_type overflow (int_type c) override {
227 if (c == EOF) return EOF;
228 char buf = c;
229 if (write (fd, &buf, 1) != 1) {
230 return EOF;
231 }
232 return c;
233 }
234 std::streamsize xsputn (const char* s, std::streamsize count) override {
235 return write(fd, s, count);
236 }
237};
238
239inline int SubProcess::spawn() {
240 assert(!is_spawned());
241 assert(stdin_pipe_out_fd == -1);
242 assert(stdout_pipe_in_fd == -1);
243 assert(stderr_pipe_in_fd == -1);
244
245 enum { IN = 0, OUT = 1 };
246
247 int ipipe[2], opipe[2], epipe[2];
248
249 ipipe[0] = ipipe[1] = opipe[0] = opipe[1] = epipe[0] = epipe[1] = -1;
250
251 int ret = 0;
252
253 if ((stdin_op == PIPE && ::pipe(ipipe) == -1) ||
254 (stdout_op == PIPE && ::pipe(opipe) == -1) ||
255 (stderr_op == PIPE && ::pipe(epipe) == -1)) {
256 ret = -errno;
257 errstr << "pipe failed: " << cpp_strerror(errno);
258 goto fail;
259 }
260
261 pid = fork();
262
263 if (pid > 0) { // Parent
264 stdin_pipe_out_fd = ipipe[OUT]; close(ipipe[IN ]);
265 stdout_pipe_in_fd = opipe[IN ]; close(opipe[OUT]);
266 stderr_pipe_in_fd = epipe[IN ]; close(epipe[OUT]);
267 return 0;
268 }
269
270 if (pid == 0) { // Child
271 close(ipipe[OUT]);
272 close(opipe[IN ]);
273 close(epipe[IN ]);
274
275 if (ipipe[IN] != -1 && ipipe[IN] != STDIN_FILENO) {
276 ::dup2(ipipe[IN], STDIN_FILENO);
277 close(ipipe[IN]);
278 }
279 if (opipe[OUT] != -1 && opipe[OUT] != STDOUT_FILENO) {
280 ::dup2(opipe[OUT], STDOUT_FILENO);
281 close(opipe[OUT]);
282 static fd_buf buf(STDOUT_FILENO);
283 std::cout.rdbuf(&buf);
284 }
285 if (epipe[OUT] != -1 && epipe[OUT] != STDERR_FILENO) {
286 ::dup2(epipe[OUT], STDERR_FILENO);
287 close(epipe[OUT]);
288 static fd_buf buf(STDERR_FILENO);
289 std::cerr.rdbuf(&buf);
290 }
291
292 int maxfd = sysconf(_SC_OPEN_MAX);
293 if (maxfd == -1)
294 maxfd = 16384;
295 for (int fd = 0; fd <= maxfd; fd++) {
296 if (fd == STDIN_FILENO && stdin_op != CLOSE)
297 continue;
298 if (fd == STDOUT_FILENO && stdout_op != CLOSE)
299 continue;
300 if (fd == STDERR_FILENO && stderr_op != CLOSE)
301 continue;
302 ::close(fd);
303 }
304
305 exec();
306 ceph_abort(); // Never reached
307 }
308
309 ret = -errno;
310 errstr << "fork failed: " << cpp_strerror(errno);
311
312fail:
313 close(ipipe[0]);
314 close(ipipe[1]);
315 close(opipe[0]);
316 close(opipe[1]);
317 close(epipe[0]);
318 close(epipe[1]);
319
320 return ret;
321}
322
323inline void SubProcess::exec() {
324 assert(is_child());
325
326 std::vector<const char *> args;
327 args.push_back(cmd.c_str());
328 for (std::vector<std::string>::iterator i = cmd_args.begin();
329 i != cmd_args.end();
330 i++) {
331 args.push_back(i->c_str());
332 }
333 args.push_back(NULL);
334
335 int ret = execvp(cmd.c_str(), (char * const *)&args[0]);
336 assert(ret == -1);
337
338 std::cerr << cmd << ": exec failed: " << cpp_strerror(errno) << "\n";
339 _exit(EXIT_FAILURE);
340}
341
342inline int SubProcess::join() {
343 assert(is_spawned());
344
345 close(stdin_pipe_out_fd);
346 close(stdout_pipe_in_fd);
347 close(stderr_pipe_in_fd);
348
349 int status;
350
351 while (waitpid(pid, &status, 0) == -1)
352 assert(errno == EINTR);
353
354 pid = -1;
355
356 if (WIFEXITED(status)) {
357 if (WEXITSTATUS(status) != EXIT_SUCCESS)
358 errstr << cmd << ": exit status: " << WEXITSTATUS(status);
359 return WEXITSTATUS(status);
360 }
361 if (WIFSIGNALED(status)) {
362 errstr << cmd << ": got signal: " << WTERMSIG(status);
363 return 128 + WTERMSIG(status);
364 }
365 errstr << cmd << ": waitpid: unknown status returned\n";
366 return EXIT_FAILURE;
367}
368
369inline SubProcessTimed::SubProcessTimed(const char *cmd, std_fd_op stdin_op,
370 std_fd_op stdout_op, std_fd_op stderr_op,
371 int timeout_, int sigkill_) :
372 SubProcess(cmd, stdin_op, stdout_op, stderr_op),
373 timeout(timeout_),
374 sigkill(sigkill_) {
375}
376
377static bool timedout = false; // only used after fork
378static void timeout_sighandler(int sig) {
379 timedout = true;
380}
381static void dummy_sighandler(int sig) {}
382
383inline void SubProcessTimed::exec() {
384 assert(is_child());
385
386 if (timeout <= 0) {
387 SubProcess::exec();
388 ceph_abort(); // Never reached
389 }
390
391 sigset_t mask, oldmask;
392 int pid;
393
394 // Restore default action for SIGTERM in case the parent process decided
395 // to ignore it.
396 if (signal(SIGTERM, SIG_DFL) == SIG_ERR) {
397 std::cerr << cmd << ": signal failed: " << cpp_strerror(errno) << "\n";
398 goto fail_exit;
399 }
400 // Because SIGCHLD is ignored by default, setup dummy handler for it,
401 // so we can mask it.
402 if (signal(SIGCHLD, dummy_sighandler) == SIG_ERR) {
403 std::cerr << cmd << ": signal failed: " << cpp_strerror(errno) << "\n";
404 goto fail_exit;
405 }
406 // Setup timeout handler.
407 if (signal(SIGALRM, timeout_sighandler) == SIG_ERR) {
408 std::cerr << cmd << ": signal failed: " << cpp_strerror(errno) << "\n";
409 goto fail_exit;
410 }
411 // Block interesting signals.
412 sigemptyset(&mask);
413 sigaddset(&mask, SIGINT);
414 sigaddset(&mask, SIGTERM);
415 sigaddset(&mask, SIGCHLD);
416 sigaddset(&mask, SIGALRM);
417 if (sigprocmask(SIG_SETMASK, &mask, &oldmask) == -1) {
418 std::cerr << cmd << ": sigprocmask failed: " << cpp_strerror(errno) << "\n";
419 goto fail_exit;
420 }
421
422 pid = fork();
423
424 if (pid == -1) {
425 std::cerr << cmd << ": fork failed: " << cpp_strerror(errno) << "\n";
426 goto fail_exit;
427 }
428
429 if (pid == 0) { // Child
430 // Restore old sigmask.
431 if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) {
432 std::cerr << cmd << ": sigprocmask failed: " << cpp_strerror(errno) << "\n";
433 goto fail_exit;
434 }
435 (void)setpgid(0, 0); // Become process group leader.
436 SubProcess::exec();
437 ceph_abort(); // Never reached
438 }
439
440 // Parent
441 (void)alarm(timeout);
442
443 for (;;) {
444 int signo;
445 if (sigwait(&mask, &signo) == -1) {
446 std::cerr << cmd << ": sigwait failed: " << cpp_strerror(errno) << "\n";
447 goto fail_exit;
448 }
449 switch (signo) {
450 case SIGCHLD:
451 int status;
452 if (waitpid(pid, &status, WNOHANG) == -1) {
453 std::cerr << cmd << ": waitpid failed: " << cpp_strerror(errno) << "\n";
454 goto fail_exit;
455 }
456 if (WIFEXITED(status))
457 _exit(WEXITSTATUS(status));
458 if (WIFSIGNALED(status))
459 _exit(128 + WTERMSIG(status));
460 std::cerr << cmd << ": unknown status returned\n";
461 goto fail_exit;
462 case SIGINT:
463 case SIGTERM:
464 // Pass SIGINT and SIGTERM, which are usually used to terminate
465 // a process, to the child.
466 if (::kill(pid, signo) == -1) {
467 std::cerr << cmd << ": kill failed: " << cpp_strerror(errno) << "\n";
468 goto fail_exit;
469 }
470 continue;
471 case SIGALRM:
472 std::cerr << cmd << ": timed out (" << timeout << " sec)\n";
473 if (::killpg(pid, sigkill) == -1) {
474 std::cerr << cmd << ": kill failed: " << cpp_strerror(errno) << "\n";
475 goto fail_exit;
476 }
477 continue;
478 default:
479 std::cerr << cmd << ": sigwait: invalid signal: " << signo << "\n";
480 goto fail_exit;
481 }
482 }
483
484fail_exit:
485 _exit(EXIT_FAILURE);
486}
487
488#endif