1 /* Copyright (C) 1994, 2000 artofcode LLC.  All rights reserved.
2 
3   This program is free software; you can redistribute it and/or modify it
4   under the terms of the GNU General Public License as published by the
5   Free Software Foundation; either version 2 of the License, or (at your
6   option) any later version.
7 
8   This program is distributed in the hope that it will be useful, but
9   WITHOUT ANY WARRANTY; without even the implied warranty of
10   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11   General Public License for more details.
12 
13   You should have received a copy of the GNU General Public License along
14   with this program; if not, write to the Free Software Foundation, Inc.,
15   59 Temple Place, Suite 330, Boston, MA, 02111-1307.
16 
17 */
18 
19 /*$Id: sfxfd.c,v 1.6.4.1.2.1 2003/01/17 00:49:05 giles Exp $ */
20 /* File stream implementation using direct OS calls */
21 /******
22  ****** NOTE: THIS FILE MAY NOT COMPILE ON NON-UNIX PLATFORMS, AND MAY
23  ****** REQUIRE EDITING ON SOME UNIX PLATFORMS.
24  ******/
25 #include "stdio_.h"		/* includes std.h */
26 #include "errno_.h"
27 #include "memory_.h"
28 #include "unistd_.h"            /* for read, write, fsync, lseek */
29 
30 #include "gdebug.h"
31 #include "gpcheck.h"
32 #include "stream.h"
33 #include "strimpl.h"
34 
35 /*
36  * This is an alternate implementation of file streams.  It still uses
37  * FILE * in the interface, but it uses direct OS calls for I/O.
38  * It also includes workarounds for the nasty habit of System V Unix
39  * of breaking out of read and write operations with EINTR, EAGAIN,
40  * and/or EWOULDBLOCK "errors".
41  *
42  * The interface should be identical to that of sfxstdio.c.  However, in
43  * order to allow both implementations to exist in the same executable, we
44  * optionally use different names for sread_file, swrite_file, and
45  * sappend_file, and omit sread_subfile (the public procedures).
46  * See sfxboth.c.
47  */
48 #ifdef KEEP_FILENO_API
49 /* Provide prototypes to avoid compiler warnings. */
50 void
51     sread_fileno(P4(stream *, FILE *, byte *, uint)),
52     swrite_fileno(P4(stream *, FILE *, byte *, uint)),
53     sappend_fileno(P4(stream *, FILE *, byte *, uint));
54 #else
55 #  define sread_fileno sread_file
56 #  define swrite_fileno swrite_file
57 #  define sappend_fileno sappend_file
58 #endif
59 
60 /* Forward references for file stream procedures */
61 private int
62     s_fileno_available(P2(stream *, long *)),
63     s_fileno_read_seek(P2(stream *, long)),
64     s_fileno_read_close(P1(stream *)),
65     s_fileno_read_process(P4(stream_state *, stream_cursor_read *,
66 			     stream_cursor_write *, bool));
67 private int
68     s_fileno_write_seek(P2(stream *, long)),
69     s_fileno_write_flush(P1(stream *)),
70     s_fileno_write_close(P1(stream *)),
71     s_fileno_write_process(P4(stream_state *, stream_cursor_read *,
72 			      stream_cursor_write *, bool));
73 private int
74     s_fileno_switch(P2(stream *, bool));
75 
76 /* Get the file descriptor number of a stream. */
77 inline private int
sfileno(const stream * s)78 sfileno(const stream *s)
79 {
80     return fileno(s->file);
81 }
82 
83 /* Get the current position of a file descriptor. */
84 inline private long
ltell(int fd)85 ltell(int fd)
86 {
87     return lseek(fd, 0L, SEEK_CUR);
88 }
89 
90 /* Define the System V interrupts that require retrying a call. */
91 private bool
errno_is_retry(int errn)92 errno_is_retry(int errn)
93 {
94     switch (errn) {
95 #ifdef EINTR
96     case EINTR: return true;
97 #endif
98 #if defined(EAGAIN) && (!defined(EINTR) || EAGAIN != EINTR)
99     case EAGAIN: return true;
100 #endif
101 #if defined(EWOULDBLOCK) && (!defined(EINTR) || EWOULDBLOCK != EINTR) && (!defined(EAGAIN) || EWOULDBLOCK != EAGAIN)
102     case EWOULDBLOCK: return true;
103 #endif
104     default: return false;
105     }
106 }
107 
108 /* ------ File reading ------ */
109 
110 /* Initialize a stream for reading an OS file. */
111 void
sread_fileno(register stream * s,FILE * file,byte * buf,uint len)112 sread_fileno(register stream * s, FILE * file, byte * buf, uint len)
113 {
114     static const stream_procs p = {
115 	s_fileno_available, s_fileno_read_seek, s_std_read_reset,
116 	s_std_read_flush, s_fileno_read_close, s_fileno_read_process,
117 	s_fileno_switch
118     };
119     /*
120      * There is no really portable way to test seekability,
121      * but this should work on most systems.
122      */
123     int fd = fileno(file);
124     long curpos = ltell(fd);
125     bool seekable = (curpos != -1L && lseek(fd, curpos, SEEK_SET) != -1L);
126 
127     s_std_init(s, buf, len, &p,
128 	       (seekable ? s_mode_read + s_mode_seek : s_mode_read));
129     if_debug2('s', "[s]read file=0x%lx, fd=%d\n", (ulong) file,
130 	      fileno(file));
131     s->file = file;
132     s->file_modes = s->modes;
133     s->file_offset = 0;
134     s->file_limit = max_long;
135 }
136 
137 /* Confine reading to a subfile.  This is primarily for reusable streams. */
138 /*
139  * We omit this procedure if we are also include sfxstdio.c, which has an
140  * identical definition.
141  */
142 #ifndef KEEP_FILENO_API
143 int
sread_subfile(stream * s,long start,long length)144 sread_subfile(stream *s, long start, long length)
145 {
146     if (s->file == 0 || s->modes != s_mode_read + s_mode_seek ||
147 	s->file_offset != 0 || s->file_limit != max_long ||
148 	((s->position < start || s->position > start + length) &&
149 	 sseek(s, start) < 0)
150 	)
151 	return ERRC;
152     s->position -= start;
153     s->file_offset = start;
154     s->file_limit = length;
155     return 0;
156 }
157 #endif
158 
159 /* Procedures for reading from a file */
160 private int
s_fileno_available(register stream * s,long * pl)161 s_fileno_available(register stream * s, long *pl)
162 {
163     long max_avail = s->file_limit - stell(s);
164     long buf_avail = sbufavailable(s);
165     int fd = sfileno(s);
166 
167     *pl = min(max_avail, buf_avail);
168     if (sseekable(s)) {
169 	long pos, end;
170 
171 	pos = ltell(fd);
172 	if (pos < 0)
173 	    return ERRC;
174 	end = lseek(fd, 0L, SEEK_END);
175 	if (lseek(fd, pos, SEEK_SET) < 0 || end < 0)
176 	    return ERRC;
177 	buf_avail += end - pos;
178 	*pl = min(max_avail, buf_avail);
179 	if (*pl == 0)
180 	    *pl = -1;		/* EOF */
181     } else {
182 	if (*pl == 0)
183 	    *pl = -1;		/* EOF */
184     }
185     return 0;
186 }
187 private int
s_fileno_read_seek(register stream * s,long pos)188 s_fileno_read_seek(register stream * s, long pos)
189 {
190     uint end = s->srlimit - s->cbuf + 1;
191     long offset = pos - s->position;
192 
193     if (offset >= 0 && offset <= end) {  /* Staying within the same buffer */
194 	s->srptr = s->cbuf + offset - 1;
195 	return 0;
196     }
197     if (pos < 0 || pos > s->file_limit ||
198 	lseek(sfileno(s), s->file_offset + pos, SEEK_SET) < 0
199 	)
200 	return ERRC;
201     s->srptr = s->srlimit = s->cbuf - 1;
202     s->end_status = 0;
203     s->position = pos;
204     return 0;
205 }
206 private int
s_fileno_read_close(stream * s)207 s_fileno_read_close(stream * s)
208 {
209     FILE *file = s->file;
210 
211     if (file != 0) {
212 	s->file = 0;
213 	return (fclose(file) ? ERRC : 0);
214     }
215     return 0;
216 }
217 
218 /* Process a buffer for a file reading stream. */
219 /* This is the first stream in the pipeline, so pr is irrelevant. */
220 private int
s_fileno_read_process(stream_state * st,stream_cursor_read * ignore_pr,stream_cursor_write * pw,bool last)221 s_fileno_read_process(stream_state * st, stream_cursor_read * ignore_pr,
222 		      stream_cursor_write * pw, bool last)
223 {
224     stream *s = (stream *)st;	/* no separate state */
225     int fd = sfileno(s);
226     uint max_count;
227     int nread, status;
228 
229 again:
230     max_count = pw->limit - pw->ptr;
231     status = 1;
232     if (s->file_limit < max_long) {
233 	long limit_count = s->file_offset + s->file_limit - ltell(fd);
234 
235 	if (max_count > limit_count)
236 	    max_count = limit_count, status = EOFC;
237     }
238     /*
239      * In the Mac MetroWerks compiler, the prototype for read incorrectly
240      * declares the second argument of read as char * rather than void *.
241      * Work around this here.
242      */
243     nread = read(fd, (void *)(pw->ptr + 1), max_count);
244     if (nread > 0)
245 	pw->ptr += nread;
246     else if (nread == 0)
247 	status = EOFC;
248     else if (errno_is_retry(errno))	/* Handle System V interrupts */
249 	goto again;
250     else
251 	status = ERRC;
252     process_interrupts();
253     return status;
254 }
255 
256 /* ------ File writing ------ */
257 
258 /* Initialize a stream for writing an OS file. */
259 void
swrite_fileno(register stream * s,FILE * file,byte * buf,uint len)260 swrite_fileno(register stream * s, FILE * file, byte * buf, uint len)
261 {
262     static const stream_procs p = {
263 	s_std_noavailable, s_fileno_write_seek, s_std_write_reset,
264 	s_fileno_write_flush, s_fileno_write_close, s_fileno_write_process,
265 	s_fileno_switch
266     };
267 
268     s_std_init(s, buf, len, &p,
269 	       (file == stdout ? s_mode_write : s_mode_write + s_mode_seek));
270     if_debug2('s', "[s]write file=0x%lx, fd=%d\n", (ulong) file,
271 	      fileno(file));
272     s->file = file;
273     s->file_modes = s->modes;
274     s->file_offset = 0;		/* in case we switch to reading later */
275     s->file_limit = max_long;	/* ibid. */
276 }
277 /* Initialize for appending to an OS file. */
278 void
sappend_fileno(register stream * s,FILE * file,byte * buf,uint len)279 sappend_fileno(register stream * s, FILE * file, byte * buf, uint len)
280 {
281     swrite_fileno(s, file, buf, len);
282     s->modes = s_mode_write + s_mode_append;	/* no seek */
283     s->file_modes = s->modes;
284     s->position = lseek(fileno(file), 0L, SEEK_END);
285 }
286 /* Procedures for writing on a file */
287 private int
s_fileno_write_seek(stream * s,long pos)288 s_fileno_write_seek(stream * s, long pos)
289 {
290     /* We must flush the buffer to reposition. */
291     int code = sflush(s);
292 
293     if (code < 0)
294 	return code;
295     if (lseek(sfileno(s), pos, SEEK_SET) < 0)
296 	return ERRC;
297     s->position = pos;
298     return 0;
299 }
300 private int
s_fileno_write_flush(register stream * s)301 s_fileno_write_flush(register stream * s)
302 {
303     int result = s_process_write_buf(s, false);
304 
305     discard(fsync(sfileno(s)));
306     return result;
307 }
308 private int
s_fileno_write_close(register stream * s)309 s_fileno_write_close(register stream * s)
310 {
311     s_process_write_buf(s, true);
312     return s_fileno_read_close(s);
313 }
314 
315 /* Process a buffer for a file writing stream. */
316 /* This is the last stream in the pipeline, so pw is irrelevant. */
317 private int
s_fileno_write_process(stream_state * st,stream_cursor_read * pr,stream_cursor_write * ignore_pw,bool last)318 s_fileno_write_process(stream_state * st, stream_cursor_read * pr,
319 		       stream_cursor_write * ignore_pw, bool last)
320 {
321     int nwrite, status;
322     uint count;
323 
324 again:
325     count = pr->limit - pr->ptr;
326     /* Some versions of the DEC C library on AXP architectures */
327     /* give an error on write if the count is zero! */
328     if (count == 0) {
329 	process_interrupts();
330 	return 0;
331     }
332     /* See above regarding the Mac MetroWorks compiler. */
333     nwrite = write(sfileno((stream *)st), (const void *)(pr->ptr + 1), count);
334     if (nwrite >= 0) {
335 	pr->ptr += nwrite;
336 	status = 0;
337     } else if (errno_is_retry(errno))	/* Handle System V interrupts */
338 	goto again;
339     else
340 	status = ERRC;
341     process_interrupts();
342     return status;
343 }
344 
345 /* ------ File switching ------ */
346 
347 /* Switch a file stream to reading or writing. */
348 private int
s_fileno_switch(stream * s,bool writing)349 s_fileno_switch(stream * s, bool writing)
350 {
351     uint modes = s->file_modes;
352     int fd = sfileno(s);
353     long pos;
354 
355     if (writing) {
356 	if (!(s->file_modes & s_mode_write))
357 	    return ERRC;
358 	pos = stell(s);
359 	if_debug2('s', "[s]switch 0x%lx to write at %ld\n",
360 		  (ulong) s, pos);
361 	lseek(fd, pos, SEEK_SET);	/* pacify OS */
362 	if (modes & s_mode_append) {
363 	    sappend_file(s, s->file, s->cbuf, s->cbsize);  /* sets position */
364 	} else {
365 	    swrite_file(s, s->file, s->cbuf, s->cbsize);
366 	    s->position = pos;
367 	}
368 	s->modes = modes;
369     } else {
370 	if (!(s->file_modes & s_mode_read))
371 	    return ERRC;
372 	pos = stell(s);
373 	if_debug2('s', "[s]switch 0x%lx to read at %ld\n",
374 		  (ulong) s, pos);
375 	if (sflush(s) < 0)
376 	    return ERRC;
377 	lseek(fd, 0L, SEEK_CUR);	/* pacify OS */
378 	sread_file(s, s->file, s->cbuf, s->cbsize);
379 	s->modes |= modes & s_mode_append;	/* don't lose append info */
380 	s->position = pos;
381     }
382     s->file_modes = modes;
383     return 0;
384 }
385