rcm_main.c revision 47856d53b04240402f14fafd3c9c7201f4809da4
/*
* 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);
}