xref: /illumos-gate/usr/src/cmd/vi/port/ex_temp.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 
31 /* Copyright (c) 1981 Regents of the University of California */
32 
33 #pragma ident	"%Z%%M%	%I%	%E% SMI"
34 
35 #include "ex.h"
36 #include "ex_temp.h"
37 #include "ex_vis.h"
38 #include "ex_tty.h"
39 #include <unistd.h>
40 
41 /*
42  * Editor temporary file routines.
43  * Very similar to those of ed, except uses 2 input buffers.
44  */
45 #define	READ	0
46 #define	WRITE	1
47 
48 unsigned char	tfname[PATH_MAX+1];
49 static unsigned char	rfname[PATH_MAX+1];
50 static unsigned char	tempname[PATH_MAX+1];
51 int	havetmp;
52 short	tfile = -1;
53 static short	rfile = -1;
54 
55 extern int junk();
56 
57 void
58 fileinit(void)
59 {
60 	unsigned char *p;
61 	pid_t j;
62 	int i;
63 	struct stat64 stbuf;
64 
65 	if (tline == INCRMT * (HBLKS+2))
66 		return;
67 	cleanup(0);
68 	if (tfile != -1)
69 		close(tfile);
70 	tline = INCRMT * (HBLKS+2);
71 	blocks[0] = HBLKS;
72 	blocks[1] = HBLKS+1;
73 	blocks[2] = -1;
74 	dirtcnt = 0;
75 	iblock = -1;
76 	iblock2 = -1;
77 	oblock = -1;
78 	if (strlen(svalue(vi_DIRECTORY)) > (PATH_MAX -13))
79 		error(gettext("User set directory too long"));
80 	CP(tfname, svalue(vi_DIRECTORY));
81 	if (stat64((char *)tfname, &stbuf)) {
82 dumbness:
83 		if (setexit() == 0)
84 			filioerr(tfname);
85 		else
86 			putNFL();
87 		cleanup(1);
88 		exit(++errcnt);
89 	}
90 	if (!ISDIR(stbuf)) {
91 		errno = ENOTDIR;
92 		goto dumbness;
93 	}
94 	CP(tempname, tfname);
95 	ichanged = 0;
96 	ichang2 = 0;
97 	(void) strcat(tfname, "/ExXXXXXX");
98 	if ((tfile = mkstemp((char *)tfname)) < 0)
99 		goto dumbness;
100 #ifdef VMUNIX
101 	{
102 		extern int stilinc;		/* see below */
103 		stilinc = 0;
104 	}
105 #endif
106 	havetmp = 1;
107 /* 	brk((unsigned char *)fendcore); */
108 }
109 
110 void
111 cleanup(bool all)
112 {
113 	pid_t pgrp;
114 	if (all) {
115 		if (kflag)
116 			crypt_close(perm);
117 		if (xtflag)
118 			crypt_close(tperm);
119 		putpad((unsigned char *)exit_ca_mode);
120 		flush();
121 		if (ioctl(2, TIOCGPGRP, &pgrp) == 0) {
122 			if (pgrp == getpgid(0)) {
123 #ifdef XPG4
124 				if (envlines != -1 || envcolumns != -1) {
125 					struct winsize jwin;
126 					jwin.ws_row = oldlines;
127 					jwin.ws_col = oldcolumns;
128 					ioctl(0, TIOCSWINSZ, &jwin);
129 				}
130 #endif /* XPG4 */
131 				resetterm();
132 				normtty--;
133 			}
134 		} else {
135 #ifdef XPG4
136 			if (envlines != -1 || envcolumns != -1) {
137 				struct winsize jwin;
138 				jwin.ws_row = oldlines;
139 				jwin.ws_col = oldcolumns;
140 				ioctl(0, TIOCSWINSZ, &jwin);
141 			}
142 #endif /* XPG4 */
143 			resetterm();
144 			normtty--;
145 		}
146 	}
147 	if (havetmp)
148 		unlink((char *)tfname);
149 	havetmp = 0;
150 	if (all && rfile >= 0) {
151 		unlink((char *)rfname);
152 		close(rfile);
153 		rfile = -1;
154 	}
155 	if (all == 1)
156 		exit(errcnt);
157 }
158 
159 void
160 getline(line tl)
161 {
162 	unsigned char *bp, *lp;
163 	int nl;
164 
165 	lp = linebuf;
166 	bp = getblock(tl, READ);
167 	nl = nleft;
168 	tl &= ~OFFMSK;
169 	while (*lp++ = *bp++)
170 		if (--nl == 0) {
171 			bp = getblock(tl += INCRMT, READ);
172 			nl = nleft;
173 		}
174 }
175 
176 int
177 putline(void)
178 {
179 	unsigned char *bp, *lp;
180 	unsigned char tmpbp;
181 	int nl;
182 	line tl;
183 
184 	dirtcnt++;
185 	lp = linebuf;
186 	change();
187 	tl = tline;
188 	bp = getblock(tl, WRITE);
189 	nl = nleft;
190 	tl &= ~OFFMSK;
191 	while (*bp = *lp++) {
192 		tmpbp = *bp;
193 		if (tmpbp == '\n') {
194 			*bp = 0;
195 			linebp = lp;
196 			break;
197 		} else if (junk(*bp++)) {
198 			checkjunk(tmpbp);
199 			*--bp;
200 		}
201 		if (--nl == 0) {
202 			bp = getblock(tl += INCRMT, WRITE);
203 			nl = nleft;
204 		}
205 	}
206 	tl = tline;
207 	tline += (((lp - linebuf) + BNDRY - 1) >> SHFT) & 077776;
208 	return (tl);
209 }
210 
211 int	read();
212 int	write();
213 
214 unsigned char *
215 getblock(atl, iof)
216 	line atl;
217 	int iof;
218 {
219 	int bno, off;
220 	unsigned char *p1, *p2;
221 	int n;
222 	line *tmpptr;
223 
224 	bno = (atl >> OFFBTS) & BLKMSK;
225 	off = (atl << SHFT) & LBTMSK;
226 	if (bno >= NMBLKS) {
227 		/*
228 		 * When we overflow tmpfile buffers,
229 		 * throw away line which could not be
230 		 * put into buffer.
231 		 */
232 		for (tmpptr = dot; tmpptr < unddol; tmpptr++)
233 			*tmpptr = *(tmpptr+1);
234 		if (dot == dol)
235 			dot--;
236 		dol--;
237 		unddol--;
238 		error(gettext(" Tmp file too large"));
239 	}
240 	nleft = BUFSIZE - off;
241 	if (bno == iblock) {
242 		ichanged |= iof;
243 		hitin2 = 0;
244 		return (ibuff + off);
245 	}
246 	if (bno == iblock2) {
247 		ichang2 |= iof;
248 		hitin2 = 1;
249 		return (ibuff2 + off);
250 	}
251 	if (bno == oblock)
252 		return (obuff + off);
253 	if (iof == READ) {
254 		if (hitin2 == 0) {
255 			if (ichang2) {
256 				if (xtflag)
257 					if (run_crypt(0L, ibuff2,
258 							CRSIZE, tperm) == -1)
259 						filioerr(tfname);
260 				blkio(iblock2, ibuff2, write);
261 			}
262 			ichang2 = 0;
263 			iblock2 = bno;
264 			blkio(bno, ibuff2, read);
265 			if (xtflag)
266 				if (run_crypt(0L, ibuff2, CRSIZE, tperm) == -1)
267 					filioerr(tfname);
268 			hitin2 = 1;
269 			return (ibuff2 + off);
270 		}
271 		hitin2 = 0;
272 		if (ichanged) {
273 			if (xtflag)
274 				if (run_crypt(0L, ibuff, CRSIZE, tperm) == -1)
275 					filioerr(tfname);
276 			blkio(iblock, ibuff, write);
277 		}
278 		ichanged = 0;
279 		iblock = bno;
280 		blkio(bno, ibuff, read);
281 		if (xtflag)
282 			if (run_crypt(0L, ibuff, CRSIZE, tperm) == -1)
283 				filioerr(tfname);
284 		return (ibuff + off);
285 	}
286 	if (oblock >= 0) {
287 		if (xtflag) {
288 			/*
289 			 * Encrypt block before writing, so some devious
290 			 * person can't look at temp file while editing.
291 			 */
292 			p1 = obuff;
293 			p2 = crbuf;
294 			n = CRSIZE;
295 			while (n--)
296 				*p2++ = *p1++;
297 			if (run_crypt(0L, crbuf, CRSIZE, tperm) == -1)
298 				filioerr(tfname);
299 			blkio(oblock, crbuf, write);
300 		} else
301 			blkio(oblock, obuff, write);
302 	}
303 	oblock = bno;
304 	return (obuff + off);
305 }
306 
307 #ifdef	VMUNIX
308 #define	INCORB	64
309 unsigned char	incorb[INCORB+1][BUFSIZE];
310 #define	pagrnd(a)	((unsigned char *)(((int)a)&~(BUFSIZE-1)))
311 int	stilinc;	/* up to here not written yet */
312 #endif
313 
314 void
315 blkio(short b, unsigned char *buf, int (*iofcn)())
316 {
317 
318 #ifdef VMUNIX
319 	if (b < INCORB) {
320 		if (iofcn == read) {
321 			bcopy(pagrnd(incorb[b+1]), buf, BUFSIZE);
322 			return;
323 		}
324 		bcopy(buf, pagrnd(incorb[b+1]), BUFSIZE);
325 		if (laste) {
326 			if (b >= stilinc)
327 				stilinc = b + 1;
328 			return;
329 		}
330 	} else if (stilinc)
331 		tflush();
332 #endif
333 	lseek(tfile, (long)(unsigned)b * BUFSIZE, 0);
334 	if ((*iofcn)(tfile, buf, BUFSIZE) != BUFSIZE)
335 		filioerr(tfname);
336 }
337 
338 #ifdef VMUNIX
339 void
340 tlaste(void)
341 {
342 
343 	if (stilinc)
344 		dirtcnt = 0;
345 }
346 
347 void
348 tflush(void)
349 {
350 	int i = stilinc;
351 
352 	stilinc = 0;
353 	lseek(tfile, (long)0, 0);
354 	if (write(tfile, pagrnd(incorb[1]), i * BUFSIZE) != (i * BUFSIZE))
355 		filioerr(tfname);
356 }
357 #endif
358 
359 /*
360  * Synchronize the state of the temporary file in case
361  * a crash occurs.
362  */
363 void
364 synctmp(void)
365 {
366 	int cnt;
367 	line *a;
368 	short *bp;
369 	unsigned char *p1, *p2;
370 	int n;
371 
372 #ifdef VMUNIX
373 	if (stilinc)
374 		return;
375 #endif
376 	if (dol == zero)
377 		return;
378 	/*
379 	 * In theory, we need to encrypt iblock and iblock2 before writing
380 	 * them out, as well as oblock, but in practice ichanged and ichang2
381 	 * can never be set, so this isn't really needed.  Likewise, the
382 	 * code in getblock above for iblock+iblock2 isn't needed.
383 	 */
384 	if (ichanged)
385 		blkio(iblock, ibuff, write);
386 	ichanged = 0;
387 	if (ichang2)
388 		blkio(iblock2, ibuff2, write);
389 	ichang2 = 0;
390 	if (oblock != -1)
391 	if (xtflag) {
392 		/*
393 		 * Encrypt block before writing, so some devious
394 		 * person can't look at temp file while editing.
395 		 */
396 		p1 = obuff;
397 		p2 = crbuf;
398 		n = CRSIZE;
399 		while (n--)
400 			*p2++ = *p1++;
401 			if (run_crypt(0L, crbuf, CRSIZE, tperm) == -1)
402 				filioerr(tfname);
403 		blkio(oblock, crbuf, write);
404 	} else
405 		blkio(oblock, obuff, write);
406 	time(&H.Time);
407 	uid = getuid();
408 	if (xtflag)
409 		H.encrypted = 1;
410 	else
411 		H.encrypted = 0;
412 	*zero = (line) H.Time;
413 	for (a = zero, bp = blocks; a <= dol;
414 	    a += BUFSIZE / sizeof (*a), bp++) {
415 		if (bp >= &H.Blocks[LBLKS-1])
416 			error(gettext(
417 			    "file too large to recover with -r option"));
418 		if (*bp < 0) {
419 			tline = (tline + OFFMSK) &~ OFFMSK;
420 			*bp = ((tline >> OFFBTS) & BLKMSK);
421 			if (*bp > NMBLKS)
422 				error(gettext(" Tmp file too large"));
423 			tline += INCRMT;
424 			oblock = *bp + 1;
425 			bp[1] = -1;
426 		}
427 		lseek(tfile, (long)(unsigned)*bp * BUFSIZE, 0);
428 		cnt = ((dol - a) + 2) * sizeof (line);
429 		if (cnt > BUFSIZE)
430 			cnt = BUFSIZE;
431 		if (write(tfile, (char *)a, cnt) != cnt) {
432 oops:
433 			*zero = 0;
434 			filioerr(tfname);
435 		}
436 		*zero = 0;
437 	}
438 	flines = lineDOL();
439 	lseek(tfile, 0l, 0);
440 	if (write(tfile, (char *)&H, sizeof (H)) != sizeof (H))
441 		goto oops;
442 }
443 
444 void
445 TSYNC(void)
446 {
447 
448 	if (dirtcnt > MAXDIRT) {
449 #ifdef VMUNIX
450 		if (stilinc)
451 			tflush();
452 #endif
453 		dirtcnt = 0;
454 		synctmp();
455 	}
456 }
457 
458 /*
459  * Named buffer routines.
460  * These are implemented differently than the main buffer.
461  * Each named buffer has a chain of blocks in the register file.
462  * Each block contains roughly 508 chars of text,
463  * and a previous and next block number.  We also have information
464  * about which blocks came from deletes of multiple partial lines,
465  * e.g. deleting a sentence or a LISP object.
466  *
467  * We maintain a free map for the temp file.  To free the blocks
468  * in a register we must read the blocks to find how they are chained
469  * together.
470  *
471  * BUG:		The default savind of deleted lines in numbered
472  *		buffers may be rather inefficient; it hasn't been profiled.
473  */
474 struct	strreg {
475 	short	rg_flags;
476 	short	rg_nleft;
477 	short	rg_first;
478 	short	rg_last;
479 } strregs[('z'-'a'+1) + ('9'-'0'+1)], *strp;
480 
481 struct	rbuf {
482 	short	rb_prev;
483 	short	rb_next;
484 	unsigned char	rb_text[BUFSIZE - 2 * sizeof (short)];
485 } *rbuf, KILLrbuf, putrbuf, YANKrbuf, regrbuf;
486 #ifdef VMUNIX
487 short	rused[256];
488 #else
489 short	rused[32];
490 #endif
491 short	rnleft;
492 short	rblock;
493 short	rnext;
494 unsigned char	*rbufcp;
495 
496 void
497 regio(short b, int (*iofcn)())
498 {
499 
500 	if (rfile == -1) {
501 		CP(rfname, tempname);
502 		(void) strcat(rfname, "/RxXXXXXX");
503 		if ((rfile = mkstemp((char *)rfname)) < 0)
504 			filioerr(rfname);
505 	}
506 	lseek(rfile, (long)b * BUFSIZE, 0);
507 	if ((*iofcn)(rfile, rbuf, BUFSIZE) != BUFSIZE)
508 		filioerr(rfname);
509 	rblock = b;
510 }
511 
512 int
513 REGblk(void)
514 {
515 	int i, j, m;
516 
517 	for (i = 0; i < sizeof (rused) / sizeof (rused[0]); i++) {
518 		m = (rused[i] ^ 0177777) & 0177777;
519 		if (i == 0)
520 			m &= ~1;
521 		if (m != 0) {
522 			j = 0;
523 			while ((m & 1) == 0)
524 				j++, m >>= 1;
525 			rused[i] |= (1 << j);
526 #ifdef RDEBUG
527 			viprintf("allocating block %d\n", i * 16 + j);
528 #endif
529 			return (i * 16 + j);
530 		}
531 	}
532 	error(gettext("Out of register space (ugh)"));
533 	/*NOTREACHED*/
534 	return (0);
535 }
536 
537 struct	strreg *
538 mapreg(c)
539 	int c;
540 {
541 
542 	if (isupper(c))
543 		c = tolower(c);
544 	return (isdigit(c) ? &strregs[('z'-'a'+1)+(c-'0')] : &strregs[c-'a']);
545 }
546 
547 int	shread();
548 
549 void
550 KILLreg(int c)
551 {
552 	struct strreg *sp;
553 
554 	rbuf = &KILLrbuf;
555 	sp = mapreg(c);
556 	rblock = sp->rg_first;
557 	sp->rg_first = sp->rg_last = 0;
558 	sp->rg_flags = sp->rg_nleft = 0;
559 	while (rblock != 0) {
560 #ifdef RDEBUG
561 		viprintf("freeing block %d\n", rblock);
562 #endif
563 		rused[rblock / 16] &= ~(1 << (rblock % 16));
564 		regio(rblock, shread);
565 		rblock = rbuf->rb_next;
566 	}
567 }
568 
569 /*VARARGS*/
570 int
571 shread(void)
572 {
573 	struct front { short a; short b; };
574 
575 	if (read(rfile, (char *)rbuf, sizeof (struct front)) ==
576 	    sizeof (struct front))
577 		return (sizeof (struct rbuf));
578 	return (0);
579 }
580 
581 int	getREG();
582 
583 int
584 putreg(unsigned char c)
585 {
586 	line *odot = dot;
587 	line *odol = dol;
588 	int cnt;
589 
590 	deletenone();
591 	appendnone();
592 	rbuf = &putrbuf;
593 	rnleft = 0;
594 	rblock = 0;
595 	rnext = mapreg(c)->rg_first;
596 	if (rnext == 0) {
597 		if (inopen) {
598 			splitw++;
599 			vclean();
600 			vgoto(WECHO, 0);
601 		}
602 		vreg = -1;
603 		error(gettext("Nothing in register %c"), c);
604 	}
605 	if (inopen && partreg(c)) {
606 		if (!FIXUNDO) {
607 			splitw++; vclean(); vgoto(WECHO, 0); vreg = -1;
608 			error(gettext("Can't put partial line inside macro"));
609 		}
610 		squish();
611 		addr1 = addr2 = dol;
612 	}
613 	cnt = append(getREG, addr2);
614 	if (inopen && partreg(c)) {
615 		unddol = dol;
616 		dol = odol;
617 		dot = odot;
618 		pragged(0);
619 	}
620 	killcnt(cnt);
621 	notecnt = cnt;
622 	return (0);
623 }
624 
625 short
626 partreg(unsigned char c)
627 {
628 
629 	return (mapreg(c)->rg_flags);
630 }
631 
632 void
633 notpart(int c)
634 {
635 
636 	if (c)
637 		mapreg(c)->rg_flags = 0;
638 }
639 
640 int
641 getREG(void)
642 {
643 	unsigned char *lp = linebuf;
644 	int c;
645 
646 	for (;;) {
647 		if (rnleft == 0) {
648 			if (rnext == 0)
649 				return (EOF);
650 			regio(rnext, read);
651 			rnext = rbuf->rb_next;
652 			rbufcp = rbuf->rb_text;
653 			rnleft = sizeof (rbuf->rb_text);
654 		}
655 		c = *rbufcp;
656 		if (c == 0)
657 			return (EOF);
658 		rbufcp++, --rnleft;
659 		if (c == '\n') {
660 			*lp++ = 0;
661 			return (0);
662 		}
663 		*lp++ = c;
664 	}
665 }
666 
667 int
668 YANKreg(int c)
669 {
670 	line *addr;
671 	struct strreg *sp;
672 	unsigned char savelb[LBSIZE];
673 
674 	if (isdigit(c))
675 		kshift();
676 	if (islower(c))
677 		KILLreg(c);
678 	strp = sp = mapreg(c);
679 	sp->rg_flags = inopen && cursor && wcursor;
680 	rbuf = &YANKrbuf;
681 	if (sp->rg_last) {
682 		regio(sp->rg_last, read);
683 		rnleft = sp->rg_nleft;
684 		rbufcp = &rbuf->rb_text[sizeof (rbuf->rb_text) - rnleft];
685 	} else {
686 		rblock = 0;
687 		rnleft = 0;
688 	}
689 	CP(savelb, linebuf);
690 	for (addr = addr1; addr <= addr2; addr++) {
691 		getline(*addr);
692 		if (sp->rg_flags) {
693 			if (addr == addr2)
694 				*wcursor = 0;
695 			if (addr == addr1)
696 				strcpy(linebuf, cursor);
697 		}
698 		YANKline();
699 	}
700 	rbflush();
701 	killed();
702 	CP(linebuf, savelb);
703 	return (0);
704 }
705 
706 void
707 kshift(void)
708 {
709 	int i;
710 
711 	KILLreg('9');
712 	for (i = '8'; i >= '0'; i--)
713 		copy(mapreg(i+1), mapreg(i), sizeof (struct strreg));
714 }
715 
716 void
717 YANKline(void)
718 {
719 	unsigned char *lp = linebuf;
720 	struct rbuf *rp = rbuf;
721 	int c;
722 
723 	do {
724 		c = *lp++;
725 		if (c == 0)
726 			c = '\n';
727 		if (rnleft == 0) {
728 			rp->rb_next = REGblk();
729 			rbflush();
730 			rblock = rp->rb_next;
731 			rp->rb_next = 0;
732 			rp->rb_prev = rblock;
733 			rnleft = sizeof (rp->rb_text);
734 			rbufcp = rp->rb_text;
735 		}
736 		*rbufcp++ = c;
737 		--rnleft;
738 	} while (c != '\n');
739 	if (rnleft)
740 		*rbufcp = 0;
741 }
742 
743 void
744 rbflush(void)
745 {
746 	struct strreg *sp = strp;
747 
748 	if (rblock == 0)
749 		return;
750 	regio(rblock, write);
751 	if (sp->rg_first == 0)
752 		sp->rg_first = rblock;
753 	sp->rg_last = rblock;
754 	sp->rg_nleft = rnleft;
755 }
756 
757 /* Register c to char buffer buf of size buflen */
758 void
759 regbuf(c, buf, buflen)
760 unsigned char c;
761 unsigned char *buf;
762 int buflen;
763 {
764 	unsigned char *p, *lp;
765 
766 	rbuf = &regrbuf;
767 	rnleft = 0;
768 	rblock = 0;
769 	rnext = mapreg(c)->rg_first;
770 	if (rnext == 0) {
771 		*buf = 0;
772 		error(gettext("Nothing in register %c"), c);
773 	}
774 	p = buf;
775 	while (getREG() == 0) {
776 		lp = linebuf;
777 		while (*lp) {
778 			if (p >= &buf[buflen])
779 				error(value(vi_TERSE) ?
780 gettext("Register too long") : gettext("Register too long to fit in memory"));
781 			*p++ = *lp++;
782 		}
783 		*p++ = '\n';
784 	}
785 	if (partreg(c)) p--;
786 	*p = '\0';
787 	getDOT();
788 }
789 
790 #ifdef TRACE
791 
792 /*
793  * Test code for displaying named registers.
794  */
795 
796 shownam()
797 {
798 	int k;
799 
800 	viprintf("\nRegister   Contents\n");
801 	viprintf("========   ========\n");
802 	for (k = 'a'; k <= 'z'; k++) {
803 		rbuf = &putrbuf;
804 		rnleft = 0;
805 		rblock = 0;
806 		rnext = mapreg(k)->rg_first;
807 		viprintf(" %c:", k);
808 		if (rnext == 0)
809 			viprintf("\t\tNothing in register.\n");
810 		while (getREG() == 0) {
811 			viprintf("\t\t%s\n", linebuf);
812 		}
813 	}
814 	return (0);
815 }
816 
817 /*
818  * Test code for displaying numbered registers.
819  */
820 
821 shownbr()
822 {
823 	int k;
824 
825 	viprintf("\nRegister   Contents\n");
826 	viprintf("========   ========\n");
827 	for (k = '1'; k <= '9'; k++) {
828 		rbuf = &putrbuf;
829 		rnleft = 0;
830 		rblock = 0;
831 		rnext = mapreg(k)->rg_first;
832 		viprintf(" %c:", k);
833 		if (rnext == 0)
834 			viprintf("\t\tNothing in register.\n");
835 		while (getREG() == 0) {
836 			viprintf("\t\t%s\n", linebuf);
837 		}
838 	}
839 	return (0);
840 }
841 #endif
842