/*
* Copyright (c) 2000-2002, 2005 Sendmail, Inc. and its suppliers.
* All rights reserved.
* Copyright (c) 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Chris Torek.
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the sendmail distribution.
*/
#include "sm/generic.h"
SM_RCSID("@(#)$Id: fseek.c,v 1.13 2006/07/14 14:42:25 ca Exp $")
#include "sm/assert.h"
#include "sm/types.h"
#include "sm/stat.h"
#include "sm/fcntl.h"
#include "sm/error.h"
#include "sm/time.h"
#include "sm/io.h"
#include "io-int.h"
#define POS_ERR (-(off_t)1)
/*
** SM_IO_SEEK -- position the file pointer
**
** Parameters:
** fp -- the file pointer to be seek'd
** offset -- seek offset based on 'whence'
** whence -- indicates where seek is relative from.
** One of SM_IO_SEEK_{CUR,SET,END}.
** Returns:
** usual sm_error code
*/
sm_ret_T
sm_io_seek(sm_file_T *fp, long offset, int whence)
{
bool havepos;
off_t target, curoff;
size_t n;
struct stat st;
int ret;
seek_F *seekfn;
SM_IS_FP(fp);
/* Have to be able to seek. */
seekfn = f_seek(*fp);
if (seekfn == NULL)
return sm_error_perm(SM_EM_IO, ESPIPE);
/* historic practice */
/*
** Change any SM_IO_SEEK_CUR to SM_IO_SEEK_SET, and check `whence'
** argument. After this, whence is either SM_IO_SEEK_SET or
** SM_IO_SEEK_END.
*/
switch (whence)
{
case SM_IO_SEEK_CUR:
/*
** In order to seek relative to the current stream offset,
** we have to first find the current stream offset a la
** ftell (see ftell for details).
*/
/* may adjust seek offset on append stream */
sm_flush(fp);
if (f_flags(*fp) & SMOFF)
curoff = fp->f_lseekoff;
else
{
curoff = (*seekfn)(fp, (off_t) 0, SM_IO_SEEK_CUR);
if (curoff == -1L)
{
ret = -1;
goto clean;
}
}
if (f_flags(*fp) & SMRD)
curoff -= f_r(*fp);
else if (f_flags(*fp) & SMWR && f_p(*fp) != NULL)
curoff += f_p(*fp) - f_bfbase(*fp);
offset += curoff;
whence = SM_IO_SEEK_SET;
havepos = true;
break;
case SM_IO_SEEK_SET:
case SM_IO_SEEK_END:
curoff = 0; /* just to keep gcc quiet */
havepos = false;
break;
default:
errno = EINVAL;
return -1;
}
/*
** Can only optimise if:
** reading (and not reading-and-writing);
** not unbuffered; and
** this is a `regular' Unix file (and hence seekfn==sm_stdseek).
** We must check SMNBF first, because it is possible to have SMNBF
** and SMOPT both set.
*/
if (f_bfbase(*fp) == NULL)
sm_makefilebuf(fp);
if (f_flags(*fp) & (SMWR | SMRW | SMNBF | SMNPT))
goto dumb;
if ((f_flags(*fp) & SMOPT) == 0)
{
if (seekfn != sm_stdseek ||
f_fd(*fp) < 0 || fstat(f_fd(*fp), &st) ||
(st.st_mode & S_IFMT) != S_IFREG)
{
f_flags(*fp) |= SMNPT;
goto dumb;
}
#if WIN32
fp->f_blksize = 4096; /* XXX temp */
#else
fp->f_blksize = st.st_blksize;
#endif
f_flags(*fp) |= SMOPT;
}
/*
** We are reading; we can try to optimise.
** Figure out where we are going and where we are now.
*/
if (whence == SM_IO_SEEK_SET)
target = offset;
else
{
if (fstat(f_fd(*fp), &st))
goto dumb;
target = st.st_size + offset;
}
if (!havepos)
{
if (f_flags(*fp) & SMOFF)
curoff = fp->f_lseekoff;
else
{
curoff = (*seekfn)(fp, (off_t) 0, SM_IO_SEEK_CUR);
if (curoff == POS_ERR)
goto dumb;
}
curoff -= f_r(*fp);
}
/*
** Compute the number of bytes in the input buffer (pretending
** that any ungetc() input has been discarded). Adjust current
** offset backwards by this count so that it represents the
** file offset for the first byte in the current input buffer.
*/
n = f_p(*fp) - f_bfbase(*fp);
curoff -= n;
n += f_r(*fp);
/*
** If the target offset is within the current buffer,
** simply adjust the pointers, clear SMFEOF, undo ungetc(),
** and return. (If the buffer was modified, we have to
** skip this; see getln in fget.c.)
*/
if (target >= curoff && target < curoff + (off_t) n)
{
int o;
o = target - curoff;
f_p(*fp) = f_bfbase(*fp) + o;
f_r(*fp) = n - o;
f_flags(*fp) &= ~SMFEOF;
ret = 0;
goto clean;
}
/*
** The place we want to get to is not within the current buffer,
** but we can still be kind to the kernel copyout mechanism.
** By aligning the file offset to a block boundary, we can let
** the kernel use the VM hardware to map pages instead of
** copying bytes laboriously. Using a block boundary also
** ensures that we only read one block, rather than two.
*/
curoff = target & ~(fp->f_blksize - 1);
if ((*seekfn)(fp, curoff, SM_IO_SEEK_SET) == POS_ERR)
goto dumb;
f_r(*fp) = 0;
f_p(*fp) = f_bfbase(*fp);
f_flags(*fp) &= ~SMFEOF;
n = target - curoff;
if (n)
{
if (sm_refill(fp) || f_r(*fp) < (int) n)
goto dumb;
f_p(*fp) += n;
f_r(*fp) -= n;
}
ret = 0;
clean:
return ret;
dumb:
/*
** We get here if we cannot optimise the seek ... just
** do it. Allow the seek function to change f_bfbase(*fp).
*/
if (sm_flush(fp) != 0 ||
(*seekfn)(fp, (off_t) offset, whence) == POS_ERR)
{
ret = -1;
goto clean;
}
/* success: clear SMFEOF indicator */
f_p(*fp) = f_bfbase(*fp);
f_r(*fp) = 0;
f_flags(*fp) &= ~SMFEOF;
ret = 0;
goto clean;
}
syntax highlighted by Code2HTML, v. 0.9.1