/***************************************************************************
* CVSID: $Id$
*
* hald.c : main startup for HAL daemon
*
* Copyright (C) 2003 David Zeuthen, <david@fubar.dk>
* Copyright (C) 2005 Danny Kukawka, <danny.kukawka@web.de>
*
* 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 <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <pwd.h>
#include <stdint.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <grp.h>
#include <syslog.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
/*#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)
{
ssize_t written;
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 */
written = 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;
guint sigterm_iochn_listener_source_id;
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;
ssize_t written;
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 ());
written = 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 */
sigterm_iochn_listener_source_id = 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)
{
ssize_t written;
char buf[1] = {0};
HAL_INFO (("Device probing completed"));
if (!hald_dbus_init ()) {
hald_runner_kill_all();
exit (1);
}
/* tell parent to exit */
written = 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
}
/** @} */