1 /************************************************************************** 2 Copyright: 3 (C) 2008 - 2012 Alexander Shaduri <ashaduri 'at' gmail.com> 4 License: See LICENSE_gsmartcontrol.txt 5 ***************************************************************************/ 6 7 #ifndef APP_CMDEX_H 8 #define APP_CMDEX_H 9 10 #include <glib.h> 11 #include <string> 12 13 #include "hz/process_signal.h" // hz::SIGNAL_* 14 #include "hz/error_holder.h" 15 #include "hz/sync.h" // hz::SyncPolicyNone 16 17 18 19 /// Command executor. 20 /// There are two ways to detect when the command exits: 21 /// 1. Add a callback to signal_exited. 22 /// 2. Manually poll stopped_cleanup_needed() (from the same thread). 23 /// In both cases, stopped_cleanup() must be called afterwards 24 /// (from the main thread). 25 class Cmdex : public hz::ErrorHolder<hz::SyncPolicyNone> { 26 public: 27 28 using hz::ErrorHolder<hz::SyncPolicyNone>::ptr_error_list_t; ///< Auto-deleting pointer container 29 using hz::ErrorHolder<hz::SyncPolicyNone>::error_list_t; ///< Error list type 30 31 /// A function that translates the exit error code into a readable string 32 typedef std::string (*exit_status_translator_func_t)(int, void*); 33 34 /// A function that is called whenever a process exits. 35 typedef void (*exited_callback_func_t)(void*); 36 37 38 /// Constructor 39 Cmdex(exited_callback_func_t exited_cb = NULL, void* exited_cb_data = NULL) running_(false)40 : running_(false), kill_signal_sent_(0), child_watch_handler_called_(false), 41 pid_(0), waitpid_status_(0), 42 timer_(g_timer_new()), 43 event_source_id_term(0), event_source_id_kill(0), 44 fd_stdout_(0), fd_stderr_(0), channel_stdout_(0), channel_stderr_(0), 45 channel_stdout_buffer_size_(100 * 1024), channel_stderr_buffer_size_(10 * 1024), // 100K and 10K 46 event_source_id_stdout_(0), event_source_id_stderr_(0), 47 translator_func_(0), translator_func_data_(0), 48 exited_callback_(exited_cb), exited_callback_data_(exited_cb_data) 49 { } 50 51 52 53 /// Destructor. Don't destroy this object unless the child has exited. It will leak stuff 54 /// and possibly crash, etc... . ~Cmdex()55 ~Cmdex() 56 { 57 // This will help if object is destroyed after the command has exited, but before 58 // stopped_cleanup() has been called. 59 stopped_cleanup(); 60 61 g_timer_destroy(timer_); 62 63 // no need to destroy the channels - stopped_cleanup() calls 64 // cleanup_members(), which deletes them. 65 } 66 67 68 /// Set the command to execute. Call before execute(). 69 /// Note: The command and the arguments _must_ be shell-escaped. 70 /// Use g_shell_quote() or Glib::shell_quote(). Note that each argument 71 /// must be escaped separately. set_command(const std::string & command_exec,const std::string & command_args)72 void set_command(const std::string& command_exec, const std::string& command_args) 73 { 74 command_exec_ = command_exec; 75 command_args_ = command_args; 76 } 77 78 79 /// Launch the command. 80 bool execute(); 81 82 83 /// Send SIGTERM(15) (terminate) to the child process. 84 /// Use only after execute(). Using it after the command has exited has no effect. 85 bool try_stop(hz::signal_t sig = hz::SIGNAL_SIGTERM); 86 87 88 /// Send SIGKILL(9) (kill) to the child process. Same as 89 /// try_stop(hz::SIGNAL_SIGKILL). 90 /// Note that SIGKILL cannot be overridden in child process. 91 bool try_kill(); 92 93 94 /// Set a timeout (since call to this function) to terminate the child process, 95 /// kill it or both (use 0 to ignore the parameter). 96 /// The timeouts will be unset automatically when the command exits. 97 /// This has an effect only if the command is running (after execute()). 98 void set_stop_timeouts(int term_timeout_msec = 0, int kill_timeout_msec = 0); 99 100 /// Unset the terminate / kill timeouts. This will stop the timeout counters. 101 /// This has an effect only if the command is running (after execute()). 102 void unset_stop_timeouts(); 103 104 105 /// If stopped_cleanup_needed() returned true, call this. The command 106 /// should be exited by this time. Must be called before the next execute(). 107 void stopped_cleanup(); 108 109 110 /// Returns true if command has stopped. 111 /// Call repeatedly in a waiting function, after execute(). 112 /// When it returns true, call stopped_cleanup(). stopped_cleanup_needed()113 bool stopped_cleanup_needed() 114 { 115 return (child_watch_handler_called_); 116 } 117 118 119 /// Check if the process is running. Note that if this returns false, it doesn't mean that 120 /// the io channels have been closed or that the data may be read safely. Poll 121 /// stopped_cleanup_needed() instead. is_running()122 bool is_running() const 123 { 124 return running_; 125 } 126 127 128 129 /// Call this before execution. There is a race-like condition - when the command 130 /// outputs something, the io channel reads it from fd to its buffer and the event 131 /// source callback is called. If the command dies, the io channel callback reads 132 /// the remaining data to the channel buffer. 133 /// Since the event source callbacks (which read from the buffer and empty it) 134 /// happen rather sporadically (from the glib loop), the buffer may not get read and 135 /// emptied at all (before the command exits). This is why it's necessary to have 136 /// a buffer size which potentially can hold _all_ the command output. 137 /// A way to fight this is to increase event source priority (which may not help). 138 /// Another way is to delay the command exit so that the event source callback 139 /// catches on and reads the buffer. 140 // Use 0 to ignore the parameter. Call this before execute(). 141 void set_buffer_sizes(int stdout_buffer_size = 0, int stderr_buffer_size = 0) 142 { 143 if (stdout_buffer_size) 144 channel_stdout_buffer_size_ = stdout_buffer_size; // 100K by default 145 if (stderr_buffer_size) 146 channel_stderr_buffer_size_ = stderr_buffer_size; // 10K by default 147 } 148 149 150 151 /// If stdout_make_str_as_available_ is false, call this after stopped_cleanup(), 152 /// before next execute(). If it's true, you may call this before the command has 153 /// stopped, but it will decrease performance significantly. 154 std::string get_stdout_str(bool clear_existing = false) 155 { 156 // debug_out_dump("app", str_stdout_); 157 if (clear_existing) { 158 std::string ret = str_stdout_; 159 str_stdout_.clear(); 160 return ret; 161 } 162 return str_stdout_; 163 } 164 165 166 /// See notes on get_stdout_str(). 167 std::string get_stderr_str(bool clear_existing = false) 168 { 169 if (clear_existing) { 170 std::string ret = str_stderr_; 171 str_stderr_.clear(); 172 return ret; 173 } 174 return str_stderr_; 175 } 176 177 178 /// Return execution time, in seconds. Call this after execute(). get_execution_time()179 double get_execution_time() 180 { 181 gulong microsec = 0; 182 return g_timer_elapsed(timer_, µsec); 183 } 184 185 186 /// Set exit status translator callback, disconnecting the old one. 187 /// Call only before execute(). set_exit_status_translator(exit_status_translator_func_t func,void * user_data)188 void set_exit_status_translator(exit_status_translator_func_t func, void* user_data) 189 { 190 translator_func_ = func; 191 translator_func_data_ = user_data; 192 } 193 194 195 /// Set exit notifier callback, disconnecting the old one. 196 /// You can poll stopped_cleanup_needed() instead of using this function. set_exited_callback(exited_callback_func_t func,void * user_data)197 void set_exited_callback(exited_callback_func_t func, void* user_data) 198 { 199 exited_callback_ = func; 200 exited_callback_data_ = user_data; 201 } 202 203 204 205 // these are sorta-private 206 207 /// Channel type, for passing to callbacks 208 enum channel_t { 209 channel_type_stdout, 210 channel_type_stderr 211 }; 212 213 214 // Callbacks (Note: These are called by the real callbacks) 215 216 /// Child watch handler 217 static void on_child_watch_handler(GPid arg_pid, int waitpid_status, gpointer data); 218 219 /// Channel I/O handler 220 static gboolean on_channel_io(GIOChannel* channel, GIOCondition cond, Cmdex* self, Cmdex::channel_t type); 221 222 223 private: 224 225 226 /// Clean up the member variables and shut down the channels if needed. 227 void cleanup_members(); 228 229 230 231 // default command and its args. std::strings, not ustrings. 232 std::string command_exec_; /// Binary name to execute. NOT affected by cleanup_members(). 233 std::string command_args_; /// Arguments that always go with the binary. NOT affected by cleanup_members(). 234 235 236 bool running_; ///< If true, the child process is running now. NOT affected by cleanup_members(). 237 int kill_signal_sent_; ///< If non-zero, the process has been sent this signal to terminate 238 bool child_watch_handler_called_; ///< true after child_watch_handler callback, before stopped_cleanup(). 239 240 GPid pid_; ///< Process ID. int in Unix, pointer in win32 241 int waitpid_status_; ///< After the command is stopped, before cleanup, this will be available (waitpid() status). 242 243 244 GTimer* timer_; ///< Keeps track of elapsed time since command execution. Value is not used by this class, but may be handy. 245 246 int event_source_id_term; ///< Timeout event source ID for SIGTERM. 247 int event_source_id_kill; ///< Timeout event source ID for SIGKILL. 248 249 250 int fd_stdout_; ///< stdout file descriptor 251 int fd_stderr_; ///< stderr file descriptor 252 253 GIOChannel* channel_stdout_; ///< stdout channel 254 GIOChannel* channel_stderr_; ///< stderr channel 255 256 int channel_stdout_buffer_size_; ///< stdout channel buffer size. NOT affected by cleanup_members(). 257 int channel_stderr_buffer_size_; ///< stderr channel buffer size. NOT affected by cleanup_members(). 258 259 int event_source_id_stdout_; ///< IO watcher event source ID for stdout 260 int event_source_id_stderr_; ///< IO watcher event source ID for stderr 261 262 std::string str_stdout_; ///< stdout data read during execution. NOT affected by cleanup_members(). 263 std::string str_stderr_; ///< stderr data read during execution. NOT affected by cleanup_members(). 264 265 266 // signals 267 268 // convert command exit status to message string 269 exit_status_translator_func_t translator_func_; ///< Exit status translator function. NOT affected by cleanup_members(). 270 void* translator_func_data_; ///< Data to supply to the exit status translator function. 271 272 // "command exited" signal callback. 273 exited_callback_func_t exited_callback_; ///< Exit notifier function. NOT affected by cleanup_members(). 274 void* exited_callback_data_; ///< Data to supply to the exit notifier function. 275 276 277 }; 278 279 280 281 282 283 284 #endif 285