1 /*****************************************************************************\
3 * Name : stdio_redirecter *
4 * Author : Chris Koeritz *
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 \*****************************************************************************/
15 #include "redirecter.h"
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>
35 using namespace application;
36 using namespace basis;
37 using namespace configuration;
38 using namespace loggers;
39 using namespace processes;
40 using namespace textual;
42 namespace application {
44 const int IO_PAUSE_PERIOD = 50; // sleep for this long between read attempts.
46 const int BUFFER_SIZE = 4096; // maximum we will read at once.
49 #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print)
51 const char *REDIRECTER_INI = "redirecter.ini";
52 // used to report process ids, since there are users that need this
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.
61 class reader_thread : public ethread
64 reader_thread(stdio_redirecter &parent, bool is_stdout)
65 : ethread(), _is_stdout(is_stdout), _parent(parent) {
68 virtual ~reader_thread() {
71 virtual void perform_activity(void *formal(ptr)) {
72 while (!should_stop()) {
73 _parent.std_thread_action(_is_stdout);
78 bool _is_stdout; // if true, then stdout, if false, then stderr.
79 stdio_redirecter &_parent;
84 stdio_redirecter::stdio_redirecter()
87 _child_in(NULL_POINTER), _child_out(NULL_POINTER), _child_err(NULL_POINTER),
88 _parent_in(NULL_POINTER), _parent_out(NULL_POINTER), _parent_err(NULL_POINTER),
89 _app_handle(NULL_POINTER),
91 _command(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),
104 stdio_redirecter::stdio_redirecter(const astring &command,
105 const astring ¶meters)
108 _child_in(NULL_POINTER), _child_out(NULL_POINTER), _child_err(NULL_POINTER),
109 _parent_in(NULL_POINTER), _parent_out(NULL_POINTER), _parent_err(NULL_POINTER),
110 _app_handle(NULL_POINTER),
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),
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; }
129 stdio_redirecter::~stdio_redirecter()
134 WHACK(_stdout_queue);
135 WHACK(_stderr_queue);
136 WHACK(_stdout_reader);
137 WHACK(_stderr_reader);
143 outcome stdio_redirecter::reset(const astring &command,
144 const astring ¶meters)
150 *_parms = parameters;
151 _persistent_result = OKAY;
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; }
160 outcome stdio_redirecter::create_pipes()
162 FUNCDEF("create_pipes");
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;
170 if (pipe(_output_fds)) {
171 LOG("failure to open an unnamed pipe for output.");
172 return ACCESS_DENIED;
174 if (pipe(_stderr_fds)) {
175 LOG("failure to open an unnamed pipe for stderr.");
176 return ACCESS_DENIED;
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 = NULL_POINTER;
185 sa.bInheritHandle = true;
187 HANDLE in_temp = NULL_POINTER, out_temp = NULL_POINTER, err_temp = NULL_POINTER;
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;
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.
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;
209 // close out the handles that we're done with and don't want the child to
211 CloseHandle(in_temp);
212 CloseHandle(out_temp);
213 CloseHandle(err_temp);
219 outcome stdio_redirecter::launch_program(int &new_process_id)
221 FUNCDEF("launch_program");
224 int fork_ret = fork();
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.
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.
247 #elif defined (__WIN32__)
248 // set up the startup info struct.
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.
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;
269 astring cmd = *_command;
272 if (cmd[cmd.end()] != '"')
277 // fork off the process.
278 PROCESS_INFORMATION pi;
279 BOOL success = CreateProcess(NULL_POINTER, to_unicode_temp(cmd), sec_attr, NULL_POINTER,
280 true, CREATE_NEW_CONSOLE, NULL_POINTER, NULL_POINTER, &si, &pi);
282 // cleanup junk we allocated.
283 if (sec_attr != NULL_POINTER) GlobalFree(sec_attr);
284 if (sec_desc != NULL_POINTER) GlobalFree(sec_desc);
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;
304 _stdout_reader->start(NULL_POINTER);
305 _stderr_reader->start(NULL_POINTER);
310 bool stdio_redirecter::running()
312 if (!_process_id) return false; // nothing to check.
313 if (_exit_value != 0) return false; // gone by now.
316 pid_t pid = waitpid(_process_id, &status, WNOHANG);
317 if (!pid) return true; // still going.
319 //hmmm: is that all we need from it? unprocessed exit value?
320 _exit_value = status;
322 if (WIFEXITED(status)) {
323 // the child exited on its own.
326 } else if (WIFSIGNALED(status)) {
327 // the child was zapped by a signal.
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);
338 // store it if we had no previous version.
339 if (exit_value != STILL_ACTIVE) {
340 _exit_value = exit_value;
345 // this one seems to still be going.
351 void stdio_redirecter::close_input()
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 = NULL_POINTER; }
357 if (_parent_in) { CloseHandle(_parent_in); _parent_in = NULL_POINTER; }
361 void stdio_redirecter::zap_program()
363 FUNCDEF("zap_program");
364 _stdout_reader->cancel();
365 _stderr_reader->cancel();
369 close(_stderr_fds[0]);
370 close(_input_fds[0]);
373 kill(_process_id, 9); // end the program without any doubt.
376 #elif defined(__WIN32__)
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);
386 if (_child_out) { CloseHandle(_child_out); _child_out = NULL_POINTER; }
387 if (_parent_out) { CloseHandle(_parent_out); _parent_out = NULL_POINTER; }
389 if (_child_err) { CloseHandle(_child_err); _child_err = NULL_POINTER; }
390 if (_parent_err) { CloseHandle(_parent_err); _parent_err = NULL_POINTER; }
392 // shut down the child process if it's still there.
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
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()));
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) {
409 LOG("hmmm, we timed out waiting for the process to exit.");
412 CloseHandle(_app_handle);
413 _app_handle = NULL_POINTER;
417 _stdout_reader->stop();
418 _stderr_reader->stop();
421 outcome stdio_redirecter::read(byte_array &received)
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();
433 outcome stdio_redirecter::write(const astring &to_write, int &written)
435 byte_array real_write(to_write.length(), (abyte *)to_write.observe());
436 return write(real_write, written);
439 outcome stdio_redirecter::write(const byte_array &to_write, int &written)
443 if (_persistent_result != OKAY) return _persistent_result;
445 int writ = ::write(_output_fds[1], to_write.observe(), to_write.length());
446 if (writ < 0) return ACCESS_DENIED;
449 #elif defined(__WIN32__)
451 BOOL ret = WriteFile(_parent_in, to_write.observe(), to_write.length(),
452 &writ, NULL_POINTER);
454 if (ret) return OKAY;
455 else return ACCESS_DENIED;
459 outcome stdio_redirecter::read_stderr(byte_array &received)
462 if (_persistent_result != OKAY) return _persistent_result;
463 auto_synchronizer l(*_lock);
464 if (!_stderr_queue->length()) return NONE_READY;
466 received = *_stderr_queue;
467 _stderr_queue->reset();
471 void stdio_redirecter::std_thread_action(bool is_stdout)
473 FUNCDEF("std_thread_action");
474 byte_array buff(BUFFER_SIZE + 1);
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);
482 //indicates end of file; set flags!
483 } else if (bytes_read > 0) {
484 ret = true; // there's new data in our buffer.
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, NULL_POINTER);
494 //hmmm: if (ret && !bytes_read) {///set eof!!! }
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);