00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 #include <sbia/basis/config.h>
00013
00014 #include <iostream>
00015
00016 #include <cstdlib>
00017 #include <cassert>
00018 #include <cstring>
00019 #include <algorithm>
00020
00021 #if UNIX
00022 # include <sys/wait.h>
00023 # include <signal.h>
00024 # include <sys/errno.h>
00025 # include <stdio.h>
00026 #endif
00027 #if MACOS
00028 # include <vproc.h>
00029
00030 #endif
00031
00032 #include <sbia/basis/except.h>
00033 #include <sbia/basis/subprocess.h>
00034
00035
00036 using namespace std;
00037
00038
00039 namespace sbia
00040 {
00041
00042 namespace basis
00043 {
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054 static const char* olds[] = {"\\\\", "\\\"", "\xFF"};
00055 static const char* news[] = {"\xFF", "\"", "\\"};
00056
00057
00058
00059
00060
00061 struct ConvertSpecialChars
00062 {
00063 void operator ()(string& str)
00064 {
00065 string::size_type i;
00066 for (unsigned int n = 0; n < 3; n++) {
00067 while ((i = str.find(olds[n])) != string::npos) {
00068 str.replace(i, strlen(olds[n]), news[n]);
00069 }
00070 }
00071 }
00072 };
00073
00074
00075 Subprocess::CommandLine Subprocess::split(const string& cmd)
00076 {
00077 const char whitespace[] = " \f\n\r\t\v";
00078
00079 CommandLine args;
00080 string::size_type j;
00081 string::size_type k;
00082 unsigned int n;
00083
00084 for (string::size_type i = 0; i < cmd.size(); i++) {
00085 if (cmd[i] == '\"') {
00086 j = i;
00087 do {
00088 j = cmd.find('\"', ++j);
00089 if (j == string::npos) break;
00090
00091
00092 k = j;
00093 n = 0;
00094
00095
00096 while (cmd[--k] == '\\') n++;
00097
00098 } while (n % 2);
00099
00100
00101
00102 if (j == string::npos) {
00103 args.push_back(cmd.substr(i));
00104 break;
00105 } else {
00106 args.push_back(cmd.substr(i + 1, j - i - 1));
00107 i = j;
00108 }
00109 } else if (isspace(cmd[i])) {
00110 j = cmd.find_first_not_of(whitespace, i);
00111 i = j - 1;
00112 } else {
00113 j = i;
00114 do {
00115 j = cmd.find_first_of(whitespace, ++j);
00116 if (j == string::npos) break;
00117
00118
00119 k = j;
00120 n = 0;
00121 if (cmd[j] == ' ') {
00122
00123
00124
00125 while (cmd[--k] == '\\') n++;
00126 }
00127
00128 } while (n % 2);
00129 if (j == string::npos) {
00130 args.push_back(cmd.substr(i));
00131 break;
00132 } else {
00133 args.push_back(cmd.substr(i, j - i));
00134 i = j - 1;
00135 }
00136 }
00137 }
00138
00139 for_each(args.begin(), args.end(), ConvertSpecialChars());
00140
00141 return args;
00142 }
00143
00144
00145 string Subprocess::tostring(const CommandLine& args)
00146 {
00147 const char whitespace[] = " \f\n\r\t\v";
00148
00149 string cmd;
00150 string arg;
00151 string::size_type j;
00152
00153 for (CommandLine::const_iterator i = args.begin(); i != args.end(); ++i) {
00154 if (!cmd.empty()) cmd.push_back(' ');
00155 if (i->find_first_of(whitespace) != string::npos) {
00156 arg = *i;
00157
00158 j = arg.find_first_of("\\\"");
00159 while (j != string::npos) {
00160 arg.insert(j, 1, '\\');
00161 j = arg.find_first_of("\\\"", j + 2);
00162 }
00163
00164 cmd.push_back('\"');
00165 cmd.append(arg);
00166 cmd.push_back('\"');
00167 } else {
00168 cmd.append(*i);
00169 }
00170 }
00171 return cmd;
00172 }
00173
00174
00175
00176
00177
00178
00179 Subprocess::Subprocess ()
00180 {
00181 #if WINDOWS
00182 ZeroMemory(&_info, sizeof(_info));
00183 _stdin = INVALID_HANDLE_VALUE;
00184 _stdout = INVALID_HANDLE_VALUE;
00185 _stderr = INVALID_HANDLE_VALUE;
00186 #else
00187 _info.pid = -1;
00188 _stdin = -1;
00189 _stdout = -1;
00190 _stderr = -1;
00191 #endif
00192 _status = -1;
00193 }
00194
00195
00196 Subprocess::~Subprocess ()
00197 {
00198 #if WINDOWS
00199 if (_info.hProcess) {
00200 terminate();
00201 if (_stdin) CloseHandle(_stdin);
00202 if (_stdout) CloseHandle(_stdout);
00203 if (_stderr) CloseHandle(_stderr);
00204 CloseHandle(_info.hProcess);
00205 CloseHandle(_info.hThread);
00206 }
00207 #else
00208 if (_info.pid > 0) kill();
00209 if (_stdin != -1) close(_stdin);
00210 if (_stdout != -1) close(_stdout);
00211 if (_stderr != -1) close(_stderr);
00212 #endif
00213 }
00214
00215
00216
00217
00218
00219
00220 bool Subprocess::popen(const CommandLine& args,
00221 const RedirectMode rm_in,
00222 const RedirectMode rm_out,
00223 const RedirectMode rm_err,
00224 const Environment* env)
00225 {
00226 if (!poll()) {
00227 cerr << "Subprocess::popen(): Previously opened process not terminated yet!" << endl;
00228 return false;
00229 }
00230 #if WINDOWS
00231 ZeroMemory(&_info, sizeof(_info));
00232 if (_stdin) CloseHandle(_stdin);
00233 if (_stdout) CloseHandle(_stdout);
00234 if (_stderr) CloseHandle(_stderr);
00235 _stdin = INVALID_HANDLE_VALUE;
00236 _stdout = INVALID_HANDLE_VALUE;
00237 _stderr = INVALID_HANDLE_VALUE;
00238 _status = -1;
00239
00240 SECURITY_ATTRIBUTES saAttr;
00241 HANDLE hStdIn[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
00242 HANDLE hStdOut[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
00243 HANDLE hStdErr[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
00244
00245
00246 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
00247 saAttr.bInheritHandle = TRUE;
00248 saAttr.lpSecurityDescriptor = NULL;
00249
00250
00251 if (rm_in == RM_PIPE && CreatePipe(&hStdIn[0], &hStdIn[1], &saAttr, 0) == 0) {
00252 cerr << "Subprocess::popen(): Failed to create pipe!" << endl;
00253 return false;
00254 }
00255
00256 if (rm_out == RM_PIPE && CreatePipe(&hStdOut[0], &hStdOut[1], &saAttr, 0) == 0) {
00257 cerr << "Subprocess::popen(): Failed to create pipe!" << endl;
00258 CloseHandle(hStdIn[0]);
00259 CloseHandle(hStdIn[1]);
00260 return false;
00261 }
00262
00263 if (rm_err == RM_PIPE && CreatePipe(&hStdErr[0], &hStdErr[1], &saAttr, 0) == 0) {
00264 cerr << "Subprocess::popen(): Failed to create pipe!" << endl;
00265 CloseHandle(hStdIn[0]);
00266 CloseHandle(hStdIn[1]);
00267 CloseHandle(hStdOut[0]);
00268 CloseHandle(hStdOut[1]);
00269 return false;
00270 }
00271
00272
00273 if ((hStdIn[1] != INVALID_HANDLE_VALUE && !SetHandleInformation(hStdIn[1], HANDLE_FLAG_INHERIT, 0)) ||
00274 (hStdOut[0] != INVALID_HANDLE_VALUE && !SetHandleInformation(hStdOut[0], HANDLE_FLAG_INHERIT, 0)) ||
00275 (hStdErr[0] != INVALID_HANDLE_VALUE && !SetHandleInformation(hStdErr[0], HANDLE_FLAG_INHERIT, 0))) {
00276 cerr << "Subprocess::popen(): Failed to create pipe!" << endl;
00277 CloseHandle(hStdIn[0]);
00278 CloseHandle(hStdIn[1]);
00279 CloseHandle(hStdOut[0]);
00280 CloseHandle(hStdOut[1]);
00281 CloseHandle(hStdErr[0]);
00282 CloseHandle(hStdErr[1]);
00283 return false;
00284 }
00285
00286
00287 STARTUPINFO siStartInfo;
00288 ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
00289 siStartInfo.cb = sizeof(STARTUPINFO);
00290 siStartInfo.hStdError = hStdErr[1];
00291 siStartInfo.hStdOutput = hStdOut[1];
00292 siStartInfo.hStdInput = hStdIn[0];
00293 siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
00294
00295 string cmd = tostring(args);
00296
00297 LPTSTR szCmdline = NULL;
00298 #ifdef UNICODE
00299 int n = MultiByteToWideChar(CP_UTF8, 0, cmd.c_str(), -1, NULL, 0);
00300 szCmdline = new TCHAR[n];
00301 if (szCmdline) {
00302 MultiByteToWideChar(CP_UTF8, 0, cmd.c_str(), -1, szCmdline, n);
00303 } else {
00304 cerr << "Subprocess::popen(): Failed to allocate memory!" << endl;
00305 CloseHandle(hStdIn[0]);
00306 CloseHandle(hStdIn[1]);
00307 CloseHandle(hStdOut[0]);
00308 CloseHandle(hStdOut[1]);
00309 CloseHandle(hStdErr[0]);
00310 CloseHandle(hStdErr[1]);
00311 return false;
00312 }
00313 #else
00314 szCmdline = new TCHAR[cmd.size() + 1];
00315 strncpy_s(szCmdline, cmd.size() + 1, cmd.c_str(), _TRUNCATE);
00316 #endif
00317
00318 if (!CreateProcess(NULL,
00319 szCmdline,
00320 NULL,
00321 NULL,
00322 TRUE,
00323 0,
00324 NULL,
00325 NULL,
00326 &siStartInfo,
00327 &_info)) {
00328 cerr << "Subprocess::popen(): Failed to fork process!" << endl;
00329 CloseHandle(hStdIn[0]);
00330 CloseHandle(hStdIn[1]);
00331 CloseHandle(hStdOut[0]);
00332 CloseHandle(hStdOut[1]);
00333 CloseHandle(hStdErr[0]);
00334 CloseHandle(hStdErr[1]);
00335 return false;
00336 }
00337
00338 delete [] szCmdline;
00339
00340
00341 if (hStdIn[0] != INVALID_HANDLE_VALUE) CloseHandle(hStdIn[0]);
00342 if (hStdOut[1] != INVALID_HANDLE_VALUE) CloseHandle(hStdOut[1]);
00343 if (hStdErr[1] != INVALID_HANDLE_VALUE) CloseHandle(hStdErr[1]);
00344
00345
00346 _stdin = hStdIn[1];
00347 _stdout = hStdOut[0];
00348 _stderr = hStdErr[0];
00349
00350 return true;
00351 #else
00352 _info.pid = -1;
00353 if (_stdin != -1) close(_stdin);
00354 if (_stdout != -1) close(_stdout);
00355 if (_stderr != -1) close(_stderr);
00356 _stdin = -1;
00357 _stdout = -1;
00358 _stderr = -1;
00359 _status = -1;
00360
00361
00362 int fdsin [2] = {-1, -1};
00363 int fdsout[2] = {-1, -1};
00364 int fdserr[2] = {-1, -1};
00365
00366 if (rm_in == RM_PIPE && pipe(fdsin) == -1) {
00367 cerr << "Subprocess::popen(): Failed to create pipe!" << endl;
00368 return false;
00369 }
00370
00371 if (rm_out == RM_PIPE && pipe(fdsout) == -1) {
00372 cerr << "Subprocess::popen(): Failed to create pipe!" << endl;
00373 if (fdsin[0] != -1) close(fdsin[0]);
00374 if (fdsin[1] != -1) close(fdsin[1]);
00375 return false;
00376 }
00377
00378 if (rm_err == RM_PIPE && pipe(fdserr) == -1) {
00379 cerr << "Subprocess::popen(): Failed to create pipe!" << endl;
00380 if (fdsin[0] != -1) close(fdsin[0]);
00381 if (fdsin[1] != -1) close(fdsin[1]);
00382 if (fdsout[0] != -1) close(fdsout[0]);
00383 if (fdsout[1] != -1) close(fdsout[1]);
00384 return false;
00385 }
00386
00387
00388 if ((_info.pid = fork()) == -1) {
00389 cerr << "Subprocess::popen(): Failed to fork process!" << endl;
00390 if (fdsin[0] != -1) close(fdsin[0]);
00391 if (fdsin[1] != -1) close(fdsin[1]);
00392 if (fdsout[0] != -1) close(fdsout[0]);
00393 if (fdsout[1] != -1) close(fdsout[1]);
00394 if (fdserr[0] != -1) close(fdserr[0]);
00395 if (fdserr[1] != -1) close(fdserr[1]);
00396 return false;
00397 }
00398
00399
00400
00401 #if MACOS
00402 vproc_transaction_begin(0);
00403 #endif
00404
00405 if (_info.pid == 0) {
00406
00407
00408 if (fdsin [1] != -1) close(fdsin [1]);
00409 if (fdsout[0] != -1) close(fdsout[0]);
00410 if (fdserr[0] != -1) close(fdserr[0]);
00411
00412
00413
00414
00415
00416
00417
00418
00419 if (fdsin[0] != -1) {
00420 dup2(fdsin[0], 0);
00421 close(fdsin[0]);
00422 }
00423 if (fdsout[1] != -1) {
00424 dup2(fdsout[1], 1);
00425 close(fdsout[1]);
00426 }
00427 if (rm_err == RM_STDOUT) {
00428 dup2(1, 2);
00429 } else if (fdserr[1] != -1) {
00430 dup2(fdserr[1], 2);
00431 close(fdserr[1]);
00432 }
00433
00434
00435
00436 char** argv = NULL;
00437
00438 argv = new char*[args.size() + 1];
00439 for (unsigned int i = 0; i < args.size(); i++) {
00440 argv[i] = const_cast<char*>(args[i].c_str());
00441 }
00442 argv[args.size()] = NULL;
00443
00444 if (env) {
00445 for (unsigned int i = 0; i < env->size(); ++ i) {
00446 putenv(const_cast<char*>(env->at(i).c_str()));
00447 }
00448 }
00449 execvp(argv[0], argv);
00450
00451
00452 delete [] argv;
00453
00454 exit(EXIT_FAILURE);
00455
00456 } else {
00457
00458
00459 if (fdsin [0] != -1) close(fdsin [0]);
00460 if (fdsout[1] != -1) close(fdsout[1]);
00461 if (fdserr[1] != -1) close(fdserr[1]);
00462
00463
00464 _stdin = fdsin [1];
00465 _stdout = fdsout[0];
00466 _stderr = fdserr[0];
00467
00468 return true;
00469 }
00470 #endif
00471 }
00472
00473
00474 bool Subprocess::poll() const
00475 {
00476 #if WINDOWS
00477 if (_info.hProcess) {
00478 DWORD dwStatus = 0;
00479 if (!GetExitCodeProcess(_info.hProcess, &dwStatus)) {
00480 BASIS_THROW(runtime_error, "GetExitCodeProcess() failed with error code " << GetLastError());
00481 }
00482
00483 if (_status != 130) _status = static_cast<int>(dwStatus);
00484
00485 if (_status == STILL_ACTIVE) {
00486
00487 return WaitForSingleObject(_info.hProcess, 0) != WAIT_TIMEOUT;
00488 } else {
00489 return true;
00490 }
00491 }
00492 #else
00493 if (_info.pid > 0) {
00494 pid_t pid = waitpid(_info.pid, &_status, WNOHANG | WUNTRACED | WCONTINUED);
00495 if (pid == -1 && errno != ECHILD) {
00496 char errormsg[256];
00497 strerror_r(errno, errormsg, sizeof(errormsg));
00498 BASIS_THROW(runtime_error, "waitpid() failed with error: " << errormsg << " (" << errno << ")");
00499 }
00500 return WIFEXITED(_status) || WIFSIGNALED(_status);
00501 }
00502 #endif
00503 return true;
00504 }
00505
00506
00507 bool Subprocess::wait()
00508 {
00509 #if WINDOWS
00510 if (_info.hProcess && WaitForSingleObject(_info.hProcess, INFINITE) != WAIT_FAILED) {
00511 DWORD dwStatus = 0;
00512 if (GetExitCodeProcess(_info.hProcess, &dwStatus)) {
00513
00514 if (_status != 130) _status = static_cast<int>(dwStatus);
00515 ZeroMemory(&_info, sizeof(_info));
00516 return true;
00517 }
00518 }
00519 #else
00520 if (_info.pid > 0) {
00521 pid_t pid = waitpid(_info.pid, &_status, 0);
00522 if (pid != -1 || errno == ECHILD) {
00523 _info.pid = -1;
00524 return true;
00525 }
00526 }
00527 #endif
00528 return false;
00529 }
00530
00531
00532 bool Subprocess::send_signal(int signal)
00533 {
00534 #if WINDOWS
00535 if (signal == 9) return kill();
00536 if (signal == 15) return terminate();
00537 return false;
00538 #else
00539 return _info.pid > 0 && ::kill(_info.pid, signal) == 0;
00540 #endif
00541 }
00542
00543
00544 bool Subprocess::terminate()
00545 {
00546 #if WINDOWS
00547
00548 if (TerminateProcess(_info.hProcess, 130) == 0) return false;
00549
00550
00551
00552
00553
00554
00555
00556
00557
00558
00559 _status = 130;
00560 Sleep(10);
00561 return true;
00562 #else
00563 return send_signal(SIGTERM);
00564 #endif
00565 }
00566
00567
00568 bool Subprocess::kill()
00569 {
00570 #if WINDOWS
00571 return terminate();
00572 #else
00573 return send_signal(SIGKILL);
00574 #endif
00575 }
00576
00577
00578 bool Subprocess::signaled() const
00579 {
00580 #if WINDOWS
00581
00582
00583
00584
00585
00586
00587
00588
00589
00590
00591
00592 return _status == 130;
00593 #else
00594 if (_info.pid > 0) {
00595 pid_t pid = waitpid(_info.pid, &_status, WNOHANG | WUNTRACED | WCONTINUED);
00596 int errnum = errno;
00597 if (pid == -1 && errnum != ECHILD) {
00598 char errormsg[256];
00599 strerror_r(errnum, errormsg, sizeof(errormsg));
00600 BASIS_THROW(runtime_error, "waitpid() failed with error: " << errormsg << " (" << errnum << ")");
00601 }
00602 }
00603 return WIFSIGNALED(_status);
00604 #endif
00605 }
00606
00607
00608 int Subprocess::pid() const
00609 {
00610 #if WINDOWS
00611 return _info.dwProcessId;
00612 #else
00613 return _info.pid;
00614 #endif
00615 }
00616
00617
00618 int Subprocess::returncode() const
00619 {
00620 #if WINDOWS
00621 return _status;
00622 #else
00623 return WEXITSTATUS(_status);
00624 #endif
00625 }
00626
00627
00628
00629
00630
00631
00632 bool Subprocess::communicate(std::istream& in, std::ostream& out, std::ostream& err)
00633 {
00634 const size_t nbuf = 1024;
00635 char buf[nbuf];
00636
00637
00638 #if WINDOWS
00639 if (_stdin != INVALID_HANDLE_VALUE) {
00640 #else
00641 if (_stdin != -1) {
00642 #endif
00643 while (!in.eof()) {
00644 in.read(buf, nbuf);
00645 if(in.bad()) return false;
00646 write(buf, static_cast<size_t>(in.gcount()));
00647 }
00648 #if WINDOWS
00649 CloseHandle(_stdin);
00650 _stdin = INVALID_HANDLE_VALUE;
00651 #else
00652 close(_stdin);
00653 _stdin = -1;
00654 #endif
00655 }
00656
00657 #if WINDOWS
00658 if (_stdout != INVALID_HANDLE_VALUE) {
00659 #else
00660 if (_stdout != -1) {
00661 #endif
00662 while (out.good()) {
00663 int n = read(buf, nbuf);
00664 if (n == -1) return false;
00665 if (n == 0) break;
00666 out.write(buf, n);
00667 if (out.bad()) return false;
00668 }
00669 #if WINDOWS
00670 CloseHandle(_stdout);
00671 _stdout = INVALID_HANDLE_VALUE;
00672 #else
00673 close(_stdout);
00674 _stdout = -1;
00675 #endif
00676 }
00677
00678 #if WINDOWS
00679 if (_stderr != INVALID_HANDLE_VALUE) {
00680 #else
00681 if (_stderr != -1) {
00682 #endif
00683 while (err.good()) {
00684 int n = read(buf, nbuf, true);
00685 if (n == -1) return false;
00686 if (n == 0) break;
00687 err.write(buf, n);
00688 if (err.bad()) return false;
00689 }
00690 #if WINDOWS
00691 CloseHandle(_stderr);
00692 _stderr = INVALID_HANDLE_VALUE;
00693 #else
00694 close(_stderr);
00695 _stderr = -1;
00696 #endif
00697 }
00698
00699 return wait();
00700 }
00701
00702
00703 bool Subprocess::communicate(std::ostream& out, std::ostream& err)
00704 {
00705 std::istringstream in;
00706 #if WINDOWS
00707 CloseHandle(_stdin);
00708 _stdin = INVALID_HANDLE_VALUE;
00709 #else
00710 close(_stdin);
00711 _stdin = -1;
00712 #endif
00713 return communicate(in, out, err);
00714 }
00715
00716
00717 bool Subprocess::communicate(std::ostream& out)
00718 {
00719 std::istringstream in;
00720 std::ostringstream err;
00721 #if WINDOWS
00722 CloseHandle(_stdin);
00723 _stdin = INVALID_HANDLE_VALUE;
00724 CloseHandle(_stderr);
00725 _stderr = INVALID_HANDLE_VALUE;
00726 #else
00727 close(_stdin);
00728 _stdin = -1;
00729 close(_stderr);
00730 _stderr = -1;
00731 #endif
00732 return communicate(in, out, err);
00733 }
00734
00735
00736 int Subprocess::write(const void* buf, size_t nbuf)
00737 {
00738 #if WINDOWS
00739 DWORD n;
00740 if (_stdin == INVALID_HANDLE_VALUE) return -1;
00741 return WriteFile(_stdin, static_cast<const char*>(buf), nbuf, &n, NULL);
00742 #else
00743 if (_stdin == -1) return -1;
00744 return ::write(_stdin, buf, nbuf);
00745 #endif
00746 }
00747
00748
00749 int Subprocess::read(void* buf, size_t nbuf, bool err)
00750 {
00751 #if WINDOWS
00752 DWORD n = 0;
00753 HANDLE h = _stdout;
00754 if (err && _stderr != INVALID_HANDLE_VALUE) h = _stderr;
00755 if (!ReadFile(h, static_cast<char*>(buf), nbuf, &n, NULL)) return -1;
00756 return n;
00757 #else
00758 int fds = _stdout;
00759 if (err && _stderr != -1) fds = _stderr;
00760 return ::read(fds, buf, nbuf);
00761 #endif
00762 }
00763
00764
00765
00766
00767
00768
00769 int Subprocess::call(const CommandLine& cmd)
00770 {
00771 Subprocess p;
00772 if (p.popen(cmd) && p.wait()) return p.returncode();
00773 return -1;
00774 }
00775
00776
00777 int Subprocess::call(const string& cmd)
00778 {
00779 Subprocess p;
00780 if (p.popen(cmd) && p.wait()) return p.returncode();
00781 return -1;
00782 }
00783
00784
00785 }
00786
00787 }