1N/A/*
1N/A * Copyright (c) 2000-2001, 2004 Sendmail, Inc. and its suppliers.
1N/A * All rights reserved.
1N/A * Copyright (c) 1990, 1993
1N/A * The Regents of the University of California. All rights reserved.
1N/A *
1N/A * This code is derived from software contributed to Berkeley by
1N/A * Chris Torek.
1N/A *
1N/A * By using this file, you agree to the terms and conditions set
1N/A * forth in the LICENSE file which can be found at the top level of
1N/A * the sendmail distribution.
1N/A */
1N/A
1N/A#pragma ident "%Z%%M% %I% %E% SMI"
1N/A
1N/A#include <sm/gen.h>
1N/ASM_RCSID("@(#)$Id: fseek.c,v 1.47 2005/06/14 23:07:20 ca Exp $")
1N/A#include <sys/types.h>
1N/A#include <sys/stat.h>
1N/A#include <fcntl.h>
1N/A#include <stdlib.h>
1N/A#include <errno.h>
1N/A#include <setjmp.h>
1N/A#include <sm/time.h>
1N/A#include <sm/signal.h>
1N/A#include <sm/io.h>
1N/A#include <sm/assert.h>
1N/A#include <sm/clock.h>
1N/A#include "local.h"
1N/A
1N/A#define POS_ERR (-(off_t)1)
1N/A
1N/Astatic void seekalrm __P((int));
1N/Astatic jmp_buf SeekTimeOut;
1N/A
1N/A/*
1N/A** SEEKALRM -- handler when timeout activated for sm_io_seek()
1N/A**
1N/A** Returns flow of control to where setjmp(SeekTimeOut) was set.
1N/A**
1N/A** Parameters:
1N/A** sig -- unused
1N/A**
1N/A** Returns:
1N/A** does not return
1N/A**
1N/A** Side Effects:
1N/A** returns flow of control to setjmp(SeekTimeOut).
1N/A**
1N/A** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
1N/A** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
1N/A** DOING.
1N/A*/
1N/A
1N/A/* ARGSUSED0 */
1N/Astatic void
1N/Aseekalrm(sig)
1N/A int sig;
1N/A{
1N/A longjmp(SeekTimeOut, 1);
1N/A}
1N/A
1N/A/*
1N/A** SM_IO_SEEK -- position the file pointer
1N/A**
1N/A** Parameters:
1N/A** fp -- the file pointer to be seek'd
1N/A** timeout -- time to complete seek (milliseconds)
1N/A** offset -- seek offset based on 'whence'
1N/A** whence -- indicates where seek is relative from.
1N/A** One of SM_IO_SEEK_{CUR,SET,END}.
1N/A** Returns:
1N/A** Failure: returns -1 (minus 1) and sets errno
1N/A** Success: returns 0 (zero)
1N/A*/
1N/A
1N/Aint
1N/Asm_io_seek(fp, timeout, offset, whence)
1N/A register SM_FILE_T *fp;
1N/A int SM_NONVOLATILE timeout;
1N/A long SM_NONVOLATILE offset;
1N/A int SM_NONVOLATILE whence;
1N/A{
1N/A bool havepos;
1N/A off_t target, curoff;
1N/A size_t n;
1N/A struct stat st;
1N/A int ret;
1N/A SM_EVENT *evt = NULL;
1N/A register off_t (*seekfn) __P((SM_FILE_T *, off_t, int));
1N/A
1N/A SM_REQUIRE_ISA(fp, SmFileMagic);
1N/A
1N/A /* make sure stdio is set up */
1N/A if (!Sm_IO_DidInit)
1N/A sm_init();
1N/A
1N/A /* Have to be able to seek. */
1N/A if ((seekfn = fp->f_seek) == NULL)
1N/A {
1N/A errno = ESPIPE; /* historic practice */
1N/A return -1;
1N/A }
1N/A
1N/A if (timeout == SM_TIME_DEFAULT)
1N/A timeout = fp->f_timeout;
1N/A if (timeout == SM_TIME_IMMEDIATE)
1N/A {
1N/A /*
1N/A ** Filling the buffer will take time and we are wanted to
1N/A ** return immediately. So...
1N/A */
1N/A
1N/A errno = EAGAIN;
1N/A return -1;
1N/A }
1N/A
1N/A#define SM_SET_ALARM() \
1N/A if (timeout != SM_TIME_FOREVER) \
1N/A { \
1N/A if (setjmp(SeekTimeOut) != 0) \
1N/A { \
1N/A errno = EAGAIN; \
1N/A return -1; \
1N/A } \
1N/A evt = sm_seteventm(timeout, seekalrm, 0); \
1N/A }
1N/A
1N/A /*
1N/A ** Change any SM_IO_SEEK_CUR to SM_IO_SEEK_SET, and check `whence'
1N/A ** argument. After this, whence is either SM_IO_SEEK_SET or
1N/A ** SM_IO_SEEK_END.
1N/A */
1N/A
1N/A switch (whence)
1N/A {
1N/A case SM_IO_SEEK_CUR:
1N/A
1N/A /*
1N/A ** In order to seek relative to the current stream offset,
1N/A ** we have to first find the current stream offset a la
1N/A ** ftell (see ftell for details).
1N/A */
1N/A
1N/A /* may adjust seek offset on append stream */
1N/A sm_flush(fp, (int *) &timeout);
1N/A SM_SET_ALARM();
1N/A if (fp->f_flags & SMOFF)
1N/A curoff = fp->f_lseekoff;
1N/A else
1N/A {
1N/A curoff = (*seekfn)(fp, (off_t) 0, SM_IO_SEEK_CUR);
1N/A if (curoff == -1L)
1N/A {
1N/A ret = -1;
1N/A goto clean;
1N/A }
1N/A }
1N/A if (fp->f_flags & SMRD)
1N/A {
1N/A curoff -= fp->f_r;
1N/A if (HASUB(fp))
1N/A curoff -= fp->f_ur;
1N/A }
1N/A else if (fp->f_flags & SMWR && fp->f_p != NULL)
1N/A curoff += fp->f_p - fp->f_bf.smb_base;
1N/A
1N/A offset += curoff;
1N/A whence = SM_IO_SEEK_SET;
1N/A havepos = true;
1N/A break;
1N/A
1N/A case SM_IO_SEEK_SET:
1N/A case SM_IO_SEEK_END:
1N/A SM_SET_ALARM();
1N/A curoff = 0; /* XXX just to keep gcc quiet */
1N/A havepos = false;
1N/A break;
1N/A
1N/A default:
1N/A errno = EINVAL;
1N/A return -1;
1N/A }
1N/A
1N/A /*
1N/A ** Can only optimise if:
1N/A ** reading (and not reading-and-writing);
1N/A ** not unbuffered; and
1N/A ** this is a `regular' Unix file (and hence seekfn==sm_stdseek).
1N/A ** We must check SMNBF first, because it is possible to have SMNBF
1N/A ** and SMSOPT both set.
1N/A */
1N/A
1N/A if (fp->f_bf.smb_base == NULL)
1N/A sm_makebuf(fp);
1N/A if (fp->f_flags & (SMWR | SMRW | SMNBF | SMNPT))
1N/A goto dumb;
1N/A if ((fp->f_flags & SMOPT) == 0)
1N/A {
1N/A if (seekfn != sm_stdseek ||
1N/A fp->f_file < 0 || fstat(fp->f_file, &st) ||
1N/A (st.st_mode & S_IFMT) != S_IFREG)
1N/A {
1N/A fp->f_flags |= SMNPT;
1N/A goto dumb;
1N/A }
1N/A fp->f_blksize = st.st_blksize;
1N/A fp->f_flags |= SMOPT;
1N/A }
1N/A
1N/A /*
1N/A ** We are reading; we can try to optimise.
1N/A ** Figure out where we are going and where we are now.
1N/A */
1N/A
1N/A if (whence == SM_IO_SEEK_SET)
1N/A target = offset;
1N/A else
1N/A {
1N/A if (fstat(fp->f_file, &st))
1N/A goto dumb;
1N/A target = st.st_size + offset;
1N/A }
1N/A
1N/A if (!havepos)
1N/A {
1N/A if (fp->f_flags & SMOFF)
1N/A curoff = fp->f_lseekoff;
1N/A else
1N/A {
1N/A curoff = (*seekfn)(fp, (off_t) 0, SM_IO_SEEK_CUR);
1N/A if (curoff == POS_ERR)
1N/A goto dumb;
1N/A }
1N/A curoff -= fp->f_r;
1N/A if (HASUB(fp))
1N/A curoff -= fp->f_ur;
1N/A }
1N/A
1N/A /*
1N/A ** Compute the number of bytes in the input buffer (pretending
1N/A ** that any ungetc() input has been discarded). Adjust current
1N/A ** offset backwards by this count so that it represents the
1N/A ** file offset for the first byte in the current input buffer.
1N/A */
1N/A
1N/A if (HASUB(fp))
1N/A {
1N/A curoff += fp->f_r; /* kill off ungetc */
1N/A n = fp->f_up - fp->f_bf.smb_base;
1N/A curoff -= n;
1N/A n += fp->f_ur;
1N/A }
1N/A else
1N/A {
1N/A n = fp->f_p - fp->f_bf.smb_base;
1N/A curoff -= n;
1N/A n += fp->f_r;
1N/A }
1N/A
1N/A /*
1N/A ** If the target offset is within the current buffer,
1N/A ** simply adjust the pointers, clear SMFEOF, undo ungetc(),
1N/A ** and return. (If the buffer was modified, we have to
1N/A ** skip this; see getln in fget.c.)
1N/A */
1N/A
1N/A if (target >= curoff && target < curoff + (off_t) n)
1N/A {
1N/A register int o = target - curoff;
1N/A
1N/A fp->f_p = fp->f_bf.smb_base + o;
1N/A fp->f_r = n - o;
1N/A if (HASUB(fp))
1N/A FREEUB(fp);
1N/A fp->f_flags &= ~SMFEOF;
1N/A ret = 0;
1N/A goto clean;
1N/A }
1N/A
1N/A /*
1N/A ** The place we want to get to is not within the current buffer,
1N/A ** but we can still be kind to the kernel copyout mechanism.
1N/A ** By aligning the file offset to a block boundary, we can let
1N/A ** the kernel use the VM hardware to map pages instead of
1N/A ** copying bytes laboriously. Using a block boundary also
1N/A ** ensures that we only read one block, rather than two.
1N/A */
1N/A
1N/A curoff = target & ~(fp->f_blksize - 1);
1N/A if ((*seekfn)(fp, curoff, SM_IO_SEEK_SET) == POS_ERR)
1N/A goto dumb;
1N/A fp->f_r = 0;
1N/A fp->f_p = fp->f_bf.smb_base;
1N/A if (HASUB(fp))
1N/A FREEUB(fp);
1N/A fp->f_flags &= ~SMFEOF;
1N/A n = target - curoff;
1N/A if (n)
1N/A {
1N/A /* Note: SM_TIME_FOREVER since fn timeout already set */
1N/A if (sm_refill(fp, SM_TIME_FOREVER) || fp->f_r < (int) n)
1N/A goto dumb;
1N/A fp->f_p += n;
1N/A fp->f_r -= n;
1N/A }
1N/A
1N/A ret = 0;
1N/Aclean:
1N/A /* We're back. So undo our timeout and handler */
1N/A if (evt != NULL)
1N/A sm_clrevent(evt);
1N/A return ret;
1N/Adumb:
1N/A /*
1N/A ** We get here if we cannot optimise the seek ... just
1N/A ** do it. Allow the seek function to change fp->f_bf.smb_base.
1N/A */
1N/A
1N/A /* Note: SM_TIME_FOREVER since fn timeout already set */
1N/A ret = SM_TIME_FOREVER;
1N/A if (sm_flush(fp, &ret) != 0 ||
1N/A (*seekfn)(fp, (off_t) offset, whence) == POS_ERR)
1N/A {
1N/A ret = -1;
1N/A goto clean;
1N/A }
1N/A
1N/A /* success: clear SMFEOF indicator and discard ungetc() data */
1N/A if (HASUB(fp))
1N/A FREEUB(fp);
1N/A fp->f_p = fp->f_bf.smb_base;
1N/A fp->f_r = 0;
1N/A fp->f_flags &= ~SMFEOF;
1N/A ret = 0;
1N/A goto clean;
1N/A}