sl@0: /* FSEEK.C
sl@0:  * 
sl@0:  * Portions Copyright (c) 1990-2004 Nokia Corporation and/or its subsidiary(-ies).
sl@0:  * All rights reserved.
sl@0:  */
sl@0: 
sl@0: /*
sl@0:  * Copyright (c) 1990 The Regents of the University of California.
sl@0:  * All rights reserved.
sl@0:  *
sl@0:  * Redistribution and use in source and binary forms are permitted
sl@0:  * provided that the above copyright notice and this paragraph are
sl@0:  * duplicated in all such forms and that any documentation,
sl@0:  * advertising materials, and other materials related to such
sl@0:  * distribution and use acknowledge that the software was developed
sl@0:  * by the University of California, Berkeley.  The name of the
sl@0:  * University may not be used to endorse or promote products derived
sl@0:  * from this software without specific prior written permission.
sl@0:  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
sl@0:  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
sl@0:  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
sl@0:  */
sl@0: 
sl@0: /*
sl@0: FUNCTION
sl@0: <<fseek>>---set file position
sl@0: 
sl@0: INDEX
sl@0: 	fseek
sl@0: 
sl@0: ANSI_SYNOPSIS
sl@0: 	#include <stdio.h>
sl@0: 	int fseek(FILE *<[fp]>, long <[offset]>, int <[whence]>)
sl@0: 
sl@0: TRAD_SYNOPSIS
sl@0: 	#include <stdio.h>
sl@0: 	int fseek(<[fp]>, <[offset]>, <[whence]>)
sl@0: 	FILE *<[fp]>;
sl@0: 	long <[offset]>;
sl@0: 	int <[whence]>;
sl@0: 
sl@0: DESCRIPTION
sl@0: Objects of type <<FILE>> can have a ``position'' that records how much
sl@0: of the file your program has already read.  Many of the <<stdio>> functions
sl@0: depend on this position, and many change it as a side effect.
sl@0: 
sl@0: You can use <<fseek>> to set the position for the file identified by
sl@0: <[fp]>.  The value of <[offset]> determines the new position, in one
sl@0: of three ways selected by the value of <[whence]> (defined as macros
sl@0: in `<<stdio.h>>'):
sl@0: 
sl@0: <<SEEK_SET>>---<[offset]> is the absolute file position (an offset
sl@0: from the beginning of the file) desired.  <[offset]> must be positive.
sl@0: 
sl@0: <<SEEK_CUR>>---<[offset]> is relative to the current file position.
sl@0: <[offset]> can meaningfully be either positive or negative.
sl@0: 
sl@0: <<SEEK_END>>---<[offset]> is relative to the current end of file.
sl@0: <[offset]> can meaningfully be either positive (to increase the size
sl@0: of the file) or negative.
sl@0: 
sl@0: See <<ftell>> to determine the current file position.
sl@0: 
sl@0: RETURNS
sl@0: <<fseek>> returns <<0>> when successful.  If <<fseek>> fails, the
sl@0: result is <<EOF>>.  The reason for failure is indicated in <<errno>>:
sl@0: either <<ESPIPE>> (the stream identified by <[fp]> doesn't support
sl@0: repositioning) or <<EINVAL>> (invalid file position).
sl@0: 
sl@0: PORTABILITY
sl@0: ANSI C requires <<fseek>>.
sl@0: 
sl@0: Supporting OS subroutines required: <<close>>, <<fstat>>, <<isatty>>,
sl@0: <<lseek>>, <<read>>, <<sbrk>>, <<write>>.
sl@0: */
sl@0: 
sl@0: #include <stdio_r.h>
sl@0: #include <time.h>
sl@0: #include <fcntl.h>
sl@0: #include <stdlib_r.h>
sl@0: #include <errno.h>
sl@0: #include <sys/stat.h>
sl@0: #include "LOCAL.H"
sl@0: 
sl@0: #define	POS_ERR	(-(fpos_t)1)
sl@0: 
sl@0: /*
sl@0:  * Seek the given file to the given offset.
sl@0:  * `Whence' must be one of the three SEEK_* macros.
sl@0:  */
sl@0: 
sl@0: /**
sl@0:  Reposition stream's position indicator.
sl@0: @return   If successful the function returns 0. Otherwise it returns nonzero.
sl@0: @param fp Pointer to an open file. 
sl@0: @param offset Number of bytes from origin. 
sl@0: @param whence Initial position from where offset is applied.
sl@0: */
sl@0: EXPORT_C int
sl@0: fseek (register FILE *fp,long offset,int whence)
sl@0: {
sl@0:   struct _reent *ptr;
sl@0:   fpos_t (*seekfn)(void *, fpos_t, int);
sl@0:   fpos_t target, curoff = -1;
sl@0:   size_t n;
sl@0:   struct stat st;
sl@0:   int havepos;
sl@0: 
sl@0:   /* Make sure stdio is set up.  */
sl@0: 
sl@0:   CHECK_INIT (fp);
sl@0:   ptr = fp->_data;
sl@0: 
sl@0:   /* If we've been doing some writing, and we're in append mode
sl@0:      then we don't really know where the filepos is.  */
sl@0: 
sl@0:   if (fp->_flags & __SAPP && fp->_flags & __SWR)
sl@0:     {
sl@0:       /* So flush the buffer and seek to the end.  */
sl@0:       fflush (fp);
sl@0:     }
sl@0: 
sl@0:   /* Have to be able to seek.  */
sl@0: 
sl@0:   if ((seekfn = fp->_seek) == NULL)
sl@0:     {
sl@0:       ptr->_errno = ESPIPE;	/* ??? */
sl@0:       return EOF;
sl@0:     }
sl@0: 
sl@0:   /*
sl@0:    * Change any SEEK_CUR to SEEK_SET, and check `whence' argument.
sl@0:    * After this, whence is either SEEK_SET or SEEK_END.
sl@0:    */
sl@0: 
sl@0:   switch (whence)
sl@0:     {
sl@0:     case SEEK_CUR:
sl@0:       /*
sl@0:        * In order to seek relative to the current stream offset,
sl@0:        * we have to first find the current stream offset a la
sl@0:        * ftell (see ftell for details).
sl@0:        */
sl@0:       if (fp->_flags & __SOFF)
sl@0: 		curoff = fp->_offset;
sl@0:       else
sl@0: 		{
sl@0: 		  curoff = (*seekfn) (fp->_cookie, (fpos_t) 0, SEEK_CUR);
sl@0: 		  if (curoff == -1L)
sl@0: 			return EOF;
sl@0: 		}
sl@0:       if (fp->_flags & __SRD)
sl@0: 		{
sl@0: 		  curoff -= fp->_r;
sl@0: 		  if (HASUB (fp))
sl@0: 			curoff -= fp->_ur;
sl@0: 		}
sl@0:       else if (fp->_flags & __SWR && fp->_p != NULL)
sl@0: 		curoff += fp->_p - fp->_bf._base;
sl@0: 
sl@0:       offset += curoff;
sl@0:       whence = SEEK_SET;
sl@0:       havepos = 1;
sl@0:       break;
sl@0: 
sl@0:     case SEEK_SET:
sl@0:     case SEEK_END:
sl@0:       havepos = 0;
sl@0:       break;
sl@0: 
sl@0:     default:
sl@0:       ptr->_errno = EINVAL;
sl@0:       return (EOF);
sl@0:     }
sl@0: 
sl@0:   /*
sl@0:    * Can only optimise if:
sl@0:    *	reading (and not reading-and-writing);
sl@0:    *	not unbuffered; and
sl@0:    *	this is a `regular' Unix file (and hence seekfn==__sseek).
sl@0:    * We must check __NBF first, because it is possible to have __NBF
sl@0:    * and __SOPT both set.
sl@0:    */
sl@0: 
sl@0:   if (fp->_bf._base == NULL)
sl@0:     __smakebuf (fp);
sl@0:   if (fp->_flags & (__SWR | __SRW | __SNBF | __SNPT))
sl@0:     goto dumb;
sl@0:   if ((fp->_flags & __SOPT) == 0)
sl@0:     {
sl@0:       if (seekfn != __sseek
sl@0: 	  || fp->_file < 0
sl@0: 	  || _fstat_r (ptr, fp->_file, &st)
sl@0: 	  || (st.st_mode & S_IFMT) != S_IFREG)
sl@0: 	{
sl@0: 	  fp->_flags |= __SNPT;
sl@0: 	  goto dumb;
sl@0: 	}
sl@0: #ifdef	HAVE_BLKSIZE
sl@0:       fp->_blksize = st.st_blksize;
sl@0: #else
sl@0:       fp->_blksize = 1024;
sl@0: #endif
sl@0:       fp->_flags |= __SOPT;
sl@0:     }
sl@0: 
sl@0:   /*
sl@0:    * We are reading; we can try to optimise.
sl@0:    * Figure out where we are going and where we are now.
sl@0:    */
sl@0: 
sl@0:   if (whence == SEEK_SET)
sl@0:     target = offset;
sl@0:   else
sl@0:     {
sl@0:       if (_fstat_r (ptr, fp->_file, &st))
sl@0: 	goto dumb;
sl@0:       target = st.st_size + offset;
sl@0:     }
sl@0: 
sl@0:   if (!havepos)
sl@0:     {
sl@0:       if (fp->_flags & __SOFF)
sl@0: 	curoff = fp->_offset;
sl@0:       else
sl@0: 	{
sl@0: 	  curoff = (*seekfn) (fp->_cookie, 0L, SEEK_CUR);
sl@0: 	  if (curoff == POS_ERR)
sl@0: 	    goto dumb;
sl@0: 	}
sl@0:       curoff -= fp->_r;
sl@0:       if (HASUB (fp))
sl@0: 	curoff -= fp->_ur;
sl@0:     }
sl@0: 
sl@0:   /*
sl@0:    * Compute the number of bytes in the input buffer (pretending
sl@0:    * that any ungetc() input has been discarded).  Adjust current
sl@0:    * offset backwards by this count so that it represents the
sl@0:    * file offset for the first byte in the current input buffer.
sl@0:    */
sl@0: 
sl@0:   if (HASUB (fp))
sl@0:     {
sl@0:       n = fp->_up - fp->_bf._base;
sl@0:       curoff -= n;
sl@0:       n += fp->_ur;
sl@0:     }
sl@0:   else
sl@0:     {
sl@0:       n = fp->_p - fp->_bf._base;
sl@0:       curoff -= n;
sl@0:       n += fp->_r;
sl@0:     }
sl@0: 
sl@0:   /*
sl@0:    * If the target offset is within the current buffer,
sl@0:    * simply adjust the pointers, clear EOF, undo ungetc(),
sl@0:    * and return.  (If the buffer was modified, we have to
sl@0:    * skip this; see fgetline.c.)
sl@0:    */
sl@0: 
sl@0:   if ((fp->_flags & __SMOD) == 0 &&
sl@0:       target >= curoff && target < (fpos_t)(curoff + n))
sl@0:     {
sl@0:       register int o = target - curoff;
sl@0: 
sl@0:       fp->_p = fp->_bf._base + o;
sl@0:       fp->_r = n - o;
sl@0:       if (HASUB (fp))
sl@0: 	FREEUB (fp);
sl@0:       fp->_flags &= ~__SEOF;
sl@0:       return 0;
sl@0:     }
sl@0: 
sl@0:   /*
sl@0:    * The place we want to get to is not within the current buffer,
sl@0:    * but we can still be kind to the kernel copyout mechanism.
sl@0:    * By aligning the file offset to a block boundary, we can let
sl@0:    * the kernel use the VM hardware to map pages instead of
sl@0:    * copying bytes laboriously.  Using a block boundary also
sl@0:    * ensures that we only read one block, rather than two.
sl@0:    */
sl@0: 
sl@0:   curoff = target & ~(fp->_blksize - 1);
sl@0:   if ((*seekfn) (fp->_cookie, curoff, SEEK_SET) == POS_ERR)
sl@0:     goto dumb;
sl@0:   fp->_r = 0;
sl@0:   if (HASUB (fp))
sl@0:     FREEUB (fp);
sl@0:   fp->_flags &= ~__SEOF;
sl@0:   n = target - curoff;
sl@0:   if (n)
sl@0:     {
sl@0:       if (__srefill (fp) || fp->_r < (fpos_t)n)
sl@0: 	goto dumb;
sl@0:       fp->_p += n;
sl@0:       fp->_r -= n;
sl@0:     }
sl@0:   return 0;
sl@0: 
sl@0:   /*
sl@0:    * We get here if we cannot optimise the seek ... just
sl@0:    * do it.  Allow the seek function to change fp->_bf._base.
sl@0:    */
sl@0: 
sl@0: dumb:
sl@0:   if (fflush (fp) || (*seekfn) (fp->_cookie, offset, whence) == POS_ERR)
sl@0:     return EOF;
sl@0:   /* success: clear EOF indicator and discard ungetc() data */
sl@0:   if (HASUB (fp))
sl@0:     FREEUB (fp);
sl@0:   fp->_p = fp->_bf._base;
sl@0:   fp->_r = 0;
sl@0:   /* fp->_w = 0; *//* unnecessary (I think...) */
sl@0:   fp->_flags &= ~__SEOF;
sl@0:   return 0;
sl@0: }