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_, &microsec);
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