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