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 */
50static 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 */
60static void		usage(void);
61static boolean_t	check_privileges(void);
62static int		daemonize(void);
63static void		init_signals(void);
64static void		signal_handler(int signum);
65static void		shutdown_daemon(void);
66
67/*
68 * Global variables.
69 */
70static char		*prog;
71static char		version[] = "1.0";
72static boolean_t	log_flag = B_FALSE;
73static boolean_t	debug_flag = B_FALSE;
74static boolean_t	exit_flag = B_FALSE;
75static 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 */
91int
92main(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 */
175static void
176usage(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 */
188static boolean_t
189check_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 */
211static int
212daemonize(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 */
280static void
281init_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 */
314static void
315signal_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 */
335static void
336shutdown_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*/
355void
356log_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*/
375void
376log_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*/
395void
396dprintf(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