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