xref: /illumos-gate/usr/src/cmd/hotplugd/hotplugd.c (revision f3041bfa)
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 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <signal.h>
33 #include <stdarg.h>
34 #include <strings.h>
35 #include <syslog.h>
36 #include <priv.h>
37 #include <wait.h>
38 #include <getopt.h>
39 #include <synch.h>
40 #include <sys/param.h>
41 #include <sys/stat.h>
42 #include <sys/types.h>
43 #include <libhotplug.h>
44 #include <libhotplug_impl.h>
45 #include "hotplugd_impl.h"
46 
47 /*
48  * Define long options for command line.
49  */
50 static const struct option lopts[] = {
51 	{ "help",	no_argument,	0, '?' },
52 	{ "version",	no_argument,	0, 'V' },
53 	{ "debug",	no_argument,	0, 'd' },
54 	{ 0, 0, 0, 0 }
55 };
56 
57 /*
58  * Local functions.
59  */
60 static void		usage(void);
61 static boolean_t	check_privileges(void);
62 static int		daemonize(void);
63 static void		init_signals(void);
64 static void		signal_handler(int signum);
65 static void		shutdown_daemon(void);
66 
67 /*
68  * Global variables.
69  */
70 static char		*prog;
71 static char		version[] = "1.0";
72 static boolean_t	log_flag = B_FALSE;
73 static boolean_t	debug_flag = B_FALSE;
74 static boolean_t	exit_flag = B_FALSE;
75 static sema_t		signal_sem;
76 
77 /*
78  * main()
79  *
80  *	The hotplug daemon is designed to be a background daemon
81  *	controlled by SMF.  So by default it will daemonize and
82  *	do some coordination with its parent process in order to
83  *	indicate proper success or failure back to SMF.  And all
84  *	output will be sent to syslog.
85  *
86  *	But if given the '-d' command line option, it will instead
87  *	run in the foreground in a standalone, debug mode.  Errors
88  *	and additional debug messages will be printed to the controlling
89  *	terminal instead of to syslog.
90  */
91 int
92 main(int argc, char *argv[])
93 {
94 	int	opt;
95 	int	pfd;
96 	int	status;
97 
98 	if ((prog = strrchr(argv[0], '/')) == NULL)
99 		prog = argv[0];
100 	else
101 		prog++;
102 
103 	/* Check privileges */
104 	if (!check_privileges()) {
105 		(void) fprintf(stderr, "Insufficient privileges.  "
106 		    "(All privileges are required.)\n");
107 		return (-1);
108 	}
109 
110 	/* Process options  */
111 	while ((opt = getopt_clip(argc, argv, "dV?", lopts, NULL)) != -1) {
112 		switch (opt) {
113 		case 'd':
114 			debug_flag = B_TRUE;
115 			break;
116 		case 'V':
117 			(void) printf("%s: Version %s\n", prog, version);
118 			return (0);
119 		default:
120 			if (optopt == '?') {
121 				usage();
122 				return (0);
123 			}
124 			(void) fprintf(stderr, "Unrecognized option '%c'.\n",
125 			    optopt);
126 			usage();
127 			return (-1);
128 		}
129 	}
130 
131 	/* Initialize semaphore for daemon shutdown */
132 	if (sema_init(&signal_sem, 1, USYNC_THREAD, NULL) != 0)
133 		exit(EXIT_FAILURE);
134 
135 	/* Initialize signal handling */
136 	init_signals();
137 
138 	/* Daemonize, if not in DEBUG mode */
139 	if (!debug_flag)
140 		pfd = daemonize();
141 
142 	/* Initialize door service */
143 	if (!door_server_init()) {
144 		if (!debug_flag) {
145 			status = EXIT_FAILURE;
146 			(void) write(pfd, &status, sizeof (status));
147 			(void) close(pfd);
148 		}
149 		exit(EXIT_FAILURE);
150 	}
151 
152 	/* Daemon initialized */
153 	if (!debug_flag) {
154 		status = 0;
155 		(void) write(pfd, &status, sizeof (status));
156 		(void) close(pfd);
157 	}
158 
159 	/* Note that daemon is running */
160 	log_info("hotplug daemon started.\n");
161 
162 	/* Wait for shutdown signal */
163 	while (!exit_flag)
164 		(void) sema_wait(&signal_sem);
165 
166 	shutdown_daemon();
167 	return (0);
168 }
169 
170 /*
171  * usage()
172  *
173  *	Print a brief usage synopsis for the command line options.
174  */
175 static void
176 usage(void)
177 {
178 	(void) printf("Usage: %s [-d]\n", prog);
179 }
180 
181 /*
182  * check_privileges()
183  *
184  *	Check if the current process has enough privileges
185  *	to run the daemon.  Note that all privileges are
186  *	required in order for RCM interactions to work.
187  */
188 static boolean_t
189 check_privileges(void)
190 {
191 	priv_set_t	*privset;
192 	boolean_t	rv = B_FALSE;
193 
194 	if ((privset = priv_allocset()) != NULL) {
195 		if (getppriv(PRIV_EFFECTIVE, privset) == 0) {
196 			rv = priv_isfullset(privset);
197 		}
198 		priv_freeset(privset);
199 	}
200 
201 	return (rv);
202 }
203 
204 /*
205  * daemonize()
206  *
207  *	Fork the daemon process into the background, and detach from
208  *	the controlling terminal.  Setup a shared pipe that will later
209  *	be used to report startup status to the parent process.
210  */
211 static int
212 daemonize(void)
213 {
214 	int		status;
215 	int		pfds[2];
216 	pid_t		pid;
217 	sigset_t	set;
218 	sigset_t	oset;
219 
220 	/*
221 	 * Temporarily block all signals.  They will remain blocked in
222 	 * the parent, but will be unblocked in the child once it has
223 	 * notified the parent of its startup status.
224 	 */
225 	(void) sigfillset(&set);
226 	(void) sigdelset(&set, SIGABRT);
227 	(void) sigprocmask(SIG_BLOCK, &set, &oset);
228 
229 	/* Create the shared pipe */
230 	if (pipe(pfds) == -1) {
231 		log_err("Cannot create pipe (%s)\n", strerror(errno));
232 		exit(EXIT_FAILURE);
233 	}
234 
235 	/* Fork the daemon process */
236 	if ((pid = fork()) == -1) {
237 		log_err("Cannot fork daemon process (%s)\n", strerror(errno));
238 		exit(EXIT_FAILURE);
239 	}
240 
241 	/* Parent:  waits for exit status from child. */
242 	if (pid > 0) {
243 		(void) close(pfds[1]);
244 		if (read(pfds[0], &status, sizeof (status)) == sizeof (status))
245 			_exit(status);
246 		if ((waitpid(pid, &status, 0) == pid) && WIFEXITED(status))
247 			_exit(WEXITSTATUS(status));
248 		log_err("Failed to spawn daemon process.\n");
249 		_exit(EXIT_FAILURE);
250 	}
251 
252 	/* Child continues... */
253 
254 	(void) setsid();
255 	(void) chdir("/");
256 	(void) umask(CMASK);
257 	(void) sigprocmask(SIG_SETMASK, &oset, NULL);
258 	(void) close(pfds[0]);
259 
260 	/* Detach from controlling terminal */
261 	(void) close(0);
262 	(void) close(1);
263 	(void) close(2);
264 	(void) open("/dev/null", O_RDONLY);
265 	(void) open("/dev/null", O_WRONLY);
266 	(void) open("/dev/null", O_WRONLY);
267 
268 	/* Use syslog for future messages */
269 	log_flag = B_TRUE;
270 	openlog(prog, LOG_PID, LOG_DAEMON);
271 
272 	return (pfds[1]);
273 }
274 
275 /*
276  * init_signals()
277  *
278  *	Initialize signal handling.
279  */
280 static void
281 init_signals(void)
282 {
283 	struct sigaction	act;
284 	sigset_t		set;
285 
286 	(void) sigfillset(&set);
287 	(void) sigdelset(&set, SIGABRT);
288 
289 	(void) sigfillset(&act.sa_mask);
290 	act.sa_handler = signal_handler;
291 	act.sa_flags = 0;
292 
293 	(void) sigaction(SIGTERM, &act, NULL);
294 	(void) sigaction(SIGHUP, &act, NULL);
295 	(void) sigaction(SIGINT, &act, NULL);
296 	(void) sigaction(SIGPIPE, &act, NULL);
297 
298 	(void) sigdelset(&set, SIGTERM);
299 	(void) sigdelset(&set, SIGHUP);
300 	(void) sigdelset(&set, SIGINT);
301 	(void) sigdelset(&set, SIGPIPE);
302 }
303 
304 /*
305  * signal_handler()
306  *
307  *	Most signals cause the hotplug daemon to shut down.
308  *	Shutdown is triggered using a semaphore to wake up
309  *	the main thread for a clean exit.
310  *
311  *	Except SIGPIPE is used to coordinate between the parent
312  *	and child processes when the daemon first starts.
313  */
314 static void
315 signal_handler(int signum)
316 {
317 	log_info("Received signal %d.\n", signum);
318 
319 	switch (signum) {
320 	case 0:
321 	case SIGPIPE:
322 		break;
323 	default:
324 		exit_flag = B_TRUE;
325 		(void) sema_post(&signal_sem);
326 		break;
327 	}
328 }
329 
330 /*
331  * shutdown_daemon()
332  *
333  *	Perform a clean shutdown of the daemon.
334  */
335 static void
336 shutdown_daemon(void)
337 {
338 	log_info("Hotplug daemon shutting down.\n");
339 
340 	door_server_fini();
341 
342 	if (log_flag)
343 		closelog();
344 
345 	(void) sema_destroy(&signal_sem);
346 }
347 
348 /*
349  * log_err()
350  *
351  *	Display an error message.  Use syslog if in daemon
352  *	mode, otherwise print to stderr when in debug mode.
353  */
354 /*PRINTFLIKE1*/
355 void
356 log_err(char *fmt, ...)
357 {
358 	va_list	ap;
359 
360 	va_start(ap, fmt);
361 	if (debug_flag || !log_flag)
362 		(void) vfprintf(stderr, fmt, ap);
363 	else
364 		vsyslog(LOG_ERR, fmt, ap);
365 	va_end(ap);
366 }
367 
368 /*
369  * log_info()
370  *
371  *	Display an information message.  Use syslog if in daemon
372  *	mode, otherwise print to stdout when in debug mode.
373  */
374 /*PRINTFLIKE1*/
375 void
376 log_info(char *fmt, ...)
377 {
378 	va_list ap;
379 
380 	va_start(ap, fmt);
381 	if (debug_flag || !log_flag)
382 		(void) vfprintf(stdout, fmt, ap);
383 	else
384 		vsyslog(LOG_INFO, fmt, ap);
385 	va_end(ap);
386 }
387 
388 /*
389  * dprintf()
390  *
391  *	Print a debug tracing statement.  Only works in debug
392  *	mode, and always prints to stdout.
393  */
394 /*PRINTFLIKE1*/
395 void
396 dprintf(char *fmt, ...)
397 {
398 	va_list	ap;
399 
400 	if (debug_flag) {
401 		va_start(ap, fmt);
402 		(void) vprintf(fmt, ap);
403 		va_end(ap);
404 	}
405 }
406