xref: /freebsd/contrib/sendmail/libsm/fseek.c (revision 4f29da19bd44f0e99f021510460a81bf754c21d2)
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
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
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