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