190792Sgshapiro/*
2261363Sgshapiro * Copyright (c) 2000-2001, 2004 Proofpoint, Inc. and its suppliers.
390792Sgshapiro *      All rights reserved.
490792Sgshapiro * Copyright (c) 1990, 1993
590792Sgshapiro *	The Regents of the University of California.  All rights reserved.
690792Sgshapiro *
790792Sgshapiro * This code is derived from software contributed to Berkeley by
890792Sgshapiro * Chris Torek.
990792Sgshapiro *
1090792Sgshapiro * By using this file, you agree to the terms and conditions set
1190792Sgshapiro * forth in the LICENSE file which can be found at the top level of
1290792Sgshapiro * the sendmail distribution.
1390792Sgshapiro */
1490792Sgshapiro
1590792Sgshapiro#include <sm/gen.h>
16266692SgshapiroSM_RCSID("@(#)$Id: fseek.c,v 1.48 2013-11-22 20:51:42 ca Exp $")
1790792Sgshapiro#include <sys/types.h>
1890792Sgshapiro#include <sys/stat.h>
1990792Sgshapiro#include <fcntl.h>
2090792Sgshapiro#include <stdlib.h>
2190792Sgshapiro#include <errno.h>
2290792Sgshapiro#include <setjmp.h>
23157001Sgshapiro#include <sm/time.h>
2490792Sgshapiro#include <sm/signal.h>
2590792Sgshapiro#include <sm/io.h>
2690792Sgshapiro#include <sm/assert.h>
2790792Sgshapiro#include <sm/clock.h>
2890792Sgshapiro#include "local.h"
2990792Sgshapiro
3090792Sgshapiro#define POS_ERR	(-(off_t)1)
3190792Sgshapiro
32141858Sgshapirostatic void	seekalrm __P((int));
3390792Sgshapirostatic jmp_buf SeekTimeOut;
3490792Sgshapiro
3590792Sgshapiro/*
3690792Sgshapiro**  SEEKALRM -- handler when timeout activated for sm_io_seek()
3790792Sgshapiro**
3890792Sgshapiro**  Returns flow of control to where setjmp(SeekTimeOut) was set.
3990792Sgshapiro**
4090792Sgshapiro**	Parameters:
4190792Sgshapiro**		sig -- unused
4290792Sgshapiro**
4390792Sgshapiro**	Returns:
4490792Sgshapiro**		does not return
4590792Sgshapiro**
4690792Sgshapiro**	Side Effects:
4790792Sgshapiro**		returns flow of control to setjmp(SeekTimeOut).
4890792Sgshapiro**
4990792Sgshapiro**	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
5090792Sgshapiro**		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
5190792Sgshapiro**		DOING.
5290792Sgshapiro*/
5390792Sgshapiro
5490792Sgshapiro/* ARGSUSED0 */
5590792Sgshapirostatic void
5690792Sgshapiroseekalrm(sig)
5790792Sgshapiro	int sig;
5890792Sgshapiro{
5990792Sgshapiro	longjmp(SeekTimeOut, 1);
6090792Sgshapiro}
6190792Sgshapiro
6290792Sgshapiro/*
6390792Sgshapiro**  SM_IO_SEEK -- position the file pointer
6490792Sgshapiro**
6590792Sgshapiro**	Parameters:
6690792Sgshapiro**		fp -- the file pointer to be seek'd
6790792Sgshapiro**		timeout -- time to complete seek (milliseconds)
6890792Sgshapiro**		offset -- seek offset based on 'whence'
6990792Sgshapiro**		whence -- indicates where seek is relative from.
7090792Sgshapiro**			One of SM_IO_SEEK_{CUR,SET,END}.
7190792Sgshapiro**	Returns:
7290792Sgshapiro**		Failure: returns -1 (minus 1) and sets errno
7390792Sgshapiro**		Success: returns 0 (zero)
7490792Sgshapiro*/
7590792Sgshapiro
7690792Sgshapiroint
7790792Sgshapirosm_io_seek(fp, timeout, offset, whence)
7890792Sgshapiro	register SM_FILE_T *fp;
7990792Sgshapiro	int SM_NONVOLATILE timeout;
8090792Sgshapiro	long SM_NONVOLATILE offset;
8190792Sgshapiro	int SM_NONVOLATILE whence;
8290792Sgshapiro{
8390792Sgshapiro	bool havepos;
8490792Sgshapiro	off_t target, curoff;
8590792Sgshapiro	size_t n;
8690792Sgshapiro	struct stat st;
8790792Sgshapiro	int ret;
8890792Sgshapiro	SM_EVENT *evt = NULL;
8990792Sgshapiro	register off_t (*seekfn) __P((SM_FILE_T *, off_t, int));
9090792Sgshapiro
9190792Sgshapiro	SM_REQUIRE_ISA(fp, SmFileMagic);
9290792Sgshapiro
9390792Sgshapiro	/* make sure stdio is set up */
9490792Sgshapiro	if (!Sm_IO_DidInit)
9590792Sgshapiro		sm_init();
9690792Sgshapiro
9790792Sgshapiro	/* Have to be able to seek. */
9890792Sgshapiro	if ((seekfn = fp->f_seek) == NULL)
9990792Sgshapiro	{
10090792Sgshapiro		errno = ESPIPE;			/* historic practice */
10190792Sgshapiro		return -1;
10290792Sgshapiro	}
10390792Sgshapiro
10490792Sgshapiro	if (timeout == SM_TIME_DEFAULT)
10590792Sgshapiro		timeout = fp->f_timeout;
10690792Sgshapiro	if (timeout == SM_TIME_IMMEDIATE)
10790792Sgshapiro	{
10890792Sgshapiro		/*
10990792Sgshapiro		**  Filling the buffer will take time and we are wanted to
11090792Sgshapiro		**  return immediately. So...
11190792Sgshapiro		*/
11290792Sgshapiro
11390792Sgshapiro		errno = EAGAIN;
11490792Sgshapiro		return -1;
11590792Sgshapiro	}
11690792Sgshapiro
11790792Sgshapiro#define SM_SET_ALARM()						\
11890792Sgshapiro	if (timeout != SM_TIME_FOREVER)				\
11990792Sgshapiro	{							\
12090792Sgshapiro		if (setjmp(SeekTimeOut) != 0)			\
12190792Sgshapiro		{						\
12290792Sgshapiro			errno = EAGAIN;				\
12390792Sgshapiro			return -1;				\
12490792Sgshapiro		}						\
12590792Sgshapiro		evt = sm_seteventm(timeout, seekalrm, 0);	\
12690792Sgshapiro	}
12790792Sgshapiro
12890792Sgshapiro	/*
12990792Sgshapiro	**  Change any SM_IO_SEEK_CUR to SM_IO_SEEK_SET, and check `whence'
13090792Sgshapiro	**  argument. After this, whence is either SM_IO_SEEK_SET or
13190792Sgshapiro	**  SM_IO_SEEK_END.
13290792Sgshapiro	*/
13390792Sgshapiro
13490792Sgshapiro	switch (whence)
13590792Sgshapiro	{
13690792Sgshapiro	  case SM_IO_SEEK_CUR:
13790792Sgshapiro
13890792Sgshapiro		/*
13990792Sgshapiro		**  In order to seek relative to the current stream offset,
14090792Sgshapiro		**  we have to first find the current stream offset a la
14190792Sgshapiro		**  ftell (see ftell for details).
14290792Sgshapiro		*/
14390792Sgshapiro
14490792Sgshapiro		/* may adjust seek offset on append stream */
14590792Sgshapiro		sm_flush(fp, (int *) &timeout);
14690792Sgshapiro		SM_SET_ALARM();
14790792Sgshapiro		if (fp->f_flags & SMOFF)
14890792Sgshapiro			curoff = fp->f_lseekoff;
14990792Sgshapiro		else
15090792Sgshapiro		{
15190792Sgshapiro			curoff = (*seekfn)(fp, (off_t) 0, SM_IO_SEEK_CUR);
15290792Sgshapiro			if (curoff == -1L)
15390792Sgshapiro			{
15490792Sgshapiro				ret = -1;
15590792Sgshapiro				goto clean;
15690792Sgshapiro			}
15790792Sgshapiro		}
15890792Sgshapiro		if (fp->f_flags & SMRD)
15990792Sgshapiro		{
16090792Sgshapiro			curoff -= fp->f_r;
16190792Sgshapiro			if (HASUB(fp))
16290792Sgshapiro				curoff -= fp->f_ur;
16390792Sgshapiro		}
16490792Sgshapiro		else if (fp->f_flags & SMWR && fp->f_p != NULL)
16590792Sgshapiro			curoff += fp->f_p - fp->f_bf.smb_base;
16690792Sgshapiro
16790792Sgshapiro		offset += curoff;
16890792Sgshapiro		whence = SM_IO_SEEK_SET;
16990792Sgshapiro		havepos = true;
17090792Sgshapiro		break;
17190792Sgshapiro
17290792Sgshapiro	  case SM_IO_SEEK_SET:
17390792Sgshapiro	  case SM_IO_SEEK_END:
17490792Sgshapiro		SM_SET_ALARM();
17590792Sgshapiro		curoff = 0;		/* XXX just to keep gcc quiet */
17690792Sgshapiro		havepos = false;
17790792Sgshapiro		break;
17890792Sgshapiro
17990792Sgshapiro	  default:
18090792Sgshapiro		errno = EINVAL;
18190792Sgshapiro		return -1;
18290792Sgshapiro	}
18390792Sgshapiro
18490792Sgshapiro	/*
18590792Sgshapiro	**  Can only optimise if:
18690792Sgshapiro	**	reading (and not reading-and-writing);
18790792Sgshapiro	**	not unbuffered; and
18890792Sgshapiro	**	this is a `regular' Unix file (and hence seekfn==sm_stdseek).
18990792Sgshapiro	**  We must check SMNBF first, because it is possible to have SMNBF
19090792Sgshapiro	**  and SMSOPT both set.
19190792Sgshapiro	*/
19290792Sgshapiro
19390792Sgshapiro	if (fp->f_bf.smb_base == NULL)
19490792Sgshapiro		sm_makebuf(fp);
19590792Sgshapiro	if (fp->f_flags & (SMWR | SMRW | SMNBF | SMNPT))
19690792Sgshapiro		goto dumb;
19790792Sgshapiro	if ((fp->f_flags & SMOPT) == 0)
19890792Sgshapiro	{
19990792Sgshapiro		if (seekfn != sm_stdseek ||
20090792Sgshapiro		    fp->f_file < 0 || fstat(fp->f_file, &st) ||
20190792Sgshapiro		    (st.st_mode & S_IFMT) != S_IFREG)
20290792Sgshapiro		{
20390792Sgshapiro			fp->f_flags |= SMNPT;
20490792Sgshapiro			goto dumb;
20590792Sgshapiro		}
20690792Sgshapiro		fp->f_blksize = st.st_blksize;
20790792Sgshapiro		fp->f_flags |= SMOPT;
20890792Sgshapiro	}
20990792Sgshapiro
21090792Sgshapiro	/*
21190792Sgshapiro	**  We are reading; we can try to optimise.
21290792Sgshapiro	**  Figure out where we are going and where we are now.
21390792Sgshapiro	*/
21490792Sgshapiro
21590792Sgshapiro	if (whence == SM_IO_SEEK_SET)
21690792Sgshapiro		target = offset;
21790792Sgshapiro	else
21890792Sgshapiro	{
21990792Sgshapiro		if (fstat(fp->f_file, &st))
22090792Sgshapiro			goto dumb;
22190792Sgshapiro		target = st.st_size + offset;
22290792Sgshapiro	}
22390792Sgshapiro
22490792Sgshapiro	if (!havepos)
22590792Sgshapiro	{
22690792Sgshapiro		if (fp->f_flags & SMOFF)
22790792Sgshapiro			curoff = fp->f_lseekoff;
22890792Sgshapiro		else
22990792Sgshapiro		{
23090792Sgshapiro			curoff = (*seekfn)(fp, (off_t) 0, SM_IO_SEEK_CUR);
23190792Sgshapiro			if (curoff == POS_ERR)
23290792Sgshapiro				goto dumb;
23390792Sgshapiro		}
23490792Sgshapiro		curoff -= fp->f_r;
23590792Sgshapiro		if (HASUB(fp))
23690792Sgshapiro			curoff -= fp->f_ur;
23790792Sgshapiro	}
23890792Sgshapiro
23990792Sgshapiro	/*
24090792Sgshapiro	**  Compute the number of bytes in the input buffer (pretending
24190792Sgshapiro	**  that any ungetc() input has been discarded).  Adjust current
24290792Sgshapiro	**  offset backwards by this count so that it represents the
24390792Sgshapiro	**  file offset for the first byte in the current input buffer.
24490792Sgshapiro	*/
24590792Sgshapiro
24690792Sgshapiro	if (HASUB(fp))
24790792Sgshapiro	{
24890792Sgshapiro		curoff += fp->f_r;	/* kill off ungetc */
24990792Sgshapiro		n = fp->f_up - fp->f_bf.smb_base;
25090792Sgshapiro		curoff -= n;
25190792Sgshapiro		n += fp->f_ur;
25290792Sgshapiro	}
25390792Sgshapiro	else
25490792Sgshapiro	{
25590792Sgshapiro		n = fp->f_p - fp->f_bf.smb_base;
25690792Sgshapiro		curoff -= n;
25790792Sgshapiro		n += fp->f_r;
25890792Sgshapiro	}
25990792Sgshapiro
26090792Sgshapiro	/*
26190792Sgshapiro	**  If the target offset is within the current buffer,
26290792Sgshapiro	**  simply adjust the pointers, clear SMFEOF, undo ungetc(),
26390792Sgshapiro	**  and return.  (If the buffer was modified, we have to
26490792Sgshapiro	**  skip this; see getln in fget.c.)
26590792Sgshapiro	*/
26690792Sgshapiro
26790792Sgshapiro	if (target >= curoff && target < curoff + (off_t) n)
26890792Sgshapiro	{
26990792Sgshapiro		register int o = target - curoff;
27090792Sgshapiro
27190792Sgshapiro		fp->f_p = fp->f_bf.smb_base + o;
27290792Sgshapiro		fp->f_r = n - o;
27390792Sgshapiro		if (HASUB(fp))
27490792Sgshapiro			FREEUB(fp);
27590792Sgshapiro		fp->f_flags &= ~SMFEOF;
27690792Sgshapiro		ret = 0;
27790792Sgshapiro		goto clean;
27890792Sgshapiro	}
27990792Sgshapiro
28090792Sgshapiro	/*
28190792Sgshapiro	**  The place we want to get to is not within the current buffer,
28290792Sgshapiro	**  but we can still be kind to the kernel copyout mechanism.
28390792Sgshapiro	**  By aligning the file offset to a block boundary, we can let
28490792Sgshapiro	**  the kernel use the VM hardware to map pages instead of
28590792Sgshapiro	**  copying bytes laboriously.  Using a block boundary also
28690792Sgshapiro	**  ensures that we only read one block, rather than two.
28790792Sgshapiro	*/
28890792Sgshapiro
28990792Sgshapiro	curoff = target & ~(fp->f_blksize - 1);
29090792Sgshapiro	if ((*seekfn)(fp, curoff, SM_IO_SEEK_SET) == POS_ERR)
29190792Sgshapiro		goto dumb;
29290792Sgshapiro	fp->f_r = 0;
29390792Sgshapiro	fp->f_p = fp->f_bf.smb_base;
29490792Sgshapiro	if (HASUB(fp))
29590792Sgshapiro		FREEUB(fp);
29690792Sgshapiro	fp->f_flags &= ~SMFEOF;
29790792Sgshapiro	n = target - curoff;
29890792Sgshapiro	if (n)
29990792Sgshapiro	{
30090792Sgshapiro		/* Note: SM_TIME_FOREVER since fn timeout already set */
30190792Sgshapiro		if (sm_refill(fp, SM_TIME_FOREVER) || fp->f_r < (int) n)
30290792Sgshapiro			goto dumb;
30390792Sgshapiro		fp->f_p += n;
30490792Sgshapiro		fp->f_r -= n;
30590792Sgshapiro	}
30690792Sgshapiro
30790792Sgshapiro	ret = 0;
30890792Sgshapiroclean:
30990792Sgshapiro	/*  We're back. So undo our timeout and handler */
31090792Sgshapiro	if (evt != NULL)
31190792Sgshapiro		sm_clrevent(evt);
31290792Sgshapiro	return ret;
31390792Sgshapirodumb:
31490792Sgshapiro	/*
31590792Sgshapiro	**  We get here if we cannot optimise the seek ... just
31690792Sgshapiro	**  do it.  Allow the seek function to change fp->f_bf.smb_base.
31790792Sgshapiro	*/
31890792Sgshapiro
31990792Sgshapiro	/* Note: SM_TIME_FOREVER since fn timeout already set */
32090792Sgshapiro	ret = SM_TIME_FOREVER;
32190792Sgshapiro	if (sm_flush(fp, &ret) != 0 ||
32290792Sgshapiro	    (*seekfn)(fp, (off_t) offset, whence) == POS_ERR)
32390792Sgshapiro	{
32490792Sgshapiro		ret = -1;
32590792Sgshapiro		goto clean;
32690792Sgshapiro	}
32790792Sgshapiro
32890792Sgshapiro	/* success: clear SMFEOF indicator and discard ungetc() data */
32990792Sgshapiro	if (HASUB(fp))
33090792Sgshapiro		FREEUB(fp);
33190792Sgshapiro	fp->f_p = fp->f_bf.smb_base;
33290792Sgshapiro	fp->f_r = 0;
33390792Sgshapiro	fp->f_flags &= ~SMFEOF;
33490792Sgshapiro	ret = 0;
33590792Sgshapiro	goto clean;
33690792Sgshapiro}
337