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