main.c revision 45291
1/*- 2 * Copyright (c) 1980, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by the University of 16 * California, Berkeley and its contributors. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34#ifndef lint 35static const char copyright[] = 36"@(#) Copyright (c) 1980, 1993\n\ 37 The Regents of the University of California. All rights reserved.\n"; 38#endif /* not lint */ 39 40#ifndef lint 41#if 0 42static char sccsid[] = "@(#)from: main.c 8.1 (Berkeley) 6/20/93"; 43#endif 44static const char rcsid[] = 45 "$Id: main.c,v 1.24 1999/03/09 22:04:44 brian Exp $"; 46#endif /* not lint */ 47 48#include <sys/param.h> 49#include <sys/stat.h> 50#include <sys/ioctl.h> 51#include <sys/resource.h> 52#include <sys/ttydefaults.h> 53#include <sys/utsname.h> 54#include <ctype.h> 55#include <errno.h> 56#include <fcntl.h> 57#include <locale.h> 58#include <libutil.h> 59#include <signal.h> 60#include <setjmp.h> 61#include <signal.h> 62#include <stdlib.h> 63#include <string.h> 64#include <syslog.h> 65#include <termios.h> 66#include <time.h> 67#include <unistd.h> 68 69#include "gettytab.h" 70#include "pathnames.h" 71#include "extern.h" 72 73/* 74 * Set the amount of running time that getty should accumulate 75 * before deciding that something is wrong and exit. 76 */ 77#define GETTY_TIMEOUT 60 /* seconds */ 78 79#undef CTRL 80#define CTRL(x) (x&037) 81 82/* defines for auto detection of incoming PPP calls (->PAP/CHAP) */ 83 84#define PPP_FRAME 0x7e /* PPP Framing character */ 85#define PPP_STATION 0xff /* "All Station" character */ 86#define PPP_ESCAPE 0x7d /* Escape Character */ 87#define PPP_CONTROL 0x03 /* PPP Control Field */ 88#define PPP_CONTROL_ESCAPED 0x23 /* PPP Control Field, escaped */ 89#define PPP_LCP_HI 0xc0 /* LCP protocol - high byte */ 90#define PPP_LCP_LOW 0x21 /* LCP protocol - low byte */ 91 92struct termios tmode, omode; 93 94int crmod, digit, lower, upper; 95 96char hostname[MAXHOSTNAMELEN]; 97char name[MAXLOGNAME*3]; 98char dev[] = _PATH_DEV; 99char ttyn[32]; 100 101#define OBUFSIZ 128 102#define TABBUFSIZ 512 103 104char defent[TABBUFSIZ]; 105char tabent[TABBUFSIZ]; 106 107char *env[128]; 108 109char partab[] = { 110 0001,0201,0201,0001,0201,0001,0001,0201, 111 0202,0004,0003,0205,0005,0206,0201,0001, 112 0201,0001,0001,0201,0001,0201,0201,0001, 113 0001,0201,0201,0001,0201,0001,0001,0201, 114 0200,0000,0000,0200,0000,0200,0200,0000, 115 0000,0200,0200,0000,0200,0000,0000,0200, 116 0000,0200,0200,0000,0200,0000,0000,0200, 117 0200,0000,0000,0200,0000,0200,0200,0000, 118 0200,0000,0000,0200,0000,0200,0200,0000, 119 0000,0200,0200,0000,0200,0000,0000,0200, 120 0000,0200,0200,0000,0200,0000,0000,0200, 121 0200,0000,0000,0200,0000,0200,0200,0000, 122 0000,0200,0200,0000,0200,0000,0000,0200, 123 0200,0000,0000,0200,0000,0200,0200,0000, 124 0200,0000,0000,0200,0000,0200,0200,0000, 125 0000,0200,0200,0000,0200,0000,0000,0201 126}; 127 128#define ERASE tmode.c_cc[VERASE] 129#define KILL tmode.c_cc[VKILL] 130#define EOT tmode.c_cc[VEOF] 131 132#define puts Gputs 133 134static void dingdong __P((int)); 135static int getname __P((void)); 136static void interrupt __P((int)); 137static void oflush __P((void)); 138static void prompt __P((void)); 139static void putchr __P((int)); 140static void putf __P((const char *)); 141static void putpad __P((const char *)); 142static void puts __P((const char *)); 143static void timeoverrun __P((int)); 144static char *getline __P((int)); 145static void setttymode __P((const char *, int)); 146static void setdefttymode __P((const char *)); 147static int opentty __P((const char *, int)); 148 149int main __P((int, char **)); 150 151jmp_buf timeout; 152 153static void 154dingdong(signo) 155 int signo; 156{ 157 alarm(0); 158 longjmp(timeout, 1); 159} 160 161jmp_buf intrupt; 162 163static void 164interrupt(signo) 165 int signo; 166{ 167 longjmp(intrupt, 1); 168} 169 170/* 171 * Action to take when getty is running too long. 172 */ 173static void 174timeoverrun(signo) 175 int signo; 176{ 177 178 syslog(LOG_ERR, "getty exiting due to excessive running time"); 179 exit(1); 180} 181 182int 183main(argc, argv) 184 int argc; 185 char **argv; 186{ 187 extern char **environ; 188 const char *tname; 189 int first_sleep = 1, first_time = 1; 190 struct rlimit limit; 191 int rval; 192 193 signal(SIGINT, SIG_IGN); 194 signal(SIGQUIT, SIG_IGN); 195 196 openlog("getty", LOG_ODELAY|LOG_CONS|LOG_PID, LOG_AUTH); 197 gethostname(hostname, sizeof(hostname)); 198 if (hostname[0] == '\0') 199 strcpy(hostname, "Amnesiac"); 200 201 /* 202 * Limit running time to deal with broken or dead lines. 203 */ 204 (void)signal(SIGXCPU, timeoverrun); 205 limit.rlim_max = RLIM_INFINITY; 206 limit.rlim_cur = GETTY_TIMEOUT; 207 (void)setrlimit(RLIMIT_CPU, &limit); 208 209 gettable("default", defent); 210 gendefaults(); 211 tname = "default"; 212 if (argc > 1) 213 tname = argv[1]; 214 215 /* 216 * The following is a work around for vhangup interactions 217 * which cause great problems getting window systems started. 218 * If the tty line is "-", we do the old style getty presuming 219 * that the file descriptors are already set up for us. 220 * J. Gettys - MIT Project Athena. 221 */ 222 if (argc <= 2 || strcmp(argv[2], "-") == 0) 223 strcpy(ttyn, ttyname(STDIN_FILENO)); 224 else { 225 strcpy(ttyn, dev); 226 strncat(ttyn, argv[2], sizeof(ttyn)-sizeof(dev)); 227 if (strcmp(argv[0], "+") != 0) { 228 chown(ttyn, 0, 0); 229 chmod(ttyn, 0600); 230 revoke(ttyn); 231 232 gettable(tname, tabent); 233 234 /* Init modem sequence has been specified 235 */ 236 if (IC) { 237 if (!opentty(ttyn, O_RDWR|O_NONBLOCK)) 238 exit(1); 239 setdefttymode(tname); 240 if (getty_chat(IC, CT, DC) > 0) { 241 syslog(LOG_ERR, "modem init problem on %s", ttyn); 242 (void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode); 243 exit(1); 244 } 245 } 246 247 if (AC) { 248 int i, rfds; 249 struct timeval timeout; 250 251 if (!opentty(ttyn, O_RDWR|O_NONBLOCK)) 252 exit(1); 253 setdefttymode(tname); 254 rfds = 1 << 0; /* FD_SET */ 255 timeout.tv_sec = RT; 256 timeout.tv_usec = 0; 257 i = select(32, (fd_set*)&rfds, (fd_set*)NULL, 258 (fd_set*)NULL, RT ? &timeout : NULL); 259 if (i < 0) { 260 syslog(LOG_ERR, "select %s: %m", ttyn); 261 } else if (i == 0) { 262 syslog(LOG_NOTICE, "recycle tty %s", ttyn); 263 (void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode); 264 exit(0); /* recycle for init */ 265 } 266 i = getty_chat(AC, CT, DC); 267 if (i > 0) { 268 syslog(LOG_ERR, "modem answer problem on %s", ttyn); 269 (void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode); 270 exit(1); 271 } 272 } else { /* blocking open */ 273 if (!opentty(ttyn, O_RDWR)) 274 exit(1); 275 } 276 } 277 } 278 279 /* Start with default tty settings */ 280 if (tcgetattr(STDIN_FILENO, &tmode) < 0) { 281 syslog(LOG_ERR, "tcgetattr %s: %m", ttyn); 282 exit(1); 283 } 284 /* 285 * Don't rely on the driver too much, and initialize crucial 286 * things according to <sys/ttydefaults.h>. Avoid clobbering 287 * the c_cc[] settings however, the console drivers might wish 288 * to leave their idea of the preferred VERASE key value 289 * there. 290 */ 291 tmode.c_iflag = TTYDEF_IFLAG; 292 tmode.c_oflag = TTYDEF_OFLAG; 293 tmode.c_lflag = TTYDEF_LFLAG; 294 tmode.c_cflag = TTYDEF_CFLAG; 295 omode = tmode; 296 297 for (;;) { 298 299 /* 300 * if a delay was specified then sleep for that 301 * number of seconds before writing the initial prompt 302 */ 303 if (first_sleep && DE) { 304 sleep(DE); 305 /* remove any noise */ 306 (void)tcflush(STDIN_FILENO, TCIOFLUSH); 307 } 308 first_sleep = 0; 309 310 setttymode(tname, 0); 311 if (AB) { 312 tname = autobaud(); 313 continue; 314 } 315 if (PS) { 316 tname = portselector(); 317 continue; 318 } 319 if (CL && *CL) 320 putpad(CL); 321 edithost(HE); 322 323 /* if this is the first time through this, and an 324 issue file has been given, then send it */ 325 if (first_time && IF) { 326 int fd; 327 328 if ((fd = open(IF, O_RDONLY)) != -1) { 329 char * cp; 330 331 while ((cp = getline(fd)) != NULL) { 332 putf(cp); 333 } 334 close(fd); 335 } 336 } 337 first_time = 0; 338 339 if (IM && *IM) 340 putf(IM); 341 if (setjmp(timeout)) { 342 cfsetispeed(&tmode, B0); 343 cfsetospeed(&tmode, B0); 344 (void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode); 345 exit(1); 346 } 347 if (TO) { 348 signal(SIGALRM, dingdong); 349 alarm(TO); 350 } 351 if (AL) { 352 const char *p = AL; 353 char *q = name; 354 int n = sizeof name; 355 356 while (*p && q < &name[sizeof name - 1]) { 357 if (isupper(*p)) 358 upper = 1; 359 else if (islower(*p)) 360 lower = 1; 361 else if (isdigit(*p)) 362 digit++; 363 *q++ = *p++; 364 } 365 } else 366 rval = getname(); 367 if (rval == 2) { 368 oflush(); 369 alarm(0); 370 limit.rlim_max = RLIM_INFINITY; 371 limit.rlim_cur = RLIM_INFINITY; 372 (void)setrlimit(RLIMIT_CPU, &limit); 373 execle(PP, "ppplogin", ttyn, (char *) 0, env); 374 syslog(LOG_ERR, "%s: %m", PP); 375 exit(1); 376 } else if (rval || AL) { 377 register int i; 378 379 oflush(); 380 alarm(0); 381 signal(SIGALRM, SIG_DFL); 382 if (name[0] == '-') { 383 puts("user names may not start with '-'."); 384 continue; 385 } 386 if (!(upper || lower || digit)) 387 continue; 388 setflags(2); 389 if (crmod) { 390 tmode.c_iflag |= ICRNL; 391 tmode.c_oflag |= ONLCR; 392 } 393#if REALLY_OLD_TTYS 394 if (upper || UC) 395 tmode.sg_flags |= LCASE; 396 if (lower || LC) 397 tmode.sg_flags &= ~LCASE; 398#endif 399 if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) { 400 syslog(LOG_ERR, "tcsetattr %s: %m", ttyn); 401 exit(1); 402 } 403 signal(SIGINT, SIG_DFL); 404 for (i = 0; environ[i] != (char *)0; i++) 405 env[i] = environ[i]; 406 makeenv(&env[i]); 407 408 limit.rlim_max = RLIM_INFINITY; 409 limit.rlim_cur = RLIM_INFINITY; 410 (void)setrlimit(RLIMIT_CPU, &limit); 411 execle(LO, "login", AL ? "-fp" : "-p", name, 412 (char *) 0, env); 413 syslog(LOG_ERR, "%s: %m", LO); 414 exit(1); 415 } 416 alarm(0); 417 signal(SIGALRM, SIG_DFL); 418 signal(SIGINT, SIG_IGN); 419 if (NX && *NX) 420 tname = NX; 421 } 422} 423 424static int 425opentty(const char *ttyn, int flags) 426{ 427 int i, j = 0; 428 int failopenlogged = 0; 429 430 while (j < 10 && (i = open(ttyn, flags)) == -1) 431 { 432 if (((j % 10) == 0) && (errno != ENXIO || !failopenlogged)) { 433 syslog(LOG_ERR, "open %s: %m", ttyn); 434 failopenlogged = 1; 435 } 436 j++; 437 sleep(60); 438 } 439 if (i == -1) { 440 syslog(LOG_ERR, "open %s: %m", ttyn); 441 return 0; 442 } 443 else { 444 login_tty(i); 445 return 1; 446 } 447} 448 449static void 450setdefttymode(tname) 451 const char * tname; 452{ 453 if (tcgetattr(STDIN_FILENO, &tmode) < 0) { 454 syslog(LOG_ERR, "tcgetattr %s: %m", ttyn); 455 exit(1); 456 } 457 tmode.c_iflag = TTYDEF_IFLAG; 458 tmode.c_oflag = TTYDEF_OFLAG; 459 tmode.c_lflag = TTYDEF_LFLAG; 460 tmode.c_cflag = TTYDEF_CFLAG; 461 omode = tmode; 462 setttymode(tname, 1); 463} 464 465static void 466setttymode(tname, raw) 467 const char * tname; 468 int raw; 469{ 470 int off = 0; 471 472 gettable(tname, tabent); 473 if (OPset || EPset || APset) 474 APset++, OPset++, EPset++; 475 setdefaults(); 476 (void)tcflush(STDIN_FILENO, TCIOFLUSH); /* clear out the crap */ 477 ioctl(STDIN_FILENO, FIONBIO, &off); /* turn off non-blocking mode */ 478 ioctl(STDIN_FILENO, FIOASYNC, &off); /* ditto for async mode */ 479 480 if (IS) 481 cfsetispeed(&tmode, speed(IS)); 482 else if (SP) 483 cfsetispeed(&tmode, speed(SP)); 484 if (OS) 485 cfsetospeed(&tmode, speed(OS)); 486 else if (SP) 487 cfsetospeed(&tmode, speed(SP)); 488 setflags(0); 489 setchars(); 490 if (raw) 491 cfmakeraw(&tmode); 492 if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) { 493 syslog(LOG_ERR, "tcsetattr %s: %m", ttyn); 494 exit(1); 495 } 496} 497 498 499static int 500getname() 501{ 502 register int c; 503 register char *np; 504 unsigned char cs; 505 int ppp_state = 0; 506 int ppp_connection = 0; 507 508 /* 509 * Interrupt may happen if we use CBREAK mode 510 */ 511 if (setjmp(intrupt)) { 512 signal(SIGINT, SIG_IGN); 513 return (0); 514 } 515 signal(SIGINT, interrupt); 516 setflags(1); 517 prompt(); 518 oflush(); 519 if (PF > 0) { 520 sleep(PF); 521 PF = 0; 522 } 523 if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) { 524 syslog(LOG_ERR, "%s: %m", ttyn); 525 exit(1); 526 } 527 crmod = digit = lower = upper = 0; 528 np = name; 529 for (;;) { 530 oflush(); 531 if (read(STDIN_FILENO, &cs, 1) <= 0) 532 exit(0); 533 if ((c = cs&0177) == 0) 534 return (0); 535 536 /* PPP detection state machine.. 537 Look for sequences: 538 PPP_FRAME, PPP_STATION, PPP_ESCAPE, PPP_CONTROL_ESCAPED or 539 PPP_FRAME, PPP_STATION, PPP_CONTROL (deviant from RFC) 540 See RFC1662. 541 Derived from code from Michael Hancock, <michaelh@cet.co.jp> 542 and Erik 'PPP' Olson, <eriko@wrq.com> 543 */ 544 545 if (PP && (cs == PPP_FRAME)) { 546 ppp_state = 1; 547 } else if (ppp_state == 1 && cs == PPP_STATION) { 548 ppp_state = 2; 549 } else if (ppp_state == 2 && cs == PPP_ESCAPE) { 550 ppp_state = 3; 551 } else if ((ppp_state == 2 && cs == PPP_CONTROL) 552 || (ppp_state == 3 && cs == PPP_CONTROL_ESCAPED)) { 553 ppp_state = 4; 554 } else if (ppp_state == 4 && cs == PPP_LCP_HI) { 555 ppp_state = 5; 556 } else if (ppp_state == 5 && cs == PPP_LCP_LOW) { 557 ppp_connection = 1; 558 break; 559 } else { 560 ppp_state = 0; 561 } 562 563 if (c == EOT || c == CTRL('d')) 564 exit(1); 565 if (c == '\r' || c == '\n' || np >= &name[sizeof name-1]) { 566 putf("\r\n"); 567 break; 568 } 569 if (islower(c)) 570 lower = 1; 571 else if (isupper(c)) 572 upper = 1; 573 else if (c == ERASE || c == '\b' || c == 0177) { 574 if (np > name) { 575 np--; 576 if (cfgetospeed(&tmode) >= 1200) 577 puts("\b \b"); 578 else 579 putchr(cs); 580 } 581 continue; 582 } else if (c == KILL || c == CTRL('u')) { 583 putchr('\r'); 584 if (cfgetospeed(&tmode) < 1200) 585 putchr('\n'); 586 /* this is the way they do it down under ... */ 587 else if (np > name) 588 puts(" \r"); 589 prompt(); 590 np = name; 591 continue; 592 } else if (isdigit(c)) 593 digit++; 594 if (IG && (c <= ' ' || c > 0176)) 595 continue; 596 *np++ = c; 597 putchr(cs); 598 } 599 signal(SIGINT, SIG_IGN); 600 *np = 0; 601 if (c == '\r') 602 crmod = 1; 603 if ((upper && !lower && !LC) || UC) 604 for (np = name; *np; np++) 605 if (isupper(*np)) 606 *np = tolower(*np); 607 return (1 + ppp_connection); 608} 609 610static void 611putpad(s) 612 register const char *s; 613{ 614 register pad = 0; 615 speed_t ospeed = cfgetospeed(&tmode); 616 617 if (isdigit(*s)) { 618 while (isdigit(*s)) { 619 pad *= 10; 620 pad += *s++ - '0'; 621 } 622 pad *= 10; 623 if (*s == '.' && isdigit(s[1])) { 624 pad += s[1] - '0'; 625 s += 2; 626 } 627 } 628 629 puts(s); 630 /* 631 * If no delay needed, or output speed is 632 * not comprehensible, then don't try to delay. 633 */ 634 if (pad == 0 || ospeed <= 0) 635 return; 636 637 /* 638 * Round up by a half a character frame, and then do the delay. 639 * Too bad there are no user program accessible programmed delays. 640 * Transmitting pad characters slows many terminals down and also 641 * loads the system. 642 */ 643 pad = (pad * ospeed + 50000) / 100000; 644 while (pad--) 645 putchr(*PC); 646} 647 648static void 649puts(s) 650 register const char *s; 651{ 652 while (*s) 653 putchr(*s++); 654} 655 656char outbuf[OBUFSIZ]; 657int obufcnt = 0; 658 659static void 660putchr(cc) 661 int cc; 662{ 663 char c; 664 665 c = cc; 666 if (!NP) { 667 c |= partab[c&0177] & 0200; 668 if (OP) 669 c ^= 0200; 670 } 671 if (!UB) { 672 outbuf[obufcnt++] = c; 673 if (obufcnt >= OBUFSIZ) 674 oflush(); 675 } else 676 write(STDOUT_FILENO, &c, 1); 677} 678 679static void 680oflush() 681{ 682 if (obufcnt) 683 write(STDOUT_FILENO, outbuf, obufcnt); 684 obufcnt = 0; 685} 686 687static void 688prompt() 689{ 690 691 putf(LM); 692 if (CO) 693 putchr('\n'); 694} 695 696 697static char * 698getline(fd) 699 int fd; 700{ 701 int i = 0; 702 static char linebuf[512]; 703 704 /* 705 * This is certainly slow, but it avoids having to include 706 * stdio.h unnecessarily. Issue files should be small anyway. 707 */ 708 while (i < (sizeof linebuf - 3) && read(fd, linebuf+i, 1)==1) { 709 if (linebuf[i] == '\n') { 710 /* Don't rely on newline mode, assume raw */ 711 linebuf[i++] = '\r'; 712 linebuf[i++] = '\n'; 713 linebuf[i] = '\0'; 714 return linebuf; 715 } 716 ++i; 717 } 718 linebuf[i] = '\0'; 719 return i ? linebuf : 0; 720} 721 722static void 723putf(cp) 724 register const char *cp; 725{ 726 extern char editedhost[]; 727 time_t t; 728 char *slash, db[100]; 729 730 static struct utsname kerninfo; 731 732 if (!*kerninfo.sysname) 733 uname(&kerninfo); 734 735 while (*cp) { 736 if (*cp != '%') { 737 putchr(*cp++); 738 continue; 739 } 740 switch (*++cp) { 741 742 case 't': 743 slash = strrchr(ttyn, '/'); 744 if (slash == (char *) 0) 745 puts(ttyn); 746 else 747 puts(&slash[1]); 748 break; 749 750 case 'h': 751 puts(editedhost); 752 break; 753 754 case 'd': { 755 t = (time_t)0; 756 (void)time(&t); 757 if (Lo) 758 (void)setlocale(LC_TIME, Lo); 759 (void)strftime(db, sizeof(db), "%+", localtime(&t)); 760 puts(db); 761 break; 762 763 case 's': 764 puts(kerninfo.sysname); 765 break; 766 767 case 'm': 768 puts(kerninfo.machine); 769 break; 770 771 case 'r': 772 puts(kerninfo.release); 773 break; 774 775 case 'v': 776 puts(kerninfo.version); 777 break; 778 } 779 780 case '%': 781 putchr('%'); 782 break; 783 } 784 cp++; 785 } 786} 787