Mercurial > hg > Applications > mh
comparison miscellany/less-177/ch.c @ 0:bce86c4163a3
Initial revision
author | kono |
---|---|
date | Mon, 18 Apr 2005 23:46:02 +0900 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:bce86c4163a3 |
---|---|
1 /* | |
2 * Low level character input from the input file. | |
3 * We use these special purpose routines which optimize moving | |
4 * both forward and backward from the current read pointer. | |
5 */ | |
6 | |
7 #include "less.h" | |
8 | |
9 public int file = -1; /* File descriptor of the input file */ | |
10 public int ignore_eoi; | |
11 | |
12 /* | |
13 * Pool of buffers holding the most recently used blocks of the input file. | |
14 */ | |
15 #define BUFSIZ 1024 | |
16 struct buf { | |
17 struct buf *next, *prev; /* Must be first to match struct filestate */ | |
18 long block; | |
19 unsigned int datasize; | |
20 unsigned char data[BUFSIZ]; | |
21 }; | |
22 | |
23 /* | |
24 * The buffer pool is kept as a doubly-linked circular list, | |
25 * in order from most- to least-recently used. | |
26 * The circular list is anchored by the file state "thisfile". | |
27 * | |
28 * The file state is maintained in a filestate structure. | |
29 * There are two such structures, one used when input is a pipe | |
30 * and the other when input is an ordinary file. | |
31 * This is so that we can leave a pipe, look and other files, | |
32 * and return to the pipe without losing buffered data. | |
33 * Buffered data can be reconstructed for a non-pipe file by | |
34 * simply re-reading the file, but a pipe cannot be re-read. | |
35 */ | |
36 | |
37 struct filestate { | |
38 struct buf *next, *prev; /* Must be first to match struct buf */ | |
39 POSITION fpos; | |
40 int nbufs; | |
41 long block; | |
42 int offset; | |
43 POSITION fsize; | |
44 }; | |
45 | |
46 #define END_OF_CHAIN ((struct buf *)thisfile) | |
47 #define buf_head thisfile->next | |
48 #define buf_tail thisfile->prev | |
49 #define ch_nbufs thisfile->nbufs | |
50 #define ch_block thisfile->block | |
51 #define ch_offset thisfile->offset | |
52 #define ch_fpos thisfile->fpos | |
53 #define ch_fsize thisfile->fsize | |
54 | |
55 static struct filestate pipefile = | |
56 { (struct buf *)&pipefile, (struct buf *)&pipefile }; | |
57 | |
58 static struct filestate nonpipefile = | |
59 { (struct buf *)&nonpipefile, (struct buf *)&nonpipefile }; | |
60 | |
61 static struct filestate *thisfile; | |
62 | |
63 extern int ispipe; | |
64 extern int autobuf; | |
65 extern int sigs; | |
66 #if LOGFILE | |
67 extern int logfile; | |
68 extern char *namelogfile; | |
69 #endif | |
70 | |
71 static int ch_addbuf(); | |
72 | |
73 | |
74 /* | |
75 * Get the character pointed to by the read pointer. | |
76 * ch_get() is a macro which is more efficient to call | |
77 * than fch_get (the function), in the usual case | |
78 * that the block desired is at the head of the chain. | |
79 */ | |
80 #define ch_get() ((ch_block == buf_head->block && \ | |
81 ch_offset < buf_head->datasize) ? \ | |
82 buf_head->data[ch_offset] : fch_get()) | |
83 static int | |
84 fch_get() | |
85 { | |
86 register struct buf *bp; | |
87 register int n; | |
88 register int slept; | |
89 POSITION pos; | |
90 POSITION len; | |
91 | |
92 slept = 0; | |
93 | |
94 /* | |
95 * Look for a buffer holding the desired block. | |
96 */ | |
97 for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) | |
98 if (bp->block == ch_block) | |
99 { | |
100 if (ch_offset >= bp->datasize) | |
101 /* | |
102 * Need more data in this buffer. | |
103 */ | |
104 goto read_more; | |
105 goto found; | |
106 } | |
107 /* | |
108 * Block is not in a buffer. | |
109 * Take the least recently used buffer | |
110 * and read the desired block into it. | |
111 * If the LRU buffer has data in it, | |
112 * and autobuf is true, and input is a pipe, | |
113 * then try to allocate a new buffer first. | |
114 */ | |
115 if (autobuf && ispipe && buf_tail->block != (long)(-1)) | |
116 if (ch_addbuf(1)) | |
117 /* | |
118 * Allocation failed: turn off autobuf. | |
119 */ | |
120 autobuf = 0; | |
121 bp = buf_tail; | |
122 bp->block = ch_block; | |
123 bp->datasize = 0; | |
124 | |
125 read_more: | |
126 pos = (ch_block * BUFSIZ) + bp->datasize; | |
127 if ((len = ch_length()) != NULL_POSITION && pos >= len) | |
128 /* | |
129 * At end of file. | |
130 */ | |
131 return (EOI); | |
132 | |
133 if (pos != ch_fpos) | |
134 { | |
135 /* | |
136 * Not at the correct position: must seek. | |
137 * If input is a pipe, we're in trouble (can't seek on a pipe). | |
138 * Some data has been lost: just return "?". | |
139 */ | |
140 if (ispipe) | |
141 return ('?'); | |
142 if (lseek(file, (offset_t)pos, 0) == BAD_LSEEK) | |
143 { | |
144 error("seek error", NULL_PARG); | |
145 quit(1); | |
146 } | |
147 ch_fpos = pos; | |
148 } | |
149 | |
150 /* | |
151 * Read the block. | |
152 * If we read less than a full block, that's ok. | |
153 * We use partial block and pick up the rest next time. | |
154 */ | |
155 n = iread(file, &bp->data[bp->datasize], | |
156 (unsigned int)(BUFSIZ - bp->datasize)); | |
157 if (n == READ_INTR) | |
158 return (EOI); | |
159 if (n < 0) | |
160 { | |
161 error("read error", NULL_PARG); | |
162 quit(1); | |
163 } | |
164 ch_fpos += n; | |
165 | |
166 #if LOGFILE | |
167 /* | |
168 * If we have a log file, write the new data to it. | |
169 */ | |
170 if (logfile >= 0 && n > 0) | |
171 write(logfile, (char *) &bp->data[bp->datasize], n); | |
172 #endif | |
173 | |
174 bp->datasize += n; | |
175 | |
176 /* | |
177 * If we have read to end of file, set ch_fsize to indicate | |
178 * the position of the end of file. | |
179 */ | |
180 if (n == 0) | |
181 { | |
182 ch_fsize = pos; | |
183 if (ignore_eoi) | |
184 { | |
185 /* | |
186 * We are ignoring EOF. | |
187 * Wait a while, then try again. | |
188 */ | |
189 if (!slept) | |
190 ierror("Waiting for data", NULL_PARG); | |
191 sleep(1); | |
192 slept = 1; | |
193 } | |
194 if (sigs) | |
195 return (EOI); | |
196 } | |
197 | |
198 found: | |
199 if (buf_head != bp) | |
200 { | |
201 /* | |
202 * Move the buffer to the head of the buffer chain. | |
203 * This orders the buffer chain, most- to least-recently used. | |
204 */ | |
205 bp->next->prev = bp->prev; | |
206 bp->prev->next = bp->next; | |
207 | |
208 bp->next = buf_head; | |
209 bp->prev = END_OF_CHAIN; | |
210 buf_head->prev = bp; | |
211 buf_head = bp; | |
212 } | |
213 | |
214 if (ch_offset >= bp->datasize) | |
215 /* | |
216 * After all that, we still don't have enough data. | |
217 * Go back and try again. | |
218 */ | |
219 goto read_more; | |
220 | |
221 return (bp->data[ch_offset]); | |
222 } | |
223 | |
224 #if LOGFILE | |
225 /* | |
226 * Close the logfile. | |
227 * If we haven't read all of standard input into it, do that now. | |
228 */ | |
229 public void | |
230 end_logfile() | |
231 { | |
232 static int tried = 0; | |
233 | |
234 if (logfile < 0) | |
235 return; | |
236 if (!tried && ch_fsize == NULL_POSITION) | |
237 { | |
238 tried = 1; | |
239 ierror("Finishing logfile", NULL_PARG); | |
240 while (ch_forw_get() != EOI) | |
241 if (sigs) | |
242 break; | |
243 } | |
244 close(logfile); | |
245 logfile = -1; | |
246 namelogfile = NULL; | |
247 } | |
248 | |
249 /* | |
250 * Start a log file AFTER less has already been running. | |
251 * Invoked from the - command; see toggle_option(). | |
252 * Write all the existing buffered data to the log file. | |
253 */ | |
254 public void | |
255 sync_logfile() | |
256 { | |
257 register struct buf *bp; | |
258 long block; | |
259 long last_block; | |
260 | |
261 last_block = (ch_fpos + BUFSIZ - 1) / BUFSIZ; | |
262 for (block = 0; block <= last_block; block++) | |
263 for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) | |
264 if (bp->block == block) | |
265 { | |
266 write(logfile, (char *) bp->data, bp->datasize); | |
267 break; | |
268 } | |
269 } | |
270 | |
271 #endif | |
272 | |
273 /* | |
274 * Determine if a specific block is currently in one of the buffers. | |
275 */ | |
276 static int | |
277 buffered(block) | |
278 long block; | |
279 { | |
280 register struct buf *bp; | |
281 | |
282 for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) | |
283 if (bp->block == block) | |
284 return (1); | |
285 return (0); | |
286 } | |
287 | |
288 /* | |
289 * Seek to a specified position in the file. | |
290 * Return 0 if successful, non-zero if can't seek there. | |
291 */ | |
292 public int | |
293 ch_seek(pos) | |
294 register POSITION pos; | |
295 { | |
296 long new_block; | |
297 POSITION len; | |
298 | |
299 len = ch_length(); | |
300 if (pos < ch_zero() || (len != NULL_POSITION && pos > len)) | |
301 return (1); | |
302 | |
303 new_block = pos / BUFSIZ; | |
304 if (ispipe && pos != ch_fpos && !buffered(new_block)) | |
305 return (1); | |
306 /* | |
307 * Set read pointer. | |
308 */ | |
309 ch_block = new_block; | |
310 ch_offset = pos % BUFSIZ; | |
311 return (0); | |
312 } | |
313 | |
314 /* | |
315 * Seek to the end of the file. | |
316 */ | |
317 public int | |
318 ch_end_seek() | |
319 { | |
320 POSITION len; | |
321 | |
322 if (!ispipe) | |
323 ch_fsize = filesize(file); | |
324 | |
325 len = ch_length(); | |
326 if (len != NULL_POSITION) | |
327 return (ch_seek(len)); | |
328 | |
329 /* | |
330 * Do it the slow way: read till end of data. | |
331 */ | |
332 while (ch_forw_get() != EOI) | |
333 if (sigs) | |
334 return (1); | |
335 return (0); | |
336 } | |
337 | |
338 /* | |
339 * Seek to the beginning of the file, or as close to it as we can get. | |
340 * We may not be able to seek there if input is a pipe and the | |
341 * beginning of the pipe is no longer buffered. | |
342 */ | |
343 public int | |
344 ch_beg_seek() | |
345 { | |
346 register struct buf *bp, *firstbp; | |
347 | |
348 /* | |
349 * Try a plain ch_seek first. | |
350 */ | |
351 if (ch_seek(ch_zero()) == 0) | |
352 return (0); | |
353 | |
354 /* | |
355 * Can't get to position 0. | |
356 * Look thru the buffers for the one closest to position 0. | |
357 */ | |
358 firstbp = bp = buf_head; | |
359 if (bp == END_OF_CHAIN) | |
360 return (1); | |
361 while ((bp = bp->next) != END_OF_CHAIN) | |
362 if (bp->block < firstbp->block) | |
363 firstbp = bp; | |
364 ch_block = firstbp->block; | |
365 ch_offset = 0; | |
366 return (0); | |
367 } | |
368 | |
369 /* | |
370 * Return the length of the file, if known. | |
371 */ | |
372 public POSITION | |
373 ch_length() | |
374 { | |
375 if (ignore_eoi) | |
376 return (NULL_POSITION); | |
377 return (ch_fsize); | |
378 } | |
379 | |
380 /* | |
381 * Return the current position in the file. | |
382 */ | |
383 #define tellpos(blk,off) ((POSITION)((((long)(blk)) * BUFSIZ) + (off))) | |
384 | |
385 public POSITION | |
386 ch_tell() | |
387 { | |
388 return (tellpos(ch_block, ch_offset)); | |
389 } | |
390 | |
391 /* | |
392 * Get the current char and post-increment the read pointer. | |
393 */ | |
394 public int | |
395 ch_forw_get() | |
396 { | |
397 register int c; | |
398 | |
399 c = ch_get(); | |
400 if (c == EOI) | |
401 return (EOI); | |
402 if (ch_offset < BUFSIZ-1) | |
403 ch_offset++; | |
404 else | |
405 { | |
406 #if __ZOFFSET /* NOT WORKING */ | |
407 if (ch_fsize != NULL_POSITION && | |
408 tellpos(ch_block+1, 0) >= ch_fsize) | |
409 return (EOI); | |
410 #endif | |
411 ch_block ++; | |
412 ch_offset = 0; | |
413 } | |
414 return (c); | |
415 } | |
416 | |
417 /* | |
418 * Pre-decrement the read pointer and get the new current char. | |
419 */ | |
420 public int | |
421 ch_back_get() | |
422 { | |
423 if (ch_offset > 0) | |
424 ch_offset --; | |
425 else | |
426 { | |
427 #if __ZOFFSET /* NOT WORKING */ | |
428 if (tellpos(ch_block-1, BUFSIZ-1) < ch_zero()) | |
429 return (EOI); | |
430 #else | |
431 if (ch_block <= 0) | |
432 return (EOI); | |
433 #endif | |
434 if (ispipe && !buffered(ch_block-1)) | |
435 return (EOI); | |
436 ch_block--; | |
437 ch_offset = BUFSIZ-1; | |
438 } | |
439 return (ch_get()); | |
440 } | |
441 | |
442 /* | |
443 * Allocate buffers. | |
444 * Caller wants us to have a total of at least want_nbufs buffers. | |
445 */ | |
446 public int | |
447 ch_nbuf(want_nbufs) | |
448 int want_nbufs; | |
449 { | |
450 PARG parg; | |
451 | |
452 if (ch_nbufs < want_nbufs && ch_addbuf(want_nbufs - ch_nbufs)) | |
453 { | |
454 /* | |
455 * Cannot allocate enough buffers. | |
456 * If we don't have ANY, then quit. | |
457 * Otherwise, just report the error and return. | |
458 */ | |
459 parg.p_int = want_nbufs - ch_nbufs; | |
460 error("Cannot allocate %d buffers", &parg); | |
461 if (ch_nbufs == 0) | |
462 quit(1); | |
463 } | |
464 return (ch_nbufs); | |
465 } | |
466 | |
467 /* | |
468 * Flush any saved file state, including buffer contents. | |
469 */ | |
470 public void | |
471 ch_flush() | |
472 { | |
473 register struct buf *bp; | |
474 | |
475 if (ispipe) | |
476 { | |
477 /* | |
478 * If input is a pipe, we don't flush buffer contents, | |
479 * since the contents can't be recovered. | |
480 */ | |
481 ch_fsize = NULL_POSITION; | |
482 return; | |
483 } | |
484 | |
485 /* | |
486 * Initialize all the buffers. | |
487 */ | |
488 for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) | |
489 bp->block = (long)(-1); | |
490 | |
491 /* | |
492 * Figure out the size of the file, if we can. | |
493 */ | |
494 ch_fsize = filesize(file); | |
495 | |
496 /* | |
497 * Seek to a known position: the beginning of the file. | |
498 */ | |
499 ch_fpos = 0; | |
500 ch_block = ch_fpos / BUFSIZ; | |
501 ch_offset = ch_fpos % BUFSIZ; | |
502 | |
503 if (lseek(file, (offset_t)0, 0) == BAD_LSEEK) | |
504 { | |
505 /* | |
506 * Warning only; even if the seek fails for some reason, | |
507 * there's a good chance we're at the beginning anyway. | |
508 * {{ I think this is bogus reasoning. }} | |
509 */ | |
510 error("seek error to 0", NULL_PARG); | |
511 } | |
512 } | |
513 | |
514 /* | |
515 * Allocate some new buffers. | |
516 * The buffers are added to the tail of the buffer chain. | |
517 */ | |
518 static int | |
519 ch_addbuf(nnew) | |
520 int nnew; | |
521 { | |
522 register struct buf *bp; | |
523 register struct buf *newbufs; | |
524 | |
525 /* | |
526 * We don't have enough buffers. | |
527 * Allocate some new ones. | |
528 */ | |
529 newbufs = (struct buf *) calloc(nnew, sizeof(struct buf)); | |
530 if (newbufs == NULL) | |
531 return (1); | |
532 | |
533 /* | |
534 * Initialize the new buffers and link them together. | |
535 * Link them all onto the tail of the buffer list. | |
536 */ | |
537 ch_nbufs += nnew; | |
538 for (bp = &newbufs[0]; bp < &newbufs[nnew]; bp++) | |
539 { | |
540 bp->next = bp + 1; | |
541 bp->prev = bp - 1; | |
542 bp->block = (long)(-1); | |
543 } | |
544 newbufs[nnew-1].next = END_OF_CHAIN; | |
545 newbufs[0].prev = buf_tail; | |
546 buf_tail->next = &newbufs[0]; | |
547 buf_tail = &newbufs[nnew-1]; | |
548 return (0); | |
549 } | |
550 | |
551 /* | |
552 * Use the pipe file state. | |
553 */ | |
554 public void | |
555 ch_pipe() | |
556 { | |
557 thisfile = &pipefile; | |
558 } | |
559 | |
560 /* | |
561 * Use the non-pipe file state. | |
562 */ | |
563 public void | |
564 ch_nonpipe() | |
565 { | |
566 thisfile = &nonpipefile; | |
567 } |