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 * 4. 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 *getline (int); 130 static void setttymode (const char *, int); 131 static void setdefttymode (const char *); 132 static int opentty (const char *, int); 133 134 int main (int, char **); 135 136 jmp_buf timeout; 137 138 static void 139 dingdong(int signo) 140 { 141 alarm(0); 142 longjmp(timeout, 1); 143 } 144 145 jmp_buf intrupt; 146 147 static void 148 interrupt(int signo) 149 { 150 longjmp(intrupt, 1); 151 } 152 153 /* 154 * Action to take when getty is running too long. 155 */ 156 static void 157 timeoverrun(int signo) 158 { 159 syslog(LOG_ERR, "getty exiting due to excessive running time"); 160 exit(1); 161 } 162 163 int 164 main(int argc, char **argv) 165 { 166 extern char **environ; 167 const char *tname; 168 int first_sleep = 1, first_time = 1; 169 struct rlimit limit; 170 int rval; 171 172 signal(SIGINT, SIG_IGN); 173 signal(SIGQUIT, SIG_IGN); 174 175 openlog("getty", LOG_ODELAY|LOG_CONS|LOG_PID, LOG_AUTH); 176 gethostname(hostname, sizeof(hostname) - 1); 177 hostname[sizeof(hostname) - 1] = '\0'; 178 if (hostname[0] == '\0') 179 strcpy(hostname, "Amnesiac"); 180 181 /* 182 * Limit running time to deal with broken or dead lines. 183 */ 184 (void)signal(SIGXCPU, timeoverrun); 185 limit.rlim_max = RLIM_INFINITY; 186 limit.rlim_cur = GETTY_TIMEOUT; 187 (void)setrlimit(RLIMIT_CPU, &limit); 188 189 gettable("default", defent); 190 gendefaults(); 191 tname = "default"; 192 if (argc > 1) 193 tname = argv[1]; 194 195 /* 196 * The following is a work around for vhangup interactions 197 * which cause great problems getting window systems started. 198 * If the tty line is "-", we do the old style getty presuming 199 * that the file descriptors are already set up for us. 200 * J. Gettys - MIT Project Athena. 201 */ 202 if (argc <= 2 || strcmp(argv[2], "-") == 0) 203 strcpy(ttyn, ttyname(STDIN_FILENO)); 204 else { 205 strcpy(ttyn, dev); 206 strncat(ttyn, argv[2], sizeof(ttyn)-sizeof(dev)); 207 if (strcmp(argv[0], "+") != 0) { 208 chown(ttyn, 0, 0); 209 chmod(ttyn, 0600); 210 revoke(ttyn); 211 212 gettable(tname, tabent); 213 214 /* Init modem sequence has been specified 215 */ 216 if (IC) { 217 if (!opentty(ttyn, O_RDWR|O_NONBLOCK)) 218 exit(1); 219 setdefttymode(tname); 220 if (getty_chat(IC, CT, DC) > 0) { 221 syslog(LOG_ERR, "modem init problem on %s", ttyn); 222 (void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode); 223 exit(1); 224 } 225 } 226 227 if (AC) { 228 int i, rfds; 229 struct timeval timeout; 230 231 if (!opentty(ttyn, O_RDWR|O_NONBLOCK)) 232 exit(1); 233 setdefttymode(tname); 234 rfds = 1 << 0; /* FD_SET */ 235 timeout.tv_sec = RT; 236 timeout.tv_usec = 0; 237 i = select(32, (fd_set*)&rfds, NULL, 238 NULL, RT ? &timeout : NULL); 239 if (i < 0) { 240 syslog(LOG_ERR, "select %s: %m", ttyn); 241 } else if (i == 0) { 242 syslog(LOG_NOTICE, "recycle tty %s", ttyn); 243 (void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode); 244 exit(0); /* recycle for init */ 245 } 246 i = getty_chat(AC, CT, DC); 247 if (i > 0) { 248 syslog(LOG_ERR, "modem answer problem on %s", ttyn); 249 (void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode); 250 exit(1); 251 } 252 } else { /* maybe blocking open */ 253 if (!opentty(ttyn, O_RDWR | (NC ? O_NONBLOCK : 0 ))) 254 exit(1); 255 } 256 } 257 } 258 259 /* Start with default tty settings */ 260 if (tcgetattr(STDIN_FILENO, &tmode) < 0) { 261 syslog(LOG_ERR, "tcgetattr %s: %m", ttyn); 262 exit(1); 263 } 264 /* 265 * Don't rely on the driver too much, and initialize crucial 266 * things according to <sys/ttydefaults.h>. Avoid clobbering 267 * the c_cc[] settings however, the console drivers might wish 268 * to leave their idea of the preferred VERASE key value 269 * there. 270 */ 271 tmode.c_iflag = TTYDEF_IFLAG; 272 tmode.c_oflag = TTYDEF_OFLAG; 273 tmode.c_lflag = TTYDEF_LFLAG; 274 tmode.c_cflag = TTYDEF_CFLAG; 275 tmode.c_cflag |= (NC ? CLOCAL : 0); 276 omode = tmode; 277 278 for (;;) { 279 280 /* 281 * if a delay was specified then sleep for that 282 * number of seconds before writing the initial prompt 283 */ 284 if (first_sleep && DE) { 285 sleep(DE); 286 /* remove any noise */ 287 (void)tcflush(STDIN_FILENO, TCIOFLUSH); 288 } 289 first_sleep = 0; 290 291 setttymode(tname, 0); 292 if (AB) { 293 tname = autobaud(); 294 continue; 295 } 296 if (PS) { 297 tname = portselector(); 298 continue; 299 } 300 if (CL && *CL) 301 putpad(CL); 302 edithost(HE); 303 304 /* if this is the first time through this, and an 305 issue file has been given, then send it */ 306 if (first_time && IF) { 307 int fd; 308 309 if ((fd = open(IF, O_RDONLY)) != -1) { 310 char * cp; 311 312 while ((cp = getline(fd)) != NULL) { 313 putf(cp); 314 } 315 close(fd); 316 } 317 } 318 first_time = 0; 319 320 if (IM && *IM && !(PL && PP)) 321 putf(IM); 322 if (setjmp(timeout)) { 323 cfsetispeed(&tmode, B0); 324 cfsetospeed(&tmode, B0); 325 (void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode); 326 exit(1); 327 } 328 if (TO) { 329 signal(SIGALRM, dingdong); 330 alarm(TO); 331 } 332 if (AL) { 333 const char *p = AL; 334 char *q = name; 335 336 while (*p && q < &name[sizeof name - 1]) { 337 if (isupper(*p)) 338 upper = 1; 339 else if (islower(*p)) 340 lower = 1; 341 else if (isdigit(*p)) 342 digit++; 343 *q++ = *p++; 344 } 345 } else if (!(PL && PP)) 346 rval = getname(); 347 if (rval == 2 || (PL && PP)) { 348 oflush(); 349 alarm(0); 350 limit.rlim_max = RLIM_INFINITY; 351 limit.rlim_cur = RLIM_INFINITY; 352 (void)setrlimit(RLIMIT_CPU, &limit); 353 execle(PP, "ppplogin", ttyn, NULL, env); 354 syslog(LOG_ERR, "%s: %m", PP); 355 exit(1); 356 } else if (rval || AL) { 357 int i; 358 359 oflush(); 360 alarm(0); 361 signal(SIGALRM, SIG_DFL); 362 if (name[0] == '-') { 363 puts("user names may not start with '-'."); 364 continue; 365 } 366 if (!(upper || lower || digit)) 367 continue; 368 set_flags(2); 369 if (crmod) { 370 tmode.c_iflag |= ICRNL; 371 tmode.c_oflag |= ONLCR; 372 } 373 #if REALLY_OLD_TTYS 374 if (upper || UC) 375 tmode.sg_flags |= LCASE; 376 if (lower || LC) 377 tmode.sg_flags &= ~LCASE; 378 #endif 379 if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) { 380 syslog(LOG_ERR, "tcsetattr %s: %m", ttyn); 381 exit(1); 382 } 383 signal(SIGINT, SIG_DFL); 384 for (i = 0; environ[i] != NULL; i++) 385 env[i] = environ[i]; 386 makeenv(&env[i]); 387 388 limit.rlim_max = RLIM_INFINITY; 389 limit.rlim_cur = RLIM_INFINITY; 390 (void)setrlimit(RLIMIT_CPU, &limit); 391 execle(LO, "login", AL ? "-fp" : "-p", name, 392 NULL, env); 393 syslog(LOG_ERR, "%s: %m", LO); 394 exit(1); 395 } 396 alarm(0); 397 signal(SIGALRM, SIG_DFL); 398 signal(SIGINT, SIG_IGN); 399 if (NX && *NX) 400 tname = NX; 401 } 402 } 403 404 static int 405 opentty(const char *ttyn, int flags) 406 { 407 int i, j = 0; 408 int failopenlogged = 0; 409 410 while (j < 10 && (i = open(ttyn, flags)) == -1) 411 { 412 if (((j % 10) == 0) && (errno != ENXIO || !failopenlogged)) { 413 syslog(LOG_ERR, "open %s: %m", ttyn); 414 failopenlogged = 1; 415 } 416 j++; 417 sleep(60); 418 } 419 if (i == -1) { 420 syslog(LOG_ERR, "open %s: %m", ttyn); 421 return 0; 422 } 423 else { 424 if (login_tty(i) < 0) { 425 if (daemon(0,0) < 0) { 426 syslog(LOG_ERR,"daemon: %m"); 427 close(i); 428 return 0; 429 } 430 if (login_tty(i) < 0) { 431 syslog(LOG_ERR, "login_tty %s: %m", ttyn); 432 close(i); 433 return 0; 434 } 435 } 436 return 1; 437 } 438 } 439 440 static void 441 setdefttymode(const char *tname) 442 { 443 if (tcgetattr(STDIN_FILENO, &tmode) < 0) { 444 syslog(LOG_ERR, "tcgetattr %s: %m", ttyn); 445 exit(1); 446 } 447 tmode.c_iflag = TTYDEF_IFLAG; 448 tmode.c_oflag = TTYDEF_OFLAG; 449 tmode.c_lflag = TTYDEF_LFLAG; 450 tmode.c_cflag = TTYDEF_CFLAG; 451 omode = tmode; 452 setttymode(tname, 1); 453 } 454 455 static void 456 setttymode(const char *tname, int raw) 457 { 458 int off = 0; 459 460 gettable(tname, tabent); 461 if (OPset || EPset || APset) 462 APset++, OPset++, EPset++; 463 setdefaults(); 464 (void)tcflush(STDIN_FILENO, TCIOFLUSH); /* clear out the crap */ 465 ioctl(STDIN_FILENO, FIONBIO, &off); /* turn off non-blocking mode */ 466 ioctl(STDIN_FILENO, FIOASYNC, &off); /* ditto for async mode */ 467 468 if (IS) 469 cfsetispeed(&tmode, speed(IS)); 470 else if (SP) 471 cfsetispeed(&tmode, speed(SP)); 472 if (OS) 473 cfsetospeed(&tmode, speed(OS)); 474 else if (SP) 475 cfsetospeed(&tmode, speed(SP)); 476 set_flags(0); 477 setchars(); 478 if (raw) 479 cfmakeraw(&tmode); 480 if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) { 481 syslog(LOG_ERR, "tcsetattr %s: %m", ttyn); 482 exit(1); 483 } 484 } 485 486 487 static int 488 getname(void) 489 { 490 int c; 491 char *np; 492 unsigned char cs; 493 int ppp_state = 0; 494 int ppp_connection = 0; 495 496 /* 497 * Interrupt may happen if we use CBREAK mode 498 */ 499 if (setjmp(intrupt)) { 500 signal(SIGINT, SIG_IGN); 501 return (0); 502 } 503 signal(SIGINT, interrupt); 504 set_flags(1); 505 prompt(); 506 oflush(); 507 if (PF > 0) { 508 sleep(PF); 509 PF = 0; 510 } 511 if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) { 512 syslog(LOG_ERR, "%s: %m", ttyn); 513 exit(1); 514 } 515 crmod = digit = lower = upper = 0; 516 np = name; 517 for (;;) { 518 oflush(); 519 if (read(STDIN_FILENO, &cs, 1) <= 0) 520 exit(0); 521 if ((c = cs&0177) == 0) 522 return (0); 523 524 /* PPP detection state machine.. 525 Look for sequences: 526 PPP_FRAME, PPP_STATION, PPP_ESCAPE, PPP_CONTROL_ESCAPED or 527 PPP_FRAME, PPP_STATION, PPP_CONTROL (deviant from RFC) 528 See RFC1662. 529 Derived from code from Michael Hancock, <michaelh@cet.co.jp> 530 and Erik 'PPP' Olson, <eriko@wrq.com> 531 */ 532 533 if (PP && (cs == PPP_FRAME)) { 534 ppp_state = 1; 535 } else if (ppp_state == 1 && cs == PPP_STATION) { 536 ppp_state = 2; 537 } else if (ppp_state == 2 && cs == PPP_ESCAPE) { 538 ppp_state = 3; 539 } else if ((ppp_state == 2 && cs == PPP_CONTROL) 540 || (ppp_state == 3 && cs == PPP_CONTROL_ESCAPED)) { 541 ppp_state = 4; 542 } else if (ppp_state == 4 && cs == PPP_LCP_HI) { 543 ppp_state = 5; 544 } else if (ppp_state == 5 && cs == PPP_LCP_LOW) { 545 ppp_connection = 1; 546 break; 547 } else { 548 ppp_state = 0; 549 } 550 551 if (c == EOT || c == CTRL('d')) 552 exit(1); 553 if (c == '\r' || c == '\n' || np >= &name[sizeof name-1]) { 554 putf("\r\n"); 555 break; 556 } 557 if (islower(c)) 558 lower = 1; 559 else if (isupper(c)) 560 upper = 1; 561 else if (c == ERASE || c == '\b' || c == 0177) { 562 if (np > name) { 563 np--; 564 if (cfgetospeed(&tmode) >= 1200) 565 puts("\b \b"); 566 else 567 putchr(cs); 568 } 569 continue; 570 } else if (c == KILL || c == CTRL('u')) { 571 putchr('\r'); 572 if (cfgetospeed(&tmode) < 1200) 573 putchr('\n'); 574 /* this is the way they do it down under ... */ 575 else if (np > name) 576 puts(" \r"); 577 prompt(); 578 np = name; 579 continue; 580 } else if (isdigit(c)) 581 digit++; 582 if (IG && (c <= ' ' || c > 0176)) 583 continue; 584 *np++ = c; 585 putchr(cs); 586 } 587 signal(SIGINT, SIG_IGN); 588 *np = 0; 589 if (c == '\r') 590 crmod = 1; 591 if ((upper && !lower && !LC) || UC) 592 for (np = name; *np; np++) 593 if (isupper(*np)) 594 *np = tolower(*np); 595 return (1 + ppp_connection); 596 } 597 598 static void 599 putpad(const char *s) 600 { 601 int pad = 0; 602 speed_t ospeed; 603 604 ospeed = cfgetospeed(&tmode); 605 606 if (isdigit(*s)) { 607 while (isdigit(*s)) { 608 pad *= 10; 609 pad += *s++ - '0'; 610 } 611 pad *= 10; 612 if (*s == '.' && isdigit(s[1])) { 613 pad += s[1] - '0'; 614 s += 2; 615 } 616 } 617 618 puts(s); 619 /* 620 * If no delay needed, or output speed is 621 * not comprehensible, then don't try to delay. 622 */ 623 if (pad == 0 || ospeed <= 0) 624 return; 625 626 /* 627 * Round up by a half a character frame, and then do the delay. 628 * Too bad there are no user program accessible programmed delays. 629 * Transmitting pad characters slows many terminals down and also 630 * loads the system. 631 */ 632 pad = (pad * ospeed + 50000) / 100000; 633 while (pad--) 634 putchr(*PC); 635 } 636 637 static void 638 puts(const char *s) 639 { 640 while (*s) 641 putchr(*s++); 642 } 643 644 char outbuf[OBUFSIZ]; 645 int obufcnt = 0; 646 647 static void 648 putchr(int cc) 649 { 650 char c; 651 652 c = cc; 653 if (!NP) { 654 c |= partab[c&0177] & 0200; 655 if (OP) 656 c ^= 0200; 657 } 658 if (!UB) { 659 outbuf[obufcnt++] = c; 660 if (obufcnt >= OBUFSIZ) 661 oflush(); 662 } else 663 write(STDOUT_FILENO, &c, 1); 664 } 665 666 static void 667 oflush(void) 668 { 669 if (obufcnt) 670 write(STDOUT_FILENO, outbuf, obufcnt); 671 obufcnt = 0; 672 } 673 674 static void 675 prompt(void) 676 { 677 putf(LM); 678 if (CO) 679 putchr('\n'); 680 } 681 682 683 static char * 684 getline(int fd) 685 { 686 int i = 0; 687 static char linebuf[512]; 688 689 /* 690 * This is certainly slow, but it avoids having to include 691 * stdio.h unnecessarily. Issue files should be small anyway. 692 */ 693 while (i < (sizeof linebuf - 3) && read(fd, linebuf+i, 1)==1) { 694 if (linebuf[i] == '\n') { 695 /* Don't rely on newline mode, assume raw */ 696 linebuf[i++] = '\r'; 697 linebuf[i++] = '\n'; 698 linebuf[i] = '\0'; 699 return linebuf; 700 } 701 ++i; 702 } 703 linebuf[i] = '\0'; 704 return i ? linebuf : 0; 705 } 706 707 static void 708 putf(const char *cp) 709 { 710 extern char editedhost[]; 711 time_t t; 712 char *slash, db[100]; 713 714 static struct utsname kerninfo; 715 716 if (!*kerninfo.sysname) 717 uname(&kerninfo); 718 719 while (*cp) { 720 if (*cp != '%') { 721 putchr(*cp++); 722 continue; 723 } 724 switch (*++cp) { 725 726 case 't': 727 slash = strrchr(ttyn, '/'); 728 if (slash == NULL) 729 puts(ttyn); 730 else 731 puts(&slash[1]); 732 break; 733 734 case 'h': 735 puts(editedhost); 736 break; 737 738 case 'd': { 739 t = (time_t)0; 740 (void)time(&t); 741 if (Lo) 742 (void)setlocale(LC_TIME, Lo); 743 (void)strftime(db, sizeof(db), DF, localtime(&t)); 744 puts(db); 745 break; 746 747 case 's': 748 puts(kerninfo.sysname); 749 break; 750 751 case 'm': 752 puts(kerninfo.machine); 753 break; 754 755 case 'r': 756 puts(kerninfo.release); 757 break; 758 759 case 'v': 760 puts(kerninfo.version); 761 break; 762 } 763 764 case '%': 765 putchr('%'); 766 break; 767 } 768 cp++; 769 } 770 } 771