xref: /illumos-gate/usr/src/cmd/fm/fmd/common/fmd_main.c (revision 66582b606a8194f7f3ba5b3a3a6dca5b0d346361)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/wait.h>
28 #include <sys/corectl.h>
29 #include <sys/resource.h>
30 
31 #include <priv_utils.h>
32 #include <signal.h>
33 #include <unistd.h>
34 #include <limits.h>
35 #include <fcntl.h>
36 #include <strings.h>
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <zone.h>
40 
41 #include <fmd_error.h>
42 #include <fmd_string.h>
43 #include <fmd_conf.h>
44 #include <fmd_dispq.h>
45 #include <fmd_subr.h>
46 #include <fmd.h>
47 
48 fmd_t fmd;
49 
50 /*
51  * For DEBUG builds, we define a set of hooks for libumem that provide useful
52  * default settings for the allocator's debugging facilities.
53  */
54 #ifdef	DEBUG
55 const char *
56 _umem_debug_init()
57 {
58 	return ("default,verbose"); /* $UMEM_DEBUG setting */
59 }
60 
61 const char *
62 _umem_logging_init(void)
63 {
64 	return ("fail,contents"); /* $UMEM_LOGGING setting */
65 }
66 #endif	/* DEBUG */
67 
68 /*
69  * We use a two-phase algorithm for becoming a daemon because we want the
70  * daemon process (the child) to do the work of becoming MT-hot and opening our
71  * event transport.  Since these operations can fail and need to result in the
72  * daemon failing to start, the parent must wait until fmd_run() completes to
73  * know whether it can return zero or non-zero status to the invoking command.
74  * The parent waits on a pipe inside this function to read the exit status.
75  * The child gets the write-end of the pipe returned by daemonize_init() and
76  * then fmd_run() uses the pipe to set the exit status and detach the parent.
77  */
78 static int
79 daemonize_init(void)
80 {
81 	const char *gzp1, *gzp2, *gzp3, *gzp4, *gzp5;
82 	int status, pfds[2];
83 	sigset_t set, oset;
84 	struct rlimit rlim;
85 	char path[PATH_MAX];
86 	pid_t pid;
87 
88 	/*
89 	 * Set our per-process core file path to leave core files in our
90 	 * var/fm/fmd directory, named after the PID to aid in debugging,
91 	 * and make sure that there is no restriction on core file size.
92 	 */
93 	(void) snprintf(path, sizeof (path),
94 	    "%s/var/fm/fmd/core.%s.%%p", fmd.d_rootdir, fmd.d_pname);
95 
96 	(void) core_set_process_path(path, strlen(path) + 1, fmd.d_pid);
97 
98 	rlim.rlim_cur = RLIM_INFINITY;
99 	rlim.rlim_max = RLIM_INFINITY;
100 
101 	(void) setrlimit(RLIMIT_CORE, &rlim);
102 
103 	/*
104 	 * Claim all the file descriptors we can.
105 	 */
106 	if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
107 		rlim.rlim_cur = rlim.rlim_max;
108 		(void) setrlimit(RLIMIT_NOFILE, &rlim);
109 	}
110 
111 	/*
112 	 * Reset all of our privilege sets to the minimum set of required
113 	 * privileges.  We continue to run as root so that files we create
114 	 * such as logs and checkpoints are secured in the /var filesystem.
115 	 *
116 	 * In a non-global zone some of the privileges we retain in a
117 	 * global zone are only optionally assigned to the zone, while others
118 	 * are prohibited:
119 	 *
120 	 * PRIV_PROC_PRIOCNTL (optional in a non-global zone):
121 	 *	There are no calls to priocntl(2) in fmd or plugins.
122 	 *
123 	 * PRIV_SYS_CONFIG (prohibited in a non-global zone):
124 	 *	Required, I think, for sysevent_post_event and/or
125 	 *	other legacy sysevent activity.  Legacy sysevent is not
126 	 *	supported in a non-global zone.
127 	 *
128 	 * PRIV_SYS_DEVICES (prohibited in a non-global zone):
129 	 *	Needed in the global zone for ioctls on various drivers
130 	 *	such as memory-controller drivers.
131 	 *
132 	 * PRIV_SYS_RES_CONFIG (prohibited in a non-global zone):
133 	 *	Require for p_online(2) calls to offline cpus.
134 	 *
135 	 * PRIV_SYS_NET_CONFIG (prohibited in a non-global zone):
136 	 *	Required for ipsec in etm (which also requires
137 	 *	PRIV_NET_PRIVADDR).
138 	 *
139 	 * We do without those privileges in a non-global zone.  It's
140 	 * possible that there are other privs we could drop since
141 	 * hardware-related plugins are not present.
142 	 */
143 	if (getzoneid() == GLOBAL_ZONEID) {
144 		gzp1 = PRIV_PROC_PRIOCNTL;
145 		gzp2 = PRIV_SYS_CONFIG;
146 		gzp3 = PRIV_SYS_DEVICES;
147 		gzp4 = PRIV_SYS_RES_CONFIG;
148 		gzp5 = PRIV_SYS_NET_CONFIG;
149 	} else {
150 		gzp1 = gzp2 = gzp3 = gzp4 = gzp5 = NULL;
151 	}
152 
153 	if (__init_daemon_priv(PU_RESETGROUPS | PU_LIMITPRIVS | PU_INHERITPRIVS,
154 	    0, 0, /* run as uid 0 and gid 0 */
155 	    PRIV_FILE_DAC_EXECUTE, PRIV_FILE_DAC_READ, PRIV_FILE_DAC_SEARCH,
156 	    PRIV_FILE_DAC_WRITE, PRIV_FILE_OWNER, PRIV_PROC_OWNER,
157 	    PRIV_SYS_ADMIN, PRIV_NET_PRIVADDR,
158 	    gzp1, gzp2, gzp3, gzp4, gzp5, NULL) != 0)
159 		fmd_error(EFMD_EXIT, "additional privileges required to run\n");
160 
161 	/*
162 	 * Block all signals prior to the fork and leave them blocked in the
163 	 * parent so we don't get in a situation where the parent gets SIGINT
164 	 * and returns non-zero exit status and the child is actually running.
165 	 * In the child, restore the signal mask once we've done our setsid().
166 	 */
167 	(void) sigfillset(&set);
168 	(void) sigdelset(&set, SIGABRT);
169 	(void) sigprocmask(SIG_BLOCK, &set, &oset);
170 
171 	if (pipe(pfds) == -1)
172 		fmd_error(EFMD_EXIT, "failed to create pipe for daemonize");
173 
174 	if ((pid = fork()) == -1)
175 		fmd_error(EFMD_EXIT, "failed to fork into background");
176 
177 	/*
178 	 * If we're the parent process, wait for either the child to send us
179 	 * the appropriate exit status over the pipe or for the read to fail
180 	 * (presumably with 0 for EOF if our child terminated abnormally).
181 	 * If the read fails, exit with either the child's exit status if it
182 	 * exited or with FMD_EXIT_ERROR if it died from a fatal signal.
183 	 */
184 	if (pid != 0) {
185 		(void) close(pfds[1]);
186 
187 		if (read(pfds[0], &status, sizeof (status)) == sizeof (status))
188 			_exit(status);
189 
190 		if (waitpid(pid, &status, 0) == pid && WIFEXITED(status))
191 			_exit(WEXITSTATUS(status));
192 
193 		_exit(FMD_EXIT_ERROR);
194 	}
195 
196 	fmd.d_pid = getpid();
197 	(void) setsid();
198 	(void) sigprocmask(SIG_SETMASK, &oset, NULL);
199 	(void) chdir("/");
200 	(void) umask(022);
201 	(void) close(pfds[0]);
202 
203 	return (pfds[1]);
204 }
205 
206 static void
207 daemonize_fini(int fd)
208 {
209 	(void) close(fd);
210 
211 	if ((fd = open("/dev/null", O_RDWR)) >= 0) {
212 		(void) fcntl(fd, F_DUP2FD, STDIN_FILENO);
213 		(void) fcntl(fd, F_DUP2FD, STDOUT_FILENO);
214 		(void) fcntl(fd, F_DUP2FD, STDERR_FILENO);
215 		(void) close(fd);
216 	}
217 }
218 
219 static void
220 handler(int sig)
221 {
222 	if (fmd.d_signal == 0)
223 		fmd.d_signal = sig;
224 }
225 
226 static int
227 usage(const char *arg0, FILE *fp)
228 {
229 	(void) fprintf(fp,
230 	    "Usage: %s [-V] [-f file] [-o opt=val] [-R dir]\n", arg0);
231 
232 	return (FMD_EXIT_USAGE);
233 }
234 
235 int
236 main(int argc, char *argv[])
237 {
238 	const char *opt_f = NULL, *opt_R = NULL;
239 	const char optstr[] = "f:o:R:V";
240 	int c, pfd = -1, opt_V = 0;
241 	char *p;
242 
243 	struct sigaction act;
244 	sigset_t set;
245 
246 	/*
247 	 * Parse the command-line once to validate all options and retrieve
248 	 * any overrides for our configuration file and root directory.
249 	 */
250 	while ((c = getopt(argc, argv, optstr)) != EOF) {
251 		switch (c) {
252 		case 'f':
253 			opt_f = optarg;
254 			break;
255 		case 'o':
256 			break; /* handle -o below */
257 		case 'R':
258 			opt_R = optarg;
259 			break;
260 		case 'V':
261 			opt_V++;
262 			break;
263 		default:
264 			return (usage(argv[0], stderr));
265 		}
266 	}
267 
268 	if (optind < argc)
269 		return (usage(argv[0], stderr));
270 
271 	if (opt_V) {
272 #ifdef DEBUG
273 		const char *debug = " (DEBUG)";
274 #else
275 		const char *debug = "";
276 #endif
277 		(void) printf("%s: version %s%s\n",
278 		    argv[0], _fmd_version, debug);
279 		return (FMD_EXIT_SUCCESS);
280 	}
281 
282 	closefrom(STDERR_FILENO + 1);
283 	fmd_create(&fmd, argv[0], opt_R, opt_f);
284 
285 	/*
286 	 * Now that we've initialized our global state, parse the command-line
287 	 * again for any configuration options specified using -o and set them.
288 	 */
289 	for (optind = 1; (c = getopt(argc, argv, optstr)) != EOF; ) {
290 		if (c == 'o') {
291 			if ((p = strchr(optarg, '=')) == NULL) {
292 				(void) fprintf(stderr, "%s: failed to set "
293 				    "option -o %s: option requires value\n",
294 				    fmd.d_pname, optarg);
295 				return (FMD_EXIT_USAGE);
296 			}
297 
298 			*p++ = '\0'; /* strike out the delimiter */
299 
300 			if (p[0] == '"' && p[strlen(p) - 1] == '"') {
301 				p[strlen(p) - 1] = '\0';
302 				(void) fmd_stresc2chr(++p);
303 			}
304 
305 			if (fmd_conf_setprop(fmd.d_conf, optarg, p) != 0) {
306 				(void) fprintf(stderr,
307 				    "%s: failed to set option -o %s: %s\n",
308 				    fmd.d_pname, optarg, fmd_strerror(errno));
309 				return (FMD_EXIT_USAGE);
310 			}
311 		}
312 	}
313 
314 	if (fmd.d_fmd_debug & FMD_DBG_HELP) {
315 		fmd_help(&fmd);
316 		fmd_destroy(&fmd);
317 		return (FMD_EXIT_SUCCESS);
318 	}
319 
320 	/*
321 	 * Update the value of fmd.d_fg based on "fg" in case it changed.  We
322 	 * use this property to decide whether to daemonize below.
323 	 */
324 	(void) fmd_conf_getprop(fmd.d_conf, "fg", &fmd.d_fg);
325 
326 	/*
327 	 * Once we're done setting our global state up, set up signal handlers
328 	 * for ensuring orderly termination on SIGTERM.  If we are starting in
329 	 * the foreground, we also use the same handler for SIGINT and SIGHUP.
330 	 */
331 	(void) sigfillset(&set);
332 	(void) sigdelset(&set, SIGABRT); /* always unblocked for ASSERT() */
333 
334 	(void) sigfillset(&act.sa_mask);
335 	act.sa_handler = handler;
336 	act.sa_flags = 0;
337 
338 	(void) sigaction(SIGTERM, &act, NULL);
339 	(void) sigdelset(&set, SIGTERM);
340 
341 	if (fmd.d_fg) {
342 		(void) sigaction(SIGHUP, &act, NULL);
343 		(void) sigdelset(&set, SIGHUP);
344 		(void) sigaction(SIGINT, &act, NULL);
345 		(void) sigdelset(&set, SIGINT);
346 
347 		(void) sigdelset(&set, SIGTSTP);
348 		(void) sigdelset(&set, SIGTTIN);
349 		(void) sigdelset(&set, SIGTTOU);
350 
351 		(void) printf("%s: [ loading modules ... ", fmd.d_pname);
352 		(void) fflush(stdout);
353 	} else
354 		pfd = daemonize_init();
355 
356 	/*
357 	 * Prior to this point, we are single-threaded.  Once fmd_run() is
358 	 * called, we will be multi-threaded from this point on.  The daemon's
359 	 * main thread will wait at the end of this function for signals.
360 	 */
361 	fmd_run(&fmd, pfd);
362 
363 	if (fmd.d_fg) {
364 		(void) printf("done ]\n");
365 		(void) printf("%s: [ awaiting events ]\n", fmd.d_pname);
366 	} else
367 		daemonize_fini(pfd);
368 
369 	while (!fmd.d_signal)
370 		(void) sigsuspend(&set);
371 
372 	fmd_destroy(&fmd);
373 	return (FMD_EXIT_SUCCESS);
374 }
375