1 /**************************************************************************
2  Copyright:
3       (C) 2008 - 2012  Alexander Shaduri <ashaduri 'at' gmail.com>
4  License: See LICENSE_gsmartcontrol.txt
5 ***************************************************************************/
6 /// \file
7 /// \author Alexander Shaduri
8 /// \ingroup applib
9 /// \weakgroup applib
10 /// @{
11 
12 #include <sys/types.h>
13 #include <cerrno>  // errno (not std::errno, it may be a macro)
14 
15 #ifdef _WIN32
16 // 	#include <io.h>  // close()
17 #else
18 	#include <sys/wait.h>  // waitpid()'s W* macros
19 // 	#include <unistd.h>  // close()
20 #endif
21 
22 #include "hz/process_signal.h"  // hz::process_signal_send, win32's W*
23 #include "hz/tls.h"
24 #include "hz/tls_policy_glib.h"
25 #include "hz/debug.h"
26 #include "hz/string_num.h"  // hz::number_to_string()
27 #include "hz/env_tools.h"  // hz::ScopedEnv
28 #include "hz/scoped_ptr.h"
29 
30 #include "cmdex.h"
31 
32 
33 using hz::Error;
34 using hz::ErrorLevel;
35 
36 
37 
38 
39 // this is needed because these callbacks are called by glib.
40 extern "C" {
41 
42 
43 	// callbacks
44 
45 	/// Child process watcher callback
cmdex_child_watch_handler(GPid arg_pid,int waitpid_status,gpointer data)46 	inline void cmdex_child_watch_handler(GPid arg_pid, int waitpid_status, gpointer data)
47 	{
48 		Cmdex::on_child_watch_handler(arg_pid, waitpid_status, data);
49 	}
50 
51 
52 	/// Child process stdout handler callback
cmdex_on_channel_io_stdout(GIOChannel * source,GIOCondition cond,gpointer data)53 	inline gboolean cmdex_on_channel_io_stdout(GIOChannel* source, GIOCondition cond, gpointer data)
54 	{
55 		return Cmdex::on_channel_io(source, cond, static_cast<Cmdex*>(data), Cmdex::channel_type_stdout);
56 	}
57 
58 
59 	/// Child process stderr handler callback
cmdex_on_channel_io_stderr(GIOChannel * source,GIOCondition cond,gpointer data)60 	inline gboolean cmdex_on_channel_io_stderr(GIOChannel* source, GIOCondition cond, gpointer data)
61 	{
62 		return Cmdex::on_channel_io(source, cond, static_cast<Cmdex*>(data), Cmdex::channel_type_stderr);
63 	}
64 
65 
66 	/// Child process termination timeout handler
cmdex_on_term_timeout(gpointer data)67 	inline gboolean cmdex_on_term_timeout(gpointer data)
68 	{
69 		DBG_FUNCTION_ENTER_MSG;
70 		Cmdex* self = static_cast<Cmdex*>(data);
71 		self->try_stop(hz::SIGNAL_SIGTERM);
72 		return false;  // one-time call
73 	}
74 
75 
76 	/// Child process kill timeout handler
cmdex_on_kill_timeout(gpointer data)77 	inline gboolean cmdex_on_kill_timeout(gpointer data)
78 	{
79 		DBG_FUNCTION_ENTER_MSG;
80 		Cmdex* self = static_cast<Cmdex*>(data);
81 		self->try_stop(hz::SIGNAL_SIGKILL);
82 		return false;  // one-time call
83 	}
84 
85 
86 
87 }  // extern "C"
88 
89 
90 
91 
92 
execute()93 bool Cmdex::execute()
94 {
95 	DBG_FUNCTION_ENTER_MSG;
96 	if (this->running_ || this->stopped_cleanup_needed()) {
97 		return false;
98 	}
99 
100 	cleanup_members();
101 	clear_errors();
102 	str_stdout_.clear();
103 	str_stderr_.clear();
104 
105 
106 	std::string cmd = command_exec_ + " " + command_args_;
107 
108 
109 	// Make command vector
110 
111 	hz::scoped_ptr<gchar*> argvp(0, g_strfreev);  // args vector
112 
113 	{
114 		int argcp = 0;  // number of args
115 		hz::scoped_ptr<GError> shell_error(0, g_error_free);
116 		if (!g_shell_parse_argv(cmd.c_str(), &argcp, &argvp.get_ref(), &shell_error.get_ref())) {
117 			push_error(Error<void>("gshell", ErrorLevel::error, shell_error->message), false);
118 			return false;
119 		}
120 	}
121 
122 
123 	// Set the locale for a child to Classic - otherwise it may mangle the output.
124 	// TODO: make this controllable.
125 	bool change_lang = true;
126 	#ifdef _WIN32
127 		// LANG is posix-only, so it has no effect on win32.
128 		// Unfortunately, I was unable to find a way to execute a child with a different
129 		// locale in win32. Locale seems to be non-inheritable, so setting it here won't help.
130 		change_lang = false;
131 	#endif
132 
133 	hz::ScopedEnv lang_env("LANG", "C", change_lang);
134 
135 
136 	debug_out_info("app", DBG_FUNC_MSG << "Executing \"" << cmd << "\".\n");
137 /*
138 	if (argvp) {
139 		debug_out_dump("app", DBG_FUNC_MSG << "Dumping argvp:\n");
140 		gchar** elem = argvp.get();
141 		while (*elem) {
142 			debug_out_dump("app", *elem << "\n");
143 			++elem;
144 		}
145 	}
146 */
147 
148 	// Execute the command
149 
150 	hz::scoped_ptr<gchar> curr_dir(g_get_current_dir(), g_free);
151 	hz::scoped_ptr<GError> spawn_error(0, g_error_free);
152 
153 /*
154 #if defined APP_CMDEX_USE_SYNC && APP_CMDEX_USE_SYNC
155 
156 	hz::scoped_ptr<gchar*> stdout_str(0, g_free);
157 	hz::scoped_ptr<gchar*> stderr_str(0, g_free);
158 	gint exit_status = 0;
159 
160 	g_timer_start(timer_);  // start the timer
161 
162 	if (!g_spawn_sync(curr_dir.get(), argvp.get(), NULL,
163 			GSpawnFlags(G_SPAWN_SEARCH_PATH),
164 			NULL, NULL,  // child setup function
165 			&stdout_str.get_ref(), &stderr_str.get_ref(), &exit_status, &spawn_error.get_ref()))
166 	{
167 		// no data is returned to &-parameters on error.
168 		push_error(Error<void>("gspawn", ErrorLevel::error, spawn_error->message), false);
169 		return false;
170 	}
171 
172 #else // async way:
173 */
174 	if (!g_spawn_async_with_pipes(curr_dir.get(), argvp.get(), NULL,
175 			GSpawnFlags(G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD),
176 			NULL, NULL,  // child setup function
177 			&this->pid_, 0, &fd_stdout_, &fd_stderr_, &spawn_error.get_ref()))
178 	{
179 		// no data is returned to &-parameters on error.
180 		push_error(Error<void>("gspawn", ErrorLevel::error, spawn_error->message), false);
181 		return false;
182 	}
183 
184 	g_timer_start(timer_);  // start the timer
185 
186 // #endif
187 
188 
189 	#ifdef _WIN32
190 		channel_stdout_ = g_io_channel_win32_new_fd(fd_stdout_);
191 		channel_stderr_ = g_io_channel_win32_new_fd(fd_stderr_);
192 	#else
193 		channel_stdout_ = g_io_channel_unix_new(fd_stdout_);
194 		channel_stderr_ = g_io_channel_unix_new(fd_stderr_);
195 	#endif
196 
197 	// The internal encoding is always UTF8. To read command output correctly, use
198 	// "" for binary data, or set io encoding to current locale.
199 	// If using locales, call g_locale_to_utf8() or g_convert() afterwards.
200 
201 	// blocking writes if the pipe is full helps for small-pipe systems (see man 7 pipe).
202 	int channel_flags = ~G_IO_FLAG_NONBLOCK;
203 
204 	// Note about GError's here:
205 	// What do we do? The command is already running, so let's ignore these
206 	// errors - it's better to get a slightly mangled buffer than to abort the
207 	// command in the mid-run.
208 	if (channel_stdout_) {
209 		// Since we invoke shutdown() manually before unref(), this would cause
210 		// a double-shutdown.
211 		// g_io_channel_set_close_on_unref(channel_stdout_, true);  // close() on fd
212 		g_io_channel_set_encoding(channel_stdout_, NULL, 0);  // binary IO
213 		g_io_channel_set_flags(channel_stdout_, GIOFlags(g_io_channel_get_flags(channel_stdout_) & channel_flags), 0);
214 		g_io_channel_set_buffer_size(channel_stdout_, channel_stdout_buffer_size_);
215 	}
216 	if (channel_stderr_) {
217 		// g_io_channel_set_close_on_unref(channel_stderr_, true);  // close() on fd
218 		g_io_channel_set_encoding(channel_stderr_, NULL, 0);  // binary IO
219 		g_io_channel_set_flags(channel_stderr_, GIOFlags(g_io_channel_get_flags(channel_stderr_) & channel_flags), 0);
220 		g_io_channel_set_buffer_size(channel_stderr_, channel_stderr_buffer_size_);
221 	}
222 
223 
224 	GIOCondition cond = GIOCondition(G_IO_IN | G_IO_PRI | G_IO_HUP | G_IO_ERR | G_IO_NVAL);
225 	// Channel reader callback must be called before other stuff so that the loss is minimal.
226 	gint io_priority = G_PRIORITY_HIGH;
227 
228 	this->event_source_id_stdout_ = g_io_add_watch_full(channel_stdout_, io_priority, cond,
229 			&cmdex_on_channel_io_stdout, this, NULL);
230 // 	g_io_channel_unref(channel_stdout_);  // g_io_add_watch_full() holds its own reference
231 
232 	this->event_source_id_stderr_ = g_io_add_watch_full(channel_stderr_, io_priority, cond,
233 			&cmdex_on_channel_io_stderr, this, NULL);
234 // 	g_io_channel_unref(channel_stderr_);  // g_io_add_watch_full() holds its own reference
235 
236 
237 	// If using SPAWN_DO_NOT_REAP_CHILD, this is needed to avoid zombies.
238 	// Note: Do NOT use glibmm slot, it doesn't work here.
239 	// (the child stops being a zombie as soon as wait*() exits and this handler is called).
240 	g_child_watch_add(this->pid_, &cmdex_child_watch_handler, this);
241 
242 
243 	this->running_ = true;  // the process is running now.
244 
245 	DBG_FUNCTION_EXIT_MSG;
246 	return true;
247 }
248 
249 
250 
251 
252 // send SIGTERM(15) (terminate)
try_stop(hz::signal_t sig)253 bool Cmdex::try_stop(hz::signal_t sig)
254 {
255 	DBG_FUNCTION_ENTER_MSG;
256 	if (!this->running_ || this->pid_ <= 0)
257 		return false;
258 
259 	// other variants: SIGHUP(1) (terminal closed), SIGINT(2) (Ctrl-C),
260 	// SIGKILL(9) (kill).
261 	// Note that SIGKILL cannot be trapped by any process.
262 
263 	if (process_signal_send(this->pid_, sig) == 0) {  // success
264 		this->kill_signal_sent_ = static_cast<int>(sig);  // just the number to compare later.
265 		return true;  // the rest is done by a handler
266 	}
267 
268 	// Possible: EPERM (no permissions), ESRCH (no such process, or zombie)
269 	push_error(Error<int>("errno", ErrorLevel::error, errno), false);
270 
271 	DBG_FUNCTION_EXIT_MSG;
272 	return false;
273 }
274 
275 
276 
try_kill()277 bool Cmdex::try_kill()
278 {
279 	DBG_TRACE_POINT_AUTO;
280 	return try_stop(hz::SIGNAL_SIGKILL);
281 }
282 
283 
284 
set_stop_timeouts(int term_timeout_msec,int kill_timeout_msec)285 void Cmdex::set_stop_timeouts(int term_timeout_msec, int kill_timeout_msec)
286 {
287 	DBG_FUNCTION_ENTER_MSG;
288 	DBG_ASSERT(term_timeout_msec == 0 || kill_timeout_msec == 0 || kill_timeout_msec > term_timeout_msec);
289 
290 	if (!this->running_)  // process not running
291 		return;
292 
293 	unset_stop_timeouts();
294 
295 	if (term_timeout_msec != 0)
296 		event_source_id_term = g_timeout_add(term_timeout_msec, &cmdex_on_term_timeout, this);
297 
298 	if (kill_timeout_msec != 0)
299 		event_source_id_kill = g_timeout_add(kill_timeout_msec, &cmdex_on_kill_timeout, this);
300 
301 	DBG_FUNCTION_EXIT_MSG;
302 }
303 
304 
305 
306 
unset_stop_timeouts()307 void Cmdex::unset_stop_timeouts()
308 {
309 	DBG_FUNCTION_ENTER_MSG;
310 	if (event_source_id_term) {
311 		GSource* source_term = g_main_context_find_source_by_id(NULL, event_source_id_term);
312 		if (source_term)
313 			g_source_destroy(source_term);
314 		event_source_id_term = 0;
315 	}
316 
317 	if (event_source_id_kill) {
318 		GSource* source_kill = g_main_context_find_source_by_id(NULL, event_source_id_kill);
319 		if (source_kill)
320 			g_source_destroy(source_kill);
321 		event_source_id_kill = 0;
322 	}
323 	DBG_FUNCTION_EXIT_MSG;
324 }
325 
326 
327 
328 // executed in main thread, manually by the caller.
stopped_cleanup()329 void Cmdex::stopped_cleanup()
330 {
331 	DBG_FUNCTION_ENTER_MSG;
332 	if (this->running_ || !this->stopped_cleanup_needed())  // huh?
333 		return;
334 
335 	// remove stop timeout callbacks
336 	unset_stop_timeouts();
337 
338 	// various statuses (see waitpid (2)):
339 	if (WIFEXITED(waitpid_status_)) {  // exited normally
340 		int exit_status = WEXITSTATUS(waitpid_status_);
341 
342 		if (exit_status != 0) {
343 			// translate the exit_code into a message
344 			std::string msg = (translator_func_ ? translator_func_(exit_status, translator_func_data_)
345 					: "[no translator function, exit code: " + hz::number_to_string(exit_status));
346 			push_error(Error<int>("exit", ErrorLevel::warn, exit_status, msg), false);
347 		}
348 
349 	} else {
350 		if (WIFSIGNALED(waitpid_status_)) {  // exited by signal
351 			int sig_num = WTERMSIG(waitpid_status_);
352 
353 			// If it's not our signal, treat as error.
354 			// Note: they will never match under win32
355 			if (sig_num != this->kill_signal_sent_) {
356 				push_error(Error<int>("signal", ErrorLevel::error, sig_num), false);
357 			} else {  // it's our signal, treat as warning
358 				push_error(Error<int>("signal", ErrorLevel::warn, sig_num), false);
359 			}
360 		}
361 	}
362 
363 	g_spawn_close_pid(this->pid_);  // needed to avoid zombies
364 
365 	cleanup_members();
366 
367 	this->running_ = false;
368 	DBG_FUNCTION_EXIT_MSG;
369 }
370 
371 
372 
373 
374 
375 // Called when child exits
on_child_watch_handler(GPid arg_pid,int waitpid_status,gpointer data)376 void Cmdex::on_child_watch_handler(GPid arg_pid, int waitpid_status, gpointer data)
377 {
378 // 	DBG_FUNCTION_ENTER_MSG;
379 	Cmdex* self = static_cast<Cmdex*>(data);
380 
381 	g_timer_stop(self->timer_);  // stop the timer
382 
383 	self->waitpid_status_ = waitpid_status;
384 	self->child_watch_handler_called_ = true;
385 	self->running_ = false;  // process is not running anymore
386 
387 	// These are needed because Windows doesn't read the remaining data otherwise.
388 	g_io_channel_flush(self->channel_stdout_, NULL);
389 	on_channel_io(self->channel_stdout_, GIOCondition(0), self, channel_type_stdout);
390 
391 	g_io_channel_flush(self->channel_stderr_, NULL);
392 	on_channel_io(self->channel_stderr_, GIOCondition(0), self, channel_type_stderr);
393 
394 	if (self->channel_stdout_) {
395 		g_io_channel_shutdown(self->channel_stdout_, false, NULL);
396 		g_io_channel_unref(self->channel_stdout_);
397 		self->channel_stdout_ = 0;
398 	}
399 
400 	if (self->channel_stderr_) {
401 		g_io_channel_shutdown(self->channel_stderr_, false, NULL);
402 		g_io_channel_unref(self->channel_stderr_);
403 		self->channel_stderr_ = 0;
404 	}
405 
406 	// Remove fd IO callbacks. They may actually be removed already (note sure about this).
407 	// This will force calling the iochannel callback (they may not be called
408 	// otherwise at all if there was no output).
409 	if (self->event_source_id_stdout_) {
410 		GSource* source_stdout = g_main_context_find_source_by_id(NULL, self->event_source_id_stdout_);
411 		if (source_stdout)
412 			g_source_destroy(source_stdout);
413 	}
414 
415 	if (self->event_source_id_stderr_) {
416 		GSource* source_stderr = g_main_context_find_source_by_id(NULL, self->event_source_id_stderr_);
417 		if (source_stderr)
418 			g_source_destroy(source_stderr);
419 	}
420 
421 	// Close std pipes.
422 	// The channel closes them now.
423 // 	close(self->fd_stdout_);
424 // 	close(self->fd_stderr_);
425 
426 	if (self->exited_callback_)
427 		self->exited_callback_(self->exited_callback_data_);
428 
429 // 	DBG_FUNCTION_EXIT_MSG;
430 }
431 
432 
433 
434 
435 
on_channel_io(GIOChannel * channel,GIOCondition cond,Cmdex * self,channel_t type)436 gboolean Cmdex::on_channel_io(GIOChannel* channel,
437 		GIOCondition cond, Cmdex* self, channel_t type)
438 {
439 // 	DBG_FUNCTION_ENTER_MSG;
440 // 	debug_out_dump("app", "Cmdex::on_channel_io("
441 // 			<< (type == channel_type_stdout ? "STDOUT" : "STDERR") << ") " << int(cond) << "\n");
442 
443 	bool continue_events = true;
444 	if ((cond & G_IO_ERR) || (cond & G_IO_HUP) || (cond & G_IO_NVAL)) {
445 		continue_events = false;  // there'll be no more data
446 	}
447 
448 	DBG_ASSERT(type == channel_type_stdout || type == channel_type_stderr);
449 
450 // 	const gsize count = 4 * 1024;
451 	// read the bytes one by one. without this, a buffered iochannel hangs while waiting for data.
452 	// we don't use unbuffered iochannels - they may lose data on program exit.
453 	const gsize count = 1;
454 	gchar buf[count] = {0};
455 
456 	std::string* output_str = 0;
457 	if (type == channel_type_stdout) {
458 		output_str = &self->str_stdout_;
459 	} else if (type == channel_type_stderr) {
460 		output_str = &self->str_stderr_;
461 	}
462 
463 
464 	// while there's anything to read, read it
465 	do {
466 		hz::scoped_ptr<GError> channel_error(0, g_error_free);
467 		gsize bytes_read = 0;
468 		GIOStatus status = g_io_channel_read_chars(channel, buf, count, &bytes_read, &channel_error.get_ref());
469 		if (bytes_read)
470 			output_str->append(buf, bytes_read);
471 
472 		if (channel_error) {
473 			self->push_error(Error<void>("giochannel", ErrorLevel::error, channel_error->message), false);
474 			break;  // stop on next invocation (is this correct?)
475 		}
476 
477 		// IO_STATUS_NORMAL and IO_STATUS_AGAIN (resource unavailable) are continuable.
478 		if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
479 			continue_events = false;
480 			break;
481 		}
482 	} while (g_io_channel_get_buffer_condition(channel) & G_IO_IN);
483 
484 // 	DBG_FUNCTION_EXIT_MSG;
485 
486 	// false if the source should be removed, true otherwise.
487 	return continue_events;
488 }
489 
490 
491 
cleanup_members()492 void Cmdex::cleanup_members()
493 {
494 	kill_signal_sent_ = 0;
495 	child_watch_handler_called_ = false;
496 	pid_ = 0;
497 	waitpid_status_ = 0;
498 	event_source_id_stdout_ = 0;
499 	event_source_id_stderr_ = 0;
500 	fd_stdout_ = 0;
501 	fd_stderr_ = 0;
502 }
503 
504 
505 
506 
507 
508 
509 /// @}
510