xref: /illumos-gate/usr/src/cmd/sendmail/libsm/fseek.c (revision 069e6b7e31ba5dcbc5441b98af272714d9a5455c)
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
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
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