Annotation of src/usr.bin/less/ch.c, Revision 1.7
1.1 etheisen 1: /*
1.4 millert 2: * Copyright (C) 1984-2002 Mark Nudelman
1.1 etheisen 3: *
1.4 millert 4: * You may distribute under the terms of either the GNU General Public
5: * License or the Less License, as specified in the README file.
1.1 etheisen 6: *
1.4 millert 7: * For more information about less, or for information on how to
8: * contact the author, see the README file.
1.1 etheisen 9: */
10:
11:
12: /*
13: * Low level character input from the input file.
14: * We use these special purpose routines which optimize moving
15: * both forward and backward from the current read pointer.
16: */
17:
18: #include "less.h"
1.4 millert 19: #if MSDOS_COMPILER==WIN32C
20: #include <errno.h>
21: #include <windows.h>
22: #endif
23:
24: typedef POSITION BLOCKNUM;
1.1 etheisen 25:
26: public int ignore_eoi;
27:
28: /*
29: * Pool of buffers holding the most recently used blocks of the input file.
30: * The buffer pool is kept as a doubly-linked circular list,
31: * in order from most- to least-recently used.
32: * The circular list is anchored by the file state "thisfile".
33: */
1.4 millert 34: #define LBUFSIZE 8192
1.1 etheisen 35: struct buf {
1.4 millert 36: struct buf *next, *prev;
37: struct buf *hnext, *hprev;
38: BLOCKNUM block;
1.1 etheisen 39: unsigned int datasize;
40: unsigned char data[LBUFSIZE];
41: };
42:
1.4 millert 43: struct buflist {
44: /* -- Following members must match struct buf */
45: struct buf *buf_next, *buf_prev;
46: struct buf *buf_hnext, *buf_hprev;
47: };
48:
1.1 etheisen 49: /*
50: * The file state is maintained in a filestate structure.
51: * A pointer to the filestate is kept in the ifile structure.
52: */
1.4 millert 53: #define BUFHASH_SIZE 64
1.1 etheisen 54: struct filestate {
55: struct buf *buf_next, *buf_prev;
1.4 millert 56: struct buflist hashtbl[BUFHASH_SIZE];
1.1 etheisen 57: int file;
58: int flags;
59: POSITION fpos;
60: int nbufs;
1.4 millert 61: BLOCKNUM block;
62: unsigned int offset;
1.1 etheisen 63: POSITION fsize;
64: };
65:
66: #define ch_bufhead thisfile->buf_next
67: #define ch_buftail thisfile->buf_prev
68: #define ch_nbufs thisfile->nbufs
69: #define ch_block thisfile->block
70: #define ch_offset thisfile->offset
71: #define ch_fpos thisfile->fpos
72: #define ch_fsize thisfile->fsize
73: #define ch_flags thisfile->flags
74: #define ch_file thisfile->file
75:
1.4 millert 76: #define END_OF_CHAIN ((struct buf *)&thisfile->buf_next)
77: #define END_OF_HCHAIN(h) ((struct buf *)&thisfile->hashtbl[h])
78: #define BUFHASH(blk) ((blk) & (BUFHASH_SIZE-1))
79:
80: #define FOR_BUFS_IN_CHAIN(h,bp) \
81: for (bp = thisfile->hashtbl[h].buf_hnext; \
82: bp != END_OF_HCHAIN(h); bp = bp->hnext)
83:
84: #define HASH_RM(bp) \
85: (bp)->hnext->hprev = (bp)->hprev; \
86: (bp)->hprev->hnext = (bp)->hnext;
87:
88: #define HASH_INS(bp,h) \
89: (bp)->hnext = thisfile->hashtbl[h].buf_hnext; \
90: (bp)->hprev = END_OF_HCHAIN(h); \
91: thisfile->hashtbl[h].buf_hnext->hprev = (bp); \
92: thisfile->hashtbl[h].buf_hnext = (bp);
93:
1.1 etheisen 94: static struct filestate *thisfile;
95: static int ch_ungotchar = -1;
1.4 millert 96: static int maxbufs = -1;
1.1 etheisen 97:
98: extern int autobuf;
99: extern int sigs;
1.4 millert 100: extern int secure;
1.1 etheisen 101: extern IFILE curr_ifile;
102: #if LOGFILE
103: extern int logfile;
104: extern char *namelogfile;
105: #endif
106:
107: static int ch_addbuf();
108:
109:
110: /*
111: * Get the character pointed to by the read pointer.
112: * ch_get() is a macro which is more efficient to call
113: * than fch_get (the function), in the usual case
114: * that the block desired is at the head of the chain.
115: */
116: #define ch_get() ((ch_block == ch_bufhead->block && \
117: ch_offset < ch_bufhead->datasize) ? \
118: ch_bufhead->data[ch_offset] : fch_get())
119: int
120: fch_get()
121: {
1.4 millert 122: register struct buf *bp;
123: register int n;
124: register int slept;
125: register int h;
1.1 etheisen 126: POSITION pos;
127: POSITION len;
128:
129: slept = FALSE;
130:
131: /*
132: * Look for a buffer holding the desired block.
133: */
1.4 millert 134: h = BUFHASH(ch_block);
135: FOR_BUFS_IN_CHAIN(h, bp)
136: {
1.1 etheisen 137: if (bp->block == ch_block)
138: {
139: if (ch_offset >= bp->datasize)
140: /*
141: * Need more data in this buffer.
142: */
143: goto read_more;
144: goto found;
145: }
1.4 millert 146: }
1.1 etheisen 147: /*
148: * Block is not in a buffer.
149: * Take the least recently used buffer
150: * and read the desired block into it.
151: * If the LRU buffer has data in it,
152: * then maybe allocate a new buffer.
153: */
1.4 millert 154: if (ch_buftail == END_OF_CHAIN || ch_buftail->block != -1)
1.1 etheisen 155: {
156: /*
157: * There is no empty buffer to use.
158: * Allocate a new buffer if:
159: * 1. We can't seek on this file and -b is not in effect; or
160: * 2. We haven't allocated the max buffers for this file yet.
161: */
162: if ((autobuf && !(ch_flags & CH_CANSEEK)) ||
1.4 millert 163: (maxbufs < 0 || ch_nbufs < maxbufs))
1.1 etheisen 164: if (ch_addbuf())
165: /*
166: * Allocation failed: turn off autobuf.
167: */
168: autobuf = OPT_OFF;
169: }
170: bp = ch_buftail;
1.4 millert 171: HASH_RM(bp); /* Remove from old hash chain. */
1.1 etheisen 172: bp->block = ch_block;
173: bp->datasize = 0;
1.4 millert 174: HASH_INS(bp, h); /* Insert into new hash chain. */
1.1 etheisen 175:
176: read_more:
177: pos = (ch_block * LBUFSIZE) + bp->datasize;
178: if ((len = ch_length()) != NULL_POSITION && pos >= len)
179: /*
180: * At end of file.
181: */
182: return (EOI);
183:
184: if (pos != ch_fpos)
185: {
186: /*
187: * Not at the correct position: must seek.
188: * If input is a pipe, we're in trouble (can't seek on a pipe).
189: * Some data has been lost: just return "?".
190: */
191: if (!(ch_flags & CH_CANSEEK))
192: return ('?');
1.7 ! deraadt 193: if (lseek(ch_file, (off_t)pos, SEEK_SET) == BAD_LSEEK)
1.1 etheisen 194: {
195: error("seek error", NULL_PARG);
196: clear_eol();
197: return (EOI);
198: }
199: ch_fpos = pos;
200: }
201:
202: /*
203: * Read the block.
204: * If we read less than a full block, that's ok.
205: * We use partial block and pick up the rest next time.
206: */
1.4 millert 207: if (ch_ungotchar != -1)
1.1 etheisen 208: {
209: bp->data[bp->datasize] = ch_ungotchar;
210: n = 1;
211: ch_ungotchar = -1;
1.4 millert 212: } else
213: {
214: n = iread(ch_file, &bp->data[bp->datasize],
215: (unsigned int)(LBUFSIZE - bp->datasize));
1.1 etheisen 216: }
217:
218: if (n == READ_INTR)
219: return (EOI);
220: if (n < 0)
221: {
1.4 millert 222: #if MSDOS_COMPILER==WIN32C
223: if (errno != EPIPE)
224: #endif
225: {
226: error("read error", NULL_PARG);
227: clear_eol();
228: }
1.1 etheisen 229: n = 0;
230: }
231:
232: #if LOGFILE
233: /*
234: * If we have a log file, write the new data to it.
235: */
1.4 millert 236: if (!secure && logfile >= 0 && n > 0)
1.1 etheisen 237: write(logfile, (char *) &bp->data[bp->datasize], n);
238: #endif
239:
240: ch_fpos += n;
241: bp->datasize += n;
242:
243: /*
244: * If we have read to end of file, set ch_fsize to indicate
245: * the position of the end of file.
246: */
247: if (n == 0)
248: {
249: ch_fsize = pos;
250: if (ignore_eoi)
251: {
252: /*
253: * We are ignoring EOF.
254: * Wait a while, then try again.
255: */
256: if (!slept)
1.4 millert 257: {
258: PARG parg;
259: parg.p_string = wait_message();
260: ierror("%s", &parg);
261: }
262: #if !MSDOS_COMPILER
1.1 etheisen 263: sleep(1);
1.4 millert 264: #else
265: #if MSDOS_COMPILER==WIN32C
266: Sleep(1000);
267: #endif
1.1 etheisen 268: #endif
269: slept = TRUE;
270: }
1.4 millert 271: if (sigs)
1.1 etheisen 272: return (EOI);
273: }
274:
275: found:
276: if (ch_bufhead != bp)
277: {
278: /*
279: * Move the buffer to the head of the buffer chain.
280: * This orders the buffer chain, most- to least-recently used.
281: */
282: bp->next->prev = bp->prev;
283: bp->prev->next = bp->next;
284: bp->next = ch_bufhead;
285: bp->prev = END_OF_CHAIN;
286: ch_bufhead->prev = bp;
287: ch_bufhead = bp;
1.4 millert 288:
289: /*
290: * Move to head of hash chain too.
291: */
292: HASH_RM(bp);
293: HASH_INS(bp, h);
1.1 etheisen 294: }
295:
296: if (ch_offset >= bp->datasize)
297: /*
298: * After all that, we still don't have enough data.
299: * Go back and try again.
300: */
301: goto read_more;
302:
303: return (bp->data[ch_offset]);
304: }
305:
306: /*
307: * ch_ungetchar is a rather kludgy and limited way to push
308: * a single char onto an input file descriptor.
309: */
310: public void
311: ch_ungetchar(c)
312: int c;
313: {
314: if (c != -1 && ch_ungotchar != -1)
315: error("ch_ungetchar overrun", NULL_PARG);
316: ch_ungotchar = c;
317: }
318:
319: #if LOGFILE
320: /*
321: * Close the logfile.
322: * If we haven't read all of standard input into it, do that now.
323: */
324: public void
325: end_logfile()
326: {
327: static int tried = FALSE;
328:
329: if (logfile < 0)
330: return;
331: if (!tried && ch_fsize == NULL_POSITION)
332: {
333: tried = TRUE;
334: ierror("Finishing logfile", NULL_PARG);
335: while (ch_forw_get() != EOI)
336: if (ABORT_SIGS())
337: break;
338: }
339: close(logfile);
340: logfile = -1;
341: namelogfile = NULL;
342: }
343:
344: /*
345: * Start a log file AFTER less has already been running.
346: * Invoked from the - command; see toggle_option().
347: * Write all the existing buffered data to the log file.
348: */
349: public void
350: sync_logfile()
351: {
1.4 millert 352: register struct buf *bp;
1.1 etheisen 353: int warned = FALSE;
1.4 millert 354: BLOCKNUM block;
355: BLOCKNUM nblocks;
1.1 etheisen 356:
357: nblocks = (ch_fpos + LBUFSIZE - 1) / LBUFSIZE;
358: for (block = 0; block < nblocks; block++)
359: {
360: for (bp = ch_bufhead; ; bp = bp->next)
361: {
362: if (bp == END_OF_CHAIN)
363: {
364: if (!warned)
365: {
366: error("Warning: log file is incomplete",
367: NULL_PARG);
368: warned = TRUE;
369: }
370: break;
371: }
372: if (bp->block == block)
373: {
374: write(logfile, (char *) bp->data, bp->datasize);
375: break;
376: }
377: }
378: }
379: }
380:
381: #endif
382:
383: /*
384: * Determine if a specific block is currently in one of the buffers.
385: */
386: static int
387: buffered(block)
1.4 millert 388: BLOCKNUM block;
1.1 etheisen 389: {
1.4 millert 390: register struct buf *bp;
391: register int h;
1.1 etheisen 392:
1.4 millert 393: h = BUFHASH(block);
394: FOR_BUFS_IN_CHAIN(h, bp)
395: {
1.1 etheisen 396: if (bp->block == block)
397: return (TRUE);
1.4 millert 398: }
1.1 etheisen 399: return (FALSE);
400: }
401:
402: /*
403: * Seek to a specified position in the file.
404: * Return 0 if successful, non-zero if can't seek there.
405: */
406: public int
407: ch_seek(pos)
1.4 millert 408: register POSITION pos;
1.1 etheisen 409: {
1.4 millert 410: BLOCKNUM new_block;
1.1 etheisen 411: POSITION len;
412:
413: len = ch_length();
414: if (pos < ch_zero() || (len != NULL_POSITION && pos > len))
415: return (1);
416:
417: new_block = pos / LBUFSIZE;
418: if (!(ch_flags & CH_CANSEEK) && pos != ch_fpos && !buffered(new_block))
419: {
420: if (ch_fpos > pos)
421: return (1);
422: while (ch_fpos < pos)
423: {
424: if (ch_forw_get() == EOI)
425: return (1);
426: if (ABORT_SIGS())
427: return (1);
428: }
429: return (0);
430: }
431: /*
432: * Set read pointer.
433: */
434: ch_block = new_block;
435: ch_offset = pos % LBUFSIZE;
436: return (0);
437: }
438:
439: /*
440: * Seek to the end of the file.
441: */
442: public int
443: ch_end_seek()
444: {
445: POSITION len;
446:
447: if (ch_flags & CH_CANSEEK)
448: ch_fsize = filesize(ch_file);
449:
450: len = ch_length();
451: if (len != NULL_POSITION)
452: return (ch_seek(len));
453:
454: /*
455: * Do it the slow way: read till end of data.
456: */
457: while (ch_forw_get() != EOI)
458: if (ABORT_SIGS())
459: return (1);
460: return (0);
461: }
462:
463: /*
464: * Seek to the beginning of the file, or as close to it as we can get.
465: * We may not be able to seek there if input is a pipe and the
466: * beginning of the pipe is no longer buffered.
467: */
468: public int
469: ch_beg_seek()
470: {
1.4 millert 471: register struct buf *bp, *firstbp;
1.1 etheisen 472:
473: /*
474: * Try a plain ch_seek first.
475: */
476: if (ch_seek(ch_zero()) == 0)
477: return (0);
478:
479: /*
480: * Can't get to position 0.
481: * Look thru the buffers for the one closest to position 0.
482: */
483: firstbp = bp = ch_bufhead;
484: if (bp == END_OF_CHAIN)
485: return (1);
486: while ((bp = bp->next) != END_OF_CHAIN)
487: if (bp->block < firstbp->block)
488: firstbp = bp;
489: ch_block = firstbp->block;
490: ch_offset = 0;
491: return (0);
492: }
493:
494: /*
495: * Return the length of the file, if known.
496: */
497: public POSITION
498: ch_length()
499: {
500: if (ignore_eoi)
501: return (NULL_POSITION);
502: return (ch_fsize);
503: }
504:
505: /*
506: * Return the current position in the file.
507: */
508: public POSITION
509: ch_tell()
510: {
1.4 millert 511: return (ch_block * LBUFSIZE) + ch_offset;
1.1 etheisen 512: }
513:
514: /*
515: * Get the current char and post-increment the read pointer.
516: */
517: public int
518: ch_forw_get()
519: {
1.4 millert 520: register int c;
1.1 etheisen 521:
522: c = ch_get();
523: if (c == EOI)
524: return (EOI);
525: if (ch_offset < LBUFSIZE-1)
526: ch_offset++;
527: else
528: {
529: ch_block ++;
530: ch_offset = 0;
531: }
532: return (c);
533: }
534:
535: /*
536: * Pre-decrement the read pointer and get the new current char.
537: */
538: public int
539: ch_back_get()
540: {
541: if (ch_offset > 0)
542: ch_offset --;
543: else
544: {
545: if (ch_block <= 0)
546: return (EOI);
547: if (!(ch_flags & CH_CANSEEK) && !buffered(ch_block-1))
548: return (EOI);
549: ch_block--;
550: ch_offset = LBUFSIZE-1;
551: }
552: return (ch_get());
553: }
554:
555: /*
1.4 millert 556: * Set max amount of buffer space.
557: * bufspace is in units of 1024 bytes. -1 mean no limit.
1.1 etheisen 558: */
1.4 millert 559: public void
560: ch_setbufspace(bufspace)
561: int bufspace;
1.1 etheisen 562: {
1.4 millert 563: if (bufspace < 0)
564: maxbufs = -1;
565: else
1.1 etheisen 566: {
1.4 millert 567: maxbufs = ((bufspace * 1024) + LBUFSIZE-1) / LBUFSIZE;
568: if (maxbufs < 1)
569: maxbufs = 1;
1.1 etheisen 570: }
571: }
572:
573: /*
574: * Flush (discard) any saved file state, including buffer contents.
575: */
576: public void
577: ch_flush()
578: {
1.4 millert 579: register struct buf *bp;
1.1 etheisen 580:
581: if (!(ch_flags & CH_CANSEEK))
582: {
583: /*
584: * If input is a pipe, we don't flush buffer contents,
585: * since the contents can't be recovered.
586: */
587: ch_fsize = NULL_POSITION;
588: return;
589: }
590:
591: /*
592: * Initialize all the buffers.
593: */
594: for (bp = ch_bufhead; bp != END_OF_CHAIN; bp = bp->next)
1.4 millert 595: bp->block = -1;
1.1 etheisen 596:
597: /*
598: * Figure out the size of the file, if we can.
599: */
600: ch_fsize = filesize(ch_file);
601:
602: /*
603: * Seek to a known position: the beginning of the file.
604: */
605: ch_fpos = 0;
606: ch_block = 0; /* ch_fpos / LBUFSIZE; */
607: ch_offset = 0; /* ch_fpos % LBUFSIZE; */
608:
1.4 millert 609: #if 1
610: /*
611: * This is a kludge to workaround a Linux kernel bug: files in
612: * /proc have a size of 0 according to fstat() but have readable
613: * data. They are sometimes, but not always, seekable.
614: * Force them to be non-seekable here.
615: */
616: if (ch_fsize == 0)
617: {
618: ch_fsize = NULL_POSITION;
619: ch_flags &= ~CH_CANSEEK;
620: }
621: #endif
622:
1.7 ! deraadt 623: if (lseek(ch_file, (off_t)0, SEEK_SET) == BAD_LSEEK)
1.1 etheisen 624: {
625: /*
626: * Warning only; even if the seek fails for some reason,
627: * there's a good chance we're at the beginning anyway.
628: * {{ I think this is bogus reasoning. }}
629: */
630: error("seek error to 0", NULL_PARG);
631: }
632: }
633:
634: /*
635: * Allocate a new buffer.
636: * The buffer is added to the tail of the buffer chain.
637: */
638: static int
639: ch_addbuf()
640: {
1.4 millert 641: register struct buf *bp;
1.1 etheisen 642:
643: /*
644: * Allocate and initialize a new buffer and link it
645: * onto the tail of the buffer list.
646: */
647: bp = (struct buf *) calloc(1, sizeof(struct buf));
648: if (bp == NULL)
649: return (1);
650: ch_nbufs++;
1.4 millert 651: bp->block = -1;
1.1 etheisen 652: bp->next = END_OF_CHAIN;
653: bp->prev = ch_buftail;
654: ch_buftail->next = bp;
655: ch_buftail = bp;
1.4 millert 656: HASH_INS(bp, 0);
1.1 etheisen 657: return (0);
658: }
659:
660: /*
1.4 millert 661: *
662: */
663: static void
664: init_hashtbl()
665: {
666: register int h;
667:
668: for (h = 0; h < BUFHASH_SIZE; h++)
669: {
670: thisfile->hashtbl[h].buf_hnext = END_OF_HCHAIN(h);
671: thisfile->hashtbl[h].buf_hprev = END_OF_HCHAIN(h);
672: }
673: }
674:
675: /*
1.1 etheisen 676: * Delete all buffers for this file.
677: */
678: static void
679: ch_delbufs()
680: {
1.4 millert 681: register struct buf *bp;
1.1 etheisen 682:
683: while (ch_bufhead != END_OF_CHAIN)
684: {
685: bp = ch_bufhead;
1.6 millert 686: bp->next->prev = bp->prev;
1.1 etheisen 687: bp->prev->next = bp->next;
688: free(bp);
689: }
690: ch_nbufs = 0;
1.4 millert 691: init_hashtbl();
1.1 etheisen 692: }
693:
694: /*
695: * Is it possible to seek on a file descriptor?
696: */
697: public int
698: seekable(f)
699: int f;
700: {
1.4 millert 701: #if MSDOS_COMPILER
702: extern int fd0;
703: if (f == fd0 && !isatty(fd0))
704: {
705: /*
706: * In MS-DOS, pipes are seekable. Check for
707: * standard input, and pretend it is not seekable.
708: */
709: return (0);
710: }
711: #endif
1.7 ! deraadt 712: return (lseek(f, (off_t)1, SEEK_SET) != BAD_LSEEK);
1.1 etheisen 713: }
714:
715: /*
716: * Initialize file state for a new file.
717: */
718: public void
719: ch_init(f, flags)
720: int f;
721: int flags;
722: {
723: /*
724: * See if we already have a filestate for this file.
725: */
726: thisfile = (struct filestate *) get_filestate(curr_ifile);
727: if (thisfile == NULL)
728: {
729: /*
730: * Allocate and initialize a new filestate.
731: */
732: thisfile = (struct filestate *)
733: calloc(1, sizeof(struct filestate));
734: thisfile->buf_next = thisfile->buf_prev = END_OF_CHAIN;
735: thisfile->nbufs = 0;
736: thisfile->flags = 0;
737: thisfile->fpos = 0;
738: thisfile->block = 0;
739: thisfile->offset = 0;
740: thisfile->file = -1;
741: thisfile->fsize = NULL_POSITION;
742: ch_flags = flags;
1.4 millert 743: init_hashtbl();
1.1 etheisen 744: /*
745: * Try to seek; set CH_CANSEEK if it works.
746: */
1.4 millert 747: if ((flags & CH_CANSEEK) && !seekable(f))
748: ch_flags &= ~CH_CANSEEK;
1.1 etheisen 749: set_filestate(curr_ifile, (void *) thisfile);
750: }
751: if (thisfile->file == -1)
752: thisfile->file = f;
753: ch_flush();
754: }
755:
756: /*
757: * Close a filestate.
758: */
759: public void
760: ch_close()
761: {
762: int keepstate = FALSE;
763:
1.5 millert 764: if (ch_flags & (CH_CANSEEK|CH_POPENED))
1.1 etheisen 765: {
766: /*
767: * We can seek or re-open, so we don't need to keep buffers.
768: */
769: ch_delbufs();
770: } else
771: keepstate = TRUE;
772: if (!(ch_flags & CH_KEEPOPEN))
773: {
774: /*
775: * We don't need to keep the file descriptor open
776: * (because we can re-open it.)
777: * But don't really close it if it was opened via popen(),
778: * because pclose() wants to close it.
779: */
1.5 millert 780: if (!(ch_flags & CH_POPENED))
1.1 etheisen 781: close(ch_file);
782: ch_file = -1;
783: } else
784: keepstate = TRUE;
785: if (!keepstate)
786: {
787: /*
788: * We don't even need to keep the filestate structure.
789: */
790: free(thisfile);
791: thisfile = NULL;
792: set_filestate(curr_ifile, (void *) NULL);
793: }
794: }
795:
796: /*
797: * Return ch_flags for the current file.
798: */
799: public int
800: ch_getflags()
801: {
802: return (ch_flags);
803: }
804:
805: #if 0
806: public void
807: ch_dump(struct filestate *fs)
808: {
809: struct buf *bp;
810: unsigned char *s;
811:
812: if (fs == NULL)
813: {
814: printf(" --no filestate\n");
815: return;
816: }
817: printf(" file %d, flags %x, fpos %x, fsize %x, blk/off %x/%x\n",
818: fs->file, fs->flags, fs->fpos,
819: fs->fsize, fs->block, fs->offset);
820: printf(" %d bufs:\n", fs->nbufs);
821: for (bp = fs->buf_next; bp != (struct buf *)fs; bp = bp->next)
822: {
823: printf("%x: blk %x, size %x \"",
824: bp, bp->block, bp->datasize);
825: for (s = bp->data; s < bp->data + 30; s++)
826: if (*s >= ' ' && *s < 0x7F)
827: printf("%c", *s);
828: else
829: printf(".");
830: printf("\"\n");
831: }
832: }
833: #endif