00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034 #pragma warning(disable: 4355) // 'this' used in base member initializer list
00035 #pragma warning(disable: 4800) // conversion from int to bool
00036 #pragma warning(disable: 4267) // conversion from unsigned to signed
00037 #pragma warning(disable: 4244) // conversion to int from double
00038
00039 #include <windows.h>
00040 #include <atlbase.h>
00041 #include <stdio.h>
00042 #include <stdlib.h>
00043 #include <string.h>
00044 #include <sven/fs_addons.h>
00045
00046 #include <stdexcept>
00047 #include <iostream>
00048 #include <string>
00049 #include <sstream>
00050 #include <stdexcept>
00051
00052 #include <Winuser.h>
00053
00054 #include <string>
00055 #include <sven_ui/commands.h>
00056 #include <sven/except_macros.h>
00057 #include <sven/string_addons.h>
00058
00059
00060
00061 #pragma warning(disable: 4275)
00062 #pragma warning(disable: 4251)
00063
00064 #include <boost/thread/mutex.hpp>
00065 #include <boost/thread/thread.hpp>
00066 #include <boost/thread/xtime.hpp>
00067 #include <boost/thread/condition.hpp>
00068
00069 #include <sven_ui/command_line_program.h>
00070 #include <sven/message_boxes.h>
00071 #include <sven/timer.h>
00072 #include <com_helpers.h>
00073
00074
00075 using namespace std;
00076 using namespace sven;
00077
00078 namespace sven_ui {
00079
00081 void thread_sleep(double seconds) {
00082 boost::xtime xt;
00083 boost::xtime_get(&xt, boost::TIME_UTC);
00084
00085
00086
00087 xt.nsec += pow(10.0,9.0)*seconds;
00088 boost::thread::sleep(xt);
00089 }
00090
00091 namespace cmdline_prog_imp {
00092
00093 size_t tstrlen(TCHAR * str) {
00094 #ifdef _UNICODE
00095 return wcslen(str);
00096 #else
00097 return strlen(str);
00098 #endif
00099 }
00100
00109 bool pipe_read(HANDLE pipe, const int BUFSIZE, TCHAR chBuf[]) {
00110
00111 DWORD dwRead,dwRead2,avail,bytes_left;
00112 if(!PeekNamedPipe (pipe,chBuf,BUFSIZE,&dwRead,&avail,&bytes_left))
00113 return false;
00114 if(!bytes_left && !dwRead) return false;
00115 if( !ReadFile( pipe, chBuf,
00116 dwRead, &dwRead2, NULL) || dwRead == 0) {
00117 return false;
00118 }
00119
00120 chBuf[dwRead2]='\0';
00121 return true;
00122 }
00123
00124
00125
00126
00127 struct CommandLineProgram {
00128
00129 bool dying;
00130
00131 boost::mutex read_mutex;
00132 ostringstream read_buf;
00133
00134 boost::mutex read_error_mutex;
00135 ostringstream read_error_buf;
00136
00137 boost::mutex write_mutex;
00138 ostringstream write_buf;
00139
00140 boost::thread * cmd_watch_thread;
00141 boost::thread * cmd_watch_error_thread;
00142 boost::thread * cmd_write_thread;
00143
00144 HANDLE hChildStdinRd, hChildStdinWr, hChildStdinWrDup,
00145 hChildStdoutRd, hChildStdoutWr, hChildStdoutRdDup,
00146 hChildStderrorRd, hChildStderrorWr, hChildStderrorRdDup,
00147 hSaveStdin, hSaveStdout, hSaveStderror;
00148 DWORD dwProcessId;
00149
00155 double write_tick;
00156
00162 double read_tick;
00163
00166 double finish_tick;
00167
00171 double finish_timeout;
00172
00173
00176 bool seperate_std_error;
00177
00180 bool writeable;
00181
00182 fs::wpath starting_dir;
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201 struct read_function {
00202 CommandLineProgram * parent;
00203
00204 read_function(CommandLineProgram * parent) : parent(parent) {}
00205
00206 void complete_reading() {
00207
00208 const int BUFSIZE = 32;
00209 TCHAR chBuf[BUFSIZE+1];
00210
00211 while(true) {
00212
00213 if(!pipe_read(parent->hChildStdoutRdDup, BUFSIZE,chBuf)) break;
00214
00215 if(tstrlen(chBuf)) {
00216 boost::mutex::scoped_lock lock(parent->read_mutex);
00217 parent->read_buf << chBuf;
00218 }
00219
00220 thread_sleep(parent->read_tick);
00221 }
00222 }
00223
00224
00225
00226 void operator()() {
00227
00228 const int BUFSIZE =32;
00229
00230 DWORD dwRead;
00231 TCHAR chBuf[BUFSIZE+1];
00232
00233
00234 while(true) {
00235
00236 if(pipe_read(parent->hChildStdoutRdDup, BUFSIZE, chBuf)) {
00237 if(tstrlen(chBuf)) {
00238 boost::mutex::scoped_lock lock(parent->read_mutex);
00239 parent->read_buf << chBuf;
00240 }
00241 }
00242
00243 {
00244 boost::mutex::scoped_lock lock(parent->write_mutex);
00245 if(parent->dying) {
00246 break;
00247 }
00248 }
00249
00250 if(parent->read_tick) thread_sleep(parent->read_tick);
00251
00252 }
00253
00254 complete_reading() ;
00255 }
00256 };
00257
00258 struct read_error_function {
00259 CommandLineProgram * parent;
00260
00261 read_error_function(CommandLineProgram * parent) : parent(parent) {}
00262
00263
00264 void complete_reading() {
00265
00266 const int BUFSIZE = 32;
00267 TCHAR chBuf[BUFSIZE+1];
00268
00269 while(true) {
00270
00271 boost::mutex::scoped_lock lock(parent->read_error_mutex);
00272 if(!pipe_read(parent->hChildStderrorRdDup, BUFSIZE,chBuf)) break;
00273
00274 if(tstrlen(chBuf)) {
00275 boost::mutex::scoped_lock lock(parent->read_error_mutex);
00276 parent->read_error_buf << chBuf;
00277 }
00278
00279 thread_sleep(parent->read_tick);
00280 }
00281 }
00282
00283 void operator()() {
00284
00285 const int BUFSIZE =32;
00286
00287 DWORD dwRead;
00288 TCHAR chBuf[BUFSIZE+1];
00289
00290
00291 while(true) {
00292
00293 if(pipe_read(parent->hChildStderrorRdDup, BUFSIZE, chBuf)) {
00294 if(tstrlen(chBuf)) {
00295 boost::mutex::scoped_lock lock(parent->read_error_mutex);
00296 parent->read_error_buf << chBuf;
00297 }
00298 }
00299
00300 {
00301 boost::mutex::scoped_lock lock(parent->write_mutex);
00302 if(parent->dying) {
00303 break;
00304 }
00305 }
00306
00307 if(parent->read_tick) thread_sleep(parent->read_tick);
00308
00309 }
00310
00311 complete_reading() ;
00312 }
00313
00314 };
00315
00316 struct write_function {
00317 CommandLineProgram * parent;
00318 write_function(CommandLineProgram * parent) : parent(parent) {}
00319
00320 void operator()() {
00321
00322 while(true) {
00323 DWORD dwWritten;
00324
00325 string to_write;
00326 to_write="";
00327 {
00328 boost::mutex::scoped_lock lock(parent->write_mutex);
00329 to_write=parent->write_buf.str();
00330 parent->write_buf.str("");
00331 }
00332 if(to_write.size()) {
00333
00334 bool bSuccess =
00335 WriteFile( parent->hChildStdinWrDup, to_write.c_str(),to_write.size(), &dwWritten, NULL );
00336
00337
00338 if(!bSuccess) {
00339 DWORD write_error= GetLastError();
00340
00341 sven::error_msg(fmt("Could not write \"%s\" to thread.\n%s") % to_write % com_help::format_error_string(write_error));
00342
00343
00344 break;
00345 }
00346
00347
00348 }
00349
00350 if(parent->write_tick) thread_sleep(parent->write_tick);
00351
00352 {
00353 boost::mutex::scoped_lock lock(parent->write_mutex);
00354 if(parent->dying) break;
00355 }
00356 }
00357
00358 }
00359 };
00360
00361 read_function reader;
00362 read_error_function read_error_er;
00363 write_function writer;
00364
00365 CommandLineProgram()
00366 : reader(this), writer(this), read_error_er(this),
00367
00368 write_tick(.05), read_tick(.01),
00369 finish_tick(.03), finish_timeout(0),
00370
00371 seperate_std_error(false), writeable(true),
00372 dying(false)
00373 {}
00374
00375 boost::mutex handle_mutex;
00376
00377
00378
00379 void create_process(cstring name) {
00380
00381 USES_CONVERSION;
00382
00383 SECURITY_ATTRIBUTES saAttr;
00384 BOOL fSuccess;
00385
00386
00387 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
00388 saAttr.bInheritHandle = TRUE;
00389 saAttr.lpSecurityDescriptor = NULL;
00390
00391
00392
00393
00394
00395
00396
00397
00398
00399
00400 hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE);
00401
00402
00403 if( !CreatePipe( &hChildStdoutRd, &hChildStdoutWr, &saAttr, 0) )
00404 {
00405 throw std::runtime_error("Stdout pipe creation failed");
00406 }
00407
00408
00409 if( !SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr) )
00410 {
00411 throw std::runtime_error("Redirecting STDOUT failed");
00412 }
00413
00414
00415 fSuccess = DuplicateHandle( GetCurrentProcess(), hChildStdoutRd,
00416 GetCurrentProcess(), &hChildStdoutRdDup ,
00417 0, FALSE,
00418 DUPLICATE_SAME_ACCESS );
00419 if( !fSuccess )
00420 {
00421 throw std::runtime_error("DuplicateHandle failed");
00422 }
00423 CloseHandle( hChildStdoutRd );
00424
00425 if(seperate_std_error) {
00426
00427 hSaveStderror = GetStdHandle(STD_ERROR_HANDLE);
00428
00429
00430 if( !CreatePipe( &hChildStderrorRd, &hChildStderrorWr, &saAttr, 0) )
00431 {
00432 throw std::runtime_error("Stderror pipe creation failed");
00433 }
00434
00435
00436 if( !SetStdHandle(STD_ERROR_HANDLE, hChildStderrorWr) )
00437 {
00438 throw std::runtime_error("Redirecting STDERROR failed");
00439 }
00440
00441
00442 fSuccess = DuplicateHandle( GetCurrentProcess(), hChildStderrorRd,
00443 GetCurrentProcess(), &hChildStderrorRdDup ,
00444 0, FALSE,
00445 DUPLICATE_SAME_ACCESS );
00446 if( !fSuccess )
00447 {
00448 throw std::runtime_error("DuplicateHandle failed");
00449 }
00450 CloseHandle( hChildStderrorRd );
00451 }
00452
00453
00454
00455
00456
00457
00458
00459
00460
00461
00462
00463 hSaveStdin = GetStdHandle(STD_INPUT_HANDLE);
00464
00465
00466 if( !CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0) )
00467 throw std::runtime_error("Stdin pipe creation failed");
00468
00469
00470
00471 if( !SetStdHandle(STD_INPUT_HANDLE, hChildStdinRd) )
00472 throw std::runtime_error("Redirecting Stdin failed");
00473
00474
00475 fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdinWr,
00476 GetCurrentProcess(), &hChildStdinWrDup,
00477 0, FALSE,
00478 DUPLICATE_SAME_ACCESS );
00479 if( !fSuccess )
00480 throw std::runtime_error("DuplicateHandle failed");
00481
00482 CloseHandle(hChildStdinWr);
00483
00485
00486 PROCESS_INFORMATION piProcInfo;
00487 STARTUPINFOW siStartInfo;
00488
00489
00490 ZeroMemory( &siStartInfo, sizeof(STARTUPINFOW) );
00491 siStartInfo.cb = sizeof(STARTUPINFOW);
00492
00493
00494 siStartInfo.dwFlags = STARTF_USESTDHANDLES;
00495 siStartInfo.hStdInput = hChildStdinRd;
00496 siStartInfo.hStdOutput = hChildStdoutWr;
00497 siStartInfo.hStdError = seperate_std_error ?
00498 hChildStderrorWr : hChildStdoutWr;
00499
00500 wchar_t * cmd_name=new wchar_t[name.size()+1];
00501 swprintf(cmd_name,L"%s",a2w(name).c_str());
00502
00503 wstring absolute_starting_dir = (fs::wpath(get_current_directory_w(), fs::native) / starting_dir).native_directory_string();
00504 if(!starting_dir.empty()) sven_ui::report("starting dir is : " + qwp(w2a(absolute_starting_dir)) );
00505
00506
00507
00508
00509
00510
00511
00512
00513
00514
00515 BOOL ret = CreateProcessW( NULL,
00516 cmd_name,
00517 NULL,
00518 NULL,
00519 TRUE,
00520
00521 CREATE_NO_WINDOW ,
00522 NULL,
00523 absolute_starting_dir.empty() ? NULL : absolute_starting_dir.c_str(),
00524 &siStartInfo,
00525 &piProcInfo);
00526 delete [] cmd_name;
00527
00528 if( ret )
00529 dwProcessId = piProcInfo.dwProcessId;
00530 if(!ret) RT_ERROR_ARGS("CreateChildProcess failed with cmd = %s", sven::qw(w2a(cmd_name)));
00531
00532
00533
00534 if( !SetStdHandle(STD_INPUT_HANDLE, hSaveStdin) )
00535 throw std::runtime_error("Re-redirecting Stdin failed" );
00536
00537 if( !SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout) )
00538 throw std::runtime_error("Re-redirecting Stdout failed");
00539
00540 if( seperate_std_error && !SetStdHandle(STD_ERROR_HANDLE, hSaveStderror) )
00541 throw std::runtime_error("Re-redirecting Stderror failed");
00542
00543 cmd_watch_thread=new boost::thread(reader);
00544 if(seperate_std_error )
00545 cmd_watch_error_thread=new boost::thread(read_error_er);
00546 cmd_write_thread=new boost::thread(writer);
00547
00548 }
00549
00550
00551 void write_line(string s) {
00552 if(!writeable) {
00553 RT_ERROR_ARGS("can't write \"%s\" to process, because the process has not been declared as writeable.",s);
00554 }
00555 boost::mutex::scoped_lock lock(write_mutex);
00556 write_buf << s << endl;
00557 }
00558
00559 string read() {
00560 boost::mutex::scoped_lock lock(read_mutex);
00561 string rval = read_buf.str();
00562 sven::delete_carriage_returns(rval);
00563 read_buf.str("");
00564 return rval;
00565 }
00566 string read_error() {
00567 boost::mutex::scoped_lock lock(read_error_mutex);
00568 string rval = read_error_buf.str();
00569 sven::delete_carriage_returns(rval);
00570 read_error_buf.str("");
00571 return rval;
00572 }
00573
00574
00575 ~CommandLineProgram() {
00576
00577
00578
00579
00580
00581
00582
00583
00584 }
00585
00586
00587 bool process_is_dead() {
00588 DWORD status;
00589 HANDLE hProcess=OpenProcess(PROCESS_QUERY_INFORMATION,FALSE,dwProcessId);
00590 if(!hProcess) throw std::runtime_error("tried to query idle for a process that doesn't seem to exist at all...");
00591 GetExitCodeProcess(hProcess,&status);
00592 CloseHandle( hProcess );
00593 return status!=STILL_ACTIVE;
00594 }
00595
00596 void terminate_threads() {
00597
00598 {
00599 boost::mutex::scoped_lock lock2(read_mutex);
00600 boost::mutex::scoped_lock lock3(write_mutex);
00601 dying =true;
00602 }
00603
00604
00605
00606
00607
00608
00609
00610 cmd_watch_thread->join();
00611 if(seperate_std_error) cmd_watch_error_thread->join();
00612 cmd_write_thread->join();
00613
00614 delete cmd_watch_thread;
00615 if(seperate_std_error) delete cmd_watch_error_thread;
00616 delete cmd_write_thread;
00617
00618
00619
00620 {
00621 boost::mutex::scoped_lock lock(handle_mutex);
00622
00623 if(!process_is_dead()) {
00624 HANDLE hProcess = OpenProcess( PROCESS_TERMINATE, FALSE, dwProcessId );
00625 if( hProcess )
00626 {
00627
00628 TerminateProcess( hProcess,0 );
00629 }
00630 }
00631
00632 CloseHandle( hChildStdinRd);
00633 CloseHandle( hChildStdoutWr);
00634 CloseHandle( hChildStdinWrDup );
00635 CloseHandle( hChildStdoutRdDup );
00636
00637 if(seperate_std_error) {
00638 CloseHandle( hChildStderrorWr);
00639 CloseHandle( hChildStderrorRdDup );
00640 }
00641 }
00642 }
00643
00644 void wait_for_complete() {
00645 if(finish_timeout>0) {
00646 sven::timer t;
00647 while(!process_is_dead()) {
00648 thread_sleep(finish_tick);
00649 if(t.elapsed()>finish_timeout) RT_ERROR_ARGS("process did not complete within specified time (%s).", t.human_readable());
00650 }
00651 }
00652 else {
00653 while(!process_is_dead()) thread_sleep(finish_tick);
00654 }
00655 }
00656
00657 void finish() {
00658 wait_for_complete();
00659 terminate_threads();
00660 }
00661 };
00662
00663 }
00664
00665
00666
00667
00668
00669
00670
00671
00672
00673 CommandLineProgram::CommandLineProgram() :
00674 wrapped(new cmdline_prog_imp::CommandLineProgram()),
00675 #define REF(x) x(wrapped->x)
00676 REF(write_tick), REF(read_tick), REF(finish_tick), REF(finish_timeout),
00677 REF(seperate_std_error), REF(writeable), REF(starting_dir)
00678 #undef REF
00679 {}
00680
00681 CommandLineProgram::~CommandLineProgram() { delete wrapped;}
00682
00683 void CommandLineProgram::write_line(cstring s) { wrapped->write_line(s); }
00684
00685 string CommandLineProgram::read() { return wrapped->read(); }
00686 string CommandLineProgram::read_error() { return wrapped->read_error(); }
00687
00688 bool CommandLineProgram::process_is_dead() { return wrapped->process_is_dead(); }
00689
00690 void CommandLineProgram::finish() { return wrapped->finish(); }
00691 void CommandLineProgram::create_process(cstring name) { return wrapped->create_process(name); }
00692 void CommandLineProgram::terminate_threads() { wrapped->terminate_threads(); }
00693
00694 }