OpenSolaris_b135/cmd/rcm_daemon/common/rcm_main.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Reconfiguration Coordination Daemon
 *
 * Accept RCM messages in the form of RCM events and process them
 * - to build and update the system resource map
 * - to allow clients to register/unregister for resource
 * - to allow dr initiators to offline a resource before removal
 * - to call into clients to perform suspend/offline actions
 *
 * The goal is to enable fully automated Dynamic Reconfiguration and better
 * DR information tracking.
 */

#include <librcm_event.h>

#include "rcm_impl.h"

/* will run in daemon mode if debug level < DEBUG_LEVEL_FORK */
#define	DEBUG_LEVEL_FORK	RCM_DEBUG

#define	DAEMON_LOCK_FILE "/var/run/rcm_daemon_lock"

static int hold_daemon_lock;
static int daemon_lock_fd;
static const char *daemon_lock_file = DAEMON_LOCK_FILE;

int debug_level = 0;
static int idle_timeout;
static int logflag = 0;
static char *prog;

static void usage(void);
static void catch_sighup(void);
static void catch_sigusr1(void);
static pid_t enter_daemon_lock(void);
static void exit_daemon_lock(void);

extern void init_poll_thread();
extern void cleanup_poll_thread();

/*
 * Print command line syntax for starting rcm_daemon
 */
static void
usage() {
	(void) fprintf(stderr,
	    gettext("usage: %s [-d debug_level] [-t idle_timeout]\n"), prog);
	rcmd_exit(EINVAL);
}

/*
 * common cleanup/exit functions to ensure releasing locks
 */
static void
rcmd_cleanup(int status)
{
	if (status == 0) {
		rcm_log_message(RCM_INFO,
		    gettext("rcm_daemon normal exit\n"));
	} else {
		rcm_log_message(RCM_ERROR,
		    gettext("rcm_daemon exit: errno = %d\n"), status);
	}

	if (hold_daemon_lock) {
		exit_daemon_lock();
	}
}

void
rcmd_exit(int status)
{
	rcmd_cleanup(status);
	exit(status);
}

/*
 * When SIGHUP is received, reload modules at the next safe moment (when
 * there is no DR activity.
 */
void
catch_sighup(void)
{
	rcm_log_message(RCM_INFO,
	    gettext("SIGHUP received, will exit when daemon is idle\n"));
	rcmd_thr_signal();
}

/*
 * When SIGUSR1 is received, exit the thread
 */
void
catch_sigusr1(void)
{
	rcm_log_message(RCM_DEBUG, "SIGUSR1 received in thread %d\n",
	    thr_self());
	cleanup_poll_thread();
	thr_exit(NULL);
}

/*
 * Use an advisory lock to ensure that only one daemon process is
 * active at any point in time.
 */
static pid_t
enter_daemon_lock(void)
{
	struct flock lock;

	rcm_log_message(RCM_TRACE1,
	    "enter_daemon_lock: lock file = %s\n", daemon_lock_file);

	daemon_lock_fd = open(daemon_lock_file, O_CREAT|O_RDWR, 0644);
	if (daemon_lock_fd < 0) {
		rcm_log_message(RCM_ERROR, gettext("open(%s) - %s\n"),
		    daemon_lock_file, strerror(errno));
		rcmd_exit(errno);
	}

	lock.l_type = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;

	if (fcntl(daemon_lock_fd, F_SETLK, &lock) == 0) {
		hold_daemon_lock = 1;
		return (getpid());
	}

	/* failed to get lock, attempt to find lock owner */
	if ((errno == EAGAIN || errno == EDEADLK) &&
	    (fcntl(daemon_lock_fd, F_GETLK, &lock) == 0)) {
		return (lock.l_pid);
	}

	/* die a horrible death */
	rcm_log_message(RCM_ERROR, gettext("lock(%s) - %s"), daemon_lock_file,
	    strerror(errno));
	exit(errno);
	/*NOTREACHED*/
}

/*
 * Drop the advisory daemon lock, close lock file
 */
static void
exit_daemon_lock(void)
{
	struct flock lock;

	lock.l_type = F_UNLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;

	if (fcntl(daemon_lock_fd, F_SETLK, &lock) == -1) {
		rcm_log_message(RCM_ERROR, gettext("unlock(%s) - %s"),
		    daemon_lock_file, strerror(errno));
	}

	(void) close(daemon_lock_fd);
}

/*PRINTFLIKE2*/
static void
rcm_log_msg_impl(int level, char *message, va_list ap)
{
	int log_level;

	if (!logflag) {
		/*
		 * RCM_ERROR goes to stderr, others go to stdout
		 */
		FILE *out = (level <= RCM_ERROR) ? stderr : stdout;
		(void) vfprintf(out, message, ap);
		return;
	}

	/*
	 * translate RCM_* to LOG_*
	 */
	switch (level) {
	case RCM_ERROR:
		log_level = LOG_ERR;
		break;

	case RCM_WARNING:
		log_level = LOG_WARNING;
		break;

	case RCM_NOTICE:
		log_level = LOG_NOTICE;
		break;

	case RCM_INFO:
		log_level = LOG_INFO;
		break;

	case RCM_DEBUG:
		log_level = LOG_DEBUG;
		break;

	default:
		/*
		 * Don't log RCM_TRACEn messages
		 */
		return;
	}

	(void) vsyslog(log_level, message, ap);
}

/*
 * print error messages to the terminal or to syslog
 */
