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