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