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