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 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  * Reconfiguration Coordination Daemon
28  *
29  * Accept RCM messages in the form of RCM events and process them
30  * - to build and update the system resource map
31  * - to allow clients to register/unregister for resource
32  * - to allow dr initiators to offline a resource before removal
33  * - to call into clients to perform suspend/offline actions
34  *
35  * The goal is to enable fully automated Dynamic Reconfiguration and better
36  * DR information tracking.
37  */
38 
39 #include <librcm_event.h>
40 
41 #include "rcm_impl.h"
42 
43 /* will run in daemon mode if debug level < DEBUG_LEVEL_FORK */
44 #define	DEBUG_LEVEL_FORK	RCM_DEBUG
45 
46 #define	DAEMON_LOCK_FILE "/var/run/rcm_daemon_lock"
47 
48 static int hold_daemon_lock;
49 static int daemon_lock_fd;
50 static const char *daemon_lock_file = DAEMON_LOCK_FILE;
51 
52 int debug_level = 0;
53 static int idle_timeout;
54 static int logflag = 0;
55 static char *prog;
56 
57 static void usage(void);
58 static void catch_sighup(int);
59 static void catch_sigusr1(int);
60 static pid_t enter_daemon_lock(void);
61 static void exit_daemon_lock(void);
62 
63 extern void init_poll_thread();
64 extern void cleanup_poll_thread();
65 
66 /*
67  * Print command line syntax for starting rcm_daemon
68  */
69 static void
usage()70 usage()
71 {
72 	(void) fprintf(stderr,
73 	    gettext("usage: %s [-d debug_level] [-t idle_timeout]\n"), prog);
74 	rcmd_exit(EINVAL);
75 }
76 
77 /*
78  * common cleanup/exit functions to ensure releasing locks
79  */
80 static void
rcmd_cleanup(int status)81 rcmd_cleanup(int status)
82 {
83 	if (status == 0) {
84 		rcm_log_message(RCM_INFO,
85 		    gettext("rcm_daemon normal exit\n"));
86 	} else {
87 		rcm_log_message(RCM_ERROR,
88 		    gettext("rcm_daemon exit: errno = %d\n"), status);
89 	}
90 
91 	if (hold_daemon_lock) {
92 		exit_daemon_lock();
93 	}
94 }
95 
96 void
rcmd_exit(int status)97 rcmd_exit(int status)
98 {
99 	rcmd_cleanup(status);
100 	exit(status);
101 }
102 
103 /*
104  * When SIGHUP is received, reload modules at the next safe moment (when
105  * there is no DR activity.
106  */
107 void
catch_sighup(int signal __unused)108 catch_sighup(int signal __unused)
109 {
110 	rcm_log_message(RCM_INFO,
111 	    gettext("SIGHUP received, will exit when daemon is idle\n"));
112 	rcmd_thr_signal();
113 }
114 
115 /*
116  * When SIGUSR1 is received, exit the thread
117  */
118 void
catch_sigusr1(int signal __unused)119 catch_sigusr1(int signal __unused)
120 {
121 	rcm_log_message(RCM_DEBUG, "SIGUSR1 received in thread %d\n",
122 	    thr_self());
123 	cleanup_poll_thread();
124 	thr_exit(NULL);
125 }
126 
127 /*
128  * Use an advisory lock to ensure that only one daemon process is
129  * active at any point in time.
130  */
131 static pid_t
enter_daemon_lock(void)132 enter_daemon_lock(void)
133 {
134 	struct flock lock;
135 
136 	rcm_log_message(RCM_TRACE1,
137 	    "enter_daemon_lock: lock file = %s\n", daemon_lock_file);
138 
139 	daemon_lock_fd = open(daemon_lock_file, O_CREAT|O_RDWR, 0644);
140 	if (daemon_lock_fd < 0) {
141 		rcm_log_message(RCM_ERROR, gettext("open(%s) - %s\n"),
142 		    daemon_lock_file, strerror(errno));
143 		rcmd_exit(errno);
144 	}
145 
146 	lock.l_type = F_WRLCK;
147 	lock.l_whence = SEEK_SET;
148 	lock.l_start = 0;
149 	lock.l_len = 0;
150 
151 	if (fcntl(daemon_lock_fd, F_SETLK, &lock) == 0) {
152 		hold_daemon_lock = 1;
153 		return (getpid());
154 	}
155 
156 	/* failed to get lock, attempt to find lock owner */
157 	if ((errno == EAGAIN || errno == EDEADLK) &&
158 	    (fcntl(daemon_lock_fd, F_GETLK, &lock) == 0)) {
159 		return (lock.l_pid);
160 	}
161 
162 	/* die a horrible death */
163 	rcm_log_message(RCM_ERROR, gettext("lock(%s) - %s"), daemon_lock_file,
164 	    strerror(errno));
165 	exit(errno);
166 	/*NOTREACHED*/
167 }
168 
169 /*
170  * Drop the advisory daemon lock, close lock file
171  */
172 static void
exit_daemon_lock(void)173 exit_daemon_lock(void)
174 {
175 	struct flock lock;
176 
177 	lock.l_type = F_UNLCK;
178 	lock.l_whence = SEEK_SET;
179 	lock.l_start = 0;
180 	lock.l_len = 0;
181 
182 	if (fcntl(daemon_lock_fd, F_SETLK, &lock) == -1) {
183 		rcm_log_message(RCM_ERROR, gettext("unlock(%s) - %s"),
184 		    daemon_lock_file, strerror(errno));
185 	}
186 
187 	(void) close(daemon_lock_fd);
188 }
189 
190 /*PRINTFLIKE2*/
191 static void
rcm_log_msg_impl(int level,char * message,va_list ap)192 rcm_log_msg_impl(int level, char *message, va_list ap)
193 {
194 	int log_level;
195 
196 	if (!logflag) {
197 		/*
198 		 * RCM_ERROR goes to stderr, others go to stdout
199 		 */
200 		FILE *out = (level <= RCM_ERROR) ? stderr : stdout;
201 		(void) vfprintf(out, message, ap);
202 		return;
203 	}
204 
205 	/*
206 	 * translate RCM_* to LOG_*
207 	 */
208 	switch (level) {
209 	case RCM_ERROR:
210 		log_level = LOG_ERR;
211 		break;
212 
213 	case RCM_WARNING:
214 		log_level = LOG_WARNING;
215 		break;
216 
217 	case RCM_NOTICE:
218 		log_level = LOG_NOTICE;
219 		break;
220 
221 	case RCM_INFO:
222 		log_level = LOG_INFO;
223 		break;
224 
225 	case RCM_DEBUG:
226 		log_level = LOG_DEBUG;
227 		break;
228 
229 	default:
230 		/*
231 		 * Don't log RCM_TRACEn messages
232 		 */
233 		return;
234 	}
235 
236 	(void) vsyslog(log_level, message, ap);
237 }
238 
239 /*
240  * print error messages to the terminal or to syslog
241  */
242 void
rcm_log_message(int level,char * message,...)243 rcm_log_message(int level, char *message, ...)
244 {
245 	va_list ap;
246 
247 	if (level > debug_level) {
248 		return;
249 	}
250 
251 	va_start(ap, message);
252 	rcm_log_msg_impl(level, message, ap);
253 	va_end(ap);
254 }
255 
256 /*
257  * Print error messages to the terminal or to syslog.
258  * Same as rcm_log_message except that it does not check for
259  * level > debug_level
260  * allowing callers to override the global debug_level.
261  */
262 void
rcm_log_msg(int level,char * message,...)263 rcm_log_msg(int level, char *message, ...)
264 {
265 	va_list ap;
266 
267 	va_start(ap, message);
268 	rcm_log_msg_impl(level, message, ap);
269 	va_end(ap);
270 }
271 
272 /*
273  * grab daemon_lock and direct messages to syslog
274  */
275 static void
detachfromtty()276 detachfromtty()
277 {
278 	(void) chdir("/");
279 	(void) setsid();
280 	(void) close(0);
281 	(void) close(1);
282 	(void) close(2);
283 	(void) open("/dev/null", O_RDWR, 0);
284 	(void) dup2(0, 1);
285 	(void) dup2(0, 2);
286 	openlog(prog, LOG_PID, LOG_DAEMON);
287 	logflag = 1;
288 }
289 
290 int
main(int argc,char ** argv)291 main(int argc, char **argv)
292 {
293 	int c;
294 	pid_t pid;
295 	extern char *optarg;
296 	sigset_t mask;
297 	struct sigaction act;
298 
299 	(void) setlocale(LC_ALL, "");
300 #ifndef	TEXT_DOMAIN
301 #define	TEXT_DOMAIN	"SYS_TEST"
302 #endif
303 	(void) textdomain(TEXT_DOMAIN);
304 
305 	if ((prog = strrchr(argv[0], '/')) == NULL) {
306 		prog = argv[0];
307 	} else {
308 		prog++;
309 	}
310 
311 	(void) enable_extended_FILE_stdio(-1, -1);
312 
313 	/*
314 	 * process arguments
315 	 */
316 	if (argc > 3) {
317 		usage();
318 	}
319 	while ((c = getopt(argc, argv, "d:t:")) != EOF) {
320 		switch (c) {
321 		case 'd':
322 			debug_level = atoi(optarg);
323 			break;
324 		case 't':
325 			idle_timeout = atoi(optarg);
326 			break;
327 		case '?':
328 		default:
329 			usage();
330 			/*NOTREACHED*/
331 		}
332 	}
333 
334 	/*
335 	 * Check permission
336 	 */
337 	if (getuid() != 0) {
338 		(void) fprintf(stderr, gettext("Must be root to run %s\n"),
339 		    prog);
340 		exit(EPERM);
341 	}
342 
343 	/*
344 	 * When rcm_daemon is started by a call to librcm, it inherits file
345 	 * descriptors from the DR initiator making a call. The file
346 	 * descriptors may correspond to devices that can be removed by DR.
347 	 * Since keeping them remain opened is problematic, close everything
348 	 * but stdin/stdout/stderr.
349 	 */
350 	closefrom(3);
351 
352 	/*
353 	 * When rcm_daemon is started by the caller, it will inherit the
354 	 * signal block mask.  We unblock all signals to make sure the
355 	 * signal handling will work normally.
356 	 */
357 	(void) sigfillset(&mask);
358 	(void) thr_sigsetmask(SIG_UNBLOCK, &mask, NULL);
359 
360 	/*
361 	 * block SIGUSR1, use it for killing specific threads
362 	 */
363 	(void) sigemptyset(&mask);
364 	(void) sigaddset(&mask, SIGUSR1);
365 	(void) thr_sigsetmask(SIG_BLOCK, &mask, NULL);
366 
367 	/*
368 	 * Setup signal handlers for SIGHUP and SIGUSR1
369 	 * SIGHUP - causes a "delayed" daemon exit, effectively the same
370 	 *	as a daemon restart.
371 	 * SIGUSR1 - causes a thr_exit(). Unblocked in selected threads.
372 	 */
373 	act.sa_flags = 0;
374 	act.sa_handler = catch_sighup;
375 	(void) sigaction(SIGHUP, &act, NULL);
376 	act.sa_handler = catch_sigusr1;
377 	(void) sigaction(SIGUSR1, &act, NULL);
378 
379 	/*
380 	 * ignore SIGPIPE so that the rcm daemon does not exit when it
381 	 * attempts to read or write from a pipe whose corresponding
382 	 * rcm script process exited.
383 	 */
384 	act.sa_handler = SIG_IGN;
385 	(void) sigaction(SIGPIPE, &act, NULL);
386 
387 	/*
388 	 * run in daemon mode
389 	 */
390 	if (debug_level < DEBUG_LEVEL_FORK) {
391 		if (fork()) {
392 			exit(0);
393 		}
394 		detachfromtty();
395 	}
396 
397 	/* only one daemon can run at a time */
398 	if ((pid = enter_daemon_lock()) != getpid()) {
399 		rcm_log_message(RCM_DEBUG, "%s pid %d already running\n",
400 		    prog, pid);
401 		exit(EDEADLK);
402 	}
403 
404 	rcm_log_message(RCM_TRACE1, "%s started, debug level = %d\n",
405 	    prog, debug_level);
406 
407 	/*
408 	 * Set daemon state to block RCM requests before rcm_daemon is
409 	 * fully initialized. See rcmd_thr_incr().
410 	 */
411 	rcmd_set_state(RCMD_INIT);
412 
413 	/*
414 	 * create rcm_daemon door and set permission to 0400
415 	 */
416 	if (create_event_service(RCM_SERVICE_DOOR, event_service) == -1) {
417 		rcm_log_message(RCM_ERROR,
418 		    gettext("cannot create door service: %s\n"),
419 		    strerror(errno));
420 		rcmd_exit(errno);
421 	}
422 	(void) chmod(RCM_SERVICE_DOOR, S_IRUSR);
423 
424 	init_poll_thread(); /* initialize poll thread related data */
425 
426 	/*
427 	 * Initialize database by asking modules to register.
428 	 */
429 	rcmd_db_init();
430 
431 	/*
432 	 * Initialize locking, including lock recovery in the event of
433 	 * unexpected daemon failure.
434 	 */
435 	rcmd_lock_init();
436 
437 	/*
438 	 * Start accepting normal requests
439 	 */
440 	rcmd_set_state(RCMD_NORMAL);
441 
442 	/*
443 	 * Start cleanup thread
444 	 */
445 	rcmd_db_clean();
446 
447 	/*
448 	 * Loop within daemon and return after a period of inactivity.
449 	 */
450 	rcmd_start_timer(idle_timeout);
451 
452 	rcmd_cleanup(0);
453 	return (0);
454 }
455