1 /* 2 * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers. 3 * All rights reserved. 4 * Copyright (c) 1990, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Chris Torek. 9 * 10 * By using this file, you agree to the terms and conditions set 11 * forth in the LICENSE file which can be found at the top level of 12 * the sendmail distribution. 13 */ 14 15 #include <sm/gen.h> 16 SM_RCSID("@(#)$Id: fseek.c,v 1.45 2001/09/11 04:04:48 gshapiro Exp $") 17 #include <sys/types.h> 18 #include <sys/stat.h> 19 #include <fcntl.h> 20 #include <stdlib.h> 21 #include <errno.h> 22 #include <setjmp.h> 23 #include <sys/time.h> 24 #include <sm/signal.h> 25 #include <sm/io.h> 26 #include <sm/assert.h> 27 #include <sm/clock.h> 28 #include "local.h" 29 30 #define POS_ERR (-(off_t)1) 31 32 static jmp_buf SeekTimeOut; 33 34 /* 35 ** SEEKALRM -- handler when timeout activated for sm_io_seek() 36 ** 37 ** Returns flow of control to where setjmp(SeekTimeOut) was set. 38 ** 39 ** Parameters: 40 ** sig -- unused 41 ** 42 ** Returns: 43 ** does not return 44 ** 45 ** Side Effects: 46 ** returns flow of control to setjmp(SeekTimeOut). 47 ** 48 ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD 49 ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE 50 ** DOING. 51 */ 52 53 /* ARGSUSED0 */ 54 static void 55 seekalrm(sig) 56 int sig; 57 { 58 longjmp(SeekTimeOut, 1); 59 } 60 61 /* 62 ** SM_IO_SEEK -- position the file pointer 63 ** 64 ** Parameters: 65 ** fp -- the file pointer to be seek'd 66 ** timeout -- time to complete seek (milliseconds) 67 ** offset -- seek offset based on 'whence' 68 ** whence -- indicates where seek is relative from. 69 ** One of SM_IO_SEEK_{CUR,SET,END}. 70 ** Returns: 71 ** Failure: returns -1 (minus 1) and sets errno 72 ** Success: returns 0 (zero) 73 */ 74 75 int 76 sm_io_seek(fp, timeout, offset, whence) 77 register SM_FILE_T *fp; 78 int SM_NONVOLATILE timeout; 79 long SM_NONVOLATILE offset; 80 int SM_NONVOLATILE whence; 81 { 82 bool havepos; 83 off_t target, curoff; 84 size_t n; 85 struct stat st; 86 int ret; 87 SM_EVENT *evt = NULL; 88 register off_t (*seekfn) __P((SM_FILE_T *, off_t, int)); 89 90 SM_REQUIRE_ISA(fp, SmFileMagic); 91 92 /* make sure stdio is set up */ 93 if (!Sm_IO_DidInit) 94 sm_init(); 95 96 /* Have to be able to seek. */ 97 if ((seekfn = fp->f_seek) == NULL) 98 { 99 errno = ESPIPE; /* historic practice */ 100 return -1; 101 } 102 103 if (timeout == SM_TIME_DEFAULT) 104 timeout = fp->f_timeout; 105 if (timeout == SM_TIME_IMMEDIATE) 106 { 107 /* 108 ** Filling the buffer will take time and we are wanted to 109 ** return immediately. So... 110 */ 111 112 errno = EAGAIN; 113 return -1; 114 } 115 116 #define SM_SET_ALARM() \ 117 if (timeout != SM_TIME_FOREVER) \ 118 { \ 119 if (setjmp(SeekTimeOut) != 0) \ 120 { \ 121 errno = EAGAIN; \ 122 return -1; \ 123 } \ 124 evt = sm_seteventm(timeout, seekalrm, 0); \ 125 } 126 127 /* 128 ** Change any SM_IO_SEEK_CUR to SM_IO_SEEK_SET, and check `whence' 129 ** argument. After this, whence is either SM_IO_SEEK_SET or 130 ** SM_IO_SEEK_END. 131 */ 132 133 switch (whence) 134 { 135 case SM_IO_SEEK_CUR: 136 137 /* 138 ** In order to seek relative to the current stream offset, 139 ** we have to first find the current stream offset a la 140 ** ftell (see ftell for details). 141 */ 142 143 /* may adjust seek offset on append stream */ 144 sm_flush(fp, (int *) &timeout); 145 SM_SET_ALARM(); 146 if (fp->f_flags & SMOFF) 147 curoff = fp->f_lseekoff; 148 else 149 { 150 curoff = (*seekfn)(fp, (off_t) 0, SM_IO_SEEK_CUR); 151 if (curoff == -1L) 152 { 153 ret = -1; 154 goto clean; 155 } 156 } 157 if (fp->f_flags & SMRD) 158 { 159 curoff -= fp->f_r; 160 if (HASUB(fp)) 161 curoff -= fp->f_ur; 162 } 163 else if (fp->f_flags & SMWR && fp->f_p != NULL) 164 curoff += fp->f_p - fp->f_bf.smb_base; 165 166 offset += curoff; 167 whence = SM_IO_SEEK_SET; 168 havepos = true; 169 break; 170 171 case SM_IO_SEEK_SET: 172 case SM_IO_SEEK_END: 173 SM_SET_ALARM(); 174 curoff = 0; /* XXX just to keep gcc quiet */ 175 havepos = false; 176 break; 177 178 default: 179 errno = EINVAL; 180 return -1; 181 } 182 183 /* 184 ** Can only optimise if: 185 ** reading (and not reading-and-writing); 186 ** not unbuffered; and 187 ** this is a `regular' Unix file (and hence seekfn==sm_stdseek). 188 ** We must check SMNBF first, because it is possible to have SMNBF 189 ** and SMSOPT both set. 190 */ 191 192 if (fp->f_bf.smb_base == NULL) 193 sm_makebuf(fp); 194 if (fp->f_flags & (SMWR | SMRW | SMNBF | SMNPT)) 195 goto dumb; 196 if ((fp->f_flags & SMOPT) == 0) 197 { 198 if (seekfn != sm_stdseek || 199 fp->f_file < 0 || fstat(fp->f_file, &st) || 200 (st.st_mode & S_IFMT) != S_IFREG) 201 { 202 fp->f_flags |= SMNPT; 203 goto dumb; 204 } 205 fp->f_blksize = st.st_blksize; 206 fp->f_flags |= SMOPT; 207 } 208 209 /* 210 ** We are reading; we can try to optimise. 211 ** Figure out where we are going and where we are now. 212 */ 213 214 if (whence == SM_IO_SEEK_SET) 215 target = offset; 216 else 217 { 218 if (fstat(fp->f_file, &st)) 219 goto dumb; 220 target = st.st_size + offset; 221 } 222 223 if (!havepos) 224 { 225 if (fp->f_flags & SMOFF) 226 curoff = fp->f_lseekoff; 227 else 228 { 229 curoff = (*seekfn)(fp, (off_t) 0, SM_IO_SEEK_CUR); 230 if (curoff == POS_ERR) 231 goto dumb; 232 } 233 curoff -= fp->f_r; 234 if (HASUB(fp)) 235 curoff -= fp->f_ur; 236 } 237 238 /* 239 ** Compute the number of bytes in the input buffer (pretending 240 ** that any ungetc() input has been discarded). Adjust current 241 ** offset backwards by this count so that it represents the 242 ** file offset for the first byte in the current input buffer. 243 */ 244 245 if (HASUB(fp)) 246 { 247 curoff += fp->f_r; /* kill off ungetc */ 248 n = fp->f_up - fp->f_bf.smb_base; 249 curoff -= n; 250 n += fp->f_ur; 251 } 252 else 253 { 254 n = fp->f_p - fp->f_bf.smb_base; 255 curoff -= n; 256 n += fp->f_r; 257 } 258 259 /* 260 ** If the target offset is within the current buffer, 261 ** simply adjust the pointers, clear SMFEOF, undo ungetc(), 262 ** and return. (If the buffer was modified, we have to 263 ** skip this; see getln in fget.c.) 264 */ 265 266 if (target >= curoff && target < curoff + (off_t) n) 267 { 268 register int o = target - curoff; 269 270 fp->f_p = fp->f_bf.smb_base + o; 271 fp->f_r = n - o; 272 if (HASUB(fp)) 273 FREEUB(fp); 274 fp->f_flags &= ~SMFEOF; 275 ret = 0; 276 goto clean; 277 } 278 279 /* 280 ** The place we want to get to is not within the current buffer, 281 ** but we can still be kind to the kernel copyout mechanism. 282 ** By aligning the file offset to a block boundary, we can let 283 ** the kernel use the VM hardware to map pages instead of 284 ** copying bytes laboriously. Using a block boundary also 285 ** ensures that we only read one block, rather than two. 286 */ 287 288 curoff = target & ~(fp->f_blksize - 1); 289 if ((*seekfn)(fp, curoff, SM_IO_SEEK_SET) == POS_ERR) 290 goto dumb; 291 fp->f_r = 0; 292 fp->f_p = fp->f_bf.smb_base; 293 if (HASUB(fp)) 294 FREEUB(fp); 295 fp->f_flags &= ~SMFEOF; 296 n = target - curoff; 297 if (n) 298 { 299 /* Note: SM_TIME_FOREVER since fn timeout already set */ 300 if (sm_refill(fp, SM_TIME_FOREVER) || fp->f_r < (int) n) 301 goto dumb; 302 fp->f_p += n; 303 fp->f_r -= n; 304 } 305 306 ret = 0; 307 clean: 308 /* We're back. So undo our timeout and handler */ 309 if (evt != NULL) 310 sm_clrevent(evt); 311 return ret; 312 dumb: 313 /* 314 ** We get here if we cannot optimise the seek ... just 315 ** do it. Allow the seek function to change fp->f_bf.smb_base. 316 */ 317 318 /* Note: SM_TIME_FOREVER since fn timeout already set */ 319 ret = SM_TIME_FOREVER; 320 if (sm_flush(fp, &ret) != 0 || 321 (*seekfn)(fp, (off_t) offset, whence) == POS_ERR) 322 { 323 ret = -1; 324 goto clean; 325 } 326 327 /* success: clear SMFEOF indicator and discard ungetc() data */ 328 if (HASUB(fp)) 329 FREEUB(fp); 330 fp->f_p = fp->f_bf.smb_base; 331 fp->f_r = 0; 332 fp->f_flags &= ~SMFEOF; 333 ret = 0; 334 goto clean; 335 } 336