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