/* * Copyright (c) 2000-2001, 2005-2006 Sendmail, Inc. and its suppliers. * All rights reserved. * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Chris Torek. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. */ #include SM_RCSID("@(#)$Id: refill.c,v 1.53 2006/02/28 18:48:25 ca Exp $") #include #include #include #include #include #include #include #include #include #include #include #include "local.h" static int sm_lflush __P((SM_FILE_T *, int *)); /* ** SM_IO_RD_TIMEOUT -- measured timeout for reads ** ** This #define uses a select() to wait for the 'fd' to become readable. ** The select() can be active for up to 'To' time. The select() may not ** use all of the the 'To' time. Hence, the amount of "wall-clock" time is ** measured to decide how much to subtract from 'To' to update it. On some ** BSD-based/like systems the timeout for a select() is updated for the ** amount of time used. On many/most systems this does not happen. Therefore ** the updating of 'To' must be done ourselves; a copy of 'To' is passed ** since a BSD-like system will have updated it and we don't want to ** double the time used! ** Note: if a valid 'fd' doesn't exist yet, don't use this (e.g. the ** sendmail buffered file type in sendmail/bf.c; see use below). ** ** Parameters ** fp -- the file pointer for the active file ** fd -- raw file descriptor (from 'fp') to use for select() ** to -- struct timeval of the timeout ** timeout -- the original timeout value ** sel_ret -- the return value from the select() ** ** Returns: ** nothing, flow through code */ #define SM_IO_RD_TIMEOUT(fp, fd, to, timeout, sel_ret) \ { \ struct timeval sm_io_to_before, sm_io_to_after, sm_io_to_diff; \ fd_set sm_io_to_mask, sm_io_x_mask; \ errno = 0; \ if (timeout == SM_TIME_IMMEDIATE) \ { \ errno = EAGAIN; \ return SM_IO_EOF; \ } \ if (FD_SETSIZE > 0 && (fd) >= FD_SETSIZE) \ { \ errno = EINVAL; \ return SM_IO_EOF; \ } \ FD_ZERO(&sm_io_to_mask); \ FD_SET((fd), &sm_io_to_mask); \ FD_ZERO(&sm_io_x_mask); \ FD_SET((fd), &sm_io_x_mask); \ if (gettimeofday(&sm_io_to_before, NULL) < 0) \ return SM_IO_EOF; \ do \ { \ (sel_ret) = select((fd) + 1, &sm_io_to_mask, NULL, \ &sm_io_x_mask, (to)); \ } while ((sel_ret) < 0 && errno == EINTR); \ if ((sel_ret) < 0) \ { \ /* something went wrong, errno set */ \ fp->f_r = 0; \ fp->f_flags |= SMERR; \ return SM_IO_EOF; \ } \ else if ((sel_ret) == 0) \ { \ /* timeout */ \ errno = EAGAIN; \ return SM_IO_EOF; \ } \ /* calulate wall-clock time used */ \ if (gettimeofday(&sm_io_to_after, NULL) < 0) \ return SM_IO_EOF; \ timersub(&sm_io_to_after, &sm_io_to_before, &sm_io_to_diff); \ timersub((to), &sm_io_to_diff, (to)); \ } /* ** SM_LFLUSH -- flush a file if it is line buffered and writable ** ** Parameters: ** fp -- file pointer to flush ** timeout -- original timeout value (in milliseconds) ** ** Returns: ** Failure: returns SM_IO_EOF and sets errno ** Success: returns 0 */ static int sm_lflush(fp, timeout) SM_FILE_T *fp; int *timeout; { if ((fp->f_flags & (SMLBF|SMWR)) == (SMLBF|SMWR)) return sm_flush(fp, timeout); return 0; } /* ** SM_REFILL -- refill a buffer ** ** Parameters: ** fp -- file pointer for buffer refill ** timeout -- time to complete filling the buffer in milliseconds ** ** Returns: ** Success: returns 0 ** Failure: returns SM_IO_EOF */ int sm_refill(fp, timeout) register SM_FILE_T *fp; int timeout; { int ret, r; struct timeval to; int fd; if (timeout == SM_TIME_DEFAULT) timeout = fp->f_timeout; if (timeout == SM_TIME_IMMEDIATE) { /* ** Filling the buffer will take time and we are wanted to ** return immediately. And we're not EOF or ERR really. ** So... the failure is we couldn't do it in time. */ errno = EAGAIN; fp->f_r = 0; /* just to be sure */ return 0; } /* make sure stdio is set up */ if (!Sm_IO_DidInit) sm_init(); fp->f_r = 0; /* largely a convenience for callers */ if (fp->f_flags & SMFEOF) return SM_IO_EOF; SM_CONVERT_TIME(fp, fd, timeout, &to); /* if not already reading, have to be reading and writing */ if ((fp->f_flags & SMRD) == 0) { if ((fp->f_flags & SMRW) == 0) { errno = EBADF; fp->f_flags |= SMERR; return SM_IO_EOF; } /* switch to reading */ if (fp->f_flags & SMWR) { if (sm_flush(fp, &timeout)) return SM_IO_EOF; fp->f_flags &= ~SMWR; fp->f_w = 0; fp->f_lbfsize = 0; } fp->f_flags |= SMRD; } else { /* ** We were reading. If there is an ungetc buffer, ** we must have been reading from that. Drop it, ** restoring the previous buffer (if any). If there ** is anything in that buffer, return. */ if (HASUB(fp)) { FREEUB(fp); if ((fp->f_r = fp->f_ur) != 0) { fp->f_p = fp->f_up; /* revert blocking state */ return 0; } } } if (fp->f_bf.smb_base == NULL) sm_makebuf(fp); /* ** Before reading from a line buffered or unbuffered file, ** flush all line buffered output files, per the ANSI C standard. */ if (fp->f_flags & (SMLBF|SMNBF)) (void) sm_fwalk(sm_lflush, &timeout); /* ** If this file is linked to another, and we are going to hang ** on the read, flush the linked file before continuing. */ if (fp->f_flushfp != NULL && (*fp->f_getinfo)(fp, SM_IO_IS_READABLE, NULL) <= 0) sm_flush(fp->f_flushfp, &timeout); fp->f_p = fp->f_bf.smb_base; /* ** The do-while loop stops trying to read when something is read ** or it appears that the timeout has expired before finding ** something available to be read (via select()). */ ret = 0; do { errno = 0; /* needed to ensure EOF correctly found */ r = (*fp->f_read)(fp, (char *)fp->f_p, fp->f_bf.smb_size); if (r <= 0) { if (r == 0 && errno == 0) break; /* EOF found */ if (IS_IO_ERROR(fd, r, timeout)) goto err; /* errno set */ /* read would block */ SM_IO_RD_TIMEOUT(fp, fd, &to, timeout, ret); } } while (r <= 0 && ret > 0); err: if (r <= 0) { if (r == 0) fp->f_flags |= SMFEOF; else fp->f_flags |= SMERR; fp->f_r = 0; return SM_IO_EOF; } fp->f_r = r; return 0; } /* ** SM_RGET -- refills buffer and returns first character ** ** Handle sm_getc() when the buffer ran out: ** Refill, then return the first character in the newly-filled buffer. ** ** Parameters: ** fp -- file pointer to work on ** timeout -- time to complete refill ** ** Returns: ** Success: first character in refilled buffer as an int ** Failure: SM_IO_EOF */ int sm_rget(fp, timeout) register SM_FILE_T *fp; int timeout; { if (sm_refill(fp, timeout) == 0) { fp->f_r--; return *fp->f_p++; } return SM_IO_EOF; }