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>
18SM_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
34static void	seekalrm __P((int));
35static 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 */
57static void
58seekalrm(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
78int
79sm_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;
310clean:
311	/*  We're back. So undo our timeout and handler */
312	if (evt != NULL)
313		sm_clrevent(evt);
314	return ret;
315dumb:
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