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