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