redirecter.cpp

Go to the documentation of this file.
00001 #ifndef REDIRECTER_IMPLEMENTATION_FILE
00002 #define REDIRECTER_IMPLEMENTATION_FILE
00003 
00004 /*****************************************************************************\
00005 *                                                                             *
00006 *  Name   : stdio_redirecter                                                  *
00007 *  Author : Chris Koeritz                                                     *
00008 *                                                                             *
00009 *******************************************************************************
00010 * Copyright (c) 2005-$now By Author.  This program is free software; you can  *
00011 * redistribute it and/or modify it under the terms of the GNU General Public  *
00012 * License as published by the Free Software Foundation; either version 2 of   *
00013 * the License or (at your option) any later version.  This is online at:      *
00014 *     http://www.fsf.org/copyleft/gpl.html                                    *
00015 * Please send any updates to: fred@gruntose.com                               *
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;  // sleep for this long between read attempts.
00037 
00038 const int BUFFER_SIZE = 4096;  // maximum we will read at once.
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   // used to report process ids, since there are users that need this
00045   // info currently.
00046 
00047 const char *PROCESS_SECTION = "process_id";
00048   // the section in the ini file where we store our process ids.
00049 //hmmm: above should be removed and pushed into stdio wrapper.
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;  // if true, then stdout, if false, then stderr.
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 &parameters)
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 &parameters)
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   // the input and output here are from the perspective of the parent
00157   // process and not the launched program.
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   // set up the security attributes structure that governs how the child
00172   // process is created.
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   // create pipes that we will hook up to the child process.  these are
00182   // currently inheritable based on the security attributes.
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     // retrieve process handle for use in system calls below.  since it's
00189     // a pseudo handle, we don't need to close it.
00190 
00191   // create new handles for the parent process (connected to this object) to
00192   // use.  the false indicates that the child should not inherit the properties
00193   // on these because otherwise it cannot close them.
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   // close out the handles that we're done with and don't want the child to
00202   // inherit.
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     // this is the child.
00219     close(_output_fds[1]);  // close our *input* pipe's output fd.
00220     dup2(_output_fds[0], 0);  // close our stdin and replace with input pipe.
00221     close(_input_fds[0]);  // close our *output* pipe's input fd.
00222     dup2(_input_fds[1], 1);  // close our stdout and replace with output pipe.
00223     close(_stderr_fds[0]);  // close stderr input fd.
00224     dup2(_stderr_fds[1], 2);  // close our stderr and pipe it to parent.
00225     // now we want to launch the program for real.
00226     char_star_array parms = portable::break_line(*_command, *_parms);
00227     execv(_command->s(), parms.observe());
00228     // oops.  failed to exec if we got to here.
00229     exit(1);
00230   } else {
00231     // this is the parent.
00232     _process_id = fork_ret;  // save the child's process id.
00233     new_process_id = _process_id;  // set the returned id.
00234     close(_output_fds[0]);  // close our *output* pipe's input fd.
00235     close(_input_fds[1]);  // close our *input* pipe's output fd.
00236     close(_stderr_fds[1]);  // close the child's stderr output side.
00237     // now we should have a set of pipes that talk to the child.
00238   }
00239 #elif defined (__WIN32__)
00240   // set up the startup info struct.
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;  // we'll hide the console window.
00249 
00250   // setup the security attributes for the new process.
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   // fork off the process.
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   // cleanup junk we allocated.
00275   if (sec_attr != NIL) GlobalFree(sec_attr);
00276   if (sec_desc != NIL) GlobalFree(sec_desc);
00277 
00278   if (success) {
00279     // toss out the thread handle since we don't use it.
00280     CloseHandle(pi.hThread);
00281     // track the important handle, for our application.
00282     _app_handle = pi.hProcess;
00283 //hmmm: boot this stuff out into the stdio_wrapper class, which is the only
00284 //      thing that should do this.
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;  // nothing to check.
00305   if (_exit_value != 0) return false;  // gone by now.
00306 #ifdef __UNIX__
00307   int status;
00308   pid_t pid = waitpid(_process_id, &status, WNOHANG);
00309   if (!pid) return true;  // still going.
00310   if (!_exit_value) {
00311 //hmmm: is that all we need from it?  unprocessed exit value?
00312     _exit_value = status;
00313   }
00314   if (WIFEXITED(status)) {
00315     // the child exited on its own.
00316     _process_id = 0;
00317     return false;
00318   } else if (WIFSIGNALED(status)) {
00319     // the child was zapped by a signal.
00320     _process_id = 0;
00321     return false;
00322   }
00323   return true;
00324 #elif defined (__WIN32__)
00325   DWORD exit_value = 0;
00326   // see if there's an exit code yet.  if this fails with false, then the
00327   // process is maybe long gone or something?
00328   BOOL ret = GetExitCodeProcess(_app_handle, &exit_value);
00329   if (ret) {
00330     // store it if we had no previous version.
00331     if (exit_value != STILL_ACTIVE) {
00332       _exit_value = exit_value;
00333       return false;
00334     }
00335     return true;
00336   } else {
00337     // this one seems to still be going.
00338     return true;
00339   }
00340 #endif
00341 }
00342 
00343 void stdio_redirecter::close_input()
00344 {
00345 #ifdef __UNIX__
00346   close(_output_fds[1]);  // shut down input to the child program.
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);  // end the program without any doubt.
00366   }
00367   _process_id = 0;
00368 #elif defined(__WIN32__)
00369   if (_app_handle) {
00370     // none of the handle closing works if the app is still running.
00371     // microsoft hasn't really got a clue, if you cannot close a file handle
00372     // when you want to, but that's apparently what's happening.
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   // shut down the child process if it's still there.
00385   if (_app_handle) {
00386     DWORD ret;
00387 
00388 //hmmm: also should only be in the stdio wrapper program.
00389 //hmmm: remove this in favor of the stdio wrapper or whomever tracking their
00390 //      own process id.
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       // it's still bumbling along; let's drop it.
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 //hmmm: signal eof too!
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 //signal eof too!
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;  // nothing to read from.
00472   int bytes_read = ::read(fd, buff.access(), BUFFER_SIZE);
00473   if (!bytes_read) {
00474 //indicates end of file; set flags!
00475   } else if (bytes_read > 0) {
00476     ret = true;  // there's new data in our buffer.
00477   }
00478 #elif defined(__WIN32__)
00479   HANDLE where = _parent_out;
00480   if (!is_stdout) where = _parent_err;
00481   if (!where) return;  // nothing to read from.
00482   // read some data from the file.  the function will return when a write
00483   // operation completes or we get that much data.
00484   DWORD bytes_read = 0;
00485   BOOL ret = ReadFile(where, buff.access(), BUFFER_SIZE, &bytes_read, NIL);
00486 //hmmm:    if (ret && !bytes_read) {///set eof!!! }
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 

Generated on Fri Nov 21 04:29:55 2008 for HOOPLE Libraries by  doxygen 1.5.1