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