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