xref: /original-bsd/lib/libc/stdio/fseek.c (revision c3e32dec)
1 /*-
2  * Copyright (c) 1990, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Chris Torek.
7  *
8  * %sccs.include.redist.c%
9  */
10 
11 #if defined(LIBC_SCCS) && !defined(lint)
12 static char sccsid[] = "@(#)fseek.c	8.1 (Berkeley) 06/04/93";
13 #endif /* LIBC_SCCS and not lint */
14 
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <fcntl.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <errno.h>
21 #include "local.h"
22 
23 #define	POS_ERR	(-(fpos_t)1)
24 
25 /*
26  * Seek the given file to the given offset.
27  * `Whence' must be one of the three SEEK_* macros.
28  */
29 fseek(fp, offset, whence)
30 	register FILE *fp;
31 	long offset;
32 	int whence;
33 {
34 #if __STDC__
35 	register fpos_t (*seekfn)(void *, fpos_t, int);
36 #else
37 	register fpos_t (*seekfn)();
38 #endif
39 	fpos_t target, curoff;
40 	size_t n;
41 	struct stat st;
42 	int havepos;
43 
44 	/* make sure stdio is set up */
45 	if (!__sdidinit)
46 		__sinit();
47 
48 	/*
49 	 * Have to be able to seek.
50 	 */
51 	if ((seekfn = fp->_seek) == NULL) {
52 		errno = ESPIPE;			/* historic practice */
53 		return (EOF);
54 	}
55 
56 	/*
57 	 * Change any SEEK_CUR to SEEK_SET, and check `whence' argument.
58 	 * After this, whence is either SEEK_SET or SEEK_END.
59 	 */
60 	switch (whence) {
61 
62 	case SEEK_CUR:
63 		/*
64 		 * In order to seek relative to the current stream offset,
65 		 * we have to first find the current stream offset a la
66 		 * ftell (see ftell for details).
67 		 */
68 		if (fp->_flags & __SOFF)
69 			curoff = fp->_offset;
70 		else {
71 			curoff = (*seekfn)(fp->_cookie, (fpos_t)0, SEEK_CUR);
72 			if (curoff == -1L)
73 				return (EOF);
74 		}
75 		if (fp->_flags & __SRD) {
76 			curoff -= fp->_r;
77 			if (HASUB(fp))
78 				curoff -= fp->_ur;
79 		} else if (fp->_flags & __SWR && fp->_p != NULL)
80 			curoff += fp->_p - fp->_bf._base;
81 
82 		offset += curoff;
83 		whence = SEEK_SET;
84 		havepos = 1;
85 		break;
86 
87 	case SEEK_SET:
88 	case SEEK_END:
89 		curoff = 0;		/* XXX just to keep gcc quiet */
90 		havepos = 0;
91 		break;
92 
93 	default:
94 		errno = EINVAL;
95 		return (EOF);
96 	}
97 
98 	/*
99 	 * Can only optimise if:
100 	 *	reading (and not reading-and-writing);
101 	 *	not unbuffered; and
102 	 *	this is a `regular' Unix file (and hence seekfn==__sseek).
103 	 * We must check __NBF first, because it is possible to have __NBF
104 	 * and __SOPT both set.
105 	 */
106 	if (fp->_bf._base == NULL)
107 		__smakebuf(fp);
108 	if (fp->_flags & (__SWR | __SRW | __SNBF | __SNPT))
109 		goto dumb;
110 	if ((fp->_flags & __SOPT) == 0) {
111 		if (seekfn != __sseek ||
112 		    fp->_file < 0 || fstat(fp->_file, &st) ||
113 		    (st.st_mode & S_IFMT) != S_IFREG) {
114 			fp->_flags |= __SNPT;
115 			goto dumb;
116 		}
117 		fp->_blksize = st.st_blksize;
118 		fp->_flags |= __SOPT;
119 	}
120 
121 	/*
122 	 * We are reading; we can try to optimise.
123 	 * Figure out where we are going and where we are now.
124 	 */
125 	if (whence == SEEK_SET)
126 		target = offset;
127 	else {
128 		if (fstat(fp->_file, &st))
129 			goto dumb;
130 		target = st.st_size + offset;
131 	}
132 
133 	if (!havepos) {
134 		if (fp->_flags & __SOFF)
135 			curoff = fp->_offset;
136 		else {
137 			curoff = (*seekfn)(fp->_cookie, (fpos_t)0, SEEK_CUR);
138 			if (curoff == POS_ERR)
139 				goto dumb;
140 		}
141 		curoff -= fp->_r;
142 		if (HASUB(fp))
143 			curoff -= fp->_ur;
144 	}
145 
146 	/*
147 	 * Compute the number of bytes in the input buffer (pretending
148 	 * that any ungetc() input has been discarded).  Adjust current
149 	 * offset backwards by this count so that it represents the
150 	 * file offset for the first byte in the current input buffer.
151 	 */
152 	if (HASUB(fp)) {
153 		n = fp->_up - fp->_bf._base;
154 		curoff -= n;
155 		n += fp->_ur;
156 	} else {
157 		n = fp->_p - fp->_bf._base;
158 		curoff -= n;
159 		n += fp->_r;
160 	}
161 
162 	/*
163 	 * If the target offset is within the current buffer,
164 	 * simply adjust the pointers, clear EOF, undo ungetc(),
165 	 * and return.  (If the buffer was modified, we have to
166 	 * skip this; see fgetline.c.)
167 	 */
168 	if ((fp->_flags & __SMOD) == 0 &&
169 	    target >= curoff && target < curoff + n) {
170 		register int o = target - curoff;
171 
172 		fp->_p = fp->_bf._base + o;
173 		fp->_r = n - o;
174 		if (HASUB(fp))
175 			FREEUB(fp);
176 		fp->_flags &= ~__SEOF;
177 		return (0);
178 	}
179 
180 	/*
181 	 * The place we want to get to is not within the current buffer,
182 	 * but we can still be kind to the kernel copyout mechanism.
183 	 * By aligning the file offset to a block boundary, we can let
184 	 * the kernel use the VM hardware to map pages instead of
185 	 * copying bytes laboriously.  Using a block boundary also
186 	 * ensures that we only read one block, rather than two.
187 	 */
188 	curoff = target & ~(fp->_blksize - 1);
189 	if ((*seekfn)(fp->_cookie, curoff, SEEK_SET) == POS_ERR)
190 		goto dumb;
191 	fp->_r = 0;
192 	if (HASUB(fp))
193 		FREEUB(fp);
194 	fp->_flags &= ~__SEOF;
195 	n = target - curoff;
196 	if (n) {
197 		if (__srefill(fp) || fp->_r < n)
198 			goto dumb;
199 		fp->_p += n;
200 		fp->_r -= n;
201 	}
202 	return (0);
203 
204 	/*
205 	 * We get here if we cannot optimise the seek ... just
206 	 * do it.  Allow the seek function to change fp->_bf._base.
207 	 */
208 dumb:
209 	if (__sflush(fp) ||
210 	    (*seekfn)(fp->_cookie, (fpos_t)offset, whence) == POS_ERR) {
211 		return (EOF);
212 	}
213 	/* success: clear EOF indicator and discard ungetc() data */
214 	if (HASUB(fp))
215 		FREEUB(fp);
216 	fp->_p = fp->_bf._base;
217 	fp->_r = 0;
218 	/* fp->_w = 0; */	/* unnecessary (I think...) */
219 	fp->_flags &= ~__SEOF;
220 	return (0);
221 }
222