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