xref: /illumos-gate/usr/src/cmd/vi/port/ex_temp.c (revision 31358b0d3ea5b07f4167f88ac393a5eb2a2249d6)
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(line atl, int iof)
213 {
214 	int bno, off;
215 	unsigned char *p1, *p2;
216 	int n;
217 	line *tmpptr;
218 
219 	bno = (atl >> OFFBTS) & BLKMSK;
220 	off = (atl << SHFT) & LBTMSK;
221 	if (bno >= NMBLKS) {
222 		/*
223 		 * When we overflow tmpfile buffers,
224 		 * throw away line which could not be
225 		 * put into buffer.
226 		 */
227 		for (tmpptr = dot; tmpptr < unddol; tmpptr++)
228 			*tmpptr = *(tmpptr+1);
229 		if (dot == dol)
230 			dot--;
231 		dol--;
232 		unddol--;
233 		error(gettext(" Tmp file too large"));
234 	}
235 	nleft = BUFSIZE - off;
236 	if (bno == iblock) {
237 		ichanged |= iof;
238 		hitin2 = 0;
239 		return (ibuff + off);
240 	}
241 	if (bno == iblock2) {
242 		ichang2 |= iof;
243 		hitin2 = 1;
244 		return (ibuff2 + off);
245 	}
246 	if (bno == oblock)
247 		return (obuff + off);
248 	if (iof == READ) {
249 		if (hitin2 == 0) {
250 			if (ichang2) {
251 				if (xtflag) {
252 					if (run_crypt(0L, ibuff2,
253 					    CRSIZE, tperm) == -1) {
254 						filioerr(tfname);
255 					}
256 				}
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(int c)
536 {
537 
538 	if (isupper(c))
539 		c = tolower(c);
540 	return (isdigit(c) ? &strregs[('z'-'a'+1)+(c-'0')] : &strregs[c-'a']);
541 }
542 
543 int	shread();
544 
545 void
546 KILLreg(int c)
547 {
548 	struct strreg *sp;
549 
550 	rbuf = &KILLrbuf;
551 	sp = mapreg(c);
552 	rblock = sp->rg_first;
553 	sp->rg_first = sp->rg_last = 0;
554 	sp->rg_flags = sp->rg_nleft = 0;
555 	while (rblock != 0) {
556 #ifdef RDEBUG
557 		viprintf("freeing block %d\n", rblock);
558 #endif
559 		rused[rblock / 16] &= ~(1 << (rblock % 16));
560 		regio(rblock, shread);
561 		rblock = rbuf->rb_next;
562 	}
563 }
564 
565 /*VARARGS*/
566 int
567 shread(void)
568 {
569 	struct front { short a; short b; };
570 
571 	if (read(rfile, (char *)rbuf, sizeof (struct front)) ==
572 	    sizeof (struct front))
573 		return (sizeof (struct rbuf));
574 	return (0);
575 }
576 
577 int	getREG();
578 
579 int
580 putreg(unsigned char c)
581 {
582 	line *odot = dot;
583 	line *odol = dol;
584 	int cnt;
585 
586 	deletenone();
587 	appendnone();
588 	rbuf = &putrbuf;
589 	rnleft = 0;
590 	rblock = 0;
591 	rnext = mapreg(c)->rg_first;
592 	if (rnext == 0) {
593 		if (inopen) {
594 			splitw++;
595 			vclean();
596 			vgoto(WECHO, 0);
597 		}
598 		vreg = -1;
599 		error(gettext("Nothing in register %c"), c);
600 	}
601 	if (inopen && partreg(c)) {
602 		if (!FIXUNDO) {
603 			splitw++; vclean(); vgoto(WECHO, 0); vreg = -1;
604 			error(gettext("Can't put partial line inside macro"));
605 		}
606 		squish();
607 		addr1 = addr2 = dol;
608 	}
609 	cnt = append(getREG, addr2);
610 	if (inopen && partreg(c)) {
611 		unddol = dol;
612 		dol = odol;
613 		dot = odot;
614 		pragged(0);
615 	}
616 	killcnt(cnt);
617 	notecnt = cnt;
618 	return (0);
619 }
620 
621 short
622 partreg(unsigned char c)
623 {
624 
625 	return (mapreg(c)->rg_flags);
626 }
627 
628 void
629 notpart(int c)
630 {
631 
632 	if (c)
633 		mapreg(c)->rg_flags = 0;
634 }
635 
636 int
637 getREG(void)
638 {
639 	unsigned char *lp = linebuf;
640 	int c;
641 
642 	for (;;) {
643 		if (rnleft == 0) {
644 			if (rnext == 0)
645 				return (EOF);
646 			regio(rnext, read);
647 			rnext = rbuf->rb_next;
648 			rbufcp = rbuf->rb_text;
649 			rnleft = sizeof (rbuf->rb_text);
650 		}
651 		c = *rbufcp;
652 		if (c == 0)
653 			return (EOF);
654 		rbufcp++, --rnleft;
655 		if (c == '\n') {
656 			*lp++ = 0;
657 			return (0);
658 		}
659 		*lp++ = c;
660 	}
661 }
662 
663 int
664 YANKreg(int c)
665 {
666 	line *addr;
667 	struct strreg *sp;
668 	unsigned char savelb[LBSIZE];
669 
670 	if (isdigit(c))
671 		kshift();
672 	if (islower(c))
673 		KILLreg(c);
674 	strp = sp = mapreg(c);
675 	sp->rg_flags = inopen && cursor && wcursor;
676 	rbuf = &YANKrbuf;
677 	if (sp->rg_last) {
678 		regio(sp->rg_last, read);
679 		rnleft = sp->rg_nleft;
680 		rbufcp = &rbuf->rb_text[sizeof (rbuf->rb_text) - rnleft];
681 	} else {
682 		rblock = 0;
683 		rnleft = 0;
684 	}
685 	CP(savelb, linebuf);
686 	for (addr = addr1; addr <= addr2; addr++) {
687 		getaline(*addr);
688 		if (sp->rg_flags) {
689 			if (addr == addr2)
690 				*wcursor = 0;
691 			if (addr == addr1)
692 				strcpy(linebuf, cursor);
693 		}
694 		YANKline();
695 	}
696 	rbflush();
697 	killed();
698 	CP(linebuf, savelb);
699 	return (0);
700 }
701 
702 void
703 kshift(void)
704 {
705 	int i;
706 
707 	KILLreg('9');
708 	for (i = '8'; i >= '0'; i--)
709 		copy(mapreg(i+1), mapreg(i), sizeof (struct strreg));
710 }
711 
712 void
713 YANKline(void)
714 {
715 	unsigned char *lp = linebuf;
716 	struct rbuf *rp = rbuf;
717 	int c;
718 
719 	do {
720 		c = *lp++;
721 		if (c == 0)
722 			c = '\n';
723 		if (rnleft == 0) {
724 			rp->rb_next = REGblk();
725 			rbflush();
726 			rblock = rp->rb_next;
727 			rp->rb_next = 0;
728 			rp->rb_prev = rblock;
729 			rnleft = sizeof (rp->rb_text);
730 			rbufcp = rp->rb_text;
731 		}
732 		*rbufcp++ = c;
733 		--rnleft;
734 	} while (c != '\n');
735 	if (rnleft)
736 		*rbufcp = 0;
737 }
738 
739 void
740 rbflush(void)
741 {
742 	struct strreg *sp = strp;
743 
744 	if (rblock == 0)
745 		return;
746 	regio(rblock, write);
747 	if (sp->rg_first == 0)
748 		sp->rg_first = rblock;
749 	sp->rg_last = rblock;
750 	sp->rg_nleft = rnleft;
751 }
752 
753 /* Register c to char buffer buf of size buflen */
754 void
755 regbuf(unsigned char c, unsigned char *buf, int buflen)
756 {
757 	unsigned char *p, *lp;
758 
759 	rbuf = &regrbuf;
760 	rnleft = 0;
761 	rblock = 0;
762 	rnext = mapreg(c)->rg_first;
763 	if (rnext == 0) {
764 		*buf = 0;
765 		error(gettext("Nothing in register %c"), c);
766 	}
767 	p = buf;
768 	while (getREG() == 0) {
769 		lp = linebuf;
770 		while (*lp) {
771 			if (p >= &buf[buflen])
772 				error(value(vi_TERSE) ?
773 gettext("Register too long") : gettext("Register too long to fit in memory"));
774 			*p++ = *lp++;
775 		}
776 		*p++ = '\n';
777 	}
778 	if (partreg(c)) p--;
779 	*p = '\0';
780 	getDOT();
781 }
782 
783 #ifdef TRACE
784 
785 /*
786  * Test code for displaying named registers.
787  */
788 
789 shownam()
790 {
791 	int k;
792 
793 	viprintf("\nRegister   Contents\n");
794 	viprintf("========   ========\n");
795 	for (k = 'a'; k <= 'z'; k++) {
796 		rbuf = &putrbuf;
797 		rnleft = 0;
798 		rblock = 0;
799 		rnext = mapreg(k)->rg_first;
800 		viprintf(" %c:", k);
801 		if (rnext == 0)
802 			viprintf("\t\tNothing in register.\n");
803 		while (getREG() == 0) {
804 			viprintf("\t\t%s\n", linebuf);
805 		}
806 	}
807 	return (0);
808 }
809 
810 /*
811  * Test code for displaying numbered registers.
812  */
813 
814 shownbr()
815 {
816 	int k;
817 
818 	viprintf("\nRegister   Contents\n");
819 	viprintf("========   ========\n");
820 	for (k = '1'; k <= '9'; k++) {
821 		rbuf = &putrbuf;
822 		rnleft = 0;
823 		rblock = 0;
824 		rnext = mapreg(k)->rg_first;
825 		viprintf(" %c:", k);
826 		if (rnext == 0)
827 			viprintf("\t\tNothing in register.\n");
828 		while (getREG() == 0) {
829 			viprintf("\t\t%s\n", linebuf);
830 		}
831 	}
832 	return (0);
833 }
834 #endif
835