├── .clang-format ├── .gitignore ├── LICENSE ├── README.md ├── cl_clang.sh ├── cl_gcc.sh ├── include └── process.h └── sample.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # BasedOnStyle: LLVM 3 | AccessModifierOffset: -2 4 | ConstructorInitializerIndentWidth: 4 5 | AlignEscapedNewlinesLeft: false 6 | AlignTrailingComments: true 7 | AllowAllParametersOfDeclarationOnNextLine: true 8 | AllowShortBlocksOnASingleLine: false 9 | AllowShortIfStatementsOnASingleLine: false 10 | AllowShortLoopsOnASingleLine: false 11 | AllowShortFunctionsOnASingleLine: None 12 | AlwaysBreakTemplateDeclarations: true 13 | AlwaysBreakBeforeMultilineStrings: false 14 | BreakBeforeBinaryOperators: true 15 | BreakBeforeTernaryOperators: true 16 | BreakConstructorInitializersBeforeComma: false 17 | BinPackParameters: true 18 | ColumnLimit: 80 19 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 20 | DerivePointerAlignment: false 21 | ExperimentalAutoDetectBinPacking: false 22 | IndentCaseLabels: true 23 | IndentWrappedFunctionNames: false 24 | IndentFunctionDeclarationAfterType: true 25 | MaxEmptyLinesToKeep: 1 26 | KeepEmptyLinesAtTheStartOfBlocks: true 27 | NamespaceIndentation: None 28 | ObjCSpaceAfterProperty: false 29 | ObjCSpaceBeforeProtocolList: true 30 | PenaltyBreakBeforeFirstCallParameter: 19 31 | PenaltyBreakComment: 60 32 | PenaltyBreakString: 1000 33 | PenaltyBreakFirstLessLess: 120 34 | PenaltyExcessCharacter: 1000000 35 | PenaltyReturnTypeOnItsOwnLine: 60 36 | PointerAlignment: Left 37 | SpacesBeforeTrailingComments: 1 38 | Cpp11BracedListStyle: true 39 | Standard: Cpp11 40 | IndentWidth: 4 41 | TabWidth: 8 42 | UseTab: Never 43 | BreakBeforeBraces: Allman 44 | SpacesInParentheses: false 45 | SpacesInAngles: false 46 | SpaceInEmptyParentheses: false 47 | SpacesInCStyleCastParentheses: false 48 | SpacesInContainerLiterals: true 49 | SpaceBeforeAssignmentOperators: true 50 | ContinuationIndentWidth: 4 51 | SpaceBeforeParens: ControlStatements 52 | ... 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Chase Geigle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | procxx 2 | ====== 3 | 4 | A simple process management library for C++ on UNIX platforms. 5 | 6 | ## Usage 7 | Here is a simple (toy) example of setting up a very basic pipeline. 8 | 9 | ```cpp 10 | // construct a child process that runs `cat` 11 | procxx::process cat{"cat"}; 12 | 13 | // construct a child process that runs `wc -c` 14 | procxx::process wc{"wc", "-c"}; 15 | 16 | // set up the pipeline and execute the child processes 17 | (cat | wc).exec(); 18 | 19 | // write "hello world" to the standard input of the cat child process 20 | cat << "hello world"; 21 | 22 | // close the write end (stdin) of the cat child 23 | cat.close(procxx::pipe_t::write_end()); 24 | 25 | // read from the `wc -c` process's stdout, line by line 26 | std::string line; 27 | while (std::getline(wc.output(), line)) 28 | std::cout << line << std::endl; 29 | ``` 30 | 31 | procxx also provides functionality for setting resource limits on the 32 | child processes, as demonstrated below. The functionality is implemented 33 | via the POSIX `rlimit` functions. 34 | 35 | ```cpp 36 | procxx::process cat{"cat"}; 37 | procxx::process wc{"wc", "-c"}; 38 | 39 | // OPTION 1: same limits for all processes in the pipeline 40 | procxx::process::limits_t limits; 41 | limits.cpu_time(3); // 3 second execution time limit 42 | limits.memory(1024*1024*1); // 1 MB memory usage limit 43 | (cat | wc).limit(limits).exec(); 44 | 45 | // OPTION 2: individual limits for each process 46 | procxx::process::limits_t limits; 47 | limits.cpu_time(3); // 3 second execution time limit 48 | limits.memory(1024*1024*1); // 1 MB memory usage limit 49 | wc.limit(limits); 50 | 51 | procxx::process::limits_t limits; 52 | limits.cpu_time(1); // 1 second execution time limit 53 | limits.memory(1024); // 1 KB memory usage limit 54 | cat.limit(limits); 55 | 56 | (cat | wc).exec(); 57 | ``` 58 | -------------------------------------------------------------------------------- /cl_clang.sh: -------------------------------------------------------------------------------- 1 | clang++ -std=c++11 -Iinclude -Weverything -Wno-c++98-compat -Wno-padded -Wno-weak-vtables sample.cpp 2 | -------------------------------------------------------------------------------- /cl_gcc.sh: -------------------------------------------------------------------------------- 1 | g++ -std=c++11 -Iinclude -Wall -Wextra sample.cpp 2 | 3 | -------------------------------------------------------------------------------- /include/process.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file process.h 3 | * @author Chase Geigle 4 | * 5 | * A simple, header-only process/pipe library for C++ on UNIX platforms. 6 | * 7 | * Released under the MIT license (see LICENSE). 8 | */ 9 | 10 | #ifndef PROCXX_PROCESS_H_ 11 | #define PROCXX_PROCESS_H_ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #ifndef PROCXX_HAS_PIPE2 36 | #define PROCXX_HAS_PIPE2 1 37 | #endif 38 | 39 | namespace procxx 40 | { 41 | 42 | /** 43 | * Represents a UNIX pipe between processes. 44 | */ 45 | class pipe_t 46 | { 47 | public: 48 | static constexpr unsigned int READ_END = 0; 49 | static constexpr unsigned int WRITE_END = 1; 50 | 51 | /** 52 | * Wrapper type that ensures sanity when dealing with operations on 53 | * the different ends of the pipe. 54 | */ 55 | class pipe_end 56 | { 57 | public: 58 | /** 59 | * Constructs a new object to represent an end of a pipe. Ensures 60 | * the end passed makes sense (e.g., is either the READ_END or the 61 | * WRITE_END of the pipe). 62 | */ 63 | pipe_end(unsigned int end) 64 | { 65 | if (end != READ_END && end != WRITE_END) 66 | throw exception{"invalid pipe end"}; 67 | end_ = end; 68 | } 69 | 70 | /** 71 | * pipe_ends are implicitly convertible to ints. 72 | */ 73 | operator unsigned int() const 74 | { 75 | return end_; 76 | } 77 | 78 | private: 79 | unsigned int end_; 80 | }; 81 | 82 | /** 83 | * Gets a pipe_end representing the read end of a pipe. 84 | */ 85 | static pipe_end read_end() 86 | { 87 | static pipe_end read{READ_END}; 88 | return read; 89 | } 90 | 91 | /** 92 | * Gets a pipe_end representing the write end of a pipe. 93 | */ 94 | static pipe_end write_end() 95 | { 96 | static pipe_end write{WRITE_END}; 97 | return write; 98 | } 99 | 100 | /** 101 | * Constructs a new pipe. 102 | */ 103 | pipe_t() 104 | { 105 | #if PROCXX_HAS_PIPE2 106 | const auto r = ::pipe2(&pipe_[0], O_CLOEXEC); 107 | if (-1 == r) 108 | throw exception("pipe2 failed: " + 109 | std::system_category().message(errno)); 110 | #else 111 | static std::mutex mutex; 112 | std::lock_guard lock{mutex}; 113 | ::pipe(&pipe_[0]); 114 | 115 | auto flags = ::fcntl(pipe_[0], F_GETFD, 0); 116 | ::fcntl(pipe_[0], F_SETFD, flags | FD_CLOEXEC); 117 | 118 | flags = ::fcntl(pipe_[1], F_GETFD, 0); 119 | ::fcntl(pipe_[1], F_SETFD, flags | FD_CLOEXEC); 120 | #endif 121 | } 122 | 123 | /** 124 | * Pipes may be move constructed. 125 | */ 126 | pipe_t(pipe_t&& other) 127 | { 128 | pipe_ = std::move(other.pipe_); 129 | other.pipe_[READ_END] = -1; 130 | other.pipe_[WRITE_END] = -1; 131 | } 132 | 133 | /** 134 | * Pipes are unique---they cannot be copied. 135 | */ 136 | pipe_t(const pipe_t&) = delete; 137 | 138 | /** 139 | * Writes length bytes from buf to the pipe. 140 | * 141 | * @param buf the buffer to get bytes from 142 | * @param length the number of bytes to write 143 | */ 144 | void write(const char* buf, uint64_t length) 145 | { 146 | auto bytes = ::write(pipe_[WRITE_END], buf, length); 147 | if (bytes == -1) 148 | { 149 | // interrupt, just attempt to write again 150 | if (errno == EINTR) 151 | return write(buf, length); 152 | // otherwise, unrecoverable error 153 | perror("pipe_t::write()"); 154 | throw exception{"failed to write"}; 155 | } 156 | if (bytes < static_cast(length)) 157 | write(buf + bytes, length - static_cast(bytes)); 158 | } 159 | 160 | /** 161 | * Reads up to length bytes from the pipe, placing them in buf. 162 | * 163 | * @param buf the buffer to write to 164 | * @param length the maximum number of bytes to read 165 | * @return the actual number of bytes read 166 | */ 167 | ssize_t read(char* buf, uint64_t length) 168 | { 169 | auto bytes = ::read(pipe_[READ_END], buf, length); 170 | return bytes; 171 | } 172 | 173 | /** 174 | * Closes both ends of the pipe. 175 | */ 176 | void close() 177 | { 178 | close(read_end()); 179 | close(write_end()); 180 | } 181 | 182 | /** 183 | * Closes a specific end of the pipe. 184 | */ 185 | void close(pipe_end end) 186 | { 187 | if (pipe_[end] != -1) 188 | { 189 | ::close(pipe_[end]); 190 | pipe_[end] = -1; 191 | } 192 | } 193 | 194 | /** 195 | * Determines if an end of the pipe is still open. 196 | */ 197 | bool open(pipe_end end) 198 | { 199 | return pipe_[end] != -1; 200 | } 201 | 202 | /** 203 | * Redirects the given file descriptor to the given end of the pipe. 204 | * 205 | * @param end the end of the pipe to connect to the file descriptor 206 | * @param fd the file descriptor to connect 207 | */ 208 | void dup(pipe_end end, int fd) 209 | { 210 | if (::dup2(pipe_[end], fd) == -1) 211 | { 212 | perror("pipe_t::dup()"); 213 | throw exception{"failed to dup"}; 214 | } 215 | } 216 | 217 | /** 218 | * Redirects the given end of the given pipe to the current pipe. 219 | * 220 | * @param end the end of the pipe to redirect 221 | * @param other the pipe to redirect to the current pipe 222 | */ 223 | void dup(pipe_end end, pipe_t& other) 224 | { 225 | dup(end, other.pipe_[end]); 226 | } 227 | 228 | /** 229 | * The destructor for pipes relinquishes any file descriptors that 230 | * have not yet been closed. 231 | */ 232 | ~pipe_t() 233 | { 234 | close(); 235 | } 236 | 237 | /** 238 | * An exception type for any unrecoverable errors that occur during 239 | * pipe operations. 240 | */ 241 | class exception : public std::runtime_error 242 | { 243 | public: 244 | using std::runtime_error::runtime_error; 245 | }; 246 | 247 | private: 248 | std::array pipe_; 249 | }; 250 | 251 | /** 252 | * Streambuf for reading/writing to pipes. 253 | * 254 | * @see http://www.mr-edd.co.uk/blog/beginners_guide_streambuf 255 | */ 256 | class pipe_ostreambuf : public std::streambuf 257 | { 258 | public: 259 | /** 260 | * Constructs a new streambuf, with the given buffer size and put_back 261 | * buffer space. 262 | */ 263 | pipe_ostreambuf(size_t buffer_size = 512, size_t put_back_size = 8) 264 | : put_back_size_{put_back_size}, 265 | in_buffer_(buffer_size + put_back_size) 266 | { 267 | auto end = &in_buffer_.back() + 1; 268 | setg(end, end, end); 269 | } 270 | 271 | ~pipe_ostreambuf() = default; 272 | 273 | int_type underflow() override 274 | { 275 | // if the buffer is not exhausted, return the next element 276 | if (gptr() < egptr()) 277 | return traits_type::to_int_type(*gptr()); 278 | 279 | auto base = &in_buffer_.front(); 280 | auto start = base; 281 | 282 | // if we are not the first fill of the buffer 283 | if (eback() == base) 284 | { 285 | // move the put_back area to the front 286 | const auto dest = base; 287 | const auto src = egptr() - put_back_size_ < dest ? dest : egptr() - put_back_size_; 288 | const auto area = static_cast(egptr() - dest) < put_back_size_ ? 289 | static_cast(egptr() - dest) : put_back_size_; 290 | std::memmove(dest, src, area); 291 | start += put_back_size_; 292 | } 293 | 294 | // start now points to the head of the usable area of the buffer 295 | auto bytes 296 | = stdout_pipe_.read(start, in_buffer_.size() - static_cast(start - base)); 297 | 298 | if (bytes == -1) 299 | { 300 | ::perror("read"); 301 | throw exception{"failed to read from pipe"}; 302 | } 303 | 304 | if (bytes == 0) 305 | return traits_type::eof(); 306 | 307 | setg(base, start, start + bytes); 308 | 309 | return traits_type::to_int_type(*gptr()); 310 | } 311 | 312 | /** 313 | * An exception for pipe_streambuf interactions. 314 | */ 315 | class exception : public std::runtime_error 316 | { 317 | public: 318 | using std::runtime_error::runtime_error; 319 | }; 320 | 321 | /** 322 | * Gets the stdout pipe. 323 | */ 324 | pipe_t& stdout_pipe() 325 | { 326 | return stdout_pipe_; 327 | } 328 | 329 | /** 330 | * Closes one of the pipes. This will flush any remaining bytes in the 331 | * output buffer. 332 | */ 333 | virtual void close(pipe_t::pipe_end end) 334 | { 335 | if (end == pipe_t::read_end()) 336 | stdout_pipe().close(pipe_t::read_end()); 337 | } 338 | 339 | protected: 340 | virtual void flush() { } 341 | 342 | size_t put_back_size_; 343 | pipe_t stdout_pipe_; 344 | std::vector in_buffer_; 345 | }; 346 | 347 | class pipe_streambuf : public pipe_ostreambuf 348 | { 349 | public: 350 | 351 | pipe_streambuf(size_t buffer_size = 512, size_t put_back_size = 8) 352 | : pipe_ostreambuf{buffer_size, put_back_size}, 353 | out_buffer_(buffer_size + 1) 354 | { 355 | auto begin = &out_buffer_.front(); 356 | setp(begin, begin + out_buffer_.size() - 1); 357 | } 358 | 359 | 360 | /** 361 | * Destroys the streambuf, which will flush any remaining content on 362 | * the output buffer. 363 | */ 364 | ~pipe_streambuf() 365 | { 366 | flush(); 367 | } 368 | 369 | int_type overflow(int_type ch) override 370 | { 371 | if (ch != traits_type::eof()) 372 | { 373 | *pptr() = static_cast(ch); // safe because of -1 in setp() in ctor 374 | pbump(1); 375 | flush(); 376 | return ch; 377 | } 378 | 379 | return traits_type::eof(); 380 | } 381 | 382 | int sync() override 383 | { 384 | flush(); 385 | return 0; 386 | } 387 | 388 | /** 389 | * Gets the stdin pipe. 390 | */ 391 | pipe_t& stdin_pipe() 392 | { 393 | return stdin_pipe_; 394 | } 395 | 396 | void close(pipe_t::pipe_end end) override 397 | { 398 | pipe_ostreambuf::close(end); 399 | if (end != pipe_t::read_end()) 400 | { 401 | flush(); 402 | stdin_pipe().close(pipe_t::write_end()); 403 | } 404 | } 405 | 406 | private: 407 | void flush() override 408 | { 409 | if (stdin_pipe_.open(pipe_t::write_end())) 410 | { 411 | stdin_pipe_.write(pbase(), static_cast(pptr() - pbase())); 412 | pbump(static_cast(-(pptr() - pbase()))); 413 | } 414 | } 415 | 416 | pipe_t stdin_pipe_; 417 | std::vector out_buffer_; 418 | }; 419 | 420 | class process; 421 | 422 | // Forward declaration. Will be defined later. 423 | bool running(pid_t pid); 424 | bool running(const process & pr); 425 | 426 | /** 427 | * A handle that represents a child process. 428 | */ 429 | class process 430 | { 431 | public: 432 | /** 433 | * Constructs a new child process, executing the given application and 434 | * passing the given arguments to it. 435 | */ 436 | template 437 | process(std::string application, Args&&... args) 438 | : args_{std::move(application), std::forward(args)...}, 439 | in_stream_{&pipe_buf_}, 440 | out_stream_{&pipe_buf_}, 441 | err_stream_{&err_buf_} 442 | { 443 | // nothing 444 | } 445 | 446 | /* 447 | * Adds an argument to the argument-list 448 | */ 449 | void add_argument(std::string arg) { 450 | args_.push_back(std::move(arg)); 451 | } 452 | 453 | /* 454 | * Add further arguments to the argument-list 455 | */ 456 | template 457 | void append_arguments(InputIterator first, InputIterator last) { 458 | args_.emplace(args_.end(), first, last); 459 | } 460 | 461 | /** 462 | * Sets the process to read from the standard output of another 463 | * process. 464 | */ 465 | void read_from(process& other) 466 | { 467 | read_from_ = &other; 468 | } 469 | 470 | /** 471 | * Executes the process. 472 | */ 473 | void exec() 474 | { 475 | if (pid_ != -1) 476 | throw exception{"process already started"}; 477 | 478 | pipe_t err_pipe; 479 | 480 | auto pid = fork(); 481 | if (pid == -1) 482 | { 483 | perror("fork()"); 484 | throw exception{"Failed to fork child process"}; 485 | } 486 | else if (pid == 0) 487 | { 488 | err_pipe.close(pipe_t::read_end()); 489 | pipe_buf_.stdin_pipe().close(pipe_t::write_end()); 490 | pipe_buf_.stdout_pipe().close(pipe_t::read_end()); 491 | pipe_buf_.stdout_pipe().dup(pipe_t::write_end(), STDOUT_FILENO); 492 | err_buf_.stdout_pipe().close(pipe_t::read_end()); 493 | err_buf_.stdout_pipe().dup(pipe_t::write_end(), STDERR_FILENO); 494 | 495 | if (read_from_) 496 | { 497 | read_from_->recursive_close_stdin(); 498 | pipe_buf_.stdin_pipe().close(pipe_t::read_end()); 499 | read_from_->pipe_buf_.stdout_pipe().dup(pipe_t::read_end(), 500 | STDIN_FILENO); 501 | } 502 | else 503 | { 504 | pipe_buf_.stdin_pipe().dup(pipe_t::read_end(), STDIN_FILENO); 505 | } 506 | 507 | std::vector args; 508 | args.reserve(args_.size() + 1); 509 | for (auto& arg : args_) 510 | args.push_back(const_cast(arg.c_str())); 511 | args.push_back(nullptr); 512 | 513 | limits_.set_limits(); 514 | execvp(args[0], args.data()); 515 | 516 | char err[sizeof(int)]; 517 | std::memcpy(err, &errno, sizeof(int)); 518 | err_pipe.write(err, sizeof(int)); 519 | err_pipe.close(); 520 | std::_Exit(EXIT_FAILURE); 521 | } 522 | else 523 | { 524 | err_pipe.close(pipe_t::write_end()); 525 | pipe_buf_.stdout_pipe().close(pipe_t::write_end()); 526 | err_buf_.stdout_pipe().close(pipe_t::write_end()); 527 | pipe_buf_.stdin_pipe().close(pipe_t::read_end()); 528 | if (read_from_) 529 | { 530 | pipe_buf_.stdin_pipe().close(pipe_t::write_end()); 531 | read_from_->pipe_buf_.stdout_pipe().close(pipe_t::read_end()); 532 | read_from_->err_buf_.stdout_pipe().close(pipe_t::read_end()); 533 | } 534 | pid_ = pid; 535 | 536 | char err[sizeof(int)]; 537 | auto bytes = err_pipe.read(err, sizeof(int)); 538 | if (bytes == sizeof(int)) 539 | { 540 | int ec = 0; 541 | std::memcpy(&ec, err, sizeof(int)); 542 | throw exception{"Failed to exec process: " 543 | + std::system_category().message(ec)}; 544 | } 545 | else 546 | { 547 | err_pipe.close(); 548 | } 549 | } 550 | } 551 | 552 | /** 553 | * Process handles may be moved. 554 | */ 555 | process(process&&) = default; 556 | 557 | /** 558 | * Process handles are unique: they may not be copied. 559 | */ 560 | process(const process&) = delete; 561 | 562 | /** 563 | * The destructor for a process will wait for the child if client code 564 | * has not already explicitly waited for it. 565 | */ 566 | ~process() 567 | { 568 | wait(); 569 | } 570 | 571 | /** 572 | * Gets the process id. 573 | */ 574 | pid_t id() const 575 | { 576 | return pid_; 577 | } 578 | 579 | /** 580 | * Simple wrapper for process limit settings. Currently supports 581 | * setting processing time and memory usage limits. 582 | */ 583 | class limits_t 584 | { 585 | public: 586 | /** 587 | * Sets the maximum amount of cpu time, in seconds. 588 | */ 589 | void cpu_time(rlim_t max) 590 | { 591 | lim_cpu_ = true; 592 | cpu_.rlim_cur = cpu_.rlim_max = max; 593 | } 594 | 595 | /** 596 | * Sets the maximum allowed memory usage, in bytes. 597 | */ 598 | void memory(rlim_t max) 599 | { 600 | lim_as_ = true; 601 | as_.rlim_cur = as_.rlim_max = max; 602 | } 603 | 604 | /** 605 | * Applies the set limits to the current process. 606 | */ 607 | void set_limits() 608 | { 609 | if (lim_cpu_ && setrlimit(RLIMIT_CPU, &cpu_) != 0) 610 | { 611 | perror("limits_t::set_limits()"); 612 | throw exception{"Failed to set cpu time limit"}; 613 | } 614 | 615 | if (lim_as_ && setrlimit(RLIMIT_AS, &as_) != 0) 616 | { 617 | perror("limits_t::set_limits()"); 618 | throw exception{"Failed to set memory limit"}; 619 | } 620 | } 621 | 622 | private: 623 | bool lim_cpu_ = false; 624 | rlimit cpu_; 625 | bool lim_as_ = false; 626 | rlimit as_; 627 | }; 628 | 629 | /** 630 | * Sets the limits for this process. 631 | */ 632 | void limit(const limits_t& limits) 633 | { 634 | limits_ = limits; 635 | } 636 | 637 | /** 638 | * Waits for the child to exit. 639 | */ 640 | void wait() 641 | { 642 | if (!waited_) 643 | { 644 | pipe_buf_.close(pipe_t::write_end()); 645 | err_buf_.close(pipe_t::write_end()); 646 | waitpid(pid_, &status_, 0); 647 | pid_ = -1; 648 | waited_ = true; 649 | } 650 | } 651 | 652 | /** 653 | * It wait() already called? 654 | */ 655 | bool waited() const 656 | { 657 | return waited_; 658 | } 659 | 660 | /** 661 | * Determines if process is running. 662 | */ 663 | bool running() const 664 | { 665 | return ::procxx::running(*this); 666 | } 667 | 668 | /** 669 | * Determines if the child exited properly. 670 | */ 671 | bool exited() const 672 | { 673 | if (!waited_) 674 | throw exception{"process::wait() not yet called"}; 675 | return WIFEXITED(status_); 676 | } 677 | 678 | /** 679 | * Determines if the child was killed. 680 | */ 681 | bool killed() const 682 | { 683 | if (!waited_) 684 | throw exception{"process::wait() not yet called"}; 685 | return WIFSIGNALED(status_); 686 | } 687 | 688 | /** 689 | * Determines if the child was stopped. 690 | */ 691 | bool stopped() const 692 | { 693 | if (!waited_) 694 | throw exception{"process::wait() not yet called"}; 695 | return WIFSTOPPED(status_); 696 | } 697 | 698 | /** 699 | * Gets the exit code for the child. If it was killed or stopped, the 700 | * signal that did so is returned instead. 701 | */ 702 | int code() const 703 | { 704 | if (!waited_) 705 | throw exception{"process::wait() not yet called"}; 706 | if (exited()) 707 | return WEXITSTATUS(status_); 708 | if (killed()) 709 | return WTERMSIG(status_); 710 | if (stopped()) 711 | return WSTOPSIG(status_); 712 | return -1; 713 | } 714 | 715 | /** 716 | * Closes the given end of the pipe. 717 | */ 718 | void close(pipe_t::pipe_end end) 719 | { 720 | pipe_buf_.close(end); 721 | err_buf_.close(end); 722 | } 723 | 724 | /** 725 | * Write operator. 726 | */ 727 | template 728 | friend std::ostream& operator<<(process& proc, T&& input) 729 | { 730 | return proc.in_stream_ << input; 731 | } 732 | 733 | /** 734 | * Conversion to std::ostream. 735 | */ 736 | std::ostream& input() 737 | { 738 | return in_stream_; 739 | } 740 | 741 | /** 742 | * Conversion to std::istream. 743 | */ 744 | std::istream& output() 745 | { 746 | return out_stream_; 747 | } 748 | 749 | /** 750 | * Conversion to std::istream. 751 | */ 752 | std::istream& error() 753 | { 754 | return err_stream_; 755 | } 756 | 757 | /** 758 | * Read operator. 759 | */ 760 | template 761 | friend std::istream& operator>>(process& proc, T& output) 762 | { 763 | return proc.out_stream_ >> output; 764 | } 765 | 766 | /** 767 | * An exception type for any unrecoverable errors that occur during 768 | * process operations. 769 | */ 770 | class exception : public std::runtime_error 771 | { 772 | public: 773 | using std::runtime_error::runtime_error; 774 | }; 775 | 776 | private: 777 | void recursive_close_stdin() 778 | { 779 | pipe_buf_.stdin_pipe().close(); 780 | if (read_from_) 781 | read_from_->recursive_close_stdin(); 782 | } 783 | 784 | std::vector args_; 785 | process* read_from_ = nullptr; 786 | limits_t limits_; 787 | pid_t pid_ = -1; 788 | pipe_streambuf pipe_buf_; 789 | pipe_ostreambuf err_buf_; 790 | std::ostream in_stream_; 791 | std::istream out_stream_; 792 | std::istream err_stream_; 793 | bool waited_ = false; 794 | int status_; 795 | }; 796 | 797 | /** 798 | * Class that represents a pipeline of child processes. The process objects 799 | * that are part of the pipeline are assumed to live longer than or as long 800 | * as the pipeline itself---the pipeline does not take ownership of the 801 | * processes. 802 | */ 803 | class pipeline 804 | { 805 | public: 806 | friend pipeline operator|(process& first, process& second); 807 | 808 | /** 809 | * Constructs a longer pipeline by adding an additional process. 810 | */ 811 | pipeline& operator|(process& tail) 812 | { 813 | tail.read_from(processes_.back()); 814 | processes_.emplace_back(tail); 815 | return *this; 816 | } 817 | 818 | /** 819 | * Sets limits on all processes in the pipieline. 820 | */ 821 | pipeline& limit(process::limits_t limits) 822 | { 823 | for_each([limits](process& p) 824 | { 825 | p.limit(limits); 826 | }); 827 | return *this; 828 | } 829 | 830 | /** 831 | * Executes all processes in the pipeline. 832 | */ 833 | void exec() const 834 | { 835 | for_each([](process& proc) 836 | { 837 | proc.exec(); 838 | }); 839 | } 840 | 841 | /** 842 | * Obtains the process at the head of the pipeline. 843 | */ 844 | process& head() const 845 | { 846 | return processes_.front(); 847 | } 848 | 849 | /** 850 | * Obtains the process at the tail of the pipeline. 851 | */ 852 | process& tail() const 853 | { 854 | return processes_.back(); 855 | } 856 | 857 | /** 858 | * Waits for all processes in the pipeline to finish. 859 | */ 860 | void wait() const 861 | { 862 | for_each([](process& proc) 863 | { 864 | proc.wait(); 865 | }); 866 | } 867 | 868 | /** 869 | * Performs an operation on each process in the pipeline. 870 | */ 871 | template 872 | void for_each(Function&& function) const 873 | { 874 | for (auto& proc : processes_) 875 | function(proc.get()); 876 | } 877 | 878 | private: 879 | explicit pipeline(process& head) : processes_{std::ref(head)} 880 | { 881 | // nothing 882 | } 883 | 884 | std::vector> processes_; 885 | }; 886 | 887 | /** 888 | * Begins constructing a pipeline from two processes. 889 | */ 890 | inline pipeline operator|(process& first, process& second) 891 | { 892 | pipeline p{first}; 893 | return p | second; 894 | } 895 | 896 | /** 897 | * Determines if process is running (zombies are seen as running). 898 | */ 899 | inline bool running(pid_t pid) 900 | { 901 | bool result = false; 902 | if (pid != -1) 903 | { 904 | if (0 == ::kill(pid, 0)) 905 | { 906 | int status; 907 | const auto r = ::waitpid(pid, &status, WNOHANG); 908 | if (-1 == r) 909 | { 910 | perror("waitpid()"); 911 | throw process::exception{"Failed to check process state " 912 | "by waitpid(): " 913 | + std::system_category().message(errno)}; 914 | } 915 | if (r == pid) 916 | // Process has changed its state. We must detect why. 917 | result = !WIFEXITED(status) && !WIFSIGNALED(status); 918 | else 919 | // No changes in the process status. It means that 920 | // process is running. 921 | result = true; 922 | } 923 | } 924 | 925 | return result; 926 | } 927 | 928 | /** 929 | * Determines if process is running (zombies are seen as running). 930 | */ 931 | inline bool running(const process & pr) 932 | { 933 | return running(pr.id()); 934 | } 935 | 936 | } 937 | 938 | #endif 939 | -------------------------------------------------------------------------------- /sample.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | int main() 6 | { 7 | procxx::process ping( "ping", "www.google.com", "-c", "2" ); 8 | ping.exec(); 9 | 10 | std::string line; 11 | while( std::getline( ping.output(), line ) ) 12 | { 13 | std::cout << line << std::endl; 14 | if( !ping.running() || !procxx::running(ping.id()) || !running(ping) ) 15 | { 16 | std::cout << "not running any more" << std::endl; 17 | break; 18 | } 19 | } 20 | 21 | ping.wait(); 22 | std::cout << "exit code: " << ping.code() << std::endl; 23 | 24 | return 0; 25 | } 26 | 27 | --------------------------------------------------------------------------------