xref: /illumos-gate/usr/src/cmd/hal/hald/hald.c (revision 49ef7e0638c8b771d8a136eae78b1c0f99acc8e0)
1 /***************************************************************************
2  * CVSID: $Id$
3  *
4  * hald.c : main startup for HAL daemon
5  *
6  * Copyright (C) 2003 David Zeuthen, <david@fubar.dk>
7  * Copyright (C) 2005 Danny Kukawka, <danny.kukawka@web.de>
8  *
9  * Licensed under the Academic Free License version 2.1
10  *
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.
15  *
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.
20  *
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
24  *
25  **************************************************************************/
26 
27 #ifdef HAVE_CONFIG_H
28 #  include <config.h>
29 #endif
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <getopt.h>
36 #include <pwd.h>
37 #include <stdint.h>
38 #include <sys/stat.h>
39 #include <fcntl.h>
40 #include <errno.h>
41 #include <signal.h>
42 #include <grp.h>
43 #include <syslog.h>
44 
45 #include <dbus/dbus.h>
46 #include <dbus/dbus-glib.h>
47 #include <dbus/dbus-glib-lowlevel.h>
48 
49 /*#include "master_slave.h"*/
50 
51 #include "logger.h"
52 #include "hald.h"
53 #include "device_store.h"
54 #include "device_info.h"
55 #include "osspec.h"
56 #include "hald_dbus.h"
57 #include "util.h"
58 #include "hald_runner.h"
59 #include "util_helper.h"
60 
61 static void delete_pid(void)
62 {
63 	unlink(HALD_PID_FILE);
64 }
65 
66 /**
67  * @defgroup HalDaemon HAL daemon
68  * @brief The HAL daemon manages persistent device objects available through
69  *        a D-BUS network API
70  */
71 
72 static HalDeviceStore *global_device_list = NULL;
73 
74 static HalDeviceStore *temporary_device_list = NULL;
75 
76 
77 static void
78 addon_terminated (HalDevice *device, guint32 exit_type,
79 		  gint return_code, gchar **error,
80 		  gpointer data1, gpointer data2)
81 {
82 	HAL_INFO (("in addon_terminated for udi=%s", device->udi));
83 
84 	/* TODO: log to syslog - addons shouldn't just terminate, this is a bug with the addon */
85 
86 	/* however, the world can stop, mark this addon as ready
87 	 * (TODO: potential bug if the addon crashed after calling libhal_device_addon_is_ready())
88 	 */
89 	if (hal_device_inc_num_ready_addons (device)) {
90 		if (hal_device_are_all_addons_ready (device)) {
91 			manager_send_signal_device_added (device);
92 		}
93 	}
94 }
95 
96 
97 
98 
99 static void
100 gdl_store_changed (HalDeviceStore *store, HalDevice *device,
101 		   gboolean is_added, gpointer user_data)
102 {
103 	if (is_added) {
104 		GSList *addons;
105 
106 		HAL_INFO (("Added device to GDL; udi=%s", hal_device_get_udi(device)));
107 
108 		if ((addons = hal_device_property_get_strlist (device, "info.addons")) != NULL) {
109 			GSList *i;
110 
111 			for (i = addons; i != NULL; i = g_slist_next (i)) {
112 				const gchar *command_line;
113 				gchar *extra_env[2] = {"HALD_ACTION=addon", NULL};
114 
115 				command_line = (const gchar *) i->data;
116 				if (hald_runner_start(device, command_line, extra_env, addon_terminated, NULL, NULL)) {
117 					HAL_INFO (("Started addon %s for udi %s",
118 						   command_line, hal_device_get_udi(device)));
119 					hal_device_inc_num_addons (device);
120 				} else {
121 					HAL_ERROR (("Cannot start addon %s for udi %s",
122 						    command_line, hal_device_get_udi(device)));
123 				}
124 			}
125 		}
126 	} else {
127 		HAL_INFO (("Removed device from GDL; udi=%s", hal_device_get_udi(device)));
128 		hald_runner_kill_device(device);
129 	}
130 
131 	/*hal_device_print (device);*/
132 
133 	if (is_added) {
134 		if (hal_device_are_all_addons_ready (device)) {
135 			manager_send_signal_device_added (device);
136 		}
137 	} else {
138 		if (hal_device_are_all_addons_ready (device)) {
139 			manager_send_signal_device_removed (device);
140 		}
141 	}
142 }
143 
144 static void
145 gdl_property_changed (HalDeviceStore *store, HalDevice *device,
146 		      const char *key, gboolean added, gboolean removed,
147 		      gpointer user_data)
148 {
149 	if (hal_device_are_all_addons_ready (device)) {
150 		device_send_signal_property_modified (device, key, removed, added);
151 	}
152 
153 	/* only execute the callouts if the property _changed_ */
154 	if (added == FALSE && removed == FALSE)
155 		/*hal_callout_property (device, key)*/;
156 }
157 
158 static void
159 gdl_capability_added (HalDeviceStore *store, HalDevice *device,
160 		      const char *capability, gpointer user_data)
161 {
162 	if (hal_device_are_all_addons_ready (device)) {
163 		manager_send_signal_new_capability (device, capability);
164 	}
165 	/*hal_callout_capability (device, capability, TRUE)*/;
166 }
167 
168 HalDeviceStore *
169 hald_get_gdl (void)
170 {
171 	if (global_device_list == NULL) {
172 		global_device_list = hal_device_store_new ();
173 
174 		g_signal_connect (global_device_list,
175 				  "store_changed",
176 				  G_CALLBACK (gdl_store_changed), NULL);
177 		g_signal_connect (global_device_list,
178 				  "device_property_changed",
179 				  G_CALLBACK (gdl_property_changed), NULL);
180 		g_signal_connect (global_device_list,
181 				  "device_capability_added",
182 				  G_CALLBACK (gdl_capability_added), NULL);
183 	}
184 
185 	return global_device_list;
186 }
187 
188 HalDeviceStore *
189 hald_get_tdl (void)
190 {
191 	if (temporary_device_list == NULL) {
192 		temporary_device_list = hal_device_store_new ();
193 
194 	}
195 
196 	return temporary_device_list;
197 }
198 
199 /**
200  * @defgroup MainDaemon Basic functions
201  * @ingroup HalDaemon
202  * @brief Basic functions in the HAL daemon
203  * @{
204  */
205 
206 /** Print out program usage.
207  *
208  */
209 static void
210 usage ()
211 {
212 	fprintf (stderr, "\n" "usage : hald [--daemon=yes|no] [--verbose=yes|no] [--help]\n");
213 	fprintf (stderr,
214 		 "\n"
215 		 "        --daemon=yes|no      Become a daemon\n"
216 		 "        --verbose=yes|no     Print out debug (overrides HALD_VERBOSE)\n"
217  		 "        --use-syslog         Print out debug messages to syslog instead of stderr.\n"
218 		 "                             Use this option to get debug messages if HAL runs as\n"
219 		 "                             daemon.\n"
220 		 "        --help               Show this information and exit\n"
221 		 "        --version            Output version information and exit"
222 		 "\n"
223 		 "The HAL daemon detects devices present in the system and provides the\n"
224 		 "org.freedesktop.Hal service through the system-wide message bus provided\n"
225 		 "by D-BUS.\n"
226 		 "\n"
227 		 "For more information visit http://freedesktop.org/Software/hal\n"
228 		 "\n");
229 }
230 
231 /** If #TRUE, we will daemonize */
232 static dbus_bool_t opt_become_daemon = TRUE;
233 
234 /** If #TRUE, we will spew out debug */
235 dbus_bool_t hald_is_verbose = FALSE;
236 dbus_bool_t hald_use_syslog = FALSE;
237 
238 static int sigterm_unix_signal_pipe_fds[2];
239 static GIOChannel *sigterm_iochn;
240 
241 static void
242 handle_sigterm (int value)
243 {
244 	ssize_t written;
245 	static char marker[1] = {'S'};
246 
247 	/* write a 'S' character to the other end to tell about
248 	 * the signal. Note that 'the other end' is a GIOChannel thingy
249 	 * that is only called from the mainloop - thus this is how we
250 	 * defer this since UNIX signal handlers are evil
251 	 *
252 	 * Oh, and write(2) is indeed reentrant */
253 	written = write (sigterm_unix_signal_pipe_fds[1], marker, 1);
254 }
255 
256 static gboolean
257 sigterm_iochn_data (GIOChannel *source,
258 		    GIOCondition condition,
259 		    gpointer user_data)
260 {
261 	GError *err = NULL;
262 	gchar data[1];
263 	gsize bytes_read;
264 
265 	/* Empty the pipe */
266 	if (G_IO_STATUS_NORMAL !=
267 	    g_io_channel_read_chars (source, data, 1, &bytes_read, &err)) {
268 		HAL_ERROR (("Error emptying sigterm pipe: %s",
269 				   err->message));
270 		g_error_free (err);
271 		goto out;
272 	}
273 
274 	HAL_INFO (("Caught SIGTERM, initiating shutdown"));
275 	hald_runner_kill_all();
276 	exit (0);
277 
278 out:
279 	return TRUE;
280 }
281 
282 
283 /** This is set to #TRUE if we are probing and #FALSE otherwise */
284 dbus_bool_t hald_is_initialising;
285 
286 static int startup_daemonize_pipe[2];
287 
288 
289 /*--------------------------------------------------------------------------------------------------*/
290 
291 static gboolean child_died = FALSE;
292 
293 static void
294 handle_sigchld (int value)
295 {
296 	child_died = TRUE;
297 }
298 
299 static int
300 parent_wait_for_child (int child_fd, pid_t child_pid)
301 {
302 	fd_set rfds;
303 	fd_set efds;
304 	struct timeval tv;
305 	int retval;
306 	int ret;
307 
308 	signal(SIGCHLD, handle_sigchld);
309 
310 	/* wait for either
311 	 *
312 	 * o Child writes something to the child_fd; means that device
313 	 *   probing is completed and the parent should exit with success
314 	 *
315 	 * o Child is killed (segfault etc.); means that parent should exit
316 	 *   with failure
317 	 *
318 	 * o Timeout; means that we should kill the child and exit with
319 	 *   failure
320 	 *
321 	 */
322 
323 	FD_ZERO(&rfds);
324 	FD_SET(child_fd, &rfds);
325 	FD_ZERO(&efds);
326 	FD_SET(child_fd, &efds);
327 	/* Wait up to 250 seconds for device probing */
328 	tv.tv_sec = 250;
329 	tv.tv_usec = 0;
330 
331 	retval = select (child_fd + 1, &rfds, NULL, &efds, &tv);
332 
333 	if (child_died) {
334 		/* written from handle_sigchld */
335 		ret = 1;
336 		goto out;
337 	}
338 
339 	if (retval > 0) {
340 		/* means child wrote to socket or closed it; all good */
341 		ret = 0;
342 		goto out;
343 	}
344 
345 	/* assume timeout; kill child */
346 	kill (child_pid, SIGTERM);
347 	ret = 2;
348 
349 out:
350 	return ret;
351 }
352 
353 /*--------------------------------------------------------------------------------------------------*/
354 
355 /** Entry point for HAL daemon
356  *
357  *  @param  argc                Number of arguments
358  *  @param  argv                Array of arguments
359  *  @return                     Exit code
360  */
361 int
362 main (int argc, char *argv[])
363 {
364 	GMainLoop *loop;
365 	guint sigterm_iochn_listener_source_id;
366 	char *path;
367 	char newpath[512];
368 
369 	openlog ("hald", LOG_PID, LOG_DAEMON);
370 #if !GLIB_CHECK_VERSION(2,35,0)
371 	g_type_init ();
372 #endif
373 	if (getenv ("HALD_VERBOSE"))
374 		hald_is_verbose = TRUE;
375 	else
376 		hald_is_verbose = FALSE;
377 
378 	/* our helpers are installed into libexec, so adjust out $PATH
379 	 * to include this at the end (since we want to overide in
380 	 * run-hald.sh and friends)
381 	 */
382 	path = getenv ("PATH");
383 	if (path != NULL) {
384 		g_strlcpy (newpath, path, sizeof (newpath));
385 		g_strlcat (newpath, ":", sizeof (newpath));
386 	} else {
387 		/* No PATH was set */
388 		newpath[0] = '\0';
389 	}
390 
391 	g_strlcat (newpath, PACKAGE_LIBEXEC_DIR, sizeof (newpath));
392 	g_strlcat (newpath, ":", sizeof (newpath));
393 	g_strlcat (newpath, PACKAGE_SCRIPT_DIR, sizeof (newpath));
394 
395 	setenv ("PATH", newpath, TRUE);
396 
397 	while (1) {
398 		int c;
399 		int option_index = 0;
400 		const char *opt;
401 		static struct option long_options[] = {
402 			{"daemon", 1, NULL, 0},
403 			{"verbose", 1, NULL, 0},
404 			{"use-syslog", 0, NULL, 0},
405 			{"help", 0, NULL, 0},
406 			{"version", 0, NULL, 0},
407 			{NULL, 0, NULL, 0}
408 		};
409 
410 		c = getopt_long (argc, argv, "",
411 				 long_options, &option_index);
412 		if (c == -1)
413 			break;
414 
415 		switch (c) {
416 		case 0:
417 			opt = long_options[option_index].name;
418 
419 			if (strcmp (opt, "help") == 0) {
420 				usage ();
421 				return 0;
422 			} else if (strcmp (opt, "version") == 0) {
423 				fprintf (stderr, "HAL package version: " PACKAGE_VERSION "\n");
424 				return 0;
425 			} else if (strcmp (opt, "daemon") == 0) {
426 				if (strcmp ("yes", optarg) == 0) {
427 					opt_become_daemon = TRUE;
428 				} else if (strcmp ("no", optarg) == 0) {
429 					opt_become_daemon = FALSE;
430 				} else {
431 					usage ();
432 					return 1;
433 				}
434 			} else if (strcmp (opt, "verbose") == 0) {
435 				if (strcmp ("yes", optarg) == 0) {
436 					hald_is_verbose = TRUE;
437 				} else if (strcmp ("no", optarg) == 0) {
438 					hald_is_verbose = FALSE;
439 				} else {
440 					usage ();
441 					return 1;
442 				}
443 			} else if (strcmp (opt, "use-syslog") == 0) {
444                                 hald_use_syslog = TRUE;
445 			}
446 
447 			break;
448 
449 		default:
450 			usage ();
451 			return 1;
452 			break;
453 		}
454 	}
455 
456 	if (hald_is_verbose)
457 		logger_enable ();
458 	else
459 		logger_disable ();
460 
461 	if (hald_use_syslog)
462 		logger_enable_syslog ();
463 	else
464 		logger_disable_syslog ();
465 
466 	/* will fork into two; only the child will return here if we are successful */
467 	/*master_slave_setup ();
468 	  sleep (100000000);*/
469 
470 	loop = g_main_loop_new (NULL, FALSE);
471 
472 	HAL_INFO ((PACKAGE_STRING));
473 
474 	if (opt_become_daemon) {
475 		int child_pid;
476 		int dev_null_fd;
477 		int pf;
478 		ssize_t written;
479 		char pid[9];
480 
481 		HAL_INFO (("Will daemonize"));
482 		HAL_INFO (("Becoming a daemon"));
483 
484 		if (pipe (startup_daemonize_pipe) != 0) {
485 			fprintf (stderr, "Could not setup pipe: %s\n", strerror(errno));
486 			exit (1);
487 		}
488 
489 
490 		if (chdir ("/") < 0) {
491 			fprintf (stderr, "Could not chdir to /: %s\n", strerror(errno));
492 			exit (1);
493 		}
494 
495 		child_pid = fork ();
496 		switch (child_pid) {
497 		case -1:
498 			fprintf (stderr, "Cannot fork(): %s\n", strerror(errno));
499 			break;
500 
501 		case 0:
502 			/* child */
503 
504 			dev_null_fd = open ("/dev/null", O_RDWR);
505 			/* ignore if we can't open /dev/null */
506 			if (dev_null_fd >= 0) {
507 				/* attach /dev/null to stdout, stdin, stderr */
508 				dup2 (dev_null_fd, 0);
509 				dup2 (dev_null_fd, 1);
510 				dup2 (dev_null_fd, 2);
511 				close (dev_null_fd);
512 			}
513 
514 			umask (022);
515 			break;
516 
517 		default:
518 			/* parent, block until child writes */
519 			exit (parent_wait_for_child (startup_daemonize_pipe[0], child_pid));
520 			break;
521 		}
522 
523 		/* Create session */
524 		setsid ();
525 
526 		/* remove old pid file */
527 		unlink (HALD_PID_FILE);
528 
529 		/* Make a new one */
530 		if ((pf= open (HALD_PID_FILE, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0644)) > 0) {
531 			snprintf (pid, sizeof(pid), "%lu\n", (long unsigned) getpid ());
532 			written = write (pf, pid, strlen(pid));
533 			close (pf);
534 			atexit (delete_pid);
535 		}
536 	} else {
537 		HAL_INFO (("Will not daemonize"));
538 	}
539 
540 
541 	/* we need to do stuff when we are expected to terminate, thus
542 	 * this involves looking for SIGTERM; UNIX signal handlers are
543 	 * evil though, so set up a pipe to transmit the signal.
544 	 */
545 
546 	/* create pipe */
547 	if (pipe (sigterm_unix_signal_pipe_fds) != 0) {
548 		DIE (("Could not setup pipe, errno=%d", errno));
549 	}
550 
551 	/* setup glib handler - 0 is for reading, 1 is for writing */
552 	sigterm_iochn = g_io_channel_unix_new (sigterm_unix_signal_pipe_fds[0]);
553 	if (sigterm_iochn == NULL)
554 		DIE (("Could not create GIOChannel"));
555 
556 	/* get callback when there is data to read */
557 	sigterm_iochn_listener_source_id = g_io_add_watch (
558 		sigterm_iochn, G_IO_IN, sigterm_iochn_data, NULL);
559 
560 	/* Finally, setup unix signal handler for TERM */
561 	signal (SIGTERM, handle_sigterm);
562 
563 	/* set up the local dbus server */
564 	if (!hald_dbus_local_server_init ())
565 		return 1;
566 	/* Start the runner helper daemon */
567 	if (!hald_runner_start_runner ()) {
568 		return 1;
569 	}
570 
571 	drop_privileges(0);
572 
573 	/* initialize operating system specific parts */
574 	osspec_init ();
575 
576 	hald_is_initialising = TRUE;
577 
578 	/* detect devices */
579 	osspec_probe ();
580 
581 	/* run the main loop and serve clients */
582 	g_main_loop_run (loop);
583 
584 	return 0;
585 }
586 
587 #ifdef HALD_MEMLEAK_DBG
588 extern int dbg_hal_device_object_delta;
589 
590 /* useful for valgrinding; see below */
591 static gboolean
592 my_shutdown (gpointer data)
593 {
594 	HalDeviceStore *gdl;
595 
596 	printf ("Num devices in TDL: %d\n", g_slist_length ((hald_get_tdl ())->devices));
597 	printf ("Num devices in GDL: %d\n", g_slist_length ((hald_get_gdl ())->devices));
598 
599 	gdl = hald_get_gdl ();
600 next:
601 	if (g_slist_length (gdl->devices) > 0) {
602 		HalDevice *d = HAL_DEVICE(gdl->devices->data);
603 		hal_device_store_remove (gdl, d);
604 		g_object_unref (d);
605 		goto next;
606 	}
607 
608 	printf ("hal_device_object_delta = %d (should be zero)\n", dbg_hal_device_object_delta);
609 	exit (1);
610 }
611 #endif
612 
613 void
614 osspec_probe_done (void)
615 {
616 	ssize_t written;
617 	char buf[1] = {0};
618 
619 	HAL_INFO (("Device probing completed"));
620 
621 	if (!hald_dbus_init ()) {
622 		hald_runner_kill_all();
623 		exit (1);
624 	}
625 
626 	/* tell parent to exit */
627 	written = write (startup_daemonize_pipe[1], buf, sizeof (buf));
628 	close (startup_daemonize_pipe[0]);
629 	close (startup_daemonize_pipe[1]);
630 
631 	hald_is_initialising = FALSE;
632 
633 #ifdef HALD_MEMLEAK_DBG
634 	g_timeout_add ((HALD_MEMLEAK_DBG) * 1000,
635 		       my_shutdown,
636 		       NULL);
637 #endif
638 }
639 
640 
641 /** @} */
642