/*************************************************************************** * CVSID: $Id$ * * hald.c : main startup for HAL daemon * * Copyright (C) 2003 David Zeuthen, * Copyright (C) 2005 Danny Kukawka, * * Licensed under the Academic Free License version 2.1 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * **************************************************************************/ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*#include "master_slave.h"*/ #include "logger.h" #include "hald.h" #include "device_store.h" #include "device_info.h" #include "osspec.h" #include "hald_dbus.h" #include "util.h" #include "hald_runner.h" #include "util_helper.h" static void delete_pid(void) { unlink(HALD_PID_FILE); } /** * @defgroup HalDaemon HAL daemon * @brief The HAL daemon manages persistent device objects available through * a D-BUS network API */ static HalDeviceStore *global_device_list = NULL; static HalDeviceStore *temporary_device_list = NULL; static void addon_terminated (HalDevice *device, guint32 exit_type, gint return_code, gchar **error, gpointer data1, gpointer data2) { HAL_INFO (("in addon_terminated for udi=%s", device->udi)); /* TODO: log to syslog - addons shouldn't just terminate, this is a bug with the addon */ /* however, the world can stop, mark this addon as ready * (TODO: potential bug if the addon crashed after calling libhal_device_addon_is_ready()) */ if (hal_device_inc_num_ready_addons (device)) { if (hal_device_are_all_addons_ready (device)) { manager_send_signal_device_added (device); } } } static void gdl_store_changed (HalDeviceStore *store, HalDevice *device, gboolean is_added, gpointer user_data) { if (is_added) { GSList *addons; HAL_INFO (("Added device to GDL; udi=%s", hal_device_get_udi(device))); if ((addons = hal_device_property_get_strlist (device, "info.addons")) != NULL) { GSList *i; for (i = addons; i != NULL; i = g_slist_next (i)) { const gchar *command_line; gchar *extra_env[2] = {"HALD_ACTION=addon", NULL}; command_line = (const gchar *) i->data; if (hald_runner_start(device, command_line, extra_env, addon_terminated, NULL, NULL)) { HAL_INFO (("Started addon %s for udi %s", command_line, hal_device_get_udi(device))); hal_device_inc_num_addons (device); } else { HAL_ERROR (("Cannot start addon %s for udi %s", command_line, hal_device_get_udi(device))); } } } } else { HAL_INFO (("Removed device from GDL; udi=%s", hal_device_get_udi(device))); hald_runner_kill_device(device); } /*hal_device_print (device);*/ if (is_added) { if (hal_device_are_all_addons_ready (device)) { manager_send_signal_device_added (device); } } else { if (hal_device_are_all_addons_ready (device)) { manager_send_signal_device_removed (device); } } } static void gdl_property_changed (HalDeviceStore *store, HalDevice *device, const char *key, gboolean added, gboolean removed, gpointer user_data) { if (hal_device_are_all_addons_ready (device)) { device_send_signal_property_modified (device, key, removed, added); } /* only execute the callouts if the property _changed_ */ if (added == FALSE && removed == FALSE) /*hal_callout_property (device, key)*/; } static void gdl_capability_added (HalDeviceStore *store, HalDevice *device, const char *capability, gpointer user_data) { if (hal_device_are_all_addons_ready (device)) { manager_send_signal_new_capability (device, capability); } /*hal_callout_capability (device, capability, TRUE)*/; } HalDeviceStore * hald_get_gdl (void) { if (global_device_list == NULL) { global_device_list = hal_device_store_new (); g_signal_connect (global_device_list, "store_changed", G_CALLBACK (gdl_store_changed), NULL); g_signal_connect (global_device_list, "device_property_changed", G_CALLBACK (gdl_property_changed), NULL); g_signal_connect (global_device_list, "device_capability_added", G_CALLBACK (gdl_capability_added), NULL); } return global_device_list; } HalDeviceStore * hald_get_tdl (void) { if (temporary_device_list == NULL) { temporary_device_list = hal_device_store_new (); } return temporary_device_list; } /** * @defgroup MainDaemon Basic functions * @ingroup HalDaemon * @brief Basic functions in the HAL daemon * @{ */ /** Print out program usage. * */ static void usage () { fprintf (stderr, "\n" "usage : hald [--daemon=yes|no] [--verbose=yes|no] [--help]\n"); fprintf (stderr, "\n" " --daemon=yes|no Become a daemon\n" " --verbose=yes|no Print out debug (overrides HALD_VERBOSE)\n" " --use-syslog Print out debug messages to syslog instead of stderr.\n" " Use this option to get debug messages if HAL runs as\n" " daemon.\n" " --help Show this information and exit\n" " --version Output version information and exit" "\n" "The HAL daemon detects devices present in the system and provides the\n" "org.freedesktop.Hal service through the system-wide message bus provided\n" "by D-BUS.\n" "\n" "For more information visit http://freedesktop.org/Software/hal\n" "\n"); } /** If #TRUE, we will daemonize */ static dbus_bool_t opt_become_daemon = TRUE; /** If #TRUE, we will spew out debug */ dbus_bool_t hald_is_verbose = FALSE; dbus_bool_t hald_use_syslog = FALSE; static int sigterm_unix_signal_pipe_fds[2]; static GIOChannel *sigterm_iochn; static void handle_sigterm (int value) { static char marker[1] = {'S'}; /* write a 'S' character to the other end to tell about * the signal. Note that 'the other end' is a GIOChannel thingy * that is only called from the mainloop - thus this is how we * defer this since UNIX signal handlers are evil * * Oh, and write(2) is indeed reentrant */ (void) write (sigterm_unix_signal_pipe_fds[1], marker, 1); } static gboolean sigterm_iochn_data (GIOChannel *source, GIOCondition condition, gpointer user_data) { GError *err = NULL; gchar data[1]; gsize bytes_read; /* Empty the pipe */ if (G_IO_STATUS_NORMAL != g_io_channel_read_chars (source, data, 1, &bytes_read, &err)) { HAL_ERROR (("Error emptying sigterm pipe: %s", err->message)); g_error_free (err); goto out; } HAL_INFO (("Caught SIGTERM, initiating shutdown")); hald_runner_kill_all(); exit (0); out: return TRUE; } /** This is set to #TRUE if we are probing and #FALSE otherwise */ dbus_bool_t hald_is_initialising; static int startup_daemonize_pipe[2]; /*--------------------------------------------------------------------------------------------------*/ static gboolean child_died = FALSE; static void handle_sigchld (int value) { child_died = TRUE; } static int parent_wait_for_child (int child_fd, pid_t child_pid) { fd_set rfds; fd_set efds; struct timeval tv; int retval; int ret; signal(SIGCHLD, handle_sigchld); /* wait for either * * o Child writes something to the child_fd; means that device * probing is completed and the parent should exit with success * * o Child is killed (segfault etc.); means that parent should exit * with failure * * o Timeout; means that we should kill the child and exit with * failure * */ FD_ZERO(&rfds); FD_SET(child_fd, &rfds); FD_ZERO(&efds); FD_SET(child_fd, &efds); /* Wait up to 250 seconds for device probing */ tv.tv_sec = 250; tv.tv_usec = 0; retval = select (child_fd + 1, &rfds, NULL, &efds, &tv); if (child_died) { /* written from handle_sigchld */ ret = 1; goto out; } if (retval > 0) { /* means child wrote to socket or closed it; all good */ ret = 0; goto out; } /* assume timeout; kill child */ kill (child_pid, SIGTERM); ret = 2; out: return ret; } /*--------------------------------------------------------------------------------------------------*/ /** Entry point for HAL daemon * * @param argc Number of arguments * @param argv Array of arguments * @return Exit code */ int main (int argc, char *argv[]) { GMainLoop *loop; char *path; char newpath[512]; openlog ("hald", LOG_PID, LOG_DAEMON); #if !GLIB_CHECK_VERSION(2,35,0) g_type_init (); #endif if (getenv ("HALD_VERBOSE")) hald_is_verbose = TRUE; else hald_is_verbose = FALSE; /* our helpers are installed into libexec, so adjust out $PATH * to include this at the end (since we want to overide in * run-hald.sh and friends) */ path = getenv ("PATH"); if (path != NULL) { g_strlcpy (newpath, path, sizeof (newpath)); g_strlcat (newpath, ":", sizeof (newpath)); } else { /* No PATH was set */ newpath[0] = '\0'; } g_strlcat (newpath, PACKAGE_LIBEXEC_DIR, sizeof (newpath)); g_strlcat (newpath, ":", sizeof (newpath)); g_strlcat (newpath, PACKAGE_SCRIPT_DIR, sizeof (newpath)); setenv ("PATH", newpath, TRUE); while (1) { int c; int option_index = 0; const char *opt; static struct option long_options[] = { {"daemon", 1, NULL, 0}, {"verbose", 1, NULL, 0}, {"use-syslog", 0, NULL, 0}, {"help", 0, NULL, 0}, {"version", 0, NULL, 0}, {NULL, 0, NULL, 0} }; c = getopt_long (argc, argv, "", long_options, &option_index); if (c == -1) break; switch (c) { case 0: opt = long_options[option_index].name; if (strcmp (opt, "help") == 0) { usage (); return 0; } else if (strcmp (opt, "version") == 0) { fprintf (stderr, "HAL package version: " PACKAGE_VERSION "\n"); return 0; } else if (strcmp (opt, "daemon") == 0) { if (strcmp ("yes", optarg) == 0) { opt_become_daemon = TRUE; } else if (strcmp ("no", optarg) == 0) { opt_become_daemon = FALSE; } else { usage (); return 1; } } else if (strcmp (opt, "verbose") == 0) { if (strcmp ("yes", optarg) == 0) { hald_is_verbose = TRUE; } else if (strcmp ("no", optarg) == 0) { hald_is_verbose = FALSE; } else { usage (); return 1; } } else if (strcmp (opt, "use-syslog") == 0) { hald_use_syslog = TRUE; } break; default: usage (); return 1; break; } } if (hald_is_verbose) logger_enable (); else logger_disable (); if (hald_use_syslog) logger_enable_syslog (); else logger_disable_syslog (); /* will fork into two; only the child will return here if we are successful */ /*master_slave_setup (); sleep (100000000);*/ loop = g_main_loop_new (NULL, FALSE); HAL_INFO ((PACKAGE_STRING)); if (opt_become_daemon) { int child_pid; int dev_null_fd; int pf; char pid[9]; HAL_INFO (("Will daemonize")); HAL_INFO (("Becoming a daemon")); if (pipe (startup_daemonize_pipe) != 0) { fprintf (stderr, "Could not setup pipe: %s\n", strerror(errno)); exit (1); } if (chdir ("/") < 0) { fprintf (stderr, "Could not chdir to /: %s\n", strerror(errno)); exit (1); } child_pid = fork (); switch (child_pid) { case -1: fprintf (stderr, "Cannot fork(): %s\n", strerror(errno)); break; case 0: /* child */ dev_null_fd = open ("/dev/null", O_RDWR); /* ignore if we can't open /dev/null */ if (dev_null_fd >= 0) { /* attach /dev/null to stdout, stdin, stderr */ dup2 (dev_null_fd, 0); dup2 (dev_null_fd, 1); dup2 (dev_null_fd, 2); close (dev_null_fd); } umask (022); break; default: /* parent, block until child writes */ exit (parent_wait_for_child (startup_daemonize_pipe[0], child_pid)); break; } /* Create session */ setsid (); /* remove old pid file */ unlink (HALD_PID_FILE); /* Make a new one */ if ((pf= open (HALD_PID_FILE, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0644)) > 0) { snprintf (pid, sizeof(pid), "%lu\n", (long unsigned) getpid ()); (void) write (pf, pid, strlen(pid)); close (pf); atexit (delete_pid); } } else { HAL_INFO (("Will not daemonize")); } /* we need to do stuff when we are expected to terminate, thus * this involves looking for SIGTERM; UNIX signal handlers are * evil though, so set up a pipe to transmit the signal. */ /* create pipe */ if (pipe (sigterm_unix_signal_pipe_fds) != 0) { DIE (("Could not setup pipe, errno=%d", errno)); } /* setup glib handler - 0 is for reading, 1 is for writing */ sigterm_iochn = g_io_channel_unix_new (sigterm_unix_signal_pipe_fds[0]); if (sigterm_iochn == NULL) DIE (("Could not create GIOChannel")); /* get callback when there is data to read */ (void) g_io_add_watch ( sigterm_iochn, G_IO_IN, sigterm_iochn_data, NULL); /* Finally, setup unix signal handler for TERM */ signal (SIGTERM, handle_sigterm); /* set up the local dbus server */ if (!hald_dbus_local_server_init ()) return 1; /* Start the runner helper daemon */ if (!hald_runner_start_runner ()) { return 1; } drop_privileges(0); /* initialize operating system specific parts */ osspec_init (); hald_is_initialising = TRUE; /* detect devices */ osspec_probe (); /* run the main loop and serve clients */ g_main_loop_run (loop); return 0; } #ifdef HALD_MEMLEAK_DBG extern int dbg_hal_device_object_delta; /* useful for valgrinding; see below */ static gboolean my_shutdown (gpointer data) { HalDeviceStore *gdl; printf ("Num devices in TDL: %d\n", g_slist_length ((hald_get_tdl ())->devices)); printf ("Num devices in GDL: %d\n", g_slist_length ((hald_get_gdl ())->devices)); gdl = hald_get_gdl (); next: if (g_slist_length (gdl->devices) > 0) { HalDevice *d = HAL_DEVICE(gdl->devices->data); hal_device_store_remove (gdl, d); g_object_unref (d); goto next; } printf ("hal_device_object_delta = %d (should be zero)\n", dbg_hal_device_object_delta); exit (1); } #endif void osspec_probe_done (void) { char buf[1] = {0}; HAL_INFO (("Device probing completed")); if (!hald_dbus_init ()) { hald_runner_kill_all(); exit (1); } /* tell parent to exit */ (void) write (startup_daemonize_pipe[1], buf, sizeof (buf)); close (startup_daemonize_pipe[0]); close (startup_daemonize_pipe[1]); hald_is_initialising = FALSE; #ifdef HALD_MEMLEAK_DBG g_timeout_add ((HALD_MEMLEAK_DBG) * 1000, my_shutdown, NULL); #endif } /** @} */