1/*
2 * Copyright (c) 2000-2001, 2004 Proofpoint, 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>
16SM_RCSID("@(#)$Id: fseek.c,v 1.48 2013-11-22 20:51:42 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
32static void	seekalrm __P((int));
33static 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 */
55static void
56seekalrm(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
76int
77sm_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;
308clean:
309	/*  We're back. So undo our timeout and handler */
310	if (evt != NULL)
311		sm_clrevent(evt);
312	return ret;
313dumb:
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