void
rcm_log_message(int level, char *message, ...)
{
	va_list ap;

	if (level > debug_level) {
		return;
	}

	va_start(ap, message);
	rcm_log_msg_impl(level, message, ap);
	va_end(ap);
}

/*
 * Print error messages to the terminal or to syslog.
 * Same as rcm_log_message except that it does not check for
 * level > debug_level
 * allowing callers to override the global debug_level.
 */
void
rcm_log_msg(int level, char *message, ...)
{
	va_list ap;

	va_start(ap, message);
	rcm_log_msg_impl(level, message, ap);
	va_end(ap);
}

/*
 * grab daemon_lock and direct messages to syslog
 */
static void
detachfromtty()
{
	(void) chdir("/");
	(void) setsid();
	(void) close(0);
	(void) close(1);
	(void) close(2);
	(void) open("/dev/null", O_RDWR, 0);
	(void) dup2(0, 1);
	(void) dup2(0, 2);
	openlog(prog, LOG_PID, LOG_DAEMON);
	logflag = 1;
}

int
main(int argc, char **argv)
{
	int c;
	pid_t pid;
	extern char *optarg;
	sigset_t mask;
	struct sigaction act;

	(void) setlocale(LC_ALL, "");
#ifndef	TEXT_DOMAIN
#define	TEXT_DOMAIN	"SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	if ((prog = strrchr(argv[0], '/')) == NULL) {
		prog = argv[0];
	} else {
		prog++;
	}

	(void) enable_extended_FILE_stdio(-1, -1);

	/*
	 * process arguments
	 */
	if (argc > 3) {
		usage();
	}
	while ((c = getopt(argc, argv, "d:t:")) != EOF) {
		switch (c) {
		case 'd':
			debug_level = atoi(optarg);
			break;
		case 't':
			idle_timeout = atoi(optarg);
			break;
		case '?':
		default:
			usage();
			/*NOTREACHED*/
		}
	}

	/*
	 * Check permission
	 */
	if (getuid() != 0) {
		(void) fprintf(stderr, gettext("Must be root to run %s\n"),
		    prog);
		exit(EPERM);
	}

	/*
	 * When rcm_daemon is started by a call to librcm, it inherits file
	 * descriptors from the DR initiator making a call. The file
	 * descriptors may correspond to devices that can be removed by DR.
	 * Since keeping them remain opened is problematic, close everything
	 * but stdin/stdout/stderr.
	 */
	closefrom(3);

	/*
	 * When rcm_daemon is started by the caller, it will inherit the
	 * signal block mask.  We unblock all signals to make sure the
	 * signal handling will work normally.
	 */
	(void) sigfillset(&mask);
	(void) thr_sigsetmask(SIG_UNBLOCK, &mask, NULL);

	/*
	 * block SIGUSR1, use it for killing specific threads
	 */
	(void) sigemptyset(&mask);
	(void) sigaddset(&mask, SIGUSR1);
	(void) thr_sigsetmask(SIG_BLOCK, &mask, NULL);

	/*
	 * Setup signal handlers for SIGHUP and SIGUSR1
	 * SIGHUP - causes a "delayed" daemon exit, effectively the same
	 *	as a daemon restart.
	 * SIGUSR1 - causes a thr_exit(). Unblocked in selected threads.
	 */
	act.sa_flags = 0;
	act.sa_handler = catch_sighup;
	(void) sigaction(SIGHUP, &act, NULL);
	act.sa_handler = catch_sigusr1;
	(void) sigaction(SIGUSR1, &act, NULL);

	/*
	 * ignore SIGPIPE so that the rcm daemon does not exit when it
	 * attempts to read or write from a pipe whose corresponding
	 * rcm script process exited.
	 */
	act.sa_handler = SIG_IGN;
	(void) sigaction(SIGPIPE, &act, NULL);

	/*
	 * run in daemon mode
	 */
	if (debug_level < DEBUG_LEVEL_FORK) {
		if (fork()) {
			exit(0);
		}
		detachfromtty();
	}

	/* only one daemon can run at a time */
	if ((pid = enter_daemon_lock()) != getpid()) {
		rcm_log_message(RCM_DEBUG, "%s pid %d already running\n",
		    prog, pid);
		exit(EDEADLK);
	}

	rcm_log_message(RCM_TRACE1, "%s started, debug level = %d\n",
	    prog, debug_level);

	/*
	 * Set daemon state to block RCM requests before rcm_daemon is
	 * fully initialized. See rcmd_thr_incr().
	 */
	rcmd_set_state(RCMD_INIT);

	/*
	 * create rcm_daemon door and set permission to 0400
	 */
	if (create_event_service(RCM_SERVICE_DOOR, event_service) == -1) {
		rcm_log_message(RCM_ERROR,
		    gettext("cannot create door service: %s\n"),
		    strerror(errno));
		rcmd_exit(errno);
	}
	(void) chmod(RCM_SERVICE_DOOR, S_IRUSR);

	init_poll_thread(); /* initialize poll thread related data */

	/*
	 * Initialize database by asking modules to register.
	 */
	rcmd_db_init();

	/*
	 * Initialize locking, including lock recovery in the event of
	 * unexpected daemon failure.
	 */
	rcmd_lock_init();

	/*
	 * Start accepting normal requests
	 */
	rcmd_set_state(RCMD_NORMAL);

	/*
	 * Start cleanup thread
	 */
	rcmd_db_clean();

	/*
	 * Loop within daemon and return after a period of inactivity.
	 */
	rcmd_start_timer(idle_timeout);

	rcmd_cleanup(0);
	return (0);
}