/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <strings.h>
#include <syslog.h>
#include <priv.h>
#include <wait.h>
#include <getopt.h>
#include <synch.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <libhotplug.h>
#include <libhotplug_impl.h>
#include "hotplugd_impl.h"
/*
* Define long options for command line.
*/
static const struct option lopts[] = {
{ "help", no_argument, 0, '?' },
{ "version", no_argument, 0, 'V' },
{ "debug", no_argument, 0, 'd' },
{ 0, 0, 0, 0 }
};
/*
* Local functions.
*/
static void usage(void);
static boolean_t check_privileges(void);
static int daemonize(void);
static void init_signals(void);
static void signal_handler(int signum);
static void shutdown_daemon(void);
/*
* Global variables.
*/
static char *prog;
static char version[] = "1.0";
static boolean_t log_flag = B_FALSE;
static boolean_t debug_flag = B_FALSE;
static boolean_t exit_flag = B_FALSE;
static sema_t signal_sem;
/*
* main()
*
* The hotplug daemon is designed to be a background daemon
* controlled by SMF. So by default it will daemonize and
* do some coordination with its parent process in order to
* indicate proper success or failure back to SMF. And all
* output will be sent to syslog.
*
* But if given the '-d' command line option, it will instead
* run in the foreground in a standalone, debug mode. Errors
* and additional debug messages will be printed to the controlling
* terminal instead of to syslog.
*/
int
main(int argc, char *argv[])
{
int opt;
int pfd;
int status;
if ((prog = strrchr(argv[0], '/')) == NULL)
prog = argv[0];
else
prog++;
/* Check privileges */
if (!check_privileges()) {
(void) fprintf(stderr, "Insufficient privileges. "
"(All privileges are required.)\n");
return (-1);
}
/* Process options */
while ((opt = getopt_clip(argc, argv, "dV?", lopts, NULL)) != -1) {
switch (opt) {
case 'd':
debug_flag = B_TRUE;
break;
case 'V':
(void) printf("%s: Version %s\n", prog, version);
return (0);
default:
if (optopt == '?') {
usage();
return (0);
}
(void) fprintf(stderr, "Unrecognized option '%c'.\n",
optopt);
usage();
return (-1);
}
}
/* Initialize semaphore for daemon shutdown */
if (sema_init(&signal_sem, 1, USYNC_THREAD, NULL) != 0)
exit(EXIT_FAILURE);
/* Initialize signal handling */
init_signals();
/* Daemonize, if not in DEBUG mode */
if (!debug_flag)
pfd = daemonize();
/* Initialize door service */
if (!door_server_init()) {
if (!debug_flag) {
status = EXIT_FAILURE;
(void) write(pfd, &status, sizeof (status));
(void) close(pfd);
}
exit(EXIT_FAILURE);
}
/* Daemon initialized */
if (!debug_flag) {
status = 0;
(void) write(pfd, &status, sizeof (status));
(void) close(pfd);
}
/* Note that daemon is running */
log_info("hotplug daemon started.\n");
/* Wait for shutdown signal */
while (!exit_flag)
(void) sema_wait(&signal_sem);
shutdown_daemon();
return (0);
}
/*
* usage()
*
* Print a brief usage synopsis for the command line options.
*/
static void
usage(void)
{
(void) printf("Usage: %s [-d]\n", prog);
}
/*
* check_privileges()
*
* Check if the current process has enough privileges
* to run the daemon. Note that all privileges are
* required in order for RCM interactions to work.
*/
static boolean_t
check_privileges(void)
{
priv_set_t *privset;
boolean_t rv = B_FALSE;
if ((privset = priv_allocset()) != NULL) {
if (getppriv(PRIV_EFFECTIVE, privset) == 0) {
rv = priv_isfullset(privset);
}
priv_freeset(privset);
}
return (rv);
}
/*
* daemonize()
*
* Fork the daemon process into the background, and detach from
* the controlling terminal. Setup a shared pipe that will later
* be used to report startup status to the parent process.
*/
static int
daemonize(void)
{
int status;
int pfds[2];
pid_t pid;
sigset_t set;
sigset_t oset;
/*
* Temporarily block all signals. They will remain blocked in
* the parent, but will be unblocked in the child once it has
* notified the parent of its startup status.
*/
(void) sigfillset(&set);
(void) sigdelset(&set, SIGABRT);
(void) sigprocmask(SIG_BLOCK, &set, &oset);
/* Create the shared pipe */
if (pipe(pfds) == -1) {
log_err("Cannot create pipe (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* Fork the daemon process */
if ((pid = fork()) == -1) {
log_err("Cannot fork daemon process (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* Parent: waits for exit status from child. */
if (pid > 0) {
(void) close(pfds[1]);
if (read(pfds[0], &status, sizeof (status)) == sizeof (status))
_exit(status);
if ((waitpid(pid, &status, 0) == pid) && WIFEXITED(status))
_exit(WEXITSTATUS(status));
log_err("Failed to spawn daemon process.\n");
_exit(EXIT_FAILURE);
}
/* Child continues... */
(void) setsid();
(void) chdir("/");
(void) umask(CMASK);
(void) sigprocmask(SIG_SETMASK, &oset, NULL);
(void) close(pfds[0]);
/* Detach from controlling terminal */
(void) close(0);
(void) close(1);
(void) close(2);
(void) open("/dev/null", O_RDONLY);
(void) open("/dev/null", O_WRONLY);
(void) open("/dev/null", O_WRONLY);
/* Use syslog for future messages */
log_flag = B_TRUE;
openlog(prog, LOG_PID, LOG_DAEMON);
return (pfds[1]);
}
/*
* init_signals()
*
* Initialize signal handling.
*/
static void
init_signals(void)
{
struct sigaction act;
sigset_t set;
(void) sigfillset(&set);
(void) sigdelset(&set, SIGABRT);
(void) sigfillset(&act.sa_mask);
act.sa_handler = signal_handler;
act.sa_flags = 0;
(void) sigaction(SIGTERM, &act, NULL);
(void) sigaction(SIGHUP, &act, NULL);
(void) sigaction(SIGINT, &act, NULL);
(void) sigaction(SIGPIPE, &act, NULL);
(void) sigdelset(&set, SIGTERM);
(void) sigdelset(&set, SIGHUP);
(void) sigdelset(&set, SIGINT);
(void) sigdelset(&set, SIGPIPE);
}
/*
* signal_handler()
*
* Most signals cause the hotplug daemon to shut down.
* Shutdown is triggered using a semaphore to wake up
* the main thread for a clean exit.
*
* Except SIGPIPE is used to coordinate between the parent
* and child processes when the daemon first starts.
*/
static void
signal_handler(int signum)
{
log_info("Received signal %d.\n", signum);
switch (signum) {
case 0:
case SIGPIPE:
break;
default:
exit_flag = B_TRUE;
(void) sema_post(&signal_sem);
break;
}
}
/*
* shutdown_daemon()
*
* Perform a clean shutdown of the daemon.
*/
static void
shutdown_daemon(void)
{
log_info("Hotplug daemon shutting down.\n");
door_server_fini();
if (log_flag)
closelog();
(void) sema_destroy(&signal_sem);
}
/*
* log_err()
*
* Display an error message. Use syslog if in daemon
* mode, otherwise print to stderr when in debug mode.
*/
/*PRINTFLIKE1*/
void
log_err(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (debug_flag || !log_flag)
(void) vfprintf(stderr, fmt, ap);
else
vsyslog(LOG_ERR, fmt, ap);
va_end(ap);
}
/*
* log_info()
*
* Display an information message. Use syslog if in daemon
* mode, otherwise print to stdout when in debug mode.
*/
/*PRINTFLIKE1*/
void
log_info(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (debug_flag || !log_flag)
(void) vfprintf(stdout, fmt, ap);
else
vsyslog(LOG_INFO, fmt, ap);
va_end(ap);
}
/*
* dprintf()
*
* Print a debug tracing statement. Only works in debug
* mode, and always prints to stdout.
*/
/*PRINTFLIKE1*/
void
dprintf(char *fmt, ...)
{
va_list ap;
if (debug_flag) {
va_start(ap, fmt);
(void) vprintf(fmt, ap);
va_end(ap);
}
}