xref: /titanic_44/usr/src/cmd/cvcd/sparc/sun4u/starcat/cvcd.c (revision 4bc0a2ef2b7ba50a7a717e7ddbf31472ad28e358)
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  * This code implements the Starcat Virtual Console host daemon (see cvcd(1M)).
31  * It accepts one TCP connection at a time on a well-known port.  Once a
32  * connection is accepted, the console redirection driver (cvcdredir(7D)) is
33  * opened, and console I/O is routed back and forth between the two file
34  * descriptors (network and redirection driver).  No security is provided or
35  * enforced within the daemon, as the Starcat platform uses a network security
36  * solution that is transparent to domain-side daemons.
37  */
38 
39 #include <stdio.h>
40 #include <stdarg.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <strings.h>
44 
45 #include <fcntl.h>
46 #include <sys/filio.h>		/* Just to get FIONBIO... */
47 #include <unistd.h>
48 #include <errno.h>
49 #include <stropts.h>
50 #include <signal.h>
51 #include <syslog.h>
52 #include <sys/utsname.h>
53 #include <sys/stat.h>
54 #include <locale.h>
55 #include <limits.h>
56 
57 #include <sys/priocntl.h>
58 #include <sys/tspriocntl.h>
59 #include <sys/rtpriocntl.h>
60 
61 #include <netdb.h>
62 #include <sys/socket.h>
63 #include <tiuser.h>
64 
65 #include <sys/sc_cvcio.h>
66 
67 
68 /*
69  *  Misc. defines.
70  */
71 #define	NODENAME	"/etc/nodename"
72 #define	NETWORK_PFD	0
73 #define	REDIR_PFD	1
74 #define	LISTEN_PFD	2
75 #define	NUM_PFDS	3
76 
77 /*
78  * Function prototypes
79  */
80 static void cvcd_set_priority(void);
81 static int  cvcd_init_host_socket(int port);
82 static void cvcd_do_network_console(void);
83 static void cvcd_err(int code, char *format, ...);
84 static void cvcd_usage(void);
85 
86 /*
87  *  Globals
88  */
89 static struct pollfd	pfds[NUM_PFDS];
90 static char		progname[MAXPATHLEN];
91 static int		debug = 0;
92 
93 int
94 main(int argc, char **argv)
95 {
96 	int			err;
97 	int			opt;
98 	int			tport = 0;
99 	char			*hostname;
100 	struct utsname		utsname;
101 	int			fd;
102 	int			i;
103 	struct servent		*se;
104 	char 			prefix[256];
105 
106 	(void) setlocale(LC_ALL, "");
107 	(void) strcpy(progname, argv[0]);
108 
109 #ifdef DEBUG
110 	while ((opt = getopt(argc, argv, "dp:")) != EOF) {
111 #else
112 	while ((opt = getopt(argc, argv, "")) != EOF) {
113 #endif
114 		switch (opt) {
115 #ifdef DEBUG
116 			case 'd' :	debug = 1;
117 					break;
118 
119 			case 'p' :	tport = atoi(optarg);
120 					break;
121 #endif  /* DEBUG */
122 
123 			default  :	cvcd_usage();
124 					exit(1);
125 		}
126 	}
127 
128 	if (uname(&utsname) == -1) {
129 		perror("HOSTNAME not defined");
130 		exit(1);
131 	}
132 	hostname = utsname.nodename;
133 
134 	/*
135 	 * hostname may still be NULL, depends on when cvcd was started
136 	 * in the boot sequence.  If it is NULL, try one more time
137 	 * to get a hostname -> look in the /etc/nodename file.
138 	 */
139 	if (!strlen(hostname)) {
140 		/*
141 		 * try to get the hostname from the /etc/nodename file
142 		 * we reuse the utsname.nodename buffer here!  hostname
143 		 * already points to it.
144 		 */
145 		if ((fd = open(NODENAME, O_RDONLY)) > 0) {
146 			if ((i = read(fd, utsname.nodename, SYS_NMLN)) <= 0) {
147 				cvcd_err(LOG_WARNING,
148 				    "failed to acquire hostname");
149 			} else {
150 				utsname.nodename[i-1] = '\0';
151 			}
152 			(void) close(fd);
153 		}
154 	}
155 	/*
156 	 * If all attempts to get the hostname have failed, put something
157 	 * meaningful in the buffer.
158 	 */
159 	if (!strlen(hostname)) {
160 		(void) strcpy(utsname.nodename, "(unknown)");
161 	}
162 
163 	/*
164 	 * Must be root.
165 	 */
166 	if (debug == 0 && geteuid() != 0) {
167 		fprintf(stderr, "cvcd: Must be root");
168 		exit(1);
169 	}
170 
171 	/*
172 	 * Daemonize...
173 	 */
174 	if (debug == 0) {
175 		for (i = 0; i < OPEN_MAX; i++) {
176 			if (debug && (i == STDERR_FILENO)) {
177 				/* Don't close stderr in debug mode! */
178 				continue;
179 			}
180 			(void) close(i);
181 		}
182 		(void) chdir("/");
183 		(void) umask(0);
184 		if (fork() != 0) {
185 			exit(0);
186 		}
187 		(void) setpgrp();
188 		(void) sprintf(prefix, "%s-(HOSTNAME:%s)", progname, hostname);
189 		openlog(prefix, LOG_CONS | LOG_NDELAY, LOG_LOCAL0);
190 	}
191 
192 	/*
193 	 * Initialize the array of pollfds used to track the listening socket,
194 	 * the connection to the console redirection driver, and the network
195 	 * connection.
196 	 */
197 	(void) memset((void *)pfds, 0, NUM_PFDS * sizeof (struct pollfd));
198 	for (i = 0; i < NUM_PFDS; i++) {
199 		pfds[i].fd = -1;
200 	}
201 
202 	/* SPR 94004 */
203 	(void) sigignore(SIGTERM);
204 
205 	/*
206 	 * SPR 83644: cvc and kadb are not compatible under heavy loads.
207 	 *	Fix: will give cvcd highest TS priority at execution time.
208 	 */
209 	cvcd_set_priority();
210 
211 	/*
212 	 * If not already determined by a command-line flag, figure out which
213 	 * port we're supposed to be listening on.
214 	 */
215 	if (tport == 0) {
216 		if ((se = getservbyname(CVCD_SERVICE, "tcp")) == NULL) {
217 			cvcd_err(LOG_ERR, "getservbyname(%s) not found",
218 				CVCD_SERVICE);
219 			exit(1);
220 		}
221 		tport = se->s_port;
222 	}
223 
224 	if (debug == 1) {
225 		cvcd_err(LOG_DEBUG, "tport = %d, debug = %d", tport, debug);
226 	}
227 
228 	/*
229 	 * Attempt to initialize the socket we'll use to listen for incoming
230 	 * connections.  No need to check the return value, as the call will
231 	 * exit if it fails.
232 	 */
233 	pfds[LISTEN_PFD].fd = cvcd_init_host_socket(tport);
234 
235 	/*
236 	 * Now that we're all set up, we loop forever waiting for connections
237 	 * (one at a time) and then driving network console activity over them.
238 	 */
239 	for (;;) {
240 		/*
241 		 * Start by waiting for an incoming connection.
242 		 */
243 		do {
244 			pfds[LISTEN_PFD].events = POLLIN;
245 			err = poll(&(pfds[LISTEN_PFD]), 1, -1);
246 			if (err == -1) {
247 				cvcd_err(LOG_ERR, "poll: %s", strerror(errno));
248 				exit(1);
249 			}
250 			if ((err > 0) &&
251 			    (pfds[LISTEN_PFD].revents & POLLIN)) {
252 				fd = accept(pfds[LISTEN_PFD].fd, NULL, NULL);
253 				if ((fd == -1) && (errno != EWOULDBLOCK)) {
254 					cvcd_err(LOG_ERR, "accept: %s",
255 					    strerror(errno));
256 					exit(1);
257 				}
258 			}
259 		} while (fd == -1);
260 
261 		/*
262 		 * We have a connection.  Set the new socket nonblocking, and
263 		 * initialize the appropriate pollfd.  In theory, the new socket
264 		 * is _already_ non-blocking because accept() is supposed to
265 		 * hand us a socket with the same properties as the socket we're
266 		 * listening on, but it won't hurt to make sure.
267 		 */
268 		opt = 1;
269 		err = ioctl(fd, FIONBIO, &opt);
270 		if (err == -1) {
271 			cvcd_err(LOG_ERR, "ioctl: %s", strerror(errno));
272 			(void) close(fd);
273 			continue;
274 		}
275 		pfds[NETWORK_PFD].fd = fd;
276 
277 		/*
278 		 * Since we're ready to do network console stuff, go ahead and
279 		 * open the Network Console redirection driver, which will
280 		 * switch traffic from the IOSRAM path to the network path if
281 		 * the network path has been selected in cvc.
282 		 */
283 		fd = open(CVCREDIR_DEV, O_RDWR|O_NDELAY);
284 		if (fd == -1) {
285 			cvcd_err(LOG_ERR, "open(redir): %s", strerror(errno));
286 			exit(1);
287 		}
288 		pfds[REDIR_PFD].fd = fd;
289 
290 		/*
291 		 * We have a network connection and we have the redirection
292 		 * driver open, so drive the network console until something
293 		 * changes.
294 		 */
295 		cvcd_do_network_console();
296 
297 		/*
298 		 * cvcd_do_network_console doesn't return until there's a
299 		 * problem, so we need to close the network connection and the
300 		 * redirection driver and start the whole loop over again.
301 		 */
302 		(void) close(pfds[NETWORK_PFD].fd);
303 		pfds[NETWORK_PFD].fd = -1;
304 		(void) close(pfds[REDIR_PFD].fd);
305 		pfds[REDIR_PFD].fd = -1;
306 	}
307 
308 	/* NOTREACHED */
309 	return (1);
310 }
311 
312 
313 /*
314  * cvcd_set_priority
315  *
316  * DESCRIBE
317  * SPR 83644: cvc and kadb are not compatible under heavy loads.
318  *	Fix: will give cvcd highest TS priority at execution time.
319  */
320 static void
321 cvcd_set_priority(void)
322 {
323 	id_t		pid, tsID;
324 	pcparms_t	pcparms;
325 	tsparms_t	*tsparmsp;
326 	short		tsmaxpri;
327 	pcinfo_t	info;
328 
329 	pid = getpid();
330 	pcparms.pc_cid = PC_CLNULL;
331 	tsparmsp = (tsparms_t *)pcparms.pc_clparms;
332 
333 	/* Get scheduler properties for this PID */
334 	if (priocntl(P_PID, pid, PC_GETPARMS, (caddr_t)&pcparms) == -1L) {
335 		cvcd_err(LOG_ERR, "Warning: can't set priority.");
336 		cvcd_err(LOG_ERR, "priocntl(GETPARMS): %s", strerror(errno));
337 		return;
338 	}
339 
340 	/* Get class ID and maximum priority for TS process class */
341 	(void) strcpy(info.pc_clname, "TS");
342 	if (priocntl(0L, 0L, PC_GETCID, (caddr_t)&info) == -1L) {
343 		cvcd_err(LOG_ERR, "Warning: can't set priority.");
344 		cvcd_err(LOG_ERR, "priocntl(GETCID): %s", strerror(errno));
345 		return;
346 	}
347 	tsmaxpri = ((struct tsinfo *)info.pc_clinfo)->ts_maxupri;
348 	tsID = info.pc_cid;
349 
350 	/* Print priority info in debug mode */
351 	if (debug) {
352 		if (pcparms.pc_cid == tsID) {
353 			cvcd_err(LOG_DEBUG,
354 			    "PID: %d, current priority: %d, Max priority: %d.",
355 			    pid, tsparmsp->ts_upri, tsmaxpri);
356 		}
357 	}
358 	/* Change proc's priority to maxtspri */
359 	pcparms.pc_cid = tsID;
360 	tsparmsp->ts_upri = tsmaxpri;
361 	tsparmsp->ts_uprilim = tsmaxpri;
362 
363 	if (priocntl(P_PID, pid, PC_SETPARMS, (caddr_t)&pcparms) == -1L) {
364 		cvcd_err(LOG_ERR, "Warning: can't set priority.");
365 		cvcd_err(LOG_ERR, "priocntl(SETPARMS): %s", strerror(errno));
366 	}
367 
368 	/* Print new priority info in debug mode */
369 	if (debug) {
370 		if (priocntl(P_PID, pid, PC_GETPARMS, (caddr_t)&pcparms) ==
371 		    -1L) {
372 			cvcd_err(LOG_ERR, "priocntl(GETPARMS): %s",
373 			    strerror(errno));
374 		} else {
375 			cvcd_err(LOG_DEBUG, "PID: %d, new priority: %d.", pid,
376 			    tsparmsp->ts_upri);
377 		}
378 	}
379 }
380 
381 
382 /*
383  * cvcd_init_host_socket
384  *
385  * Given a TCP port number, create and initialize a socket appropriate for
386  * accepting incoming connections to that port.
387  */
388 static int
389 cvcd_init_host_socket(int port)
390 {
391 	int			err;
392 	int			fd;
393 	int			optval;
394 	int 			optlen = sizeof (optval);
395 	struct sockaddr_in6	sin6;
396 
397 	/*
398 	 * Start by creating the socket, which needs to support IPv6.
399 	 */
400 	fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
401 	if (fd == -1) {
402 		cvcd_err(LOG_ERR, "socket: %s", strerror(errno));
403 		exit(1);
404 	}
405 
406 	/*
407 	 * Set the SO_REUSEADDR option, and make the socket non-blocking.
408 	 */
409 	optval = 1;
410 	err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, optlen);
411 	if (err == -1) {
412 		cvcd_err(LOG_ERR, "setsockopt: %s", strerror(errno));
413 		exit(1);
414 	}
415 
416 	err = ioctl(fd, FIONBIO, &optval);
417 	if (err == -1) {
418 		cvcd_err(LOG_ERR, "ioctl: %s", strerror(errno));
419 		exit(1);
420 	}
421 
422 	/*
423 	 * Bind the socket to our local address and port.
424 	 */
425 	bzero(&sin6, sizeof (sin6));
426 	sin6.sin6_family = AF_INET6;
427 	sin6.sin6_port = htons(port);
428 	sin6.sin6_addr = in6addr_any;
429 	err = bind(fd, (struct sockaddr *)&sin6, sizeof (sin6));
430 	if (err == -1) {
431 		cvcd_err(LOG_ERR, "bind: %s", strerror(errno));
432 		exit(1);
433 	}
434 
435 	/*
436 	 * Indicate that we want to accept connections on this socket.  Since we
437 	 * only allow one connection at a time anyway, specify a maximum backlog
438 	 * of 1.
439 	 */
440 	err = listen(fd, 1);
441 	if (err == -1) {
442 		cvcd_err(LOG_ERR, "listen: %s", strerror(errno));
443 		exit(1);
444 	}
445 
446 	return (fd);
447 }
448 
449 
450 /*
451  * cvcd_do_network_console
452  *
453  * With established connections to the network and the redirection driver,
454  * shuttle data between the two until something goes wrong.
455  */
456 static void
457 cvcd_do_network_console(void)
458 {
459 	int	i;
460 	int	err;
461 	int	count;
462 	short	revents;
463 	int	input_len = 0;
464 	int	output_len = 0;
465 	int	input_off = 0;
466 	int	output_off = 0;
467 	char	input_buf[MAXPKTSZ];
468 	char	output_buf[MAXPKTSZ];
469 
470 	for (;;) {
471 		/*
472 		 * Wait for activity on any of the open file descriptors, which
473 		 * includes the ability to write data if we have any to write.
474 		 * If poll() fails, break out of the network console processing
475 		 * loop.
476 		 */
477 		pfds[LISTEN_PFD].events = POLLIN;
478 		pfds[NETWORK_PFD].events = POLLIN;
479 		if (output_len != 0) {
480 			pfds[NETWORK_PFD].events |= POLLOUT;
481 		}
482 		pfds[REDIR_PFD].events = POLLIN;
483 		if (input_len != 0) {
484 			pfds[REDIR_PFD].events |= POLLOUT;
485 		}
486 		err = poll(pfds, NUM_PFDS, -1);
487 		if (err == -1) {
488 			cvcd_err(LOG_ERR, "poll: %s", strerror(errno));
489 			break;
490 		}
491 
492 		/*
493 		 * If any errors or hangups were detected, or one of our file
494 		 * descriptors is bad, bail out of the network console
495 		 * processing loop.
496 		 */
497 		for (i = 0; i < NUM_PFDS; i++) {
498 			revents = pfds[i].revents;
499 			if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
500 				cvcd_err(LOG_NOTICE,
501 				    "poll: status on %s fd:%s%s%s",
502 				    ((i == LISTEN_PFD) ? "listen" :
503 				    ((i == NETWORK_PFD) ? "network" : "redir")),
504 				    (revents & POLLERR) ? " error" : "",
505 				    (revents & POLLHUP) ? " hangup" : "",
506 				    (revents & POLLNVAL) ? " bad fd" : "");
507 				goto fail;	/* 'break' wouldn't work here */
508 			}
509 		}
510 
511 		/*
512 		 * Start by rejecting any connection attempts, since we only
513 		 * allow one network connection at a time.
514 		 */
515 		if (pfds[LISTEN_PFD].revents & POLLIN) {
516 			int	fd;
517 
518 			fd = accept(pfds[LISTEN_PFD].fd, NULL, NULL);
519 			if (fd > 0) {
520 				(void) close(fd);
521 			}
522 		}
523 
524 		/*
525 		 * If we have data waiting to be written in one direction or the
526 		 * other, go ahead and try to send the data on its way.  We're
527 		 * going to attempt the writes regardless of whether the poll
528 		 * indicated that the destinations are ready, because we want to
529 		 * find out if either descriptor has a problem (e.g. broken
530 		 * network link).
531 		 * If an "unexpected" error is detected, give up and break out
532 		 * of the network console processing loop.
533 		 */
534 		if (output_len != 0) {
535 			count = write(pfds[NETWORK_PFD].fd,
536 			    &(output_buf[output_off]), output_len);
537 			if ((count == -1) && (errno != EAGAIN)) {
538 				cvcd_err(LOG_ERR, "write(network): %s",
539 				    strerror(errno));
540 				break;
541 			} else if (count > 0) {
542 				output_len -= count;
543 				if (output_len == 0) {
544 					output_off = 0;
545 				} else {
546 					output_off += count;
547 				}
548 			}
549 		}
550 
551 		if (input_len != 0) {
552 			count = write(pfds[REDIR_PFD].fd,
553 			    &(input_buf[input_off]), input_len);
554 			if ((count == -1) && (errno != EAGAIN)) {
555 				cvcd_err(LOG_ERR, "write(redir): %s",
556 				    strerror(errno));
557 				break;
558 			} else if (count > 0) {
559 				input_len -= count;
560 				if (input_len == 0) {
561 					input_off = 0;
562 				} else {
563 					input_off += count;
564 				}
565 			}
566 		}
567 
568 		/*
569 		 * Finally, take a look at each data source and, if there isn't
570 		 * any residual data from that source still waiting to be
571 		 * processed, see if more data can be read.  We don't want to
572 		 * read more data from a source if we haven't finished
573 		 * processing the last data we read from it because doing so
574 		 * would maximize the amount of data lost if the network console
575 		 * failed or was closed.
576 		 * If an "unexpected" error is detected, give up and break out
577 		 * of the network console processing loop.
578 		 * The call to read() appears to be in the habit of returning 0
579 		 * when you've read all of the data from a stream that has been
580 		 * hung up, and poll apparently feels that that condition
581 		 * justifies setting POLLIN, so we're going to treat 0 as an
582 		 * error return from read().
583 		 */
584 		if ((output_len == 0) && (pfds[REDIR_PFD].revents & POLLIN)) {
585 			count = read(pfds[REDIR_PFD].fd, output_buf, MAXPKTSZ);
586 			if (count <= 0) {
587 				/*
588 				 * Reading 0 simply means there is no data
589 				 * available, since this is a terminal.
590 				 */
591 				if ((count < 0) && (errno != EAGAIN)) {
592 					cvcd_err(LOG_ERR, "read(redir): %s",
593 					    strerror(errno));
594 					break;
595 				}
596 			} else {
597 				output_len = count;
598 				output_off = 0;
599 			}
600 		}
601 
602 		if ((input_len == 0) && (pfds[NETWORK_PFD].revents & POLLIN)) {
603 			count = read(pfds[NETWORK_PFD].fd, input_buf, MAXPKTSZ);
604 			if (count <= 0) {
605 				/*
606 				 * Reading 0 here implies a hangup, since this
607 				 * is a non-blocking socket that poll() reported
608 				 * as having data available.  This will
609 				 * typically occur when the console user drops
610 				 * to OBP or intentially switches to IOSRAM
611 				 * mode.
612 				 */
613 				if (count == 0) {
614 					cvcd_err(LOG_NOTICE,
615 					    "read(network): hangup detected");
616 					break;
617 				} else if (errno != EAGAIN) {
618 					cvcd_err(LOG_ERR, "read(network): %s",
619 					    strerror(errno));
620 					break;
621 				}
622 			} else {
623 				input_len = count;
624 				input_off = 0;
625 			}
626 		}
627 	} /* End forever loop */
628 
629 	/*
630 	 * If we get here, something bad happened during an attempt to access
631 	 * either the redirection driver or the network connection.  There
632 	 * doesn't appear to be any way to avoid the possibility of losing
633 	 * console input and/or input in that case, so we should at least report
634 	 * the loss if it happens.
635 	 * XXX - We could do more, but is it worth the effort?  Logging the
636 	 *	 lost data would be pretty easy... actually preserving it
637 	 *	 in the console flow would be a lot harder.  We're more robust
638 	 *	 than the previous generation at this point, at least, so
639 	 *	 perhaps that's enough for now?
640 	 */
641 fail:
642 	if (input_len != 0) {
643 		cvcd_err(LOG_ERR, "console input lost");
644 	}
645 	if (output_len != 0) {
646 		cvcd_err(LOG_ERR, "console output lost");
647 	}
648 }
649 
650 
651 static void
652 cvcd_usage()
653 {
654 #if defined(DEBUG)
655 	(void) printf("%s [-d] [-p port]\n", progname);
656 #else
657 	(void) printf("%s\n", progname);
658 #endif  /* DEBUG */
659 }
660 
661 /*
662  * cvcd_err ()
663  *
664  * Description:
665  * Log messages via syslog daemon.
666  *
667  * Input:
668  * code - logging code
669  * format - messages to log
670  *
671  * Output:
672  * void
673  *
674  */
675 static void
676 cvcd_err(int code, char *format, ...)
677 {
678 	va_list	varg_ptr;
679 	char	buf[MAXPKTSZ];
680 
681 	va_start(varg_ptr, format);
682 	(void) vsnprintf(buf, MAXPKTSZ, format, varg_ptr);
683 	va_end(varg_ptr);
684 
685 	if (debug == 0) {
686 		syslog(code, buf);
687 	} else {
688 		(void) fprintf(stderr, "%s: %s\n", progname, buf);
689 	}
690 }
691