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