/* * 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: dldf.c,v 1.1 2006/07/18 02:45:30 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. */ /* df io functions */ static open_F sm_dfopen; static close_F sm_dfclose; static read_F sm_dfread; static write_F sm_dfwrite; static seek_F sm_dfseek; static setinfo_F sm_dfsetinfo; static getinfo_F sm_dfgetinfo; sm_stream_T SmDldfIO = SM_STREAM_STRUCT(sm_dfopen, sm_dfclose, sm_dfread, sm_dfwrite, NULL, NULL, sm_dfseek, sm_dfgetinfo, sm_dfsetinfo); struct sm_df_S { bool df_ondisk; sm_ret_T df_open_status; char *df_path; mode_t df_fmode; int df_oflags; }; typedef struct sm_df_S sm_df_T, *sm_df_P; #define df_path(fp) (((sm_df_P) f_cookie(*fp))->df_path) /* ** SM_DF_OPEN -- open file ** ** 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)). ** */ static sm_ret_T sm_df_open(sm_file_T *fp, const char *path, int oflags) { sm_df_P sm_df; SM_IS_FP(fp); sm_df = (sm_df_P) f_cookie(*fp); SM_REQUIRE(sm_df != NULL); if (sm_is_err(sm_df->df_open_status)) return sm_df->df_open_status; if (sm_df->df_ondisk || is_valid_fd(f_fd(*fp))) return SM_SUCCESS; f_fd(*fp) = open(path, oflags | O_NONBLOCK, 0600); if (!is_valid_fd(f_fd(*fp))) { sm_df->df_open_status = sm_error_perm(SM_EM_IO, errno); return sm_df->df_open_status; } sm_df->df_open_status = SM_SUCCESS; sm_df->df_ondisk = true; return f_fd(*fp); } /* ** SM_DFOPEN -- open a file with stdio behavior ** ** 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)). ** */ static sm_ret_T sm_dfopen(sm_file_T *fp, const void *info, int flags, va_list ap) { int oflags; size_t s; const char *path; sm_df_P sm_df; path = (const char *) info; 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); } sm_df = (sm_df_P) sm_malloc(sizeof(*sm_df)); if (NULL == sm_df) return sm_error_temp(SM_EM_IO, ENOMEM); sm_df->df_ondisk = false; sm_df->df_open_status = SM_NOTDONE; s = strlen(path) + 1; sm_df->df_path = (char *) sm_malloc(s); if (NULL == sm_df->df_path) { sm_free(sm_df); return sm_error_temp(SM_EM_IO, ENOMEM); } (void) strlcpy(sm_df->df_path, path, s); sm_df->df_fmode = 0640; sm_df->df_oflags = oflags; f_cookie(*fp) = sm_df; return 0; } /* ** SM_DFREAD -- 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. */ static sm_ret_T sm_dfread(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_DFWRITE -- 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 */ static sm_ret_T sm_dfwrite(sm_file_T *fp, const uchar *buf, size_t n, ssize_t *byteswritten) { ssize_t ret; sm_ret_T res; sm_df_P sm_df; SM_REQUIRE(fp != NULL); sm_df = (sm_df_P) f_cookie(*fp); SM_REQUIRE(sm_df != NULL); if (!is_valid_fd(f_fd(*fp))) { ret = sm_df_open(fp, sm_df->df_path, sm_df->df_oflags); if (sm_is_err(ret)) return ret; } /* 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_DFSEEK -- 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. */ static sm_ret_T sm_dfseek(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_DFCLOSE -- close the file ** ** Parameters: ** fp -- the file pointer to close ** flags -- ignored ** ** Returns: ** usual sm_error code. */ static sm_ret_T sm_dfclose(sm_file_T *fp, int flags) { int r; sm_ret_T ret; sm_df_P sm_df; SM_IS_FP(fp); sm_df = (sm_df_P) f_cookie(*fp); SM_REQUIRE(sm_df != NULL); ret = SM_SUCCESS; if (SM_IS_FLAG(flags, SM_IO_CF_RM)) { if (sm_df->df_ondisk) { const char *path; (void) close(f_fd(*fp)); path = df_path(fp); r = unlink(path); if (-1 == r && !sm_is_err(ret)) ret = sm_error_perm(SM_EM_IO, errno); sm_df->df_ondisk = false; } return ret; } /* need to make sure the file is on disk */ if (!sm_df->df_ondisk) ret = sm_df_open(fp, sm_df->df_path, sm_df->df_oflags); if (!sm_is_err(ret) && SM_IS_FLAG(flags, SM_IO_CF_SYNC)) { r = fsync(f_fd(*fp)); if (-1 == r) ret = sm_error_perm(SM_EM_IO, errno); } if (!sm_is_err(ret) && close(f_fd(*fp)) == -1) ret = sm_error_perm(SM_EM_IO, errno); return ret; } /* ** SM_DFSETMODE -- set the access mode for the file ** Called by sm_dfsetinfo(). ** 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_dfsetmode(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_DFGETMODE -- for getinfo determine open mode ** ** Called by sm_dfgetinfo(). ** ** 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_dfgetmode(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_DFSETINFO -- 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_dfsetinfo(sm_file_T *fp, int what, void *valp) { int r; switch (what) { case SM_IO_WHAT_COMMIT: SM_ASSERT(is_valid_fd(f_fd(*fp))); r = fsync(f_fd(*fp)); if (r == -1) return sm_error_perm(SM_EM_IO, errno); return SM_SUCCESS; case SM_IO_WHAT_MODE: return sm_dfsetmode(fp, (const int *)valp); default: return sm_error_perm(SM_EM_IO, EINVAL); } } /* ** SM_DFGETINFO -- 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_dfgetinfo(sm_file_T *fp, int what, void *valp) { switch (what) { case SM_IO_WHAT_MODE: return sm_dfgetmode(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); } }