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