1 | /*! |
---|
2 | |
---|
3 | Documentation for C++ subprocessing libraray. |
---|
4 | |
---|
5 | @copyright The code is licensed under the [MIT |
---|
6 | License](http://opensource.org/licenses/MIT): |
---|
7 | <br> |
---|
8 | Copyright © 2016-2018 Arun Muralidharan. |
---|
9 | <br> |
---|
10 | Permission is hereby granted, free of charge, to any person obtaining a copy |
---|
11 | of this software and associated documentation files (the "Software"), to deal |
---|
12 | in the Software without restriction, including without limitation the rights |
---|
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
---|
14 | copies of the Software, and to permit persons to whom the Software is |
---|
15 | furnished to do so, subject to the following conditions: |
---|
16 | <br> |
---|
17 | The above copyright notice and this permission notice shall be included in |
---|
18 | all copies or substantial portions of the Software. |
---|
19 | <br> |
---|
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
---|
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
---|
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
---|
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
---|
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
---|
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
---|
26 | SOFTWARE. |
---|
27 | |
---|
28 | @author [Arun Muralidharan] |
---|
29 | @see https://github.com/arun11299/cpp-subprocess to download the source code |
---|
30 | |
---|
31 | @version 1.0.0 |
---|
32 | */ |
---|
33 | |
---|
34 | #ifndef SUBPROCESS_HPP |
---|
35 | #define SUBPROCESS_HPP |
---|
36 | |
---|
37 | #ifdef __cplusplus |
---|
38 | |
---|
39 | #include <map> |
---|
40 | #include <algorithm> |
---|
41 | #include <iostream> |
---|
42 | #include <string> |
---|
43 | #include <cstdlib> |
---|
44 | #include <cassert> |
---|
45 | #include <cstring> |
---|
46 | #include <cstdio> |
---|
47 | #include <csignal> |
---|
48 | #include <future> |
---|
49 | #include <vector> |
---|
50 | #include <sstream> |
---|
51 | #include <memory> |
---|
52 | #include <initializer_list> |
---|
53 | #include <exception> |
---|
54 | |
---|
55 | extern "C" { |
---|
56 | #include <unistd.h> |
---|
57 | #include <fcntl.h> |
---|
58 | #include <sys/types.h> |
---|
59 | #include <sys/wait.h> |
---|
60 | #include <signal.h> |
---|
61 | } |
---|
62 | |
---|
63 | /*! |
---|
64 | * Getting started with reading this source code. |
---|
65 | * The source is mainly divided into four parts: |
---|
66 | * 1. Exception Classes: |
---|
67 | * These are very basic exception classes derived from |
---|
68 | * runtime_error exception. |
---|
69 | * There are two types of exception thrown from subprocess |
---|
70 | * library: OSError and CalledProcessError |
---|
71 | * |
---|
72 | * 2. Popen Class |
---|
73 | * This is the main class the users will deal with. It |
---|
74 | * provides with all the API's to deal with processes. |
---|
75 | * |
---|
76 | * 3. Util namespace |
---|
77 | * It includes some helper functions to split/join a string, |
---|
78 | * reading from file descriptors, waiting on a process, fcntl |
---|
79 | * options on file descriptors etc. |
---|
80 | * |
---|
81 | * 4. Detail namespace |
---|
82 | * This includes some metaprogramming and helper classes. |
---|
83 | */ |
---|
84 | |
---|
85 | |
---|
86 | namespace subprocess { |
---|
87 | |
---|
88 | // Max buffer size allocated on stack for read error |
---|
89 | // from pipe |
---|
90 | static const size_t SP_MAX_ERR_BUF_SIZ = 1024; |
---|
91 | |
---|
92 | // Default buffer capcity for OutBuffer and ErrBuffer. |
---|
93 | // If the data exceeds this capacity, the buffer size is grown |
---|
94 | // by 1.5 times its previous capacity |
---|
95 | static const size_t DEFAULT_BUF_CAP_BYTES = 8192; |
---|
96 | |
---|
97 | |
---|
98 | /*----------------------------------------------- |
---|
99 | * EXCEPTION CLASSES |
---|
100 | *----------------------------------------------- |
---|
101 | */ |
---|
102 | |
---|
103 | /*! |
---|
104 | * class: CalledProcessError |
---|
105 | * Thrown when there was error executing the command. |
---|
106 | * Check Popen class API's to know when this exception |
---|
107 | * can be thrown. |
---|
108 | * |
---|
109 | */ |
---|
110 | class CalledProcessError: public std::runtime_error |
---|
111 | { |
---|
112 | public: |
---|
113 | CalledProcessError(const std::string& error_msg): |
---|
114 | std::runtime_error(error_msg) |
---|
115 | {} |
---|
116 | }; |
---|
117 | |
---|
118 | |
---|
119 | /*! |
---|
120 | * class: OSError |
---|
121 | * Thrown when some system call fails to execute or give result. |
---|
122 | * The exception message contains the name of the failed system call |
---|
123 | * with the stringisized errno code. |
---|
124 | * Check Popen class API's to know when this exception would be |
---|
125 | * thrown. |
---|
126 | * Its usual that the API exception specification would have |
---|
127 | * this exception together with CalledProcessError. |
---|
128 | */ |
---|
129 | class OSError: public std::runtime_error |
---|
130 | { |
---|
131 | public: |
---|
132 | OSError(const std::string& err_msg, int err_code): |
---|
133 | std::runtime_error( err_msg + " : " + std::strerror(err_code) ) |
---|
134 | {} |
---|
135 | }; |
---|
136 | |
---|
137 | //-------------------------------------------------------------------- |
---|
138 | |
---|
139 | namespace util |
---|
140 | { |
---|
141 | |
---|
142 | /*! |
---|
143 | * Function: split |
---|
144 | * Parameters: |
---|
145 | * [in] str : Input string which needs to be split based upon the |
---|
146 | * delimiters provided. |
---|
147 | * [in] deleims : Delimiter characters based upon which the string needs |
---|
148 | * to be split. Default constructed to ' '(space) and '\t'(tab) |
---|
149 | * [out] vector<string> : Vector of strings split at deleimiter. |
---|
150 | */ |
---|
151 | static inline std::vector<std::string> |
---|
152 | split(const std::string& str, const std::string& delims=" \t") |
---|
153 | { |
---|
154 | std::vector<std::string> res; |
---|
155 | size_t init = 0; |
---|
156 | |
---|
157 | while (true) { |
---|
158 | auto pos = str.find_first_of(delims, init); |
---|
159 | if (pos == std::string::npos) { |
---|
160 | res.emplace_back(str.substr(init, str.length())); |
---|
161 | break; |
---|
162 | } |
---|
163 | res.emplace_back(str.substr(init, pos - init)); |
---|
164 | pos++; |
---|
165 | init = pos; |
---|
166 | } |
---|
167 | |
---|
168 | return res; |
---|
169 | } |
---|
170 | |
---|
171 | |
---|
172 | /*! |
---|
173 | * Function: join |
---|
174 | * Parameters: |
---|
175 | * [in] vec : Vector of strings which needs to be joined to form |
---|
176 | * a single string with words seperated by a seperator char. |
---|
177 | * [in] sep : String used to seperate 2 words in the joined string. |
---|
178 | * Default constructed to ' ' (space). |
---|
179 | * [out] string: Joined string. |
---|
180 | */ |
---|
181 | static inline |
---|
182 | std::string join(const std::vector<std::string>& vec, |
---|
183 | const std::string& sep = " ") |
---|
184 | { |
---|
185 | std::string res; |
---|
186 | for (auto& elem : vec) res.append(elem + sep); |
---|
187 | res.erase(--res.end()); |
---|
188 | return res; |
---|
189 | } |
---|
190 | |
---|
191 | |
---|
192 | /*! |
---|
193 | * Function: set_clo_on_exec |
---|
194 | * Sets/Resets the FD_CLOEXEC flag on the provided file descriptor |
---|
195 | * based upon the `set` parameter. |
---|
196 | * Parameters: |
---|
197 | * [in] fd : The descriptor on which FD_CLOEXEC needs to be set/reset. |
---|
198 | * [in] set : If 'true', set FD_CLOEXEC. |
---|
199 | * If 'false' unset FD_CLOEXEC. |
---|
200 | */ |
---|
201 | static inline |
---|
202 | void set_clo_on_exec(int fd, bool set = true) |
---|
203 | { |
---|
204 | int flags = fcntl(fd, F_GETFD, 0); |
---|
205 | if (set) flags |= FD_CLOEXEC; |
---|
206 | else flags &= ~FD_CLOEXEC; |
---|
207 | //TODO: should check for errors |
---|
208 | fcntl(fd, F_SETFD, flags); |
---|
209 | } |
---|
210 | |
---|
211 | |
---|
212 | /*! |
---|
213 | * Function: pipe_cloexec |
---|
214 | * Creates a pipe and sets FD_CLOEXEC flag on both |
---|
215 | * read and write descriptors of the pipe. |
---|
216 | * Parameters: |
---|
217 | * [out] : A pair of file descriptors. |
---|
218 | * First element of pair is the read descriptor of pipe. |
---|
219 | * Second element is the write descriptor of pipe. |
---|
220 | */ |
---|
221 | static inline |
---|
222 | std::pair<int, int> pipe_cloexec() throw (OSError) |
---|
223 | { |
---|
224 | int pipe_fds[2]; |
---|
225 | int res = pipe(pipe_fds); |
---|
226 | if (res) { |
---|
227 | throw OSError("pipe failure", errno); |
---|
228 | } |
---|
229 | |
---|
230 | set_clo_on_exec(pipe_fds[0]); |
---|
231 | set_clo_on_exec(pipe_fds[1]); |
---|
232 | |
---|
233 | return std::make_pair(pipe_fds[0], pipe_fds[1]); |
---|
234 | } |
---|
235 | |
---|
236 | |
---|
237 | /*! |
---|
238 | * Function: write_n |
---|
239 | * Writes `length` bytes to the file descriptor `fd` |
---|
240 | * from the buffer `buf`. |
---|
241 | * Parameters: |
---|
242 | * [in] fd : The file descriptotr to write to. |
---|
243 | * [in] buf: Buffer from which data needs to be written to fd. |
---|
244 | * [in] length: The number of bytes that needs to be written from |
---|
245 | * `buf` to `fd`. |
---|
246 | * [out] int : Number of bytes written or -1 in case of failure. |
---|
247 | */ |
---|
248 | static inline |
---|
249 | int write_n(int fd, const char* buf, size_t length) |
---|
250 | { |
---|
251 | size_t nwritten = 0; |
---|
252 | while (nwritten < length) { |
---|
253 | int written = write(fd, buf + nwritten, (int)length - nwritten); |
---|
254 | if (written == -1) return -1; |
---|
255 | nwritten += written; |
---|
256 | } |
---|
257 | return (int)nwritten; |
---|
258 | } |
---|
259 | |
---|
260 | |
---|
261 | /*! |
---|
262 | * Function: read_atmost_n |
---|
263 | * Reads at the most `read_upto` bytes from the |
---|
264 | * file descriptor `fd` before returning. |
---|
265 | * Parameters: |
---|
266 | * [in] fd : The file descriptor from which it needs to read. |
---|
267 | * [in] buf : The buffer into which it needs to write the data. |
---|
268 | * [in] read_upto: Max number of bytes which must be read from `fd`. |
---|
269 | * [out] int : Number of bytes written to `buf` or read from `fd` |
---|
270 | * OR -1 in case of error. |
---|
271 | * NOTE: In case of EINTR while reading from socket, this API |
---|
272 | * will retry to read from `fd`, but only till the EINTR counter |
---|
273 | * reaches 50 after which it will return with whatever data it read. |
---|
274 | */ |
---|
275 | static inline |
---|
276 | int read_atmost_n(int fd, char* buf, size_t read_upto) |
---|
277 | { |
---|
278 | int rbytes = 0; |
---|
279 | int eintr_cnter = 0; |
---|
280 | |
---|
281 | while (1) { |
---|
282 | int read_bytes = read(fd, buf + rbytes, (int)read_upto - rbytes); |
---|
283 | if (read_bytes == -1) { |
---|
284 | if (errno == EINTR) { |
---|
285 | if (eintr_cnter >= 50) return -1; |
---|
286 | eintr_cnter++; |
---|
287 | continue; |
---|
288 | } |
---|
289 | return -1; |
---|
290 | } |
---|
291 | if (read_bytes == 0) return rbytes; |
---|
292 | |
---|
293 | rbytes += read_bytes; |
---|
294 | } |
---|
295 | return rbytes; |
---|
296 | } |
---|
297 | |
---|
298 | |
---|
299 | /*! |
---|
300 | * Function: read_all |
---|
301 | * Reads all the available data from `fd` into |
---|
302 | * `buf`. Internally calls read_atmost_n. |
---|
303 | * Parameters: |
---|
304 | * [in] fd : The file descriptor from which to read from. |
---|
305 | * [in] buf : The buffer of type `class Buffer` into which |
---|
306 | * the read data is written to. |
---|
307 | * [out] int: Number of bytes read OR -1 in case of failure. |
---|
308 | * |
---|
309 | * NOTE: `class Buffer` is a exposed public class. See below. |
---|
310 | */ |
---|
311 | template <typename Buffer> |
---|
312 | // Requires Buffer to be of type class Buffer |
---|
313 | static inline int read_all(int fd, Buffer& buf) |
---|
314 | { |
---|
315 | size_t orig_size = buf.size(); |
---|
316 | int increment = (int)orig_size; |
---|
317 | auto buffer = buf.data(); |
---|
318 | int total_bytes_read = 0; |
---|
319 | |
---|
320 | while (1) { |
---|
321 | int rd_bytes = read_atmost_n(fd, buffer, buf.size()); |
---|
322 | if (rd_bytes == increment) { |
---|
323 | // Resize the buffer to accomodate more |
---|
324 | orig_size = orig_size * 1.5; |
---|
325 | increment = (int)orig_size - (int)buf.size(); |
---|
326 | buf.resize(orig_size); |
---|
327 | //update the buffer pointer |
---|
328 | buffer = buf.data(); |
---|
329 | buffer += rd_bytes; |
---|
330 | total_bytes_read += rd_bytes; |
---|
331 | |
---|
332 | } else if (rd_bytes != -1) { |
---|
333 | total_bytes_read += rd_bytes; |
---|
334 | break; |
---|
335 | |
---|
336 | } else { |
---|
337 | if (total_bytes_read == 0) return -1; |
---|
338 | break; |
---|
339 | } |
---|
340 | } |
---|
341 | return total_bytes_read; |
---|
342 | } |
---|
343 | |
---|
344 | |
---|
345 | /*! |
---|
346 | * Function: wait_for_child_exit |
---|
347 | * Waits for the process with pid `pid` to exit |
---|
348 | * and returns its status. |
---|
349 | * Parameters: |
---|
350 | * [in] pid : The pid of the process. |
---|
351 | * [out] pair<int, int>: |
---|
352 | * pair.first : Return code of the waitpid call. |
---|
353 | * pair.second : Exit status of the process. |
---|
354 | * |
---|
355 | * NOTE: This is a blocking call as in, it will loop |
---|
356 | * till the child is exited. |
---|
357 | */ |
---|
358 | static inline |
---|
359 | std::pair<int, int> wait_for_child_exit(int pid) |
---|
360 | { |
---|
361 | int status = 0; |
---|
362 | int ret = -1; |
---|
363 | while (1) { |
---|
364 | ret = waitpid(pid, &status, WNOHANG); |
---|
365 | if (ret == -1) break; |
---|
366 | if (ret == 0) continue; |
---|
367 | return std::make_pair(ret, status); |
---|
368 | } |
---|
369 | |
---|
370 | return std::make_pair(ret, status); |
---|
371 | } |
---|
372 | |
---|
373 | |
---|
374 | }; // end namespace util |
---|
375 | |
---|
376 | |
---|
377 | |
---|
378 | /* ------------------------------- |
---|
379 | * Popen Arguments |
---|
380 | * ------------------------------- |
---|
381 | */ |
---|
382 | |
---|
383 | /*! |
---|
384 | * The buffer size of the stdin/stdout/stderr |
---|
385 | * streams of the child process. |
---|
386 | * Default value is 0. |
---|
387 | */ |
---|
388 | struct bufsize { |
---|
389 | bufsize(int siz): bufsiz(siz) {} |
---|
390 | int bufsiz = 0; |
---|
391 | }; |
---|
392 | |
---|
393 | /*! |
---|
394 | * Option to defer spawning of the child process |
---|
395 | * till `Popen::start_process` API is called. |
---|
396 | * Default value is false. |
---|
397 | */ |
---|
398 | struct defer_spawn { |
---|
399 | defer_spawn(bool d): defer(d) {} |
---|
400 | bool defer = false; |
---|
401 | }; |
---|
402 | |
---|
403 | /*! |
---|
404 | * Option to close all file descriptors |
---|
405 | * when the child process is spawned. |
---|
406 | * The close fd list does not include |
---|
407 | * input/output/error if they are explicitly |
---|
408 | * set as part of the Popen arguments. |
---|
409 | * |
---|
410 | * Default value is false. |
---|
411 | */ |
---|
412 | struct close_fds { |
---|
413 | close_fds(bool c): close_all(c) {} |
---|
414 | bool close_all = false; |
---|
415 | }; |
---|
416 | |
---|
417 | /*! |
---|
418 | * Option to make the child process as the |
---|
419 | * session leader and thus the process |
---|
420 | * group leader. |
---|
421 | * Default value is false. |
---|
422 | */ |
---|
423 | struct session_leader { |
---|
424 | session_leader(bool sl): leader_(sl) {} |
---|
425 | bool leader_ = false; |
---|
426 | }; |
---|
427 | |
---|
428 | struct shell { |
---|
429 | shell(bool s): shell_(s) {} |
---|
430 | bool shell_ = false; |
---|
431 | }; |
---|
432 | |
---|
433 | /*! |
---|
434 | * Base class for all arguments involving string value. |
---|
435 | */ |
---|
436 | struct string_arg |
---|
437 | { |
---|
438 | string_arg(const char* arg): arg_value(arg) {} |
---|
439 | string_arg(std::string&& arg): arg_value(std::move(arg)) {} |
---|
440 | string_arg(std::string arg): arg_value(std::move(arg)) {} |
---|
441 | std::string arg_value; |
---|
442 | }; |
---|
443 | |
---|
444 | /*! |
---|
445 | * Option to specify the executable name seperately |
---|
446 | * from the args sequence. |
---|
447 | * In this case the cmd args must only contain the |
---|
448 | * options required for this executable. |
---|
449 | * |
---|
450 | * Eg: executable{"ls"} |
---|
451 | */ |
---|
452 | struct executable: string_arg |
---|
453 | { |
---|
454 | template <typename T> |
---|
455 | executable(T&& arg): string_arg(std::forward<T>(arg)) {} |
---|
456 | }; |
---|
457 | |
---|
458 | /*! |
---|
459 | * Option to set the current working directory |
---|
460 | * of the spawned process. |
---|
461 | * |
---|
462 | * Eg: cwd{"/som/path/x"} |
---|
463 | */ |
---|
464 | struct cwd: string_arg |
---|
465 | { |
---|
466 | template <typename T> |
---|
467 | cwd(T&& arg): string_arg(std::forward<T>(arg)) {} |
---|
468 | }; |
---|
469 | |
---|
470 | /*! |
---|
471 | * Option to specify environment variables required by |
---|
472 | * the spawned process. |
---|
473 | * |
---|
474 | * Eg: environment{{ {"K1", "V1"}, {"K2", "V2"},... }} |
---|
475 | */ |
---|
476 | struct environment |
---|
477 | { |
---|
478 | environment(std::map<std::string, std::string>&& env): |
---|
479 | env_(std::move(env)) {} |
---|
480 | environment(const std::map<std::string, std::string>& env): |
---|
481 | env_(env) {} |
---|
482 | std::map<std::string, std::string> env_; |
---|
483 | }; |
---|
484 | |
---|
485 | |
---|
486 | /*! |
---|
487 | * Used for redirecting input/output/error |
---|
488 | */ |
---|
489 | enum IOTYPE { |
---|
490 | STDOUT = 1, |
---|
491 | STDERR, |
---|
492 | PIPE, |
---|
493 | }; |
---|
494 | |
---|
495 | //TODO: A common base/interface for below stream structures ?? |
---|
496 | |
---|
497 | /*! |
---|
498 | * Option to specify the input channel for the child |
---|
499 | * process. It can be: |
---|
500 | * 1. An already open file descriptor. |
---|
501 | * 2. A file name. |
---|
502 | * 3. IOTYPE. Usuall a PIPE |
---|
503 | * |
---|
504 | * Eg: input{PIPE} |
---|
505 | * OR in case of redirection, output of another Popen |
---|
506 | * input{popen.output()} |
---|
507 | */ |
---|
508 | struct input |
---|
509 | { |
---|
510 | // For an already existing file descriptor. |
---|
511 | input(int fd): rd_ch_(fd) {} |
---|
512 | |
---|
513 | // FILE pointer. |
---|
514 | input (FILE* fp):input(fileno(fp)) { assert(fp); } |
---|
515 | |
---|
516 | input(const char* filename) { |
---|
517 | int fd = open(filename, O_RDONLY); |
---|
518 | if (fd == -1) throw OSError("File not found: ", errno); |
---|
519 | rd_ch_ = fd; |
---|
520 | } |
---|
521 | input(IOTYPE typ) { |
---|
522 | assert (typ == PIPE && "STDOUT/STDERR not allowed"); |
---|
523 | std::tie(rd_ch_, wr_ch_) = util::pipe_cloexec(); |
---|
524 | } |
---|
525 | |
---|
526 | int rd_ch_ = -1; |
---|
527 | int wr_ch_ = -1; |
---|
528 | }; |
---|
529 | |
---|
530 | |
---|
531 | /*! |
---|
532 | * Option to specify the output channel for the child |
---|
533 | * process. It can be: |
---|
534 | * 1. An already open file descriptor. |
---|
535 | * 2. A file name. |
---|
536 | * 3. IOTYPE. Usually a PIPE. |
---|
537 | * |
---|
538 | * Eg: output{PIPE} |
---|
539 | * OR output{"output.txt"} |
---|
540 | */ |
---|
541 | struct output |
---|
542 | { |
---|
543 | output(int fd): wr_ch_(fd) {} |
---|
544 | |
---|
545 | output (FILE* fp):output(fileno(fp)) { assert(fp); } |
---|
546 | |
---|
547 | output(const char* filename) { |
---|
548 | int fd = open(filename, O_APPEND | O_CREAT | O_RDWR, 0640); |
---|
549 | if (fd == -1) throw OSError("File not found: ", errno); |
---|
550 | wr_ch_ = fd; |
---|
551 | } |
---|
552 | output(IOTYPE typ) { |
---|
553 | assert (typ == PIPE && "STDOUT/STDERR not allowed"); |
---|
554 | std::tie(rd_ch_, wr_ch_) = util::pipe_cloexec(); |
---|
555 | } |
---|
556 | |
---|
557 | int rd_ch_ = -1; |
---|
558 | int wr_ch_ = -1; |
---|
559 | }; |
---|
560 | |
---|
561 | |
---|
562 | /*! |
---|
563 | * Option to specify the error channel for the child |
---|
564 | * process. It can be: |
---|
565 | * 1. An already open file descriptor. |
---|
566 | * 2. A file name. |
---|
567 | * 3. IOTYPE. Usually a PIPE or STDOUT |
---|
568 | * |
---|
569 | */ |
---|
570 | struct error |
---|
571 | { |
---|
572 | error(int fd): wr_ch_(fd) {} |
---|
573 | |
---|
574 | error(FILE* fp):error(fileno(fp)) { assert(fp); } |
---|
575 | |
---|
576 | error(const char* filename) { |
---|
577 | int fd = open(filename, O_APPEND | O_CREAT | O_RDWR, 0640); |
---|
578 | if (fd == -1) throw OSError("File not found: ", errno); |
---|
579 | wr_ch_ = fd; |
---|
580 | } |
---|
581 | error(IOTYPE typ) { |
---|
582 | assert ((typ == PIPE || typ == STDOUT) && "STDERR not aloowed"); |
---|
583 | if (typ == PIPE) { |
---|
584 | std::tie(rd_ch_, wr_ch_) = util::pipe_cloexec(); |
---|
585 | } else { |
---|
586 | // Need to defer it till we have checked all arguments |
---|
587 | deferred_ = true; |
---|
588 | } |
---|
589 | } |
---|
590 | |
---|
591 | bool deferred_ = false; |
---|
592 | int rd_ch_ = -1; |
---|
593 | int wr_ch_ = -1; |
---|
594 | }; |
---|
595 | |
---|
596 | // Impoverished, meager, needy, truly needy |
---|
597 | // version of type erasure to store function pointers |
---|
598 | // needed to provide the functionality of preexec_func |
---|
599 | // ATTN: Can be used only to execute functions with no |
---|
600 | // arguments and returning void. |
---|
601 | // Could have used more efficient methods, ofcourse, but |
---|
602 | // that wont yield me the consistent syntax which I am |
---|
603 | // aiming for. If you know, then please do let me know. |
---|
604 | |
---|
605 | class preexec_func |
---|
606 | { |
---|
607 | public: |
---|
608 | preexec_func() {} |
---|
609 | |
---|
610 | template <typename Func> |
---|
611 | preexec_func(Func f): holder_(new FuncHolder<Func>(f)) |
---|
612 | {} |
---|
613 | |
---|
614 | void operator()() { |
---|
615 | (*holder_)(); |
---|
616 | } |
---|
617 | |
---|
618 | private: |
---|
619 | struct HolderBase { |
---|
620 | virtual void operator()() const = 0; |
---|
621 | }; |
---|
622 | template <typename T> |
---|
623 | struct FuncHolder: HolderBase { |
---|
624 | FuncHolder(T func): func_(func) {} |
---|
625 | void operator()() const override {} |
---|
626 | // The function pointer/reference |
---|
627 | T func_; |
---|
628 | }; |
---|
629 | |
---|
630 | std::unique_ptr<HolderBase> holder_ = nullptr; |
---|
631 | }; |
---|
632 | |
---|
633 | // ~~~~ End Popen Args ~~~~ |
---|
634 | |
---|
635 | |
---|
636 | /*! |
---|
637 | * class: Buffer |
---|
638 | * This class is a very thin wrapper around std::vector<char> |
---|
639 | * This is basically used to determine the length of the actual |
---|
640 | * data stored inside the dynamically resized vector. |
---|
641 | * |
---|
642 | * This is what is returned as the output to communicate and check_output |
---|
643 | * functions, so, users must know about this class. |
---|
644 | * |
---|
645 | * OutBuffer and ErrBuffer are just different typedefs to this class. |
---|
646 | */ |
---|
647 | class Buffer |
---|
648 | { |
---|
649 | public: |
---|
650 | Buffer() {} |
---|
651 | Buffer(size_t cap) { buf.resize(cap); } |
---|
652 | void add_cap(size_t cap) { buf.resize(cap); } |
---|
653 | |
---|
654 | #if 0 |
---|
655 | Buffer(const Buffer& other): |
---|
656 | buf(other.buf), |
---|
657 | length(other.length) |
---|
658 | { |
---|
659 | std::cout << "COPY" << std::endl; |
---|
660 | } |
---|
661 | |
---|
662 | Buffer(Buffer&& other): |
---|
663 | buf(std::move(other.buf)), |
---|
664 | length(other.length) |
---|
665 | { |
---|
666 | std::cout << "MOVE" << std::endl; |
---|
667 | } |
---|
668 | #endif |
---|
669 | |
---|
670 | public: |
---|
671 | std::vector<char> buf; |
---|
672 | size_t length = 0; |
---|
673 | }; |
---|
674 | |
---|
675 | // Buffer for storing output written to output fd |
---|
676 | using OutBuffer = Buffer; |
---|
677 | // Buffer for storing output written to error fd |
---|
678 | using ErrBuffer = Buffer; |
---|
679 | |
---|
680 | |
---|
681 | // Fwd Decl. |
---|
682 | class Popen; |
---|
683 | |
---|
684 | /*--------------------------------------------------- |
---|
685 | * DETAIL NAMESPACE |
---|
686 | *--------------------------------------------------- |
---|
687 | */ |
---|
688 | |
---|
689 | namespace detail { |
---|
690 | |
---|
691 | // Metaprogram for searching a type within |
---|
692 | // a variadic parameter pack |
---|
693 | // This is particularly required to do a compile time |
---|
694 | // checking of the arguments provided to 'check_ouput' function |
---|
695 | // wherein the user is not expected to provide an 'ouput' option. |
---|
696 | |
---|
697 | template <typename... T> struct param_pack{}; |
---|
698 | |
---|
699 | template <typename F, typename T> struct has_type; |
---|
700 | |
---|
701 | template <typename F> |
---|
702 | struct has_type<F, param_pack<> > { |
---|
703 | static constexpr bool value = false; |
---|
704 | }; |
---|
705 | |
---|
706 | template <typename F, typename... T> |
---|
707 | struct has_type<F, param_pack<F, T...> > { |
---|
708 | static constexpr bool value = true; |
---|
709 | }; |
---|
710 | |
---|
711 | template <typename F, typename H, typename... T> |
---|
712 | struct has_type<F, param_pack<H,T...> > { |
---|
713 | static constexpr bool value = |
---|
714 | std::is_same<F, typename std::decay<H>::type>::value ? true : has_type<F, param_pack<T...> >::value; |
---|
715 | }; |
---|
716 | |
---|
717 | //---- |
---|
718 | |
---|
719 | /*! |
---|
720 | * A helper class to Popen class for setting |
---|
721 | * options as provided in the Popen constructor |
---|
722 | * or in check_ouput arguments. |
---|
723 | * This design allows us to _not_ have any fixed position |
---|
724 | * to any arguments and specify them in a way similar to what |
---|
725 | * can be done in python. |
---|
726 | */ |
---|
727 | struct ArgumentDeducer |
---|
728 | { |
---|
729 | ArgumentDeducer(Popen* p): popen_(p) {} |
---|
730 | |
---|
731 | void set_option(executable&& exe); |
---|
732 | void set_option(cwd&& cwdir); |
---|
733 | void set_option(bufsize&& bsiz); |
---|
734 | void set_option(environment&& env); |
---|
735 | void set_option(defer_spawn&& defer); |
---|
736 | void set_option(shell&& sh); |
---|
737 | void set_option(input&& inp); |
---|
738 | void set_option(output&& out); |
---|
739 | void set_option(error&& err); |
---|
740 | void set_option(close_fds&& cfds); |
---|
741 | void set_option(preexec_func&& prefunc); |
---|
742 | void set_option(session_leader&& sleader); |
---|
743 | |
---|
744 | private: |
---|
745 | Popen* popen_ = nullptr; |
---|
746 | }; |
---|
747 | |
---|
748 | /*! |
---|
749 | * A helper class to Popen. |
---|
750 | * This takes care of all the fork-exec logic |
---|
751 | * in the execute_child API. |
---|
752 | */ |
---|
753 | class Child |
---|
754 | { |
---|
755 | public: |
---|
756 | Child(Popen* p, int err_wr_pipe): |
---|
757 | parent_(p), |
---|
758 | err_wr_pipe_(err_wr_pipe) |
---|
759 | {} |
---|
760 | |
---|
761 | void execute_child(); |
---|
762 | |
---|
763 | private: |
---|
764 | // Lets call it parent even though |
---|
765 | // technically a bit incorrect |
---|
766 | Popen* parent_ = nullptr; |
---|
767 | int err_wr_pipe_ = -1; |
---|
768 | }; |
---|
769 | |
---|
770 | // Fwd Decl. |
---|
771 | class Streams; |
---|
772 | |
---|
773 | /*! |
---|
774 | * A helper class to Streams. |
---|
775 | * This takes care of management of communicating |
---|
776 | * with the child process with the means of the correct |
---|
777 | * file descriptor. |
---|
778 | */ |
---|
779 | class Communication |
---|
780 | { |
---|
781 | public: |
---|
782 | Communication(Streams* stream): stream_(stream) |
---|
783 | {} |
---|
784 | void operator=(const Communication&) = delete; |
---|
785 | public: |
---|
786 | int send(const char* msg, size_t length); |
---|
787 | int send(const std::vector<char>& msg); |
---|
788 | |
---|
789 | std::pair<OutBuffer, ErrBuffer> communicate(const char* msg, size_t length); |
---|
790 | std::pair<OutBuffer, ErrBuffer> communicate(const std::vector<char>& msg) |
---|
791 | { return communicate(msg.data(), msg.size()); } |
---|
792 | |
---|
793 | void set_out_buf_cap(size_t cap) { out_buf_cap_ = cap; } |
---|
794 | void set_err_buf_cap(size_t cap) { err_buf_cap_ = cap; } |
---|
795 | |
---|
796 | private: |
---|
797 | std::pair<OutBuffer, ErrBuffer> communicate_threaded( |
---|
798 | const char* msg, size_t length); |
---|
799 | |
---|
800 | private: |
---|
801 | Streams* stream_; |
---|
802 | size_t out_buf_cap_ = DEFAULT_BUF_CAP_BYTES; |
---|
803 | size_t err_buf_cap_ = DEFAULT_BUF_CAP_BYTES; |
---|
804 | }; |
---|
805 | |
---|
806 | |
---|
807 | |
---|
808 | /*! |
---|
809 | * This is a helper class to Popen. |
---|
810 | * It takes care of management of all the file descriptors |
---|
811 | * and file pointers. |
---|
812 | * It dispatches of the communication aspects to the |
---|
813 | * Communication class. |
---|
814 | * Read through the data members to understand about the |
---|
815 | * various file descriptors used. |
---|
816 | */ |
---|
817 | class Streams |
---|
818 | { |
---|
819 | public: |
---|
820 | Streams():comm_(this) {} |
---|
821 | void operator=(const Streams&) = delete; |
---|
822 | |
---|
823 | public: |
---|
824 | void setup_comm_channels(); |
---|
825 | |
---|
826 | void cleanup_fds() |
---|
827 | { |
---|
828 | if (write_to_child_ != -1 && read_from_parent_ != -1) { |
---|
829 | close(write_to_child_); |
---|
830 | } |
---|
831 | if (write_to_parent_ != -1 && read_from_child_ != -1) { |
---|
832 | close(read_from_child_); |
---|
833 | } |
---|
834 | if (err_write_ != -1 && err_read_ != -1) { |
---|
835 | close(err_read_); |
---|
836 | } |
---|
837 | } |
---|
838 | |
---|
839 | void close_parent_fds() |
---|
840 | { |
---|
841 | if (write_to_child_ != -1) close(write_to_child_); |
---|
842 | if (read_from_child_ != -1) close(read_from_child_); |
---|
843 | if (err_read_ != -1) close(err_read_); |
---|
844 | } |
---|
845 | |
---|
846 | void close_child_fds() |
---|
847 | { |
---|
848 | if (write_to_parent_ != -1) close(write_to_parent_); |
---|
849 | if (read_from_parent_ != -1) close(read_from_parent_); |
---|
850 | if (err_write_ != -1) close(err_write_); |
---|
851 | } |
---|
852 | |
---|
853 | FILE* input() { return input_.get(); } |
---|
854 | FILE* output() { return output_.get(); } |
---|
855 | FILE* error() { return error_.get(); } |
---|
856 | |
---|
857 | void input(FILE* fp) { input_.reset(fp, fclose); } |
---|
858 | void output(FILE* fp) { output_.reset(fp, fclose); } |
---|
859 | void error(FILE* fp) { error_.reset(fp, fclose); } |
---|
860 | |
---|
861 | void set_out_buf_cap(size_t cap) { comm_.set_out_buf_cap(cap); } |
---|
862 | void set_err_buf_cap(size_t cap) { comm_.set_err_buf_cap(cap); } |
---|
863 | |
---|
864 | public: /* Communication forwarding API's */ |
---|
865 | int send(const char* msg, size_t length) |
---|
866 | { return comm_.send(msg, length); } |
---|
867 | |
---|
868 | int send(const std::vector<char>& msg) |
---|
869 | { return comm_.send(msg); } |
---|
870 | |
---|
871 | std::pair<OutBuffer, ErrBuffer> communicate(const char* msg, size_t length) |
---|
872 | { return comm_.communicate(msg, length); } |
---|
873 | |
---|
874 | std::pair<OutBuffer, ErrBuffer> communicate(const std::vector<char>& msg) |
---|
875 | { return comm_.communicate(msg); } |
---|
876 | |
---|
877 | |
---|
878 | public:// Yes they are public |
---|
879 | |
---|
880 | std::shared_ptr<FILE> input_ = nullptr; |
---|
881 | std::shared_ptr<FILE> output_ = nullptr; |
---|
882 | std::shared_ptr<FILE> error_ = nullptr; |
---|
883 | |
---|
884 | // Buffer size for the IO streams |
---|
885 | int bufsiz_ = 0; |
---|
886 | |
---|
887 | // Pipes for communicating with child |
---|
888 | |
---|
889 | // Emulates stdin |
---|
890 | int write_to_child_ = -1; // Parent owned descriptor |
---|
891 | int read_from_parent_ = -1; // Child owned descriptor |
---|
892 | |
---|
893 | // Emulates stdout |
---|
894 | int write_to_parent_ = -1; // Child owned descriptor |
---|
895 | int read_from_child_ = -1; // Parent owned descriptor |
---|
896 | |
---|
897 | // Emulates stderr |
---|
898 | int err_write_ = -1; // Write error to parent (Child owned) |
---|
899 | int err_read_ = -1; // Read error from child (Parent owned) |
---|
900 | |
---|
901 | private: |
---|
902 | Communication comm_; |
---|
903 | }; |
---|
904 | |
---|
905 | }; // end namespace detail |
---|
906 | |
---|
907 | |
---|
908 | |
---|
909 | /*! |
---|
910 | * class: Popen |
---|
911 | * This is the single most important class in the whole library |
---|
912 | * and glues together all the helper classes to provide a common |
---|
913 | * interface to the client. |
---|
914 | * |
---|
915 | * API's provided by the class: |
---|
916 | * 1. Popen({"cmd"}, output{..}, error{..}, cwd{..}, ....) |
---|
917 | * Command provided as a sequence. |
---|
918 | * 2. Popen("cmd arg1"m output{..}, error{..}, cwd{..}, ....) |
---|
919 | * Command provided in a single string. |
---|
920 | * 3. wait() - Wait for the child to exit. |
---|
921 | * 4. retcode() - The return code of the exited child. |
---|
922 | * 5. pid() - PID of the spawned child. |
---|
923 | * 6. poll() - Check the status of the running child. |
---|
924 | * 7. kill(sig_num) - Kill the child. SIGTERM used by default. |
---|
925 | * 8. send(...) - Send input to the input channel of the child. |
---|
926 | * 9. communicate(...) - Get the output/error from the child and close the channels |
---|
927 | * from the parent side. |
---|
928 | *10. input() - Get the input channel/File pointer. Can be used for |
---|
929 | * cutomizing the way of sending input to child. |
---|
930 | *11. output() - Get the output channel/File pointer. Usually used |
---|
931 | in case of redirection. See piping examples. |
---|
932 | *12. error() - Get the error channel/File poiner. Usually used |
---|
933 | in case of redirection. |
---|
934 | *13. start_process() - Start the child process. Only to be used when |
---|
935 | * `defer_spawn` option was provided in Popen constructor. |
---|
936 | */ |
---|
937 | class Popen |
---|
938 | { |
---|
939 | public: |
---|
940 | friend struct detail::ArgumentDeducer; |
---|
941 | friend class detail::Child; |
---|
942 | |
---|
943 | template <typename... Args> |
---|
944 | Popen(const std::string& cmd_args, Args&& ...args): |
---|
945 | args_(cmd_args) |
---|
946 | { |
---|
947 | vargs_ = util::split(cmd_args); |
---|
948 | init_args(std::forward<Args>(args)...); |
---|
949 | |
---|
950 | // Setup the communication channels of the Popen class |
---|
951 | stream_.setup_comm_channels(); |
---|
952 | |
---|
953 | if (!defer_process_start_) execute_process(); |
---|
954 | } |
---|
955 | |
---|
956 | template <typename... Args> |
---|
957 | Popen(std::initializer_list<const char*> cmd_args, Args&& ...args) |
---|
958 | { |
---|
959 | vargs_.insert(vargs_.end(), cmd_args.begin(), cmd_args.end()); |
---|
960 | init_args(std::forward<Args>(args)...); |
---|
961 | |
---|
962 | // Setup the communication channels of the Popen class |
---|
963 | stream_.setup_comm_channels(); |
---|
964 | |
---|
965 | if (!defer_process_start_) execute_process(); |
---|
966 | } |
---|
967 | |
---|
968 | void start_process() throw (CalledProcessError, OSError); |
---|
969 | |
---|
970 | int pid() const noexcept { return child_pid_; } |
---|
971 | |
---|
972 | int retcode() const noexcept { return retcode_; } |
---|
973 | |
---|
974 | int wait() throw(OSError); |
---|
975 | |
---|
976 | int poll() throw(OSError); |
---|
977 | |
---|
978 | // Does not fail, Caller is expected to recheck the |
---|
979 | // status with a call to poll() |
---|
980 | void kill(int sig_num = 9); |
---|
981 | |
---|
982 | void set_out_buf_cap(size_t cap) { stream_.set_out_buf_cap(cap); } |
---|
983 | |
---|
984 | void set_err_buf_cap(size_t cap) { stream_.set_err_buf_cap(cap); } |
---|
985 | |
---|
986 | int send(const char* msg, size_t length) |
---|
987 | { return stream_.send(msg, length); } |
---|
988 | |
---|
989 | int send(const std::vector<char>& msg) |
---|
990 | { return stream_.send(msg); } |
---|
991 | |
---|
992 | std::pair<OutBuffer, ErrBuffer> communicate(const char* msg, size_t length) |
---|
993 | { |
---|
994 | auto res = stream_.communicate(msg, length); |
---|
995 | retcode_ = wait(); |
---|
996 | return res; |
---|
997 | } |
---|
998 | |
---|
999 | std::pair<OutBuffer, ErrBuffer> communicate(const std::vector<char>& msg) |
---|
1000 | { |
---|
1001 | auto res = stream_.communicate(msg); |
---|
1002 | retcode_ = wait(); |
---|
1003 | return res; |
---|
1004 | } |
---|
1005 | |
---|
1006 | std::pair<OutBuffer, ErrBuffer> communicate() |
---|
1007 | { |
---|
1008 | return communicate(nullptr, 0); |
---|
1009 | } |
---|
1010 | |
---|
1011 | FILE* input() { return stream_.input(); } |
---|
1012 | FILE* output() { return stream_.output();} |
---|
1013 | FILE* error() { return stream_.error(); } |
---|
1014 | |
---|
1015 | private: |
---|
1016 | template <typename F, typename... Args> |
---|
1017 | void init_args(F&& farg, Args&&... args); |
---|
1018 | void init_args(); |
---|
1019 | void populate_c_argv(); |
---|
1020 | void execute_process() throw (CalledProcessError, OSError); |
---|
1021 | |
---|
1022 | private: |
---|
1023 | detail::Streams stream_; |
---|
1024 | |
---|
1025 | bool defer_process_start_ = false; |
---|
1026 | bool close_fds_ = false; |
---|
1027 | bool has_preexec_fn_ = false; |
---|
1028 | bool shell_ = false; |
---|
1029 | bool session_leader_ = false; |
---|
1030 | |
---|
1031 | std::string exe_name_; |
---|
1032 | std::string cwd_; |
---|
1033 | std::map<std::string, std::string> env_; |
---|
1034 | preexec_func preexec_fn_; |
---|
1035 | |
---|
1036 | // Command in string format |
---|
1037 | std::string args_; |
---|
1038 | // Comamnd provided as sequence |
---|
1039 | std::vector<std::string> vargs_; |
---|
1040 | std::vector<char*> cargv_; |
---|
1041 | |
---|
1042 | bool child_created_ = false; |
---|
1043 | // Pid of the child process |
---|
1044 | int child_pid_ = -1; |
---|
1045 | |
---|
1046 | int retcode_ = -1; |
---|
1047 | }; |
---|
1048 | |
---|
1049 | inline void Popen::init_args() { |
---|
1050 | populate_c_argv(); |
---|
1051 | } |
---|
1052 | |
---|
1053 | template <typename F, typename... Args> |
---|
1054 | inline void Popen::init_args(F&& farg, Args&&... args) |
---|
1055 | { |
---|
1056 | detail::ArgumentDeducer argd(this); |
---|
1057 | argd.set_option(std::forward<F>(farg)); |
---|
1058 | init_args(std::forward<Args>(args)...); |
---|
1059 | } |
---|
1060 | |
---|
1061 | inline void Popen::populate_c_argv() |
---|
1062 | { |
---|
1063 | cargv_.clear(); |
---|
1064 | cargv_.reserve(vargs_.size() + 1); |
---|
1065 | for (auto& arg : vargs_) cargv_.push_back(&arg[0]); |
---|
1066 | cargv_.push_back(nullptr); |
---|
1067 | } |
---|
1068 | |
---|
1069 | inline void Popen::start_process() throw (CalledProcessError, OSError) |
---|
1070 | { |
---|
1071 | // The process was started/tried to be started |
---|
1072 | // in the constructor itself. |
---|
1073 | // For explicitly calling this API to start the |
---|
1074 | // process, 'defer_spawn' argument must be set to |
---|
1075 | // true in the constructor. |
---|
1076 | if (!defer_process_start_) { |
---|
1077 | assert (0); |
---|
1078 | return; |
---|
1079 | } |
---|
1080 | execute_process(); |
---|
1081 | } |
---|
1082 | |
---|
1083 | inline int Popen::wait() throw (OSError) |
---|
1084 | { |
---|
1085 | int ret, status; |
---|
1086 | std::tie(ret, status) = util::wait_for_child_exit(pid()); |
---|
1087 | if (ret == -1) { |
---|
1088 | if (errno != ECHILD) throw OSError("waitpid failed", errno); |
---|
1089 | return 0; |
---|
1090 | } |
---|
1091 | if (WIFEXITED(status)) return WEXITSTATUS(status); |
---|
1092 | if (WIFSIGNALED(status)) return WTERMSIG(status); |
---|
1093 | else return 255; |
---|
1094 | |
---|
1095 | return 0; |
---|
1096 | } |
---|
1097 | |
---|
1098 | inline int Popen::poll() throw (OSError) |
---|
1099 | { |
---|
1100 | int status; |
---|
1101 | if (!child_created_) return -1; // TODO: ?? |
---|
1102 | |
---|
1103 | // Returns zero if child is still running |
---|
1104 | int ret = waitpid(child_pid_, &status, WNOHANG); |
---|
1105 | if (ret == 0) return -1; |
---|
1106 | |
---|
1107 | if (ret == child_pid_) { |
---|
1108 | if (WIFSIGNALED(status)) { |
---|
1109 | retcode_ = WTERMSIG(status); |
---|
1110 | } else if (WIFEXITED(status)) { |
---|
1111 | retcode_ = WEXITSTATUS(status); |
---|
1112 | } else { |
---|
1113 | retcode_ = 255; |
---|
1114 | } |
---|
1115 | return retcode_; |
---|
1116 | } |
---|
1117 | |
---|
1118 | if (ret == -1) { |
---|
1119 | // From subprocess.py |
---|
1120 | // This happens if SIGCHLD is set to be ignored |
---|
1121 | // or waiting for child process has otherwise been disabled |
---|
1122 | // for our process. This child is dead, we cannot get the |
---|
1123 | // status. |
---|
1124 | if (errno == ECHILD) retcode_ = 0; |
---|
1125 | else throw OSError("waitpid failed", errno); |
---|
1126 | } else { |
---|
1127 | retcode_ = ret; |
---|
1128 | } |
---|
1129 | |
---|
1130 | return retcode_; |
---|
1131 | } |
---|
1132 | |
---|
1133 | inline void Popen::kill(int sig_num) |
---|
1134 | { |
---|
1135 | if (session_leader_) killpg(child_pid_, sig_num); |
---|
1136 | else ::kill(child_pid_, sig_num); |
---|
1137 | } |
---|
1138 | |
---|
1139 | |
---|
1140 | inline void Popen::execute_process() throw (CalledProcessError, OSError) |
---|
1141 | { |
---|
1142 | int err_rd_pipe, err_wr_pipe; |
---|
1143 | std::tie(err_rd_pipe, err_wr_pipe) = util::pipe_cloexec(); |
---|
1144 | |
---|
1145 | if (shell_) { |
---|
1146 | auto new_cmd = util::join(vargs_); |
---|
1147 | vargs_.clear(); |
---|
1148 | vargs_.insert(vargs_.begin(), {"/bin/sh", "-c"}); |
---|
1149 | vargs_.push_back(new_cmd); |
---|
1150 | populate_c_argv(); |
---|
1151 | } |
---|
1152 | |
---|
1153 | if (exe_name_.length()) { |
---|
1154 | vargs_.insert(vargs_.begin(), exe_name_); |
---|
1155 | populate_c_argv(); |
---|
1156 | } |
---|
1157 | exe_name_ = vargs_[0]; |
---|
1158 | |
---|
1159 | child_pid_ = fork(); |
---|
1160 | |
---|
1161 | if (child_pid_ < 0) { |
---|
1162 | close(err_rd_pipe); |
---|
1163 | close(err_wr_pipe); |
---|
1164 | throw OSError("fork failed", errno); |
---|
1165 | } |
---|
1166 | |
---|
1167 | child_created_ = true; |
---|
1168 | |
---|
1169 | if (child_pid_ == 0) |
---|
1170 | { |
---|
1171 | // Close descriptors belonging to parent |
---|
1172 | stream_.close_parent_fds(); |
---|
1173 | |
---|
1174 | //Close the read end of the error pipe |
---|
1175 | close(err_rd_pipe); |
---|
1176 | |
---|
1177 | detail::Child chld(this, err_wr_pipe); |
---|
1178 | chld.execute_child(); |
---|
1179 | } |
---|
1180 | else |
---|
1181 | { |
---|
1182 | close (err_wr_pipe);// close child side of pipe, else get stuck in read below |
---|
1183 | |
---|
1184 | stream_.close_child_fds(); |
---|
1185 | |
---|
1186 | try { |
---|
1187 | char err_buf[SP_MAX_ERR_BUF_SIZ] = {0,}; |
---|
1188 | |
---|
1189 | int read_bytes = util::read_atmost_n( |
---|
1190 | err_rd_pipe, |
---|
1191 | err_buf, |
---|
1192 | SP_MAX_ERR_BUF_SIZ); |
---|
1193 | close(err_rd_pipe); |
---|
1194 | |
---|
1195 | if (read_bytes || strlen(err_buf)) { |
---|
1196 | // Call waitpid to reap the child process |
---|
1197 | // waitpid suspends the calling process until the |
---|
1198 | // child terminates. |
---|
1199 | wait(); |
---|
1200 | |
---|
1201 | // Throw whatever information we have about child failure |
---|
1202 | throw CalledProcessError(err_buf); |
---|
1203 | } |
---|
1204 | } catch (std::exception& exp) { |
---|
1205 | stream_.cleanup_fds(); |
---|
1206 | throw exp; |
---|
1207 | } |
---|
1208 | |
---|
1209 | } |
---|
1210 | } |
---|
1211 | |
---|
1212 | namespace detail { |
---|
1213 | |
---|
1214 | inline void ArgumentDeducer::set_option(executable&& exe) { |
---|
1215 | popen_->exe_name_ = std::move(exe.arg_value); |
---|
1216 | } |
---|
1217 | |
---|
1218 | inline void ArgumentDeducer::set_option(cwd&& cwdir) { |
---|
1219 | popen_->cwd_ = std::move(cwdir.arg_value); |
---|
1220 | } |
---|
1221 | |
---|
1222 | inline void ArgumentDeducer::set_option(bufsize&& bsiz) { |
---|
1223 | popen_->stream_.bufsiz_ = bsiz.bufsiz; |
---|
1224 | } |
---|
1225 | |
---|
1226 | inline void ArgumentDeducer::set_option(environment&& env) { |
---|
1227 | popen_->env_ = std::move(env.env_); |
---|
1228 | } |
---|
1229 | |
---|
1230 | inline void ArgumentDeducer::set_option(defer_spawn&& defer) { |
---|
1231 | popen_->defer_process_start_ = defer.defer; |
---|
1232 | } |
---|
1233 | |
---|
1234 | inline void ArgumentDeducer::set_option(shell&& sh) { |
---|
1235 | popen_->shell_ = sh.shell_; |
---|
1236 | } |
---|
1237 | |
---|
1238 | inline void ArgumentDeducer::set_option(session_leader&& sleader) { |
---|
1239 | popen_->session_leader_ = sleader.leader_; |
---|
1240 | } |
---|
1241 | |
---|
1242 | inline void ArgumentDeducer::set_option(input&& inp) { |
---|
1243 | if (inp.rd_ch_ != -1) popen_->stream_.read_from_parent_ = inp.rd_ch_; |
---|
1244 | if (inp.wr_ch_ != -1) popen_->stream_.write_to_child_ = inp.wr_ch_; |
---|
1245 | } |
---|
1246 | |
---|
1247 | inline void ArgumentDeducer::set_option(output&& out) { |
---|
1248 | if (out.wr_ch_ != -1) popen_->stream_.write_to_parent_ = out.wr_ch_; |
---|
1249 | if (out.rd_ch_ != -1) popen_->stream_.read_from_child_ = out.rd_ch_; |
---|
1250 | } |
---|
1251 | |
---|
1252 | inline void ArgumentDeducer::set_option(error&& err) { |
---|
1253 | if (err.deferred_) { |
---|
1254 | if (popen_->stream_.write_to_parent_) { |
---|
1255 | popen_->stream_.err_write_ = popen_->stream_.write_to_parent_; |
---|
1256 | } else { |
---|
1257 | throw std::runtime_error("Set output before redirecting error to output"); |
---|
1258 | } |
---|
1259 | } |
---|
1260 | if (err.wr_ch_ != -1) popen_->stream_.err_write_ = err.wr_ch_; |
---|
1261 | if (err.rd_ch_ != -1) popen_->stream_.err_read_ = err.rd_ch_; |
---|
1262 | } |
---|
1263 | |
---|
1264 | inline void ArgumentDeducer::set_option(close_fds&& cfds) { |
---|
1265 | popen_->close_fds_ = cfds.close_all; |
---|
1266 | } |
---|
1267 | |
---|
1268 | inline void ArgumentDeducer::set_option(preexec_func&& prefunc) { |
---|
1269 | popen_->preexec_fn_ = std::move(prefunc); |
---|
1270 | popen_->has_preexec_fn_ = true; |
---|
1271 | } |
---|
1272 | |
---|
1273 | |
---|
1274 | inline void Child::execute_child() { |
---|
1275 | int sys_ret = -1; |
---|
1276 | auto& stream = parent_->stream_; |
---|
1277 | |
---|
1278 | try { |
---|
1279 | if (stream.write_to_parent_ == 0) |
---|
1280 | stream.write_to_parent_ = dup(stream.write_to_parent_); |
---|
1281 | |
---|
1282 | if (stream.err_write_ == 0 || stream.err_write_ == 1) |
---|
1283 | stream.err_write_ = dup(stream.err_write_); |
---|
1284 | |
---|
1285 | // Make the child owned descriptors as the |
---|
1286 | // stdin, stdout and stderr for the child process |
---|
1287 | auto _dup2_ = [](int fd, int to_fd) { |
---|
1288 | if (fd == to_fd) { |
---|
1289 | // dup2 syscall does not reset the |
---|
1290 | // CLOEXEC flag if the descriptors |
---|
1291 | // provided to it are same. |
---|
1292 | // But, we need to reset the CLOEXEC |
---|
1293 | // flag as the provided descriptors |
---|
1294 | // are now going to be the standard |
---|
1295 | // input, output and error |
---|
1296 | util::set_clo_on_exec(fd, false); |
---|
1297 | } else if(fd != -1) { |
---|
1298 | int res = dup2(fd, to_fd); |
---|
1299 | if (res == -1) throw OSError("dup2 failed", errno); |
---|
1300 | } |
---|
1301 | }; |
---|
1302 | |
---|
1303 | // Create the standard streams |
---|
1304 | _dup2_(stream.read_from_parent_, 0); // Input stream |
---|
1305 | _dup2_(stream.write_to_parent_, 1); // Output stream |
---|
1306 | _dup2_(stream.err_write_, 2); // Error stream |
---|
1307 | |
---|
1308 | // Close the duped descriptors |
---|
1309 | if (stream.read_from_parent_ != -1 && stream.read_from_parent_ > 2) |
---|
1310 | close(stream.read_from_parent_); |
---|
1311 | |
---|
1312 | if (stream.write_to_parent_ != -1 && stream.write_to_parent_ > 2) |
---|
1313 | close(stream.write_to_parent_); |
---|
1314 | |
---|
1315 | if (stream.err_write_ != -1 && stream.err_write_ > 2) |
---|
1316 | close(stream.err_write_); |
---|
1317 | |
---|
1318 | // Close all the inherited fd's except the error write pipe |
---|
1319 | if (parent_->close_fds_) { |
---|
1320 | long max_fd = sysconf(_SC_OPEN_MAX); |
---|
1321 | if (max_fd == -1) throw OSError("sysconf failed", errno); |
---|
1322 | |
---|
1323 | for (int i = 3; i < max_fd; i++) { |
---|
1324 | if (i == err_wr_pipe_) continue; |
---|
1325 | close(i); |
---|
1326 | } |
---|
1327 | } |
---|
1328 | |
---|
1329 | // Change the working directory if provided |
---|
1330 | if (parent_->cwd_.length()) { |
---|
1331 | sys_ret = chdir(parent_->cwd_.c_str()); |
---|
1332 | if (sys_ret == -1) throw OSError("chdir failed", errno); |
---|
1333 | } |
---|
1334 | |
---|
1335 | if (parent_->has_preexec_fn_) { |
---|
1336 | parent_->preexec_fn_(); |
---|
1337 | } |
---|
1338 | |
---|
1339 | if (parent_->session_leader_) { |
---|
1340 | sys_ret = setsid(); |
---|
1341 | if (sys_ret == -1) throw OSError("setsid failed", errno); |
---|
1342 | } |
---|
1343 | |
---|
1344 | // Replace the current image with the executable |
---|
1345 | if (parent_->env_.size()) { |
---|
1346 | for (auto& kv : parent_->env_) { |
---|
1347 | setenv(kv.first.c_str(), kv.second.c_str(), 1); |
---|
1348 | } |
---|
1349 | sys_ret = execvp(parent_->exe_name_.c_str(), parent_->cargv_.data()); |
---|
1350 | } else { |
---|
1351 | sys_ret = execvp(parent_->exe_name_.c_str(), parent_->cargv_.data()); |
---|
1352 | } |
---|
1353 | |
---|
1354 | if (sys_ret == -1) throw OSError("execve failed", errno); |
---|
1355 | |
---|
1356 | } catch (const OSError& exp) { |
---|
1357 | // Just write the exception message |
---|
1358 | // TODO: Give back stack trace ? |
---|
1359 | std::string err_msg(exp.what()); |
---|
1360 | //ATTN: Can we do something on error here ? |
---|
1361 | util::write_n(err_wr_pipe_, err_msg.c_str(), err_msg.length()); |
---|
1362 | throw exp; |
---|
1363 | } |
---|
1364 | |
---|
1365 | // Calling application would not get this |
---|
1366 | // exit failure |
---|
1367 | exit (EXIT_FAILURE); |
---|
1368 | } |
---|
1369 | |
---|
1370 | |
---|
1371 | inline void Streams::setup_comm_channels() |
---|
1372 | { |
---|
1373 | if (write_to_child_ != -1) input(fdopen(write_to_child_, "wb")); |
---|
1374 | if (read_from_child_ != -1) output(fdopen(read_from_child_, "rb")); |
---|
1375 | if (err_read_ != -1) error(fdopen(err_read_, "rb")); |
---|
1376 | |
---|
1377 | auto handles = {input(), output(), error()}; |
---|
1378 | |
---|
1379 | for (auto& h : handles) { |
---|
1380 | if (h == nullptr) continue; |
---|
1381 | switch (bufsiz_) { |
---|
1382 | case 0: |
---|
1383 | setvbuf(h, nullptr, _IONBF, BUFSIZ); |
---|
1384 | break; |
---|
1385 | case 1: |
---|
1386 | setvbuf(h, nullptr, _IONBF, BUFSIZ); |
---|
1387 | break; |
---|
1388 | default: |
---|
1389 | setvbuf(h, nullptr, _IOFBF, bufsiz_); |
---|
1390 | }; |
---|
1391 | } |
---|
1392 | } |
---|
1393 | |
---|
1394 | inline int Communication::send(const char* msg, size_t length) |
---|
1395 | { |
---|
1396 | if (stream_->input() == nullptr) return -1; |
---|
1397 | return std::fwrite(msg, sizeof(char), (int)length, stream_->input()); |
---|
1398 | } |
---|
1399 | |
---|
1400 | inline int Communication::send(const std::vector<char>& msg) |
---|
1401 | { |
---|
1402 | return send(msg.data(), msg.size()); |
---|
1403 | } |
---|
1404 | |
---|
1405 | inline std::pair<OutBuffer, ErrBuffer> |
---|
1406 | Communication::communicate(const char* msg, size_t length) |
---|
1407 | { |
---|
1408 | // Optimization from subprocess.py |
---|
1409 | // If we are using one pipe, or no pipe |
---|
1410 | // at all, using select() or threads is unnecessary. |
---|
1411 | auto hndls = {stream_->input(), stream_->output(), stream_->error()}; |
---|
1412 | int count = std::count(std::begin(hndls), std::end(hndls), nullptr); |
---|
1413 | const int len_conv = (int)length; |
---|
1414 | |
---|
1415 | if (count >= 2) { |
---|
1416 | OutBuffer obuf; |
---|
1417 | ErrBuffer ebuf; |
---|
1418 | if (stream_->input()) { |
---|
1419 | if (msg) { |
---|
1420 | int wbytes = std::fwrite(msg, sizeof(char), (int)length, stream_->input()); |
---|
1421 | if (wbytes < len_conv) { |
---|
1422 | if (errno != EPIPE && errno != EINVAL) { |
---|
1423 | throw OSError("fwrite error", errno); |
---|
1424 | } |
---|
1425 | } |
---|
1426 | } |
---|
1427 | // Close the input stream |
---|
1428 | stream_->input_.reset(); |
---|
1429 | } else if (stream_->output()) { |
---|
1430 | // Read till EOF |
---|
1431 | // ATTN: This could be blocking, if the process |
---|
1432 | // at the other end screws up, we get screwed as well |
---|
1433 | obuf.add_cap(out_buf_cap_); |
---|
1434 | |
---|
1435 | int rbytes = util::read_all( |
---|
1436 | fileno(stream_->output()), |
---|
1437 | obuf.buf); |
---|
1438 | |
---|
1439 | if (rbytes == -1) { |
---|
1440 | throw OSError("read to obuf failed", errno); |
---|
1441 | } |
---|
1442 | |
---|
1443 | obuf.length = rbytes; |
---|
1444 | // Close the output stream |
---|
1445 | stream_->output_.reset(); |
---|
1446 | |
---|
1447 | } else if (stream_->error()) { |
---|
1448 | // Same screwness applies here as well |
---|
1449 | ebuf.add_cap(err_buf_cap_); |
---|
1450 | |
---|
1451 | int rbytes = util::read_atmost_n( |
---|
1452 | fileno(stream_->error()), |
---|
1453 | ebuf.buf.data(), |
---|
1454 | ebuf.buf.size()); |
---|
1455 | |
---|
1456 | if (rbytes == -1) { |
---|
1457 | throw OSError("read to ebuf failed", errno); |
---|
1458 | } |
---|
1459 | |
---|
1460 | ebuf.length = rbytes; |
---|
1461 | // Close the error stream |
---|
1462 | stream_->error_.reset(); |
---|
1463 | } |
---|
1464 | return std::make_pair(std::move(obuf), std::move(ebuf)); |
---|
1465 | } |
---|
1466 | |
---|
1467 | return communicate_threaded(msg, length); |
---|
1468 | } |
---|
1469 | |
---|
1470 | |
---|
1471 | inline std::pair<OutBuffer, ErrBuffer> |
---|
1472 | Communication::communicate_threaded(const char* msg, size_t length) |
---|
1473 | { |
---|
1474 | OutBuffer obuf; |
---|
1475 | ErrBuffer ebuf; |
---|
1476 | std::future<int> out_fut, err_fut; |
---|
1477 | const int length_conv = (int)length; |
---|
1478 | |
---|
1479 | if (stream_->output()) { |
---|
1480 | obuf.add_cap(out_buf_cap_); |
---|
1481 | |
---|
1482 | out_fut = std::async(std::launch::async, |
---|
1483 | [&obuf, this] { |
---|
1484 | return util::read_all(fileno(this->stream_->output()), obuf.buf); |
---|
1485 | }); |
---|
1486 | } |
---|
1487 | if (stream_->error()) { |
---|
1488 | ebuf.add_cap(err_buf_cap_); |
---|
1489 | |
---|
1490 | err_fut = std::async(std::launch::async, |
---|
1491 | [&ebuf, this] { |
---|
1492 | return util::read_all(fileno(this->stream_->error()), ebuf.buf); |
---|
1493 | }); |
---|
1494 | } |
---|
1495 | if (stream_->input()) { |
---|
1496 | if (msg) { |
---|
1497 | int wbytes = std::fwrite(msg, sizeof(char), (int)length, stream_->input()); |
---|
1498 | if (wbytes < length_conv) { |
---|
1499 | if (errno != EPIPE && errno != EINVAL) { |
---|
1500 | throw OSError("fwrite error", errno); |
---|
1501 | } |
---|
1502 | } |
---|
1503 | } |
---|
1504 | stream_->input_.reset(); |
---|
1505 | } |
---|
1506 | |
---|
1507 | if (out_fut.valid()) { |
---|
1508 | int res = out_fut.get(); |
---|
1509 | if (res != -1) obuf.length = res; |
---|
1510 | else obuf.length = 0; |
---|
1511 | } |
---|
1512 | if (err_fut.valid()) { |
---|
1513 | int res = err_fut.get(); |
---|
1514 | if (res != -1) ebuf.length = res; |
---|
1515 | else ebuf.length = 0; |
---|
1516 | } |
---|
1517 | |
---|
1518 | return std::make_pair(std::move(obuf), std::move(ebuf)); |
---|
1519 | } |
---|
1520 | |
---|
1521 | }; // end namespace detail |
---|
1522 | |
---|
1523 | |
---|
1524 | |
---|
1525 | // Convenience Functions |
---|
1526 | // |
---|
1527 | // |
---|
1528 | namespace detail |
---|
1529 | { |
---|
1530 | template<typename F, typename... Args> |
---|
1531 | OutBuffer check_output_impl(F& farg, Args&&... args) |
---|
1532 | { |
---|
1533 | static_assert(!detail::has_type<output, detail::param_pack<Args...> >::value, "output not allowed in args"); |
---|
1534 | auto p = Popen(std::forward<F>(farg), std::forward<Args>(args)..., output{PIPE}); |
---|
1535 | auto res = p.communicate(); |
---|
1536 | auto retcode = p.poll(); |
---|
1537 | if (retcode > 0) { |
---|
1538 | throw CalledProcessError("Command failed : Non zero retcode"); |
---|
1539 | } |
---|
1540 | return std::move(res.first); |
---|
1541 | } |
---|
1542 | |
---|
1543 | template<typename F, typename... Args> |
---|
1544 | int call_impl(F& farg, Args&&... args) |
---|
1545 | { |
---|
1546 | return Popen(std::forward<F>(farg), std::forward<Args>(args)...).wait(); |
---|
1547 | } |
---|
1548 | |
---|
1549 | static inline void pipeline_impl(std::vector<Popen>& cmds) |
---|
1550 | { |
---|
1551 | /* EMPTY IMPL */ |
---|
1552 | } |
---|
1553 | |
---|
1554 | template<typename... Args> |
---|
1555 | static inline void pipeline_impl(std::vector<Popen>& cmds, |
---|
1556 | const std::string& cmd, |
---|
1557 | Args&&... args) |
---|
1558 | { |
---|
1559 | if (cmds.size() == 0) { |
---|
1560 | cmds.emplace_back(cmd, output{PIPE}, defer_spawn{true}); |
---|
1561 | } else { |
---|
1562 | cmds.emplace_back(cmd, input{cmds.back().output()}, output{PIPE}, defer_spawn{true}); |
---|
1563 | } |
---|
1564 | |
---|
1565 | pipeline_impl(cmds, std::forward<Args>(args)...); |
---|
1566 | } |
---|
1567 | |
---|
1568 | } |
---|
1569 | |
---|
1570 | /*----------------------------------------------------------- |
---|
1571 | * CONVIENIENCE FUNCTIONS |
---|
1572 | *----------------------------------------------------------- |
---|
1573 | */ |
---|
1574 | |
---|
1575 | |
---|
1576 | /*! |
---|
1577 | * Run the command with arguments and wait for it to complete. |
---|
1578 | * The parameters passed to the argument are exactly the same |
---|
1579 | * one would use for Popen constructors. |
---|
1580 | */ |
---|
1581 | template<typename... Args> |
---|
1582 | int call(std::initializer_list<const char*> plist, Args&&... args) |
---|
1583 | { |
---|
1584 | return (detail::call_impl(plist, std::forward<Args>(args)...)); |
---|
1585 | } |
---|
1586 | |
---|
1587 | template<typename... Args> |
---|
1588 | int call(const std::string& arg, Args&&... args) |
---|
1589 | { |
---|
1590 | return (detail::call_impl(arg, std::forward<Args>(args)...)); |
---|
1591 | } |
---|
1592 | |
---|
1593 | |
---|
1594 | /*! |
---|
1595 | * Run the command with arguments and wait for it to complete. |
---|
1596 | * If the exit code was non-zero it raises a CalledProcessError. |
---|
1597 | * The arguments are the same as for the Popen constructor. |
---|
1598 | */ |
---|
1599 | template <typename... Args> |
---|
1600 | OutBuffer check_output(std::initializer_list<const char*> plist, Args&&... args) |
---|
1601 | { |
---|
1602 | return (detail::check_output_impl(plist, std::forward<Args>(args)...)); |
---|
1603 | } |
---|
1604 | |
---|
1605 | template <typename... Args> |
---|
1606 | OutBuffer check_output(const std::string& arg, Args&&... args) |
---|
1607 | { |
---|
1608 | return (detail::check_output_impl(arg, std::forward<Args>(args)...)); |
---|
1609 | } |
---|
1610 | |
---|
1611 | |
---|
1612 | /*! |
---|
1613 | * An easy way to pipeline easy commands. |
---|
1614 | * Provide the commands that needs to be pipelined in the order they |
---|
1615 | * would appear in a regular command. |
---|
1616 | * It would wait for the last command provided in the pipeline |
---|
1617 | * to finish and then return the OutBuffer. |
---|
1618 | */ |
---|
1619 | template<typename... Args> |
---|
1620 | // Args expected to be flat commands using string instead of initializer_list |
---|
1621 | OutBuffer pipeline(Args&&... args) |
---|
1622 | { |
---|
1623 | std::vector<Popen> pcmds; |
---|
1624 | detail::pipeline_impl(pcmds, std::forward<Args>(args)...); |
---|
1625 | |
---|
1626 | for (auto& p : pcmds) p.start_process(); |
---|
1627 | return (pcmds.back().communicate().first); |
---|
1628 | } |
---|
1629 | |
---|
1630 | }; |
---|
1631 | |
---|
1632 | #endif // __cplusplus |
---|
1633 | |
---|
1634 | #endif // SUBPROCESS_HPP |
---|