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