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