first check-in of feisty meow codebase. many things broken still due to recent
[feisty_meow.git] / core / library / application / redirecter.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : stdio_redirecter                                                  *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 2005-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 \*****************************************************************************/
14
15 #include "redirecter.h"
16
17 #include <application/windoze_helper.h>
18 #include <basis/byte_array.h>
19 #include <basis/utf_conversion.h>
20 #include <basis/functions.h>
21 #include <basis/mutex.h>
22 #include <configuration/application_configuration.h>
23 #include <configuration/ini_configurator.h>
24 #include <loggers/program_wide_logger.h>
25 #include <processes/ethread.h>
26 #include <processes/launch_process.h>
27 #include <textual/byte_formatter.h>
28
29 #include <stdlib.h>
30 #ifdef __UNIX__
31   #include <unistd.h>
32   #include <sys/wait.h>
33 #endif
34
35 using namespace application;
36 using namespace basis;
37 using namespace configuration;
38 using namespace loggers;
39 using namespace processes;
40 using namespace textual;
41
42 namespace application {
43
44 const int IO_PAUSE_PERIOD = 50;  // sleep for this long between read attempts.
45
46 const int BUFFER_SIZE = 4096;  // maximum we will read at once.
47
48 #undef LOG
49 #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print)
50
51 const char *REDIRECTER_INI = "redirecter.ini";
52   // used to report process ids, since there are users that need this
53   // info currently.
54
55 const char *PROCESS_SECTION = "process_id";
56   // the section in the ini file where we store our process ids.
57 //hmmm: above should be removed and pushed into stdio wrapper.
58
59 //////////////
60
61 class reader_thread : public ethread
62 {
63 public:
64   reader_thread(stdio_redirecter &parent, bool is_stdout)
65   : ethread(), _is_stdout(is_stdout), _parent(parent) {
66   }
67
68   virtual ~reader_thread() {
69   }
70
71   virtual void perform_activity(void *formal(ptr)) {
72     while (!should_stop()) {
73       _parent.std_thread_action(_is_stdout);
74     }
75   }
76
77 private:
78   bool _is_stdout;  // if true, then stdout, if false, then stderr.
79   stdio_redirecter &_parent;
80 };
81
82 //////////////
83
84 stdio_redirecter::stdio_redirecter()
85 :
86 #ifdef __WIN32__
87   _child_in(NIL), _child_out(NIL), _child_err(NIL),
88   _parent_in(NIL), _parent_out(NIL), _parent_err(NIL),
89   _app_handle(NIL),
90 #endif
91   _command(new astring),
92   _parms(new astring),
93   _persistent_result(OKAY),
94   _stdout_reader(new reader_thread(*this, true)),
95   _stderr_reader(new reader_thread(*this, false)),
96   _stdout_queue(new byte_array),
97   _stderr_queue(new byte_array),
98   _lock(new mutex),
99   _process_id(0),
100   _exit_value(0)
101 {
102 }
103
104 stdio_redirecter::stdio_redirecter(const astring &command,
105     const astring &parameters)
106 :
107 #ifdef __WIN32__
108   _child_in(NIL), _child_out(NIL), _child_err(NIL),
109   _parent_in(NIL), _parent_out(NIL), _parent_err(NIL),
110   _app_handle(NIL),
111 #endif
112   _command(new astring(command)),
113   _parms(new astring(parameters)),
114   _persistent_result(OKAY),
115   _stdout_reader(new reader_thread(*this, true)),
116   _stderr_reader(new reader_thread(*this, false)),
117   _stdout_queue(new byte_array),
118   _stderr_queue(new byte_array),
119   _lock(new mutex),
120   _process_id(0),
121   _exit_value(0)
122 {
123   outcome ret = create_pipes();
124   if (ret != OKAY) { _persistent_result = ret; return; }
125   ret = launch_program(_process_id);
126   if (ret != OKAY) { _persistent_result = ret; return; }
127 }
128
129 stdio_redirecter::~stdio_redirecter()
130 {
131   zap_program();
132   _process_id = 0;
133
134   WHACK(_stdout_queue);
135   WHACK(_stderr_queue);
136   WHACK(_stdout_reader);
137   WHACK(_stderr_reader);
138   WHACK(_command);
139   WHACK(_parms);
140   WHACK(_lock);
141 }
142
143 outcome stdio_redirecter::reset(const astring &command,
144     const astring &parameters)
145 {
146   zap_program();
147   _process_id = 0;
148
149   *_command = command;
150   *_parms = parameters;
151   _persistent_result = OKAY;
152
153   outcome ret = create_pipes();
154   if (ret != OKAY) { _persistent_result = ret; return ret; }
155   ret = launch_program(_process_id);
156   if (ret != OKAY) { _persistent_result = ret; return ret; }
157   return ret;
158 }
159
160 outcome stdio_redirecter::create_pipes()
161 {
162   FUNCDEF("create_pipes");
163 #ifdef __UNIX__
164   // the input and output here are from the perspective of the parent
165   // process and not the launched program.
166   if (pipe(_input_fds)) {
167     LOG("failure to open an unnamed pipe for input.");
168     return ACCESS_DENIED;
169   }
170   if (pipe(_output_fds)) {
171     LOG("failure to open an unnamed pipe for output.");
172     return ACCESS_DENIED;
173   }
174   if (pipe(_stderr_fds)) {
175     LOG("failure to open an unnamed pipe for stderr.");
176     return ACCESS_DENIED;
177   }
178 #elif defined (__WIN32__)
179   // set up the security attributes structure that governs how the child
180   // process is created.
181   SECURITY_ATTRIBUTES sa;
182   ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
183   sa.nLength= sizeof(SECURITY_ATTRIBUTES);
184   sa.lpSecurityDescriptor = NIL;
185   sa.bInheritHandle = true;
186
187   HANDLE in_temp = NIL, out_temp = NIL, err_temp = NIL;
188
189   // create pipes that we will hook up to the child process.  these are
190   // currently inheritable based on the security attributes.
191   if (!CreatePipe(&_child_in, &in_temp, &sa, 0)) return ACCESS_DENIED;
192   if (!CreatePipe(&out_temp, &_child_out, &sa, 0)) return ACCESS_DENIED;
193   if (!CreatePipe(&err_temp, &_child_err, &sa, 0)) return ACCESS_DENIED;
194
195   HANDLE process_handle = GetCurrentProcess();
196     // retrieve process handle for use in system calls below.  since it's
197     // a pseudo handle, we don't need to close it.
198
199   // create new handles for the parent process (connected to this object) to
200   // use.  the false indicates that the child should not inherit the properties
201   // on these because otherwise it cannot close them.
202   if (!DuplicateHandle(process_handle, in_temp, process_handle, &_parent_in,
203       0, false, DUPLICATE_SAME_ACCESS)) return ACCESS_DENIED;
204   if (!DuplicateHandle(process_handle, out_temp, process_handle, &_parent_out,
205       0, false, DUPLICATE_SAME_ACCESS)) return ACCESS_DENIED;
206   if (!DuplicateHandle(process_handle, err_temp, process_handle, &_parent_err,
207       0, false, DUPLICATE_SAME_ACCESS)) return ACCESS_DENIED;
208
209   // close out the handles that we're done with and don't want the child to
210   // inherit.
211   CloseHandle(in_temp);
212   CloseHandle(out_temp);
213   CloseHandle(err_temp);
214 #endif
215
216   return OKAY;
217 }
218
219 outcome stdio_redirecter::launch_program(int &new_process_id)
220 {
221 //  FUNCDEF("launch_program");
222   new_process_id = 0;
223 #ifdef __UNIX__
224   int fork_ret = fork();
225   if (fork_ret == 0) {
226     // this is the child.
227     close(_output_fds[1]);  // close our *input* pipe's output fd.
228     dup2(_output_fds[0], 0);  // close our stdin and replace with input pipe.
229     close(_input_fds[0]);  // close our *output* pipe's input fd.
230     dup2(_input_fds[1], 1);  // close our stdout and replace with output pipe.
231     close(_stderr_fds[0]);  // close stderr input fd.
232     dup2(_stderr_fds[1], 2);  // close our stderr and pipe it to parent.
233     // now we want to launch the program for real.
234     processes::char_star_array parms = launch_process::break_line(*_command, *_parms);
235     execv(_command->s(), parms.observe());
236     // oops.  failed to exec if we got to here.
237     exit(1);
238   } else {
239     // this is the parent.
240     _process_id = fork_ret;  // save the child's process id.
241     new_process_id = _process_id;  // set the returned id.
242     close(_output_fds[0]);  // close our *output* pipe's input fd.
243     close(_input_fds[1]);  // close our *input* pipe's output fd.
244     close(_stderr_fds[1]);  // close the child's stderr output side.
245     // now we should have a set of pipes that talk to the child.
246   }
247 #elif defined (__WIN32__)
248   // set up the startup info struct.
249   STARTUPINFO si;
250   ZeroMemory(&si, sizeof(STARTUPINFO));
251   si.cb = sizeof(STARTUPINFO);
252   si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
253   si.hStdInput  = _child_in;
254   si.hStdOutput = _child_out;
255   si.hStdError  = _child_err;
256   si.wShowWindow = SW_HIDE;  // we'll hide the console window.
257
258   // setup the security attributes for the new process.
259   SECURITY_DESCRIPTOR *sec_desc = (SECURITY_DESCRIPTOR *)GlobalAlloc
260       (GPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
261   InitializeSecurityDescriptor(sec_desc, SECURITY_DESCRIPTOR_REVISION);
262   SetSecurityDescriptorDacl(sec_desc, -1, 0, 0);
263   LPSECURITY_ATTRIBUTES sec_attr = (LPSECURITY_ATTRIBUTES)GlobalAlloc(GPTR,
264       sizeof(SECURITY_ATTRIBUTES));
265   sec_attr->nLength = sizeof(SECURITY_ATTRIBUTES);
266   sec_attr->lpSecurityDescriptor = sec_desc;
267   sec_attr->bInheritHandle = true;
268
269   astring cmd = *_command;
270   if (cmd[0] != '"')
271     cmd.insert(0, "\"");
272   if (cmd[cmd.end()] != '"')
273     cmd += "\"";
274   cmd += " ";
275   cmd += *_parms;
276
277   // fork off the process.
278   PROCESS_INFORMATION pi;
279   BOOL success = CreateProcess(NIL, to_unicode_temp(cmd), sec_attr, NIL,
280       true, CREATE_NEW_CONSOLE, NIL, NIL, &si, &pi);
281
282   // cleanup junk we allocated.
283   if (sec_attr != NIL) GlobalFree(sec_attr);
284   if (sec_desc != NIL) GlobalFree(sec_desc);
285
286   if (success) {
287     // toss out the thread handle since we don't use it.
288     CloseHandle(pi.hThread);
289     // track the important handle, for our application.
290     _app_handle = pi.hProcess;
291 //hmmm: boot this stuff out into the stdio_wrapper class, which is the only
292 //      thing that should do this.
293     ini_configurator ini(REDIRECTER_INI, ini_configurator::RETURN_ONLY,
294             ini_configurator::APPLICATION_DIRECTORY);
295     ini.store(PROCESS_SECTION, a_sprintf("%d", application_configuration::process_id()),
296         a_sprintf("%d", pi.dwProcessId));
297     _process_id = pi.dwProcessId;
298     new_process_id = _process_id;
299   } else {
300     return NOT_FOUND;
301   }
302 #endif
303
304   _stdout_reader->start(NIL);
305   _stderr_reader->start(NIL);
306
307   return OKAY;
308 }
309
310 bool stdio_redirecter::running()
311 {
312   if (!_process_id) return false;  // nothing to check.
313   if (_exit_value != 0) return false;  // gone by now.
314 #ifdef __UNIX__
315   int status;
316   pid_t pid = waitpid(_process_id, &status, WNOHANG);
317   if (!pid) return true;  // still going.
318   if (!_exit_value) {
319 //hmmm: is that all we need from it?  unprocessed exit value?
320     _exit_value = status;
321   }
322   if (WIFEXITED(status)) {
323     // the child exited on its own.
324     _process_id = 0;
325     return false;
326   } else if (WIFSIGNALED(status)) {
327     // the child was zapped by a signal.
328     _process_id = 0;
329     return false;
330   }
331   return true;
332 #elif defined (__WIN32__)
333   DWORD exit_value = 0;
334   // see if there's an exit code yet.  if this fails with false, then the
335   // process is maybe long gone or something?
336   BOOL ret = GetExitCodeProcess(_app_handle, &exit_value);
337   if (ret) {
338     // store it if we had no previous version.
339     if (exit_value != STILL_ACTIVE) {
340       _exit_value = exit_value;
341       return false;
342     }
343     return true;
344   } else {
345     // this one seems to still be going.
346     return true;
347   }
348 #endif
349 }
350
351 void stdio_redirecter::close_input()
352 {
353 #ifdef __UNIX__
354   close(_output_fds[1]);  // shut down input to the child program.
355 #elif defined(__WIN32__)
356   if (_child_in) { CloseHandle(_child_in); _child_in = NIL; }
357   if (_parent_in) { CloseHandle(_parent_in); _parent_in = NIL; }
358 #endif
359 }
360
361 void stdio_redirecter::zap_program()
362 {
363   FUNCDEF("zap_program");
364   _stdout_reader->cancel();
365   _stderr_reader->cancel();
366
367 #ifdef __UNIX__
368   close_input();
369   close(_stderr_fds[0]);
370   close(_input_fds[0]);
371
372   if (_process_id) {
373     kill(_process_id, 9);  // end the program without any doubt.
374   }
375   _process_id = 0;
376 #elif defined(__WIN32__)
377   if (_app_handle) {
378     // none of the handle closing works if the app is still running.
379     // microsoft hasn't really got a clue, if you cannot close a file handle
380     // when you want to, but that's apparently what's happening.
381     TerminateProcess(_app_handle, 1);
382   }
383
384   close_input();
385
386   if (_child_out) { CloseHandle(_child_out); _child_out = NIL; }
387   if (_parent_out) { CloseHandle(_parent_out); _parent_out = NIL; }
388
389   if (_child_err) { CloseHandle(_child_err); _child_err = NIL; }
390   if (_parent_err) { CloseHandle(_parent_err); _parent_err = NIL; }
391
392   // shut down the child process if it's still there.
393   if (_app_handle) {
394     DWORD ret;
395
396 //hmmm: also should only be in the stdio wrapper program.
397 //hmmm: remove this in favor of the stdio wrapper or whomever tracking their
398 //      own process id.
399     ini_configurator ini(REDIRECTER_INI, ini_configurator::RETURN_ONLY,
400             ini_configurator::APPLICATION_DIRECTORY);
401     ini.delete_entry(PROCESS_SECTION, a_sprintf("%d", application_configuration::process_id()));
402
403     GetExitCodeProcess(_app_handle, &ret);
404     if (ret == STILL_ACTIVE) {
405       // it's still bumbling along; let's drop it.
406       TerminateProcess(_app_handle, 1);
407       if (WaitForSingleObject(_app_handle, 1000) == WAIT_TIMEOUT) {
408 ///problem!
409         LOG("hmmm, we timed out waiting for the process to exit.");
410       }
411     }
412     CloseHandle(_app_handle);
413     _app_handle = NIL;
414   }
415 #endif
416
417   _stdout_reader->stop();
418   _stderr_reader->stop();
419 }
420
421 outcome stdio_redirecter::read(byte_array &received)
422 {
423   received.reset();
424   if (_persistent_result != OKAY) return _persistent_result;
425   auto_synchronizer l(*_lock);
426   if (!_stdout_queue->length()) return NONE_READY;
427 //hmmm: signal eof too!
428   received = *_stdout_queue;
429   _stdout_queue->reset();
430   return common::OKAY;
431 }
432
433 outcome stdio_redirecter::write(const astring &to_write, int &written)
434 {
435   byte_array real_write(to_write.length(), (abyte *)to_write.observe());
436   return write(real_write, written);
437 }
438
439 outcome stdio_redirecter::write(const byte_array &to_write, int &written)
440 {
441 //  FUNCDEF("write");
442   written = 0;
443   if (_persistent_result != OKAY) return _persistent_result;
444 #ifdef __UNIX__
445   int writ = ::write(_output_fds[1], to_write.observe(), to_write.length());
446   if (writ < 0) return ACCESS_DENIED;
447   written = writ;
448   return OKAY;
449 #elif defined(__WIN32__)
450   DWORD writ = 0;
451   BOOL ret = WriteFile(_parent_in, to_write.observe(), to_write.length(),
452       &writ, NIL);
453   written = writ;
454   if (ret) return OKAY;
455   else return ACCESS_DENIED;
456 #endif
457 }
458
459 outcome stdio_redirecter::read_stderr(byte_array &received)
460 {
461   received.reset();
462   if (_persistent_result != OKAY) return _persistent_result;
463   auto_synchronizer l(*_lock);
464   if (!_stderr_queue->length()) return NONE_READY;
465 //signal eof too!
466   received = *_stderr_queue;
467   _stderr_queue->reset();
468   return common::OKAY;
469 }
470
471 void stdio_redirecter::std_thread_action(bool is_stdout)
472 {
473 //  FUNCDEF("std_thread_action");
474   byte_array buff(BUFFER_SIZE + 1);
475 #ifdef __UNIX__
476   bool ret = false;
477   int fd = _input_fds[0];
478   if (!is_stdout) fd = _stderr_fds[0];
479   if (!fd) return;  // nothing to read from.
480   int bytes_read = ::read(fd, buff.access(), BUFFER_SIZE);
481   if (!bytes_read) {
482 //indicates end of file; set flags!
483   } else if (bytes_read > 0) {
484     ret = true;  // there's new data in our buffer.
485   }
486 #elif defined(__WIN32__)
487   HANDLE where = _parent_out;
488   if (!is_stdout) where = _parent_err;
489   if (!where) return;  // nothing to read from.
490   // read some data from the file.  the function will return when a write
491   // operation completes or we get that much data.
492   DWORD bytes_read = 0;
493   BOOL ret = ReadFile(where, buff.access(), BUFFER_SIZE, &bytes_read, NIL);
494 //hmmm:    if (ret && !bytes_read) {///set eof!!! }
495 #endif
496   if (ret && bytes_read) {
497     auto_synchronizer l(*_lock);
498     byte_array *queue = _stdout_queue;
499     if (!is_stdout) queue = _stderr_queue;
500     *queue += buff.subarray(0, bytes_read - 1);
501   }
502 }
503
504 } //namespace.
505
506