fmd_main.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
0N/A/*
0N/A * CDDL HEADER START
0N/A *
0N/A * The contents of this file are subject to the terms of the
0N/A * Common Development and Distribution License, Version 1.0 only
0N/A * (the "License"). You may not use this file except in compliance
0N/A * with the License.
0N/A *
0N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
0N/A * or http://www.opensolaris.org/os/licensing.
0N/A * See the License for the specific language governing permissions
0N/A * and limitations under the License.
0N/A *
0N/A * When distributing Covered Code, include this CDDL HEADER in each
0N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
0N/A * If applicable, add the following below this CDDL HEADER, with the
0N/A * fields enclosed by brackets "[]" replaced with your own identifying
0N/A * information: Portions Copyright [yyyy] [name of copyright owner]
873N/A *
0N/A * CDDL HEADER END
0N/A */
0N/A/*
0N/A * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
0N/A * Use is subject to license terms.
3231N/A */
0N/A
0N/A#pragma ident "%Z%%M% %I% %E% SMI"
2086N/A
0N/A#include <sys/types.h>
0N/A#include <sys/stat.h>
0N/A#include <sys/wait.h>
0N/A#include <sys/corectl.h>
0N/A#include <sys/resource.h>
0N/A
0N/A#include <priv_utils.h>
0N/A#include <signal.h>
0N/A#include <unistd.h>
510N/A#include <limits.h>
1177N/A#include <fcntl.h>
1177N/A#include <strings.h>
0N/A#include <stdlib.h>
0N/A#include <stdio.h>
1400N/A
1400N/A#include <fmd_error.h>
2086N/A#include <fmd_string.h>
0N/A#include <fmd_conf.h>
0N/A#include <fmd_dispq.h>
0N/A#include <fmd_subr.h>
0N/A#include <fmd.h>
0N/A
0N/Afmd_t fmd;
0N/A
0N/A/*
0N/A * For DEBUG builds, we define a set of hooks for libumem that provide useful
0N/A * default settings for the allocator's debugging facilities.
0N/A */
0N/A#ifdef DEBUG
0N/Aconst char *
0N/A_umem_debug_init()
1400N/A{
1400N/A return ("default,verbose"); /* $UMEM_DEBUG setting */
1400N/A}
1400N/A
1400N/Aconst char *
0N/A_umem_logging_init(void)
0N/A{
0N/A return ("fail,contents"); /* $UMEM_LOGGING setting */
0N/A}
0N/A#endif /* DEBUG */
0N/A
0N/A/*
0N/A * We use a two-phase algorithm for becoming a daemon because we want the
0N/A * daemon process (the child) to do the work of becoming MT-hot and opening our
0N/A * event transport. Since these operations can fail and need to result in the
0N/A * daemon failing to start, the parent must wait until fmd_run() completes to
0N/A * know whether it can return zero or non-zero status to the invoking command.
0N/A * The parent waits on a pipe inside this function to read the exit status.
0N/A * The child gets the write-end of the pipe returned by daemonize_init() and
0N/A * then fmd_run() uses the pipe to set the exit status and detach the parent.
0N/A */
0N/Astatic int
0N/Adaemonize_init(void)
0N/A{
0N/A int status, pfds[2];
0N/A sigset_t set, oset;
0N/A struct rlimit rlim;
0N/A char path[PATH_MAX];
0N/A pid_t pid;
0N/A
0N/A /*
0N/A * Set our per-process core file path to leave core files in our
0N/A * var/fm/fmd directory, named after the PID to aid in debugging,
0N/A * and make sure that there is no restriction on core file size.
0N/A */
0N/A (void) snprintf(path, sizeof (path),
0N/A "%s/var/fm/fmd/core.%s.%%p", fmd.d_rootdir, fmd.d_pname);
0N/A
0N/A (void) core_set_process_path(path, strlen(path) + 1, fmd.d_pid);
0N/A
0N/A rlim.rlim_cur = RLIM_INFINITY;
0N/A rlim.rlim_max = RLIM_INFINITY;
0N/A
0N/A (void) setrlimit(RLIMIT_CORE, &rlim);
0N/A
0N/A /*
0N/A * Reset all of our privilege sets to the minimum set of required
0N/A * privileges. We continue to run as root so that files we create
0N/A * such as logs and checkpoints are secured in the /var filesystem.
0N/A */
0N/A if (__init_daemon_priv(PU_RESETGROUPS | PU_LIMITPRIVS | PU_INHERITPRIVS,
0N/A 0, 0, /* run as uid 0 and gid 0 */
0N/A PRIV_FILE_DAC_EXECUTE, PRIV_FILE_DAC_READ, PRIV_FILE_DAC_SEARCH,
0N/A PRIV_FILE_DAC_WRITE, PRIV_FILE_OWNER, PRIV_PROC_OWNER,
0N/A PRIV_PROC_PRIOCNTL, PRIV_SYS_ADMIN, PRIV_SYS_CONFIG,
0N/A PRIV_SYS_DEVICES, PRIV_SYS_RES_CONFIG, NULL) != 0)
0N/A fmd_error(EFMD_EXIT, "additional privileges required to run\n");
0N/A
0N/A /*
0N/A * Block all signals prior to the fork and leave them blocked in the
0N/A * parent so we don't get in a situation where the parent gets SIGINT
0N/A * and returns non-zero exit status and the child is actually running.
0N/A * In the child, restore the signal mask once we've done our setsid().
0N/A */
0N/A (void) sigfillset(&set);
0N/A (void) sigdelset(&set, SIGABRT);
0N/A (void) sigprocmask(SIG_BLOCK, &set, &oset);
0N/A
0N/A if (pipe(pfds) == -1)
0N/A fmd_error(EFMD_EXIT, "failed to create pipe for daemonize");
0N/A
0N/A if ((pid = fork()) == -1)
0N/A fmd_error(EFMD_EXIT, "failed to fork into background");
0N/A
0N/A /*
0N/A * If we're the parent process, wait for either the child to send us
0N/A * the appropriate exit status over the pipe or for the read to fail
0N/A * (presumably with 0 for EOF if our child terminated abnormally).
0N/A * If the read fails, exit with either the child's exit status if it
0N/A * exited or with FMD_EXIT_ERROR if it died from a fatal signal.
0N/A */
0N/A if (pid != 0) {
0N/A (void) close(pfds[1]);
0N/A
0N/A if (read(pfds[0], &status, sizeof (status)) == sizeof (status))
0N/A _exit(status);
0N/A
0N/A if (waitpid(pid, &status, 0) == pid && WIFEXITED(status))
0N/A _exit(WEXITSTATUS(status));
0N/A
0N/A _exit(FMD_EXIT_ERROR);
0N/A }
0N/A
0N/A fmd.d_pid = getpid();
0N/A (void) setsid();
0N/A (void) sigprocmask(SIG_SETMASK, &oset, NULL);
0N/A (void) chdir("/");
0N/A (void) umask(022);
0N/A (void) close(pfds[0]);
0N/A
0N/A return (pfds[1]);
0N/A}
0N/A
2086N/Astatic void
2086N/Adaemonize_fini(int fd)
0N/A{
0N/A (void) close(fd);
0N/A
0N/A if ((fd = open("/dev/null", O_RDWR)) >= 0) {
0N/A (void) fcntl(fd, F_DUP2FD, STDIN_FILENO);
0N/A (void) fcntl(fd, F_DUP2FD, STDOUT_FILENO);
0N/A (void) fcntl(fd, F_DUP2FD, STDERR_FILENO);
0N/A (void) close(fd);
0N/A }
0N/A}
868N/A
868N/Astatic void
1400N/Ahandler(int sig)
868N/A{
0N/A if (fmd.d_signal == 0)
2086N/A fmd.d_signal = sig;
2086N/A}
2086N/A
0N/Astatic int
0N/Ausage(const char *arg0, FILE *fp)
0N/A{
0N/A (void) fprintf(fp,
0N/A "Usage: %s [-V] [-f file] [-o opt=val] [-R dir]\n", arg0);
0N/A
0N/A return (FMD_EXIT_USAGE);
0N/A}
0N/A
0N/Aint
0N/Amain(int argc, char *argv[])
0N/A{
0N/A const char *opt_f = NULL, *opt_R = NULL;
0N/A const char optstr[] = "f:o:R:V";
0N/A int c, pfd = -1, opt_V = 0;
0N/A char *p;
0N/A
0N/A struct sigaction act;
0N/A sigset_t set;
0N/A
0N/A /*
0N/A * Parse the command-line once to validate all options and retrieve
0N/A * any overrides for our configuration file and root directory.
0N/A */
0N/A while ((c = getopt(argc, argv, optstr)) != EOF) {
0N/A switch (c) {
0N/A case 'f':
0N/A opt_f = optarg;
0N/A break;
0N/A case 'o':
0N/A break; /* handle -o below */
0N/A case 'R':
0N/A opt_R = optarg;
0N/A break;
0N/A case 'V':
0N/A opt_V++;
0N/A break;
0N/A default:
0N/A return (usage(argv[0], stderr));
0N/A }
0N/A }
0N/A
0N/A if (optind < argc)
0N/A return (usage(argv[0], stderr));
510N/A
510N/A if (opt_V) {
510N/A#ifdef DEBUG
0N/A (void) printf("%s: version %s (DEBUG)\n",
0N/A#else
510N/A (void) printf("%s: version %s\n",
0N/A#endif
0N/A argv[0], _fmd_version);
0N/A return (FMD_EXIT_SUCCESS);
0N/A }
0N/A
0N/A closefrom(STDERR_FILENO + 1);
0N/A fmd_create(&fmd, argv[0], opt_R, opt_f);
0N/A
0N/A /*
0N/A * Now that we've initialized our global state, parse the command-line
0N/A * again for any configuration options specified using -o and set them.
0N/A */
0N/A for (optind = 1; (c = getopt(argc, argv, optstr)) != EOF; ) {
0N/A if (c == 'o') {
0N/A if ((p = strchr(optarg, '=')) == NULL) {
0N/A (void) fprintf(stderr, "%s: failed to set "
0N/A "option -o %s: option requires value\n",
0N/A fmd.d_pname, optarg);
0N/A return (FMD_EXIT_USAGE);
0N/A }
0N/A
0N/A *p++ = '\0'; /* strike out the delimiter */
0N/A
0N/A if (p[0] == '"' && p[strlen(p) - 1] == '"') {
0N/A p[strlen(p) - 1] = '\0';
0N/A (void) fmd_stresc2chr(++p);
0N/A }
0N/A
0N/A if (fmd_conf_setprop(fmd.d_conf, optarg, p) != 0) {
0N/A (void) fprintf(stderr,
0N/A "%s: failed to set option -o %s: %s\n",
0N/A fmd.d_pname, optarg, fmd_strerror(errno));
0N/A return (FMD_EXIT_USAGE);
0N/A }
0N/A }
0N/A }
0N/A
0N/A if (fmd.d_fmd_debug & FMD_DBG_HELP) {
0N/A fmd_help(&fmd);
0N/A fmd_destroy(&fmd);
0N/A return (FMD_EXIT_SUCCESS);
}
/*
* Update the value of fmd.d_fg based on "fg" in case it changed. We
* use this property to decide whether to daemonize below.
*/
(void) fmd_conf_getprop(fmd.d_conf, "fg", &fmd.d_fg);
/*
* Once we're done setting our global state up, set up signal handlers
* for ensuring orderly termination on SIGTERM. If we are starting in
* the foreground, we also use the same handler for SIGINT and SIGHUP.
*/
(void) sigfillset(&set);
(void) sigdelset(&set, SIGABRT); /* always unblocked for ASSERT() */
(void) sigfillset(&act.sa_mask);
act.sa_handler = handler;
act.sa_flags = 0;
(void) sigaction(SIGTERM, &act, NULL);
(void) sigdelset(&set, SIGTERM);
if (fmd.d_fg) {
(void) sigaction(SIGHUP, &act, NULL);
(void) sigdelset(&set, SIGHUP);
(void) sigaction(SIGINT, &act, NULL);
(void) sigdelset(&set, SIGINT);
(void) sigdelset(&set, SIGTSTP);
(void) sigdelset(&set, SIGTTIN);
(void) sigdelset(&set, SIGTTOU);
(void) printf("%s: [ loading modules ... ", fmd.d_pname);
(void) fflush(stdout);
} else
pfd = daemonize_init();
/*
* Prior to this point, we are single-threaded. Once fmd_run() is
* called, we will be multi-threaded from this point on. The daemon's
* main thread will wait at the end of this function for signals.
*/
fmd_run(&fmd, pfd);
if (fmd.d_fg) {
(void) printf("done ]\n");
(void) printf("%s: [ awaiting events ]\n", fmd.d_pname);
} else
daemonize_fini(pfd);
while (!fmd.d_signal)
(void) sigsuspend(&set);
fmd_destroy(&fmd);
return (FMD_EXIT_SUCCESS);
}