1 /***************************************************************************
4 * runner.c - Process running code
6 * Copyright (C) 2006 Sjoerd Simons, <sjoerd@luon.net>
7 * Copyright (C) 2007 Codethink Ltd. Author Rob Taylor <rob.taylor@codethink.co.uk>
9 * Licensed under the Academic Free License version 2.1
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 **************************************************************************/
29 #include <sys/types.h>
35 #define DBUS_API_SUBJECT_TO_CHANGE
36 #include <dbus/dbus-glib-lowlevel.h>
42 /* Successful run of the program */
43 #define HALD_RUN_SUCCESS 0x0
44 /* Process was killed because of running too long */
45 #define HALD_RUN_TIMEOUT 0x1
46 /* Failed to start for some reason */
47 #define HALD_RUN_FAILED 0x2
48 /* Killed on purpose, e.g. hal_util_kill_device_helpers */
49 #define HALD_RUN_KILLED 0x4
51 GHashTable
*udi_hash
= NULL
;
52 GList
*singletons
= NULL
;
63 gboolean emit_pid_exited
;
67 del_run_data(run_data
*rd
)
72 del_run_request(rd
->r
);
74 dbus_message_unref(rd
->msg
);
76 g_spawn_close_pid(rd
->pid
);
78 if (rd
->stderr_v
>= 0)
82 g_source_remove(rd
->timeout
);
91 result
= g_new0(run_request
, 1);
92 g_assert(result
!= NULL
);
97 del_run_request(run_request
*r
)
102 free_string_array(r
->environment
);
103 free_string_array(r
->argv
);
109 send_reply(DBusConnection
*con
, DBusMessage
*msg
, guint32 exit_type
, gint32 return_code
, gchar
**error
)
112 DBusMessageIter iter
;
115 if (con
== NULL
|| msg
== NULL
)
118 reply
= dbus_message_new_method_return(msg
);
119 g_assert(reply
!= NULL
);
121 dbus_message_iter_init_append(reply
, &iter
);
122 dbus_message_iter_append_basic(&iter
, DBUS_TYPE_UINT32
, &exit_type
);
123 dbus_message_iter_append_basic(&iter
, DBUS_TYPE_INT32
, &return_code
);
124 if (error
!= NULL
) for (i
= 0; error
[i
] != NULL
; i
++) {
125 dbus_message_iter_append_basic(&iter
, DBUS_TYPE_STRING
, &error
[i
]);
128 dbus_connection_send(con
, reply
, NULL
);
129 dbus_message_unref(reply
);
133 remove_run_data(run_data
*rd
)
137 if (rd
->r
->is_singleton
) {
138 singletons
= g_list_remove(singletons
, rd
);
140 /* Remove to the hashtable */
141 list
= (GList
*)g_hash_table_lookup(udi_hash
, rd
->r
->udi
);
142 list
= g_list_remove(list
, rd
);
143 /* The hash table will take care to not leak the dupped string */
144 g_hash_table_insert(udi_hash
, g_strdup(rd
->r
->udi
), list
);
149 run_exited(GPid pid
, gint status
, gpointer data
)
151 run_data
*rd
= (run_data
*)data
;
154 printf("pid %d: rc=%d signaled=%d: %s\n",
155 pid
, WEXITSTATUS(status
), WIFSIGNALED(status
), rd
->r
->argv
[0]);
157 if (rd
->sent_kill
== TRUE
) {
158 /* We send it a kill, so ignore */
162 /* Check if it was a normal exit */
163 if (!WIFEXITED(status
)) {
164 /* No not normal termination ? crash ? */
165 send_reply(rd
->con
, rd
->msg
, HALD_RUN_FAILED
, 0, NULL
);
169 if (rd
->stderr_v
>= 0) {
170 /* Need to read stderr */
171 error
= get_string_array_from_fd(rd
->stderr_v
);
176 send_reply(rd
->con
, rd
->msg
, HALD_RUN_SUCCESS
, WEXITSTATUS(status
), error
);
177 free_string_array(error
);
180 remove_run_data (rd
);
182 /* emit a signal that this PID exited */
183 if(rd
->con
!= NULL
&& rd
->emit_pid_exited
) {
185 gint64 ppid
= rd
->pid
;
186 signal
= dbus_message_new_signal ("/org/freedesktop/HalRunner",
187 "org.freedesktop.HalRunner",
188 "StartedProcessExited");
189 dbus_message_append_args (signal
,
190 DBUS_TYPE_INT64
, &(ppid
),
192 dbus_connection_send(rd
->con
, signal
, NULL
);
199 run_timedout(gpointer data
) {
200 run_data
*rd
= (run_data
*)data
;
201 /* Time is up, kill the process, send reply that it was killed!
202 * Don't wait for exit, because it could hang in state D
204 kill(rd
->pid
, SIGTERM
);
205 /* Ensure the timeout is not removed in the delete */
207 /* So the exit watch will know it's killed in case it runs*/
208 rd
->sent_kill
= TRUE
;
210 send_reply(rd
->con
, rd
->msg
, HALD_RUN_TIMEOUT
, 0, NULL
);
211 remove_run_data (rd
);
216 find_program(char **argv
)
218 /* Search for the program in the dirs where it's allowed to be */
225 program
= g_path_get_basename(argv
[0]);
227 /* first search $PATH to make e.g. run-hald.sh work */
228 path
= g_find_program_in_path (program
);
233 /* Replace program in argv[0] with the full path */
240 /* Run the given request and reply it's result on msg */
242 run_request_run (run_request
*r
, DBusConnection
*con
, DBusMessage
*msg
, GPid
*out_pid
)
245 GError
*error
= NULL
;
246 gint
*stdin_p
= NULL
;
247 gint
*stderr_p
= NULL
;
251 gboolean program_exists
= FALSE
;
252 char *program_dir
= NULL
;
255 printf("Run started %s (%u) (%d) \n!", r
->argv
[0], r
->timeout
,
257 if (r
->input
!= NULL
) {
260 if (r
->error_on_stderr
) {
261 stderr_p
= &stderr_v
;
264 program_exists
= find_program(r
->argv
);
266 if (program_exists
) {
267 program_dir
= g_path_get_dirname (r
->argv
[0]);
268 printf(" full path is '%s', program_dir is '%s'\n", r
->argv
[0], program_dir
);
271 if (!program_exists
||
272 !g_spawn_async_with_pipes(program_dir
, r
->argv
, r
->environment
,
273 G_SPAWN_DO_NOT_REAP_CHILD
,
275 stdin_p
, NULL
, stderr_p
, &error
)) {
276 g_free (program_dir
);
279 send_reply(con
, msg
, HALD_RUN_FAILED
, 0, NULL
);
282 g_free (program_dir
);
285 if (write(stdin_v
, r
->input
, strlen(r
->input
)) != (ssize_t
) strlen(r
->input
))
286 printf("Warning: Error while writing r->input (%s) to stdin_v.\n", r
->input
);
290 rd
= g_new0(run_data
,1);
291 g_assert(rd
!= NULL
);
295 dbus_message_ref(msg
);
299 rd
->stderr_v
= stderr_v
;
300 rd
->sent_kill
= FALSE
;
302 /* Add watch for exit of the program */
303 rd
->watch
= g_child_watch_add(pid
, run_exited
, rd
);
305 /* Add timeout if needed */
307 rd
->timeout
= g_timeout_add(r
->timeout
, run_timedout
, rd
);
311 if (r
->is_singleton
) {
312 singletons
= g_list_prepend(singletons
, rd
);
314 /* Add to the hashtable */
315 list
= (GList
*)g_hash_table_lookup(udi_hash
, r
->udi
);
316 list
= g_list_prepend(list
, rd
);
318 /* The hash table will take care to not leak the dupped string */
319 g_hash_table_insert(udi_hash
, g_strdup(r
->udi
), list
);
322 /* send back PID if requested.. and only emit StartedProcessExited in this case */
323 if (out_pid
!= NULL
) {
325 rd
->emit_pid_exited
= TRUE
;
331 kill_rd(gpointer data
, gpointer user_data
)
333 run_data
*rd
= (run_data
*)data
;
335 kill(rd
->pid
, SIGTERM
);
336 printf("Sent kill to %d\n", rd
->pid
);
337 if (rd
->timeout
!= 0) {
338 /* Remove the timeout watch */
339 g_source_remove(rd
->timeout
);
343 /* So the exit watch will know it's killed in case it runs */
344 rd
->sent_kill
= TRUE
;
347 send_reply(rd
->con
, rd
->msg
, HALD_RUN_KILLED
, 0, NULL
);
351 do_kill_udi(gchar
*udi
)
354 list
= (GList
*)g_hash_table_lookup(udi_hash
, udi
);
355 g_list_foreach(list
, kill_rd
, NULL
);
359 /* Kill all running request for a udi */
361 run_kill_udi(gchar
*udi
)
364 g_hash_table_remove(udi_hash
, udi
);
368 hash_kill_udi(gpointer key
, gpointer value
, gpointer user_data
) {
373 /* Kill all running request*/
377 g_hash_table_foreach_remove(udi_hash
, hash_kill_udi
, NULL
);
378 g_list_foreach(singletons
, kill_rd
, NULL
);
384 udi_hash
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);