1 /*
2 * Copyright (c) 2000-2001, 2004 Sendmail, Inc. and its suppliers.
3 * All rights reserved.
4 * Copyright (c) 1990, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Chris Torek.
9 *
10 * By using this file, you agree to the terms and conditions set
11 * forth in the LICENSE file which can be found at the top level of
12 * the sendmail distribution.
13 */
14
15 #include <sm/gen.h>
16 SM_RCSID("@(#)$Id: fseek.c,v 1.47 2005/06/14 23:07:20 ca Exp $")
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <fcntl.h>
20 #include <stdlib.h>
21 #include <errno.h>
22 #include <setjmp.h>
23 #include <sm/time.h>
24 #include <sm/signal.h>
25 #include <sm/io.h>
26 #include <sm/assert.h>
27 #include <sm/clock.h>
28 #include "local.h"
29
30 #define POS_ERR (-(off_t)1)
31
32 static void seekalrm __P((int));
33 static jmp_buf SeekTimeOut;
34
35 /*
36 ** SEEKALRM -- handler when timeout activated for sm_io_seek()
37 **
38 ** Returns flow of control to where setjmp(SeekTimeOut) was set.
39 **
40 ** Parameters:
41 ** sig -- unused
42 **
43 ** Returns:
44 ** does not return
45 **
46 ** Side Effects:
47 ** returns flow of control to setjmp(SeekTimeOut).
48 **
49 ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
50 ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
51 ** DOING.
52 */
53
54 /* ARGSUSED0 */
55 static void
seekalrm(sig)56 seekalrm(sig)
57 int sig;
58 {
59 longjmp(SeekTimeOut, 1);
60 }
61
62 /*
63 ** SM_IO_SEEK -- position the file pointer
64 **
65 ** Parameters:
66 ** fp -- the file pointer to be seek'd
67 ** timeout -- time to complete seek (milliseconds)
68 ** offset -- seek offset based on 'whence'
69 ** whence -- indicates where seek is relative from.
70 ** One of SM_IO_SEEK_{CUR,SET,END}.
71 ** Returns:
72 ** Failure: returns -1 (minus 1) and sets errno
73 ** Success: returns 0 (zero)
74 */
75
76 int
sm_io_seek(fp,timeout,offset,whence)77 sm_io_seek(fp, timeout, offset, whence)
78 register SM_FILE_T *fp;
79 int SM_NONVOLATILE timeout;
80 long SM_NONVOLATILE offset;
81 int SM_NONVOLATILE whence;
82 {
83 bool havepos;
84 off_t target, curoff;
85 size_t n;
86 struct stat st;
87 int ret;
88 SM_EVENT *evt = NULL;
89 register off_t (*seekfn) __P((SM_FILE_T *, off_t, int));
90
91 SM_REQUIRE_ISA(fp, SmFileMagic);
92
93 /* make sure stdio is set up */
94 if (!Sm_IO_DidInit)
95 sm_init();
96
97 /* Have to be able to seek. */
98 if ((seekfn = fp->f_seek) == NULL)
99 {
100 errno = ESPIPE; /* historic practice */
101 return -1;
102 }
103
104 if (timeout == SM_TIME_DEFAULT)
105 timeout = fp->f_timeout;
106 if (timeout == SM_TIME_IMMEDIATE)
107 {
108 /*
109 ** Filling the buffer will take time and we are wanted to
110 ** return immediately. So...
111 */
112
113 errno = EAGAIN;
114 return -1;
115 }
116
117 #define SM_SET_ALARM() \
118 if (timeout != SM_TIME_FOREVER) \
119 { \
120 if (setjmp(SeekTimeOut) != 0) \
121 { \
122 errno = EAGAIN; \
123 return -1; \
124 } \
125 evt = sm_seteventm(timeout, seekalrm, 0); \
126 }
127
128 /*
129 ** Change any SM_IO_SEEK_CUR to SM_IO_SEEK_SET, and check `whence'
130 ** argument. After this, whence is either SM_IO_SEEK_SET or
131 ** SM_IO_SEEK_END.
132 */
133
134 switch (whence)
135 {
136 case SM_IO_SEEK_CUR:
137
138 /*
139 ** In order to seek relative to the current stream offset,
140 ** we have to first find the current stream offset a la
141 ** ftell (see ftell for details).
142 */
143
144 /* may adjust seek offset on append stream */
145 sm_flush(fp, (int *) &timeout);
146 SM_SET_ALARM();
147 if (fp->f_flags & SMOFF)
148 curoff = fp->f_lseekoff;
149 else
150 {
151 curoff = (*seekfn)(fp, (off_t) 0, SM_IO_SEEK_CUR);
152 if (curoff == -1L)
153 {
154 ret = -1;
155 goto clean;
156 }
157 }
158 if (fp->f_flags & SMRD)
159 {
160 curoff -= fp->f_r;
161 if (HASUB(fp))
162 curoff -= fp->f_ur;
163 }
164 else if (fp->f_flags & SMWR && fp->f_p != NULL)
165 curoff += fp->f_p - fp->f_bf.smb_base;
166
167 offset += curoff;
168 whence = SM_IO_SEEK_SET;
169 havepos = true;
170 break;
171
172 case SM_IO_SEEK_SET:
173 case SM_IO_SEEK_END:
174 SM_SET_ALARM();
175 curoff = 0; /* XXX just to keep gcc quiet */
176 havepos = false;
177 break;
178
179 default:
180 errno = EINVAL;
181 return -1;
182 }
183
184 /*
185 ** Can only optimise if:
186 ** reading (and not reading-and-writing);
187 ** not unbuffered; and
188 ** this is a `regular' Unix file (and hence seekfn==sm_stdseek).
189 ** We must check SMNBF first, because it is possible to have SMNBF
190 ** and SMSOPT both set.
191 */
192
193 if (fp->f_bf.smb_base == NULL)
194 sm_makebuf(fp);
195 if (fp->f_flags & (SMWR | SMRW | SMNBF | SMNPT))
196 goto dumb;
197 if ((fp->f_flags & SMOPT) == 0)
198 {
199 if (seekfn != sm_stdseek ||
200 fp->f_file < 0 || fstat(fp->f_file, &st) ||
201 (st.st_mode & S_IFMT) != S_IFREG)
202 {
203 fp->f_flags |= SMNPT;
204 goto dumb;
205 }
206 fp->f_blksize = st.st_blksize;
207 fp->f_flags |= SMOPT;
208 }
209
210 /*
211 ** We are reading; we can try to optimise.
212 ** Figure out where we are going and where we are now.
213 */
214
215 if (whence == SM_IO_SEEK_SET)
216 target = offset;
217 else
218 {
219 if (fstat(fp->f_file, &st))
220 goto dumb;
221 target = st.st_size + offset;
222 }
223
224 if (!havepos)
225 {
226 if (fp->f_flags & SMOFF)
227 curoff = fp->f_lseekoff;
228 else
229 {
230 curoff = (*seekfn)(fp, (off_t) 0, SM_IO_SEEK_CUR);
231 if (curoff == POS_ERR)
232 goto dumb;
233 }
234 curoff -= fp->f_r;
235 if (HASUB(fp))
236 curoff -= fp->f_ur;
237 }
238
239 /*
240 ** Compute the number of bytes in the input buffer (pretending
241 ** that any ungetc() input has been discarded). Adjust current
242 ** offset backwards by this count so that it represents the
243 ** file offset for the first byte in the current input buffer.
244 */
245
246 if (HASUB(fp))
247 {
248 curoff += fp->f_r; /* kill off ungetc */
249 n = fp->f_up - fp->f_bf.smb_base;
250 curoff -= n;
251 n += fp->f_ur;
252 }
253 else
254 {
255 n = fp->f_p - fp->f_bf.smb_base;
256 curoff -= n;
257 n += fp->f_r;
258 }
259
260 /*
261 ** If the target offset is within the current buffer,
262 ** simply adjust the pointers, clear SMFEOF, undo ungetc(),
263 ** and return. (If the buffer was modified, we have to
264 ** skip this; see getln in fget.c.)
265 */
266
267 if (target >= curoff && target < curoff + (off_t) n)
268 {
269 register int o = target - curoff;
270
271 fp->f_p = fp->f_bf.smb_base + o;
272 fp->f_r = n - o;
273 if (HASUB(fp))
274 FREEUB(fp);
275 fp->f_flags &= ~SMFEOF;
276 ret = 0;
277 goto clean;
278 }
279
280 /*
281 ** The place we want to get to is not within the current buffer,
282 ** but we can still be kind to the kernel copyout mechanism.
283 ** By aligning the file offset to a block boundary, we can let
284 ** the kernel use the VM hardware to map pages instead of
285 ** copying bytes laboriously. Using a block boundary also
286 ** ensures that we only read one block, rather than two.
287 */
288
289 curoff = target & ~(fp->f_blksize - 1);
290 if ((*seekfn)(fp, curoff, SM_IO_SEEK_SET) == POS_ERR)
291 goto dumb;
292 fp->f_r = 0;
293 fp->f_p = fp->f_bf.smb_base;
294 if (HASUB(fp))
295 FREEUB(fp);
296 fp->f_flags &= ~SMFEOF;
297 n = target - curoff;
298 if (n)
299 {
300 /* Note: SM_TIME_FOREVER since fn timeout already set */
301 if (sm_refill(fp, SM_TIME_FOREVER) || fp->f_r < (int) n)
302 goto dumb;
303 fp->f_p += n;
304 fp->f_r -= n;
305 }
306
307 ret = 0;
308 clean:
309 /* We're back. So undo our timeout and handler */
310 if (evt != NULL)
311 sm_clrevent(evt);
312 return ret;
313 dumb:
314 /*
315 ** We get here if we cannot optimise the seek ... just
316 ** do it. Allow the seek function to change fp->f_bf.smb_base.
317 */
318
319 /* Note: SM_TIME_FOREVER since fn timeout already set */
320 ret = SM_TIME_FOREVER;
321 if (sm_flush(fp, &ret) != 0 ||
322 (*seekfn)(fp, (off_t) offset, whence) == POS_ERR)
323 {
324 ret = -1;
325 goto clean;
326 }
327
328 /* success: clear SMFEOF indicator and discard ungetc() data */
329 if (HASUB(fp))
330 FREEUB(fp);
331 fp->f_p = fp->f_bf.smb_base;
332 fp->f_r = 0;
333 fp->f_flags &= ~SMFEOF;
334 ret = 0;
335 goto clean;
336 }
337