xref: /illumos-gate/usr/src/cmd/hal/hald/hald_runner.c (revision 9a5d73e03cd3312ddb571a748c40a63c58bd66e5)
1 /***************************************************************************
2  * CVSID: $Id$
3  *
4  * hald_runner.c - Interface to the hal runner helper daemon
5  *
6  * Copyright (C) 2006 Sjoerd Simons, <sjoerd@luon.net>
7  *
8  * Licensed under the Academic Free License version 2.1
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23  *
24  **************************************************************************/
25 
26 #ifdef HAVE_CONFIG_H
27 #  include <config.h>
28 #endif
29 
30 #include <sys/utsname.h>
31 #include <stdio.h>
32 
33 #include <glib.h>
34 #include <dbus/dbus.h>
35 #include <dbus/dbus-glib-lowlevel.h>
36 
37 #include "hald.h"
38 #include "util.h"
39 #include "logger.h"
40 #include "hald_dbus.h"
41 #include "hald_runner.h"
42 
43 typedef struct {
44   HalDevice *d;
45   HalRunTerminatedCB cb;
46   gpointer data1;
47   gpointer data2;
48 } HelperData;
49 
50 #define DBUS_SERVER_ADDRESS "unix:tmpdir=" HALD_SOCKET_DIR
51 
52 static DBusConnection *runner_connection = NULL;
53 
54 typedef struct
55 {
56 	GPid pid;
57 	HalDevice *device;
58 	HalRunTerminatedCB cb;
59 	gpointer data1;
60 	gpointer data2;
61 } RunningProcess;
62 
63 /* mapping from PID to RunningProcess */
64 static GHashTable *running_processes;
65 
66 static gboolean
67 rprd_foreach (gpointer key,
68 	      gpointer value,
69 	      gpointer user_data)
70 {
71 	gboolean remove = FALSE;
72 	RunningProcess *rp = value;
73 	HalDevice *device = user_data;
74 
75 	if (rp->device == device) {
76 		remove = TRUE;
77 		g_free (rp);
78 	}
79 
80 	return remove;
81 }
82 
83 static void
84 running_processes_remove_device (HalDevice *device)
85 {
86 	g_hash_table_foreach_remove (running_processes, rprd_foreach, device);
87 }
88 
89 void
90 runner_device_finalized (HalDevice *device)
91 {
92 	running_processes_remove_device (device);
93 }
94 
95 
96 static DBusHandlerResult
97 runner_server_message_handler (DBusConnection *connection,
98 			       DBusMessage *message,
99 			       void *user_data)
100 {
101 
102 	/*HAL_INFO (("runner_server_message_handler: destination=%s obj_path=%s interface=%s method=%s",
103 		   dbus_message_get_destination (message),
104 		   dbus_message_get_path (message),
105 		   dbus_message_get_interface (message),
106 		   dbus_message_get_member (message)));*/
107 	if (dbus_message_is_signal (message,
108 				    "org.freedesktop.HalRunner",
109 				    "StartedProcessExited")) {
110 		dbus_uint64_t dpid;
111 		DBusError error;
112 		dbus_error_init (&error);
113 		if (dbus_message_get_args (message, &error,
114 					   DBUS_TYPE_INT64, &dpid,
115 					   DBUS_TYPE_INVALID)) {
116 			RunningProcess *rp;
117 			GPid pid;
118 
119 			pid = (GPid) dpid;
120 
121 			/*HAL_INFO (("Previously started process with pid %d exited", pid));*/
122 			rp = g_hash_table_lookup (running_processes, (gpointer) pid);
123 			if (rp != NULL) {
124 				rp->cb (rp->device, 0, 0, NULL, rp->data1, rp->data2);
125 				g_hash_table_remove (running_processes, (gpointer) pid);
126 				g_free (rp);
127 			}
128 		}
129 	}
130 	return DBUS_HANDLER_RESULT_HANDLED;
131 }
132 
133 static void
134 runner_server_unregister_handler (DBusConnection *connection, void *user_data)
135 {
136 	HAL_INFO (("unregistered"));
137 }
138 
139 
140 static void
141 handle_connection(DBusServer *server,
142                   DBusConnection *new_connection,
143                   void *data)
144 {
145 
146 	if (runner_connection == NULL) {
147 		DBusObjectPathVTable vtable = { &runner_server_unregister_handler,
148 						&runner_server_message_handler,
149 						NULL, NULL, NULL, NULL};
150 
151 		runner_connection = new_connection;
152 		dbus_connection_ref (new_connection);
153 		dbus_connection_setup_with_g_main (new_connection, NULL);
154 
155 		dbus_connection_register_fallback (new_connection,
156 						   "/org/freedesktop",
157 						   &vtable,
158 						   NULL);
159 
160 		/* dbus_server_unref(server); */
161 
162 	}
163 }
164 
165 static void
166 runner_died(GPid pid, gint status, gpointer data) {
167   g_spawn_close_pid (pid);
168   DIE (("Runner died"));
169 }
170 
171 gboolean
172 hald_runner_start_runner(void)
173 {
174   DBusServer *server = NULL;
175   DBusError err;
176   GError *error = NULL;
177   GPid pid;
178   char *argv[] = { NULL, NULL};
179   char *env[] =  { NULL, NULL, NULL, NULL};
180   const char *hald_runner_path;
181   char *server_addr;
182 
183   running_processes = g_hash_table_new (g_direct_hash, g_direct_equal);
184 
185   dbus_error_init(&err);
186   server = dbus_server_listen(DBUS_SERVER_ADDRESS, &err);
187   if (server == NULL) {
188     HAL_ERROR (("Cannot create D-BUS server for the runner"));
189     goto error;
190   }
191 
192   dbus_server_setup_with_g_main(server, NULL);
193   dbus_server_set_new_connection_function(server, handle_connection,
194                                           NULL, NULL);
195 
196 
197   argv[0] = "hald-runner";
198   server_addr = dbus_server_get_address (server);
199   env[0] = g_strdup_printf("HALD_RUNNER_DBUS_ADDRESS=%s", server_addr);
200   dbus_free (server_addr);
201   hald_runner_path = g_getenv("HALD_RUNNER_PATH");
202   if (hald_runner_path != NULL) {
203 	  env[1] = g_strdup_printf ("PATH=%s:" PACKAGE_LIBEXEC_DIR ":" PACKAGE_SCRIPT_DIR ":" PACKAGE_BIN_DIR, hald_runner_path);
204   } else {
205 	  env[1] = g_strdup_printf ("PATH=" PACKAGE_LIBEXEC_DIR ":" PACKAGE_SCRIPT_DIR ":" PACKAGE_BIN_DIR);
206   }
207 
208   /*env[2] = "DBUS_VERBOSE=1";*/
209 
210 
211   if (!g_spawn_async(NULL, argv, env, G_SPAWN_DO_NOT_REAP_CHILD|G_SPAWN_SEARCH_PATH,
212         NULL, NULL, &pid, &error)) {
213     HAL_ERROR (("Could not spawn runner : '%s'", error->message));
214     g_error_free (error);
215     goto error;
216   }
217   g_free(env[0]);
218   g_free(env[1]);
219 
220   HAL_INFO (("Runner has pid %d", pid));
221 
222   g_child_watch_add(pid, runner_died, NULL);
223   while (runner_connection == NULL) {
224     /* Wait for the runner */
225     g_main_context_iteration(NULL, TRUE);
226   }
227   return TRUE;
228 
229 error:
230   if (server != NULL)
231     dbus_server_unref(server);
232   return FALSE;
233 }
234 
235 static gboolean
236 add_property_to_msg (HalDevice *device, HalProperty *property,
237                                      gpointer user_data)
238 {
239   char *prop_upper, *value;
240   char *c;
241   gchar *env;
242   DBusMessageIter *iter = (DBusMessageIter *)user_data;
243 
244   prop_upper = g_ascii_strup (hal_property_get_key (property), -1);
245 
246   /* periods aren't valid in the environment, so replace them with
247    * underscores. */
248   for (c = prop_upper; *c; c++) {
249     if (*c == '.')
250       *c = '_';
251   }
252 
253   value = hal_property_to_string (property);
254   env = g_strdup_printf ("HAL_PROP_%s=%s", prop_upper, value);
255   dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &env);
256 
257   g_free (env);
258   g_free (value);
259   g_free (prop_upper);
260 
261   return TRUE;
262 }
263 
264 static void
265 add_env(DBusMessageIter *iter, const gchar *key, const gchar *value) {
266   gchar *env;
267   env = g_strdup_printf ("%s=%s", key, value);
268   dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &env);
269   g_free(env);
270 }
271 
272 static void
273 add_basic_env(DBusMessageIter *iter, const gchar *udi) {
274   struct utsname un;
275   char *server_addr;
276 
277   if (hald_is_verbose) {
278     add_env(iter, "HALD_VERBOSE", "1");
279   }
280   if (hald_is_initialising) {
281     add_env(iter, "HALD_STARTUP", "1");
282   }
283   if (hald_use_syslog) {
284     add_env(iter, "HALD_USE_SYSLOG", "1");
285   }
286   add_env(iter, "UDI", udi);
287   server_addr = hald_dbus_local_server_addr();
288   add_env(iter, "HALD_DIRECT_ADDR", server_addr);
289   dbus_free (server_addr);
290 #ifdef HAVE_POLKIT
291   add_env(iter, "HAVE_POLKIT", "1");
292 #endif
293 
294   if (uname(&un) >= 0) {
295     char *sysname;
296 
297     sysname = g_ascii_strdown(un.sysname, -1);
298     add_env(iter, "HALD_UNAME_S", sysname);
299     g_free(sysname);
300   }
301 }
302 
303 static void
304 add_extra_env(DBusMessageIter *iter, gchar **env) {
305   int i;
306   if (env != NULL) for (i = 0; env[i] != NULL; i++) {
307     dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &env[i]);
308   }
309 }
310 
311 static gboolean
312 add_command(DBusMessageIter *iter, const gchar *command_line) {
313   gint argc;
314   gint x;
315   char **argv;
316   GError *err = NULL;
317   DBusMessageIter array_iter;
318 
319   if (!g_shell_parse_argv(command_line, &argc, &argv, &err)) {
320     HAL_ERROR (("Error parsing commandline '%s': %s",
321                  command_line, err->message));
322     g_error_free (err);
323     return FALSE;
324   }
325   if (!dbus_message_iter_open_container(iter,
326                                    DBUS_TYPE_ARRAY,
327                                    DBUS_TYPE_STRING_AS_STRING,
328                                    &array_iter))
329     DIE (("No memory"));
330   for (x = 0 ; argv[x] != NULL; x++) {
331     dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, &argv[x]);
332   }
333   dbus_message_iter_close_container(iter, &array_iter);
334 
335   g_strfreev(argv);
336   return TRUE;
337 }
338 
339 static gboolean
340 add_first_part(DBusMessageIter *iter, HalDevice *device,
341                    const gchar *command_line, char **extra_env) {
342   DBusMessageIter array_iter;
343   const char *udi;
344 
345   udi = hal_device_get_udi(device);
346   dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &udi);
347 
348   dbus_message_iter_open_container(iter,
349                                    DBUS_TYPE_ARRAY,
350                                    DBUS_TYPE_STRING_AS_STRING,
351                                    &array_iter);
352   hal_device_property_foreach (device, add_property_to_msg, &array_iter);
353   add_basic_env(&array_iter, udi);
354   add_extra_env(&array_iter, extra_env);
355   dbus_message_iter_close_container(iter, &array_iter);
356 
357   if (!add_command(iter, command_line)) {
358     return FALSE;
359   }
360   return TRUE;
361 }
362 
363 /* Start a helper, returns true on a successfull start */
364 gboolean
365 hald_runner_start (HalDevice *device, const gchar *command_line, char **extra_env,
366 		   HalRunTerminatedCB cb, gpointer data1, gpointer data2)
367 {
368   DBusMessage *msg, *reply;
369   DBusError err;
370   DBusMessageIter iter;
371 
372   dbus_error_init(&err);
373   msg = dbus_message_new_method_call("org.freedesktop.HalRunner",
374                                      "/org/freedesktop/HalRunner",
375                                      "org.freedesktop.HalRunner",
376                                      "Start");
377   if (msg == NULL)
378     DIE(("No memory"));
379   dbus_message_iter_init_append(msg, &iter);
380 
381   if (!add_first_part(&iter, device, command_line, extra_env))
382     goto error;
383 
384   /* Wait for the reply, should be almost instantanious */
385   reply =
386     dbus_connection_send_with_reply_and_block(runner_connection,
387                                               msg, -1, &err);
388   if (reply) {
389     gboolean ret =
390       (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN);
391 
392     if (ret) {
393 	dbus_int64_t pid_from_runner;
394 	if (dbus_message_get_args (reply, &err,
395 				   DBUS_TYPE_INT64, &pid_from_runner,
396 				   DBUS_TYPE_INVALID)) {
397 		if (cb != NULL) {
398 			RunningProcess *rp;
399 			rp = g_new0 (RunningProcess, 1);
400 			rp->pid = (GPid) pid_from_runner;
401 			rp->cb = cb;
402 			rp->device = device;
403 			rp->data1 = data1;
404 			rp->data2 = data2;
405 
406 			g_hash_table_insert (running_processes, (gpointer) rp->pid, rp);
407 		}
408 	} else {
409 	  HAL_ERROR (("Error extracting out_pid from runner's Start()"));
410 	}
411     }
412 
413     dbus_message_unref(reply);
414     dbus_message_unref(msg);
415     return ret;
416   }
417 
418 error:
419   dbus_message_unref(msg);
420   return FALSE;
421 }
422 
423 static void
424 call_notify(DBusPendingCall *pending, void *user_data)
425 {
426   HelperData *hb = (HelperData *)user_data;
427   dbus_uint32_t exitt = HALD_RUN_SUCCESS;
428   dbus_int32_t return_code = 0;
429   DBusMessage *m;
430   GArray *error = NULL;
431   DBusMessageIter iter;
432 
433   error = g_array_new(TRUE, FALSE, sizeof(char *));
434 
435   m = dbus_pending_call_steal_reply(pending);
436   if (dbus_message_get_type(m) != DBUS_MESSAGE_TYPE_METHOD_RETURN)
437     goto malformed;
438 
439   if (!dbus_message_iter_init(m, &iter) ||
440        dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32)
441     goto malformed;
442   dbus_message_iter_get_basic(&iter, &exitt);
443 
444   if (!dbus_message_iter_next(&iter) ||
445         dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32)
446     goto malformed;
447   dbus_message_iter_get_basic(&iter, &return_code);
448 
449   while (dbus_message_iter_next(&iter) &&
450     dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) {
451     const char *value;
452     dbus_message_iter_get_basic(&iter, &value);
453     g_array_append_vals(error, &value, 1);
454   }
455 
456   hb->cb(hb->d, exitt, return_code,
457       (gchar **)error->data, hb->data1, hb->data2);
458 
459   g_object_unref (hb->d);
460 
461   dbus_message_unref(m);
462   dbus_pending_call_unref (pending);
463   g_array_free(error, TRUE);
464 
465   return;
466 malformed:
467   /* Send a Fail callback on malformed messages */
468   HAL_ERROR (("Malformed or unexpected reply message"));
469   hb->cb(hb->d, HALD_RUN_FAILED, return_code, NULL, hb->data1, hb->data2);
470 
471   g_object_unref (hb->d);
472 
473   dbus_message_unref(m);
474   dbus_pending_call_unref (pending);
475   g_array_free(error, TRUE);
476 }
477 
478 /* Run a helper program using the commandline, with input as infomation on
479  * stdin */
480 void
481 hald_runner_run_method(HalDevice *device,
482                            const gchar *command_line, char **extra_env,
483                            gchar *input, gboolean error_on_stderr,
484                            guint32 timeout,
485                            HalRunTerminatedCB  cb,
486                            gpointer data1, gpointer data2) {
487   DBusMessage *msg;
488   DBusMessageIter iter;
489   DBusPendingCall *call;
490   HelperData *hd = NULL;
491   msg = dbus_message_new_method_call("org.freedesktop.HalRunner",
492                              "/org/freedesktop/HalRunner",
493                              "org.freedesktop.HalRunner",
494                              "Run");
495   if (msg == NULL)
496     DIE(("No memory"));
497   dbus_message_iter_init_append(msg, &iter);
498 
499   if (!add_first_part(&iter, device, command_line, extra_env))
500     goto error;
501 
502   dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &input);
503   dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &error_on_stderr);
504   dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &timeout);
505 
506   if (!dbus_connection_send_with_reply(runner_connection,
507                                               msg, &call, INT_MAX))
508     DIE (("No memory"));
509 
510   hd = malloc(sizeof(HelperData));
511   hd->d = device;
512   hd->cb = cb;
513   hd->data1 = data1;
514   hd->data2 = data2;
515 
516   g_object_ref (device);
517 
518   dbus_pending_call_set_notify(call, call_notify, hd, free);
519   dbus_message_unref(msg);
520   return;
521 error:
522   dbus_message_unref(msg);
523   free(hd);
524   cb(device, HALD_RUN_FAILED, 0, NULL, data1, data2);
525 }
526 
527 void
528 hald_runner_run(HalDevice *device,
529                     const gchar *command_line, char **extra_env,
530                     guint timeout,
531                     HalRunTerminatedCB  cb,
532                     gpointer data1, gpointer data2) {
533   hald_runner_run_method(device, command_line, extra_env,
534                              "", FALSE, timeout, cb, data1, data2);
535 }
536 
537 
538 
539 void
540 hald_runner_kill_device(HalDevice *device) {
541   DBusMessage *msg, *reply;
542   DBusError err;
543   DBusMessageIter iter;
544   const char *udi;
545 
546   running_processes_remove_device (device);
547 
548   msg = dbus_message_new_method_call("org.freedesktop.HalRunner",
549                              "/org/freedesktop/HalRunner",
550                              "org.freedesktop.HalRunner",
551                              "Kill");
552   if (msg == NULL)
553     DIE(("No memory"));
554   dbus_message_iter_init_append(msg, &iter);
555   udi = hal_device_get_udi(device);
556   dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &udi);
557 
558   /* Wait for the reply, should be almost instantanious */
559   dbus_error_init(&err);
560   reply =
561     dbus_connection_send_with_reply_and_block(runner_connection, msg, -1, &err);
562   if (reply) {
563     dbus_message_unref(reply);
564   }
565 
566   dbus_message_unref(msg);
567 }
568 
569 void
570 hald_runner_kill_all(HalDevice *device) {
571   DBusMessage *msg, *reply;
572   DBusError err;
573 
574   running_processes_remove_device (device);
575 
576   msg = dbus_message_new_method_call("org.freedesktop.HalRunner",
577                              "/org/freedesktop/HalRunner",
578                              "org.freedesktop.HalRunner",
579                              "KillAll");
580   if (msg == NULL)
581     DIE(("No memory"));
582 
583   /* Wait for the reply, should be almost instantanious */
584   dbus_error_init(&err);
585   reply =
586     dbus_connection_send_with_reply_and_block(runner_connection,
587                                               msg, -1, &err);
588   if (reply) {
589     dbus_message_unref(reply);
590   }
591 
592   dbus_message_unref(msg);
593 }
594