00001 #ifndef REDIRECTER_IMPLEMENTATION_FILE
00002 #define REDIRECTER_IMPLEMENTATION_FILE
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 #include "ini_config.h"
00019 #include "redirecter.h"
00020
00021 #include <basis/byte_array.h>
00022 #include <basis/convert_utf.h>
00023 #include <basis/function.h>
00024 #include <basis/log_base.h>
00025 #include <basis/mutex.h>
00026 #include <basis/portable.h>
00027 #include <mechanisms/ithread.h>
00028 #include <textual/byte_format.h>
00029
00030 #include <stdlib.h>
00031 #ifdef __UNIX__
00032 #include <unistd.h>
00033 #include <sys/wait.h>
00034 #endif
00035
00036 const int IO_PAUSE_PERIOD = 50;
00037
00038 const int BUFFER_SIZE = 4096;
00039
00040 #undef LOG
00041 #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger(), to_print)
00042
00043 const char *REDIRECTER_INI = "redirecter.ini";
00044
00045
00046
00047 const char *PROCESS_SECTION = "process_id";
00048
00049
00050
00052
00053 class reader_thread : public ithread
00054 {
00055 public:
00056 reader_thread(stdio_redirecter &parent, bool is_stdout)
00057 : ithread(), _is_stdout(is_stdout), _parent(parent) {
00058 }
00059
00060 virtual ~reader_thread() {
00061 }
00062
00063 virtual void perform_activity(void *formal(ptr)) {
00064 while (!should_stop()) {
00065 _parent.std_thread_action(_is_stdout);
00066 }
00067 }
00068
00069 private:
00070 bool _is_stdout;
00071 stdio_redirecter &_parent;
00072 };
00073
00075
00076 stdio_redirecter::stdio_redirecter()
00077 :
00078 #ifdef __WIN32__
00079 _child_in(NIL), _child_out(NIL), _child_err(NIL),
00080 _parent_in(NIL), _parent_out(NIL), _parent_err(NIL),
00081 _app_handle(NIL),
00082 #endif
00083 _command(new istring),
00084 _parms(new istring),
00085 _persistent_result(OKAY),
00086 _stdout_reader(new reader_thread(*this, true)),
00087 _stderr_reader(new reader_thread(*this, false)),
00088 _stdout_queue(new byte_array),
00089 _stderr_queue(new byte_array),
00090 _lock(new mutex),
00091 _process_id(0),
00092 _exit_value(0)
00093 {
00094 }
00095
00096 stdio_redirecter::stdio_redirecter(const istring &command,
00097 const istring ¶meters)
00098 :
00099 #ifdef __WIN32__
00100 _child_in(NIL), _child_out(NIL), _child_err(NIL),
00101 _parent_in(NIL), _parent_out(NIL), _parent_err(NIL),
00102 _app_handle(NIL),
00103 #endif
00104 _command(new istring(command)),
00105 _parms(new istring(parameters)),
00106 _persistent_result(OKAY),
00107 _stdout_reader(new reader_thread(*this, true)),
00108 _stderr_reader(new reader_thread(*this, false)),
00109 _stdout_queue(new byte_array),
00110 _stderr_queue(new byte_array),
00111 _lock(new mutex),
00112 _process_id(0),
00113 _exit_value(0)
00114 {
00115 outcome ret = create_pipes();
00116 if (ret != OKAY) { _persistent_result = ret; return; }
00117 ret = launch_program(_process_id);
00118 if (ret != OKAY) { _persistent_result = ret; return; }
00119 }
00120
00121 stdio_redirecter::~stdio_redirecter()
00122 {
00123 zap_program();
00124 _process_id = 0;
00125
00126 WHACK(_stdout_queue);
00127 WHACK(_stderr_queue);
00128 WHACK(_stdout_reader);
00129 WHACK(_stderr_reader);
00130 WHACK(_command);
00131 WHACK(_parms);
00132 WHACK(_lock);
00133 }
00134
00135 outcome stdio_redirecter::reset(const istring &command,
00136 const istring ¶meters)
00137 {
00138 zap_program();
00139 _process_id = 0;
00140
00141 *_command = command;
00142 *_parms = parameters;
00143 _persistent_result = OKAY;
00144
00145 outcome ret = create_pipes();
00146 if (ret != OKAY) { _persistent_result = ret; return ret; }
00147 ret = launch_program(_process_id);
00148 if (ret != OKAY) { _persistent_result = ret; return ret; }
00149 return ret;
00150 }
00151
00152 outcome stdio_redirecter::create_pipes()
00153 {
00154 FUNCDEF("create_pipes");
00155 #ifdef __UNIX__
00156
00157
00158 if (pipe(_input_fds)) {
00159 LOG("failure to open an unnamed pipe for input.");
00160 return ACCESS_DENIED;
00161 }
00162 if (pipe(_output_fds)) {
00163 LOG("failure to open an unnamed pipe for output.");
00164 return ACCESS_DENIED;
00165 }
00166 if (pipe(_stderr_fds)) {
00167 LOG("failure to open an unnamed pipe for stderr.");
00168 return ACCESS_DENIED;
00169 }
00170 #elif defined (__WIN32__)
00171
00172
00173 SECURITY_ATTRIBUTES sa;
00174 ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
00175 sa.nLength= sizeof(SECURITY_ATTRIBUTES);
00176 sa.lpSecurityDescriptor = NIL;
00177 sa.bInheritHandle = true;
00178
00179 HANDLE in_temp = NIL, out_temp = NIL, err_temp = NIL;
00180
00181
00182
00183 if (!CreatePipe(&_child_in, &in_temp, &sa, 0)) return ACCESS_DENIED;
00184 if (!CreatePipe(&out_temp, &_child_out, &sa, 0)) return ACCESS_DENIED;
00185 if (!CreatePipe(&err_temp, &_child_err, &sa, 0)) return ACCESS_DENIED;
00186
00187 HANDLE process_handle = GetCurrentProcess();
00188
00189
00190
00191
00192
00193
00194 if (!DuplicateHandle(process_handle, in_temp, process_handle, &_parent_in,
00195 0, false, DUPLICATE_SAME_ACCESS)) return ACCESS_DENIED;
00196 if (!DuplicateHandle(process_handle, out_temp, process_handle, &_parent_out,
00197 0, false, DUPLICATE_SAME_ACCESS)) return ACCESS_DENIED;
00198 if (!DuplicateHandle(process_handle, err_temp, process_handle, &_parent_err,
00199 0, false, DUPLICATE_SAME_ACCESS)) return ACCESS_DENIED;
00200
00201
00202
00203 CloseHandle(in_temp);
00204 CloseHandle(out_temp);
00205 CloseHandle(err_temp);
00206 #endif
00207
00208 return OKAY;
00209 }
00210
00211 outcome stdio_redirecter::launch_program(int &new_process_id)
00212 {
00213 FUNCDEF("launch_program");
00214 new_process_id = 0;
00215 #ifdef __UNIX__
00216 int fork_ret = fork();
00217 if (fork_ret == 0) {
00218
00219 close(_output_fds[1]);
00220 dup2(_output_fds[0], 0);
00221 close(_input_fds[0]);
00222 dup2(_input_fds[1], 1);
00223 close(_stderr_fds[0]);
00224 dup2(_stderr_fds[1], 2);
00225
00226 char_star_array parms = portable::break_line(*_command, *_parms);
00227 execv(_command->s(), parms.observe());
00228
00229 exit(1);
00230 } else {
00231
00232 _process_id = fork_ret;
00233 new_process_id = _process_id;
00234 close(_output_fds[0]);
00235 close(_input_fds[1]);
00236 close(_stderr_fds[1]);
00237
00238 }
00239 #elif defined (__WIN32__)
00240
00241 STARTUPINFO si;
00242 ZeroMemory(&si, sizeof(STARTUPINFO));
00243 si.cb = sizeof(STARTUPINFO);
00244 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
00245 si.hStdInput = _child_in;
00246 si.hStdOutput = _child_out;
00247 si.hStdError = _child_err;
00248 si.wShowWindow = SW_HIDE;
00249
00250
00251 SECURITY_DESCRIPTOR *sec_desc = (SECURITY_DESCRIPTOR *)GlobalAlloc
00252 (GPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
00253 InitializeSecurityDescriptor(sec_desc, SECURITY_DESCRIPTOR_REVISION);
00254 SetSecurityDescriptorDacl(sec_desc, -1, 0, 0);
00255 LPSECURITY_ATTRIBUTES sec_attr = (LPSECURITY_ATTRIBUTES)GlobalAlloc(GPTR,
00256 sizeof(SECURITY_ATTRIBUTES));
00257 sec_attr->nLength = sizeof(SECURITY_ATTRIBUTES);
00258 sec_attr->lpSecurityDescriptor = sec_desc;
00259 sec_attr->bInheritHandle = true;
00260
00261 istring cmd = *_command;
00262 if (cmd[0] != '"')
00263 cmd.insert(0, "\"");
00264 if (cmd[cmd.end()] != '"')
00265 cmd += "\"";
00266 cmd += " ";
00267 cmd += *_parms;
00268
00269
00270 PROCESS_INFORMATION pi;
00271 BOOL success = CreateProcess(NIL, to_unicode_temp(cmd), sec_attr, NIL,
00272 true, CREATE_NEW_CONSOLE, NIL, NIL, &si, &pi);
00273
00274
00275 if (sec_attr != NIL) GlobalFree(sec_attr);
00276 if (sec_desc != NIL) GlobalFree(sec_desc);
00277
00278 if (success) {
00279
00280 CloseHandle(pi.hThread);
00281
00282 _app_handle = pi.hProcess;
00283
00284
00285 ini_configurator ini(REDIRECTER_INI, ini_configurator::RETURN_ONLY,
00286 ini_configurator::APPLICATION_DIRECTORY);
00287 ini.store(PROCESS_SECTION, isprintf("%d", portable::process_id()),
00288 isprintf("%d", pi.dwProcessId));
00289 _process_id = pi.dwProcessId;
00290 new_process_id = _process_id;
00291 } else {
00292 return NOT_FOUND;
00293 }
00294 #endif
00295
00296 _stdout_reader->start(NIL);
00297 _stderr_reader->start(NIL);
00298
00299 return OKAY;
00300 }
00301
00302 bool stdio_redirecter::running()
00303 {
00304 if (!_process_id) return false;
00305 if (_exit_value != 0) return false;
00306 #ifdef __UNIX__
00307 int status;
00308 pid_t pid = waitpid(_process_id, &status, WNOHANG);
00309 if (!pid) return true;
00310 if (!_exit_value) {
00311
00312 _exit_value = status;
00313 }
00314 if (WIFEXITED(status)) {
00315
00316 _process_id = 0;
00317 return false;
00318 } else if (WIFSIGNALED(status)) {
00319
00320 _process_id = 0;
00321 return false;
00322 }
00323 return true;
00324 #elif defined (__WIN32__)
00325 DWORD exit_value = 0;
00326
00327
00328 BOOL ret = GetExitCodeProcess(_app_handle, &exit_value);
00329 if (ret) {
00330
00331 if (exit_value != STILL_ACTIVE) {
00332 _exit_value = exit_value;
00333 return false;
00334 }
00335 return true;
00336 } else {
00337
00338 return true;
00339 }
00340 #endif
00341 }
00342
00343 void stdio_redirecter::close_input()
00344 {
00345 #ifdef __UNIX__
00346 close(_output_fds[1]);
00347 #elif defined(__WIN32__)
00348 if (_child_in) { CloseHandle(_child_in); _child_in = NIL; }
00349 if (_parent_in) { CloseHandle(_parent_in); _parent_in = NIL; }
00350 #endif
00351 }
00352
00353 void stdio_redirecter::zap_program()
00354 {
00355 FUNCDEF("zap_program");
00356 _stdout_reader->cancel();
00357 _stderr_reader->cancel();
00358
00359 #ifdef __UNIX__
00360 close_input();
00361 close(_stderr_fds[0]);
00362 close(_input_fds[0]);
00363
00364 if (_process_id) {
00365 kill(_process_id, 9);
00366 }
00367 _process_id = 0;
00368 #elif defined(__WIN32__)
00369 if (_app_handle) {
00370
00371
00372
00373 TerminateProcess(_app_handle, 1);
00374 }
00375
00376 close_input();
00377
00378 if (_child_out) { CloseHandle(_child_out); _child_out = NIL; }
00379 if (_parent_out) { CloseHandle(_parent_out); _parent_out = NIL; }
00380
00381 if (_child_err) { CloseHandle(_child_err); _child_err = NIL; }
00382 if (_parent_err) { CloseHandle(_parent_err); _parent_err = NIL; }
00383
00384
00385 if (_app_handle) {
00386 DWORD ret;
00387
00388
00389
00390
00391 ini_configurator ini(REDIRECTER_INI, ini_configurator::RETURN_ONLY,
00392 ini_configurator::APPLICATION_DIRECTORY);
00393 ini.delete_entry(PROCESS_SECTION, isprintf("%d", portable::process_id()));
00394
00395 GetExitCodeProcess(_app_handle, &ret);
00396 if (ret == STILL_ACTIVE) {
00397
00398 TerminateProcess(_app_handle, 1);
00399 if (WaitForSingleObject(_app_handle, 1000) == WAIT_TIMEOUT) {
00401 LOG("hmmm, we timed out waiting for the process to exit.");
00402 }
00403 }
00404 CloseHandle(_app_handle);
00405 _app_handle = NIL;
00406 }
00407 #endif
00408
00409 _stdout_reader->stop();
00410 _stderr_reader->stop();
00411 }
00412
00413 outcome stdio_redirecter::read(byte_array &received)
00414 {
00415 received.reset();
00416 if (_persistent_result != OKAY) return _persistent_result;
00417 auto_synchronizer l(*_lock);
00418 if (!_stdout_queue->length()) return NONE_READY;
00419
00420 received = *_stdout_queue;
00421 _stdout_queue->reset();
00422 return common::OKAY;
00423 }
00424
00425 outcome stdio_redirecter::write(const istring &to_write, int &written)
00426 {
00427 byte_array real_write(to_write.length(), (byte *)to_write.observe());
00428 return write(real_write, written);
00429 }
00430
00431 outcome stdio_redirecter::write(const byte_array &to_write, int &written)
00432 {
00433 FUNCDEF("write");
00434 written = 0;
00435 if (_persistent_result != OKAY) return _persistent_result;
00436 #ifdef __UNIX__
00437 int writ = ::write(_output_fds[1], to_write.observe(), to_write.length());
00438 if (writ < 0) return ACCESS_DENIED;
00439 written = writ;
00440 return OKAY;
00441 #elif defined(__WIN32__)
00442 DWORD writ = 0;
00443 BOOL ret = WriteFile(_parent_in, to_write.observe(), to_write.length(),
00444 &writ, NIL);
00445 written = writ;
00446 if (ret) return OKAY;
00447 else return ACCESS_DENIED;
00448 #endif
00449 }
00450
00451 outcome stdio_redirecter::read_stderr(byte_array &received)
00452 {
00453 received.reset();
00454 if (_persistent_result != OKAY) return _persistent_result;
00455 auto_synchronizer l(*_lock);
00456 if (!_stderr_queue->length()) return NONE_READY;
00457
00458 received = *_stderr_queue;
00459 _stderr_queue->reset();
00460 return common::OKAY;
00461 }
00462
00463 void stdio_redirecter::std_thread_action(bool is_stdout)
00464 {
00465 FUNCDEF("std_thread_action");
00466 byte_array buff(BUFFER_SIZE + 1);
00467 #ifdef __UNIX__
00468 bool ret = false;
00469 int fd = _input_fds[0];
00470 if (!is_stdout) fd = _stderr_fds[0];
00471 if (!fd) return;
00472 int bytes_read = ::read(fd, buff.access(), BUFFER_SIZE);
00473 if (!bytes_read) {
00474
00475 } else if (bytes_read > 0) {
00476 ret = true;
00477 }
00478 #elif defined(__WIN32__)
00479 HANDLE where = _parent_out;
00480 if (!is_stdout) where = _parent_err;
00481 if (!where) return;
00482
00483
00484 DWORD bytes_read = 0;
00485 BOOL ret = ReadFile(where, buff.access(), BUFFER_SIZE, &bytes_read, NIL);
00486
00487 #endif
00488 if (ret && bytes_read) {
00489 auto_synchronizer l(*_lock);
00490 byte_array *queue = _stdout_queue;
00491 if (!is_stdout) queue = _stderr_queue;
00492 *queue += buff.subarray(0, bytes_read - 1);
00493 }
00494 }
00495
00496
00497 #endif //REDIRECTER_IMPLEMENTATION_FILE
00498