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