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