/* * Copyright (c) 2000-2002, 2004, 2005 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/generic.h" SM_RCSID("@(#)$Id: stdio.c,v 1.33 2006/07/16 02:07:40 ca Exp $") #include "sm/error.h" #include "fcntl.h" #include "sm/memops.h" #include "sm/stat.h" #include "sm/time.h" #include "sm/heap.h" #include "sm/assert.h" #include "sm/varargs.h" #include "sm/io.h" #include "sm/fdset.h" #include "io-int.h" /* ** Overall: ** Small standard I/O/seek/close functions. ** These maintain the `known seek offset' for seek optimization. */ /* ** SM_STDOPEN -- open a file with stdio behavior ** ** Not associated with the system's stdio in libc. ** ** Parameters: ** fp -- file pointer to be associated with the open ** info -- pathname of the file to be opened ** flags -- indicates type of access methods ** ** Returns: ** Failure: error code ** Success: 0 or greater (fd of file from open(2)). ** */ /* ARGSUSED3 */ sm_ret_T sm_stdopen(sm_file_T *fp, const void *info, int flags, va_list ap) { const char *path = (const char *) info; int oflags; switch (flags) { case SM_IO_RDWR: oflags = O_RDWR; break; case SM_IO_RDWRCR: oflags = O_RDWR | O_CREAT; break; case SM_IO_RDWRTR: oflags = O_RDWR | O_CREAT | O_TRUNC; break; case SM_IO_RDONLY: oflags = O_RDONLY; break; case SM_IO_WRONLY: oflags = O_WRONLY | O_CREAT | O_TRUNC; break; case SM_IO_APPEND: oflags = O_APPEND | O_WRONLY | O_CREAT; break; case SM_IO_APPENDRW: oflags = O_APPEND | O_RDWR | O_CREAT; break; default: return sm_error_perm(SM_EM_IO, EINVAL); } f_fd(*fp) = open(path, oflags | O_NONBLOCK, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); if (f_fd(*fp) < 0) return sm_error_perm(SM_EM_IO, errno); if (oflags & O_APPEND) (void) f_seek(*fp)(fp, (off_t) 0, SEEK_END); return f_fd(*fp); } /* ** SM_STDREAD -- read from the file ** ** Parameters: ** fp -- file pointer to read from ** buf -- location to place read data ** n -- number of bytes to read ** bytesread -- number of bytes read (output) ** ** Returns: ** usual sm_error code ** ** Side Effects: ** Updates internal offset into file. */ sm_ret_T sm_stdread(sm_file_T *fp, uchar *buf, size_t n, ssize_t *bytesread) { ssize_t ret; sm_ret_T res; /* should we decrease the timeout in the loop? */ *bytesread = 0; do { if (fp->f_timeout > 0) { res = sm_read_wait(f_fd(*fp), fp->f_timeout); if (sm_is_err(res)) return res; } errno = 0; ret = read(f_fd(*fp), buf, n); } while (ret == -1 && errno == EINTR); if (ret == -1) return sm_error_perm(SM_EM_IO, errno); *bytesread = ret; /* if the read succeeded, update the current offset */ if (ret > 0) fp->f_lseekoff += ret; return SM_SUCCESS; } /* ** SM_STDWRITE -- write to the file ** ** Parameters: ** fp -- file pointer ro write to ** buf -- location of data to be written ** n -- number of bytes to write ** byteswritten -- number of bytes written (output) ** ** Returns: ** usual sm_error code */ sm_ret_T sm_stdwrite(sm_file_T *fp, const uchar *buf, size_t n, ssize_t *byteswritten) { ssize_t ret; sm_ret_T res; /* should we decrease the timeout in the loop? */ *byteswritten = 0; do { if (fp->f_timeout > 0) { res = sm_write_wait(f_fd(*fp), fp->f_timeout); if (sm_is_err(res)) return res; } errno = 0; ret = write(f_fd(*fp), buf, n); } while (ret == -1 && errno == EINTR); if (ret == -1) return sm_error_perm(SM_EM_IO, errno); *byteswritten = ret; return SM_SUCCESS; } /* ** SM_STDSEEK -- set the file offset position ** ** Parmeters: ** fp -- file pointer to position ** offset -- how far to position from "base" (set by 'whence') ** whence -- indicates where the "base" of the 'offset' to start ** ** Results: ** Failure: -1 and sets errno ** Success: the current offset ** ** Side Effects: ** Updates the internal value of the offset. */ sm_ret_T sm_stdseek(sm_file_T *fp, off_t offset, int whence) { off_t ret; ret = lseek(f_fd(*fp), (off_t) offset, whence); if (ret == (off_t) -1) return sm_error_perm(SM_EM_IO, errno); fp->f_lseekoff = ret; return SM_SUCCESS; } /* ** SM_STDCLOSE -- close the file ** ** Parameters: ** fp -- the file pointer to close ** flags -- ignored ** ** Returns: ** usual sm_error code. */ sm_ret_T sm_stdclose(sm_file_T *fp, int flags) { if (close(f_fd(*fp)) == -1) return sm_error_perm(SM_EM_IO, errno); return SM_SUCCESS; } /* ** SM_STDSETMODE -- set the access mode for the file ** Called by sm_stdsetinfo(). ** Do we really want to allow changing the mode?? ** ** Parameters: ** fp -- file pointer ** mode -- new mode to set the file access to ** ** Results: ** usual sm_error code. */ static sm_ret_T sm_stdsetmode(sm_file_T *fp, const int *mode) { int flags = 0; switch (*mode) { case SM_IO_RDWR: flags |= SMRW; break; case SM_IO_RDONLY: flags |= SMRD; break; case SM_IO_WRONLY: flags |= SMWR; break; case SM_IO_APPEND: default: return sm_error_perm(SM_EM_IO, EINVAL); } f_flags(*fp) = f_flags(*fp) & ~SMMODEMASK; f_flags(*fp) |= flags; return SM_SUCCESS; } /* ** SM_STDGETMODE -- for getinfo determine open mode ** ** Called by sm_stdgetinfo(). ** ** Parameters: ** fp -- the file mode being determined ** mode -- internal mode to map to external value ** ** Results: ** Success: external mode value ** Failure: usual sm_error code. */ static sm_ret_T sm_stdgetmode(sm_file_T *fp, int *mode) { switch (f_flags(*fp) & SMMODEMASK) { case SMRW: *mode = SM_IO_RDWR; break; case SMRD: *mode = SM_IO_RDONLY; break; case SMWR: *mode = SM_IO_WRONLY; break; default: return sm_error_perm(SM_EM_IO, EINVAL); } return SM_SUCCESS; } /* ** SM_STDSETINFO -- set/modify information for a file ** ** Parameters: ** fp -- file to set info for ** what -- type of info to set ** valp -- location of data used for setting ** ** Returns: ** usual sm_error code. */ sm_ret_T sm_stdsetinfo(sm_file_T *fp, int what, void *valp) { switch (what) { case SM_IO_WHAT_MODE: return sm_stdsetmode(fp, (const int *)valp); default: return sm_error_perm(SM_EM_IO, EINVAL); } } /* ** SM_STDGETINFO -- get information about the open file ** ** Parameters: ** fp -- file to get info for ** what -- type of info to get ** valp -- location to place found info ** ** Returns: ** Success: may or may not place info in 'valp' depending ** on 'what' value, and returns values >=0. Return ** value may be the obtained info ** Failure: usual sm_error code. */ sm_ret_T sm_stdgetinfo(sm_file_T *fp, int what, void *valp) { switch (what) { case SM_IO_WHAT_MODE: return sm_stdgetmode(fp, (int *)valp); case SM_IO_WHAT_FD: return f_fd(*fp); case SM_IO_WHAT_SIZE: { struct stat st; if (fstat(f_fd(*fp), &st) < 0) return sm_error_perm(SM_EM_IO, errno); return st.st_size; } case SM_IO_IS_READABLE: { fd_set readfds; struct timeval timeout; FD_ZERO(&readfds); SM_FD_SET(f_fd(*fp), &readfds); timeout.tv_sec = 0; timeout.tv_usec = 0; if (select(f_fd(*fp) + 1, FDSET_CAST &readfds, NULL, NULL, &timeout) > 0 && SM_FD_ISSET(f_fd(*fp), &readfds)) return 1; return 0; } default: return sm_error_perm(SM_EM_IO, EINVAL); } } /* ** SM_STDFDOPEN -- open file by primitive 'fd' rather than pathname ** ** I/O function to handle fdopen() stdio equivalence. The rest of ** the functions are the same as the sm_stdopen() above. ** ** Parameters: ** fp -- the file pointer to be associated with the open ** name -- the primitive file descriptor for association ** flags -- indicates type of access methods ** ** Results: ** usual sm_error code. */ /* ARGSUSED3 */ sm_ret_T sm_stdfdopen(sm_file_T *fp, const void *info, int flags, va_list ap) { int oflags, tmp, fdflags, fd; fd = *((int *) info); switch (flags) { case SM_IO_RDWR: oflags = O_RDWR | O_CREAT; break; case SM_IO_RDONLY: oflags = O_RDONLY; break; case SM_IO_WRONLY: oflags = O_WRONLY | O_CREAT | O_TRUNC; break; case SM_IO_APPEND: oflags = O_APPEND | O_WRONLY | O_CREAT; break; case SM_IO_APPENDRW: oflags = O_APPEND | O_RDWR | O_CREAT; break; default: return sm_error_perm(SM_EM_IO, EINVAL); } /* Make sure the mode the user wants is a subset of the actual mode. */ errno = 0; fdflags = fcntl(fd, F_GETFL, 0); if (fdflags < 0) return sm_error_perm(SM_EM_IO, errno); tmp = fdflags & O_ACCMODE; if (tmp != O_RDWR && (tmp != (oflags & O_ACCMODE))) return sm_error_perm(SM_EM_IO, EINVAL); f_fd(*fp) = fd; if (oflags & O_APPEND) (void) (*fp->f_stream.fs_seek)(fp, (off_t)0, SEEK_END); return SM_SUCCESS; } /* ** SM_IO_FOPEN -- open a file ** ** Same interface and semantics as the open() system call, ** except that it returns sm_file_T* instead of a file descriptor. ** ** Parameters: ** pathname -- path of file to open ** flags -- flags controlling the open ** ... -- option "mode" for opening the file ** ** Returns: ** Returns NULL and sets errno if open() fails. ** Returns an sm_file_T pointer on success. */ sm_ret_T sm_io_fopen(char *pathname, int flags, sm_file_T **newfp, ...) { mode_t mode; sm_file_T *fp; int ioflags; sm_ret_T res; if (flags & O_CREAT) { va_list ap; va_start(ap, newfp); mode = (mode_t) va_arg(ap, int); va_end(ap); } else mode = 0; switch (flags & O_ACCMODE) { case O_RDONLY: ioflags = SMRD; break; case O_WRONLY: ioflags = SMWR; break; case O_RDWR: ioflags = SMRW; break; default: sm_abort("sm_io_fopen: bad flags 0%o", flags); } res = sm_fp(SmStStdio, ioflags, NULL, &fp); if (sm_is_err(res)) return res; errno = 0; f_fd(*fp) = open(pathname, flags, mode); if (f_fd(*fp) == -1) { f_flags(*fp) = 0; fp->sm_magic = SM_MAGIC_NULL; return sm_error_perm(SM_EM_IO, errno); } *newfp = fp; return SM_SUCCESS; }