/* * Copyright (c) 2002-2005 Sendmail, Inc. and its suppliers. * All rights reserved. * * 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: cdb.c,v 1.40 2006/07/18 02:43:18 ca Exp $") #include "sm/error.h" #include "sm/fcntl.h" #include "sm/memops.h" #include "sm/stat.h" #include "sm/time.h" #include "sm/heap.h" #include "sm/assert.h" #include "sm/str.h" #include "sm/io.h" #include "sm/fdset.h" #include "sm/cdb.h" #include "cdb.h" static read_F sm_cdbread; static write_F sm_cdbwrite; static close_F sm_cdbclose; static getinfo_F sm_cdbgetinfo; static setinfo_F sm_cdbsetinfo; static seek_F sm_cdbseek; /* we could use f_cookie() to store the handle for the cdb pass it to open via SM_WHAT_CDB_HANDLE. missing: cdb_start() cdb_end() */ sm_stream_T SmCDBSt = SM_STREAM_STRUCT(sm_cdbopen, sm_cdbclose, sm_cdbread, sm_cdbwrite, NULL, NULL, sm_cdbseek, sm_cdbgetinfo, sm_cdbsetinfo); struct cdb_cookie_S { char *cdbck_path; cdb_ctx_P cdbck_ctx; /* store the cdb handle here */ }; typedef struct cdb_cookie_S cdb_cookie_T, *cdb_cookie_P; #define cdb_path(fp) (((cdb_cookie_P) f_cookie(*fp))->cdbck_path) /* ** CDB_CRT_PATH -- construct a path name based on cdb id ** ** Parameters: ** cdb_id -- cdb id ** base -- base name ** cdb_path -- generated path name (output) ** path_len -- maximum length of cdb_path ** ** Returns: ** usual sm_error code, E2BIG ** ** Interdependencies: ** This function depends on the structure of ta_id as defined ** in sm/mta.h, cdb_id must have that structure. ** cdb_remove() and cdb_cookie_new() invoke this function, ** cdbfs.c depends on the directory hashing used by this function. ** ** Last code review: 2005-03-17 06:36:45 ** Last code change: */ sm_ret_T cdb_crt_path(const char *cdb_id, const char *base, char *cdb_path, size_t path_len) { size_t t, l, p; SM_REQUIRE(cdb_id != NULL); SM_REQUIRE(cdb_path != NULL); SM_REQUIRE(path_len > 5); t = l = strlen(cdb_id) + 3; p = 0; if (base != NULL && *base != '\0') { p = strlen(base); t += p; } if (t > path_len || t < 6) return sm_error_perm(SM_EM_CDB, SM_E_RANGE); if (base != NULL && *base != '\0') strlcpy(cdb_path, base, t); cdb_path[p + 0] = cdb_id[l - 6]; /* one level hashing */ cdb_path[p + 1] = '/'; cdb_path[p + 2] = '\0'; strlcat(cdb_path, cdb_id, t); return SM_SUCCESS; } /* ** CDB_COOKIE_NEW -- create a CDB cookie based on cdb id ** ** Parameters: ** cdb_id -- cdb id ** cdb_ctx -- CDB context ** ** Returns: ** CDB cookie (NULL on error [ENOMEM]) ** ** Last code review: 2005-03-17 06:38:20 ** Last code change: */ static cdb_cookie_P cdb_cookie_new(const char *cdb_id, cdb_ctx_P cdb_ctx) { size_t l; cdb_cookie_P cdb_cookie; sm_ret_T ret; SM_ASSERT(cdb_id != NULL); SM_IS_CDB_CTX(cdb_ctx); cdb_cookie = (cdb_cookie_P) sm_zalloc(sizeof(*cdb_cookie)); if (cdb_cookie == NULL) return NULL; l = strlen(cdb_id) + 3; SM_ASSERT(l >= 5); if (cdb_ctx->cdbx_base != NULL && *cdb_ctx->cdbx_base != '\0') l += strlen(cdb_ctx->cdbx_base); cdb_cookie->cdbck_path = (char *) sm_malloc(l); if (cdb_cookie->cdbck_path == NULL) goto error; cdb_cookie->cdbck_ctx = cdb_ctx; ret = cdb_crt_path(cdb_id, cdb_ctx->cdbx_base, cdb_cookie->cdbck_path, l); if (sm_is_err(ret)) goto error; return cdb_cookie; error: if (cdb_cookie != NULL) { if (cdb_cookie->cdbck_path != NULL) sm_free(cdb_cookie->cdbck_path); sm_free_size(cdb_cookie, sizeof(*cdb_cookie)); } return NULL; } /* ** CDB_COOKIE_FREE -- free a CDB cookie ** ** Parameters: ** cdb_cookie -- CDB cookie ** ** Returns: ** none ** ** Last code review: 2005-03-17 06:41:35 ** Last code change: 2005-03-17 06:41:32 */ static void cdb_cookie_free(cdb_cookie_P cdb_cookie) { if (cdb_cookie == NULL) return; if (cdb_cookie->cdbck_path != NULL) sm_free(cdb_cookie->cdbck_path); sm_free_size(cdb_cookie, sizeof(*cdb_cookie)); return; } /* ** SM_CDBOPEN -- open a file for CDB ** ** Parameters: ** fp -- file pointer to be associated with the open ** info -- filename of the file to be opened ** flags -- indicates type of access methods ** ap -- further arguments ** ** Returns: ** usual sm_error code; ENOMEM, EINVAL ** ** Last code review: 2005-03-17 06:43:47; see below ** Last code change: */ sm_ret_T sm_cdbopen(sm_file_T *fp, const void *info, int flags, va_list ap) { int oflags, l; const char *path; cdb_cookie_P cdb_cookie; cdb_ctx_P cdb_ctx; cdb_ctx = NULL; 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_WREXCL: oflags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL; break; case SM_IO_RDWRCRX: oflags = O_RDWR | O_CREAT | O_TRUNC | O_EXCL; 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_CDB, EINVAL); } for (;;) { l = va_arg(ap, int); if (l == SM_IO_WHAT_END) break; switch (l) { case SM_IO_WHAT_CDB_HDL: cdb_ctx = va_arg(ap, cdb_ctx_P); SM_IS_CDB_CTX(cdb_ctx); break; default: /* ignore unknown values? */ /* what should we do about the argument then? */ (void *)va_arg(ap, int); break; } } SM_IS_CDB_CTX(cdb_ctx); cdb_cookie = cdb_cookie_new(path, cdb_ctx); if (cdb_cookie == NULL) return sm_error_temp(SM_EM_CDB, ENOMEM); f_cookie(*fp) = (void *) cdb_cookie; f_fd(*fp) = open(cdb_cookie->cdbck_path, oflags | O_NONBLOCK, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); if (f_fd(*fp) < 0) { cdb_cookie_free(cdb_cookie); f_cookie(*fp) = NULL; return sm_error_perm(SM_EM_CDB, errno); } if (oflags & O_APPEND) (void) f_seek(*fp)(fp, (off_t) 0, SEEK_END); return f_fd(*fp); } /* ** SM_CDBREAD -- read from CDB 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. ** ** Last code review: 2005-03-17 06:45:42 ** Last code change: */ static sm_ret_T sm_cdbread(sm_file_T *fp, uchar *buf, size_t n, ssize_t *bytesread) { ssize_t ret; *bytesread = 0; do { errno = 0; ret = read(f_fd(*fp), buf, n); } while (-1 == ret && errno == EINTR); if (-1 == ret) return sm_error_perm(SM_EM_CDB, errno); *bytesread = ret; /* if the read succeeded, update the current offset */ if (ret > 0) fp->f_lseekoff += ret; return SM_SUCCESS; } /* ** SM_CDBWRITE -- write to CDB 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 ** ** Last code review: 2005-03-17 18:08:38 ** Last code change: */ static sm_ret_T sm_cdbwrite(sm_file_T *fp, const uchar *buf, size_t n, ssize_t *byteswritten) { ssize_t ret; *byteswritten = 0; do { errno = 0; ret = write(f_fd(*fp), buf, n); } while (-1 == ret && errno == EINTR); if (-1 == ret) return sm_error_perm(SM_EM_CDB, errno); *byteswritten = ret; return SM_SUCCESS; } /* ** SM_CDBSEEK -- set CDB 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: ** usual sm_error code; errno from lseek() ** ** Side Effects: ** Updates the internal value of the offset. ** ** Last code review: 2005-03-17 18:11:17 ** Last code change: */ static sm_ret_T sm_cdbseek(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_CDB, errno); fp->f_lseekoff = ret; return SM_SUCCESS; } /* ** SM_CDBCLOSE -- close CDB file ** ** Parameters: ** fp -- the file pointer to close ** flags -- flags ** ** Returns: ** usual sm_error code; errno from close() ** ** Last code review: 2006-07-16 02:20:45 ** Last code change: */ static sm_ret_T sm_cdbclose(sm_file_T *fp, int flags) { int r; sm_ret_T ret; ret = SM_SUCCESS; /* already closed? (due to abort) */ if (f_cookie(*fp) == NULL) return ret; if (SM_IS_FLAG(flags, SM_IO_CF_RM)) { const char *path; r = close(f_fd(*fp)); if (-1 == r && !sm_is_err(ret)) ret = sm_error_perm(SM_EM_CDB, errno); path = cdb_path(fp); r = unlink(path); if (-1 == r && !sm_is_err(ret)) ret = sm_error_perm(SM_EM_CDB, errno); } else { if (SM_IS_FLAG(flags, SM_IO_CF_SYNC)) { SM_ASSERT(is_valid_fd(f_fd(*fp))); r = fsync(f_fd(*fp)); if (-1 == r) ret = sm_error_perm(SM_EM_CDB, errno); } r = close(f_fd(*fp)); if (-1 == r && !sm_is_err(ret)) ret = sm_error_perm(SM_EM_CDB, errno); } cdb_cookie_free((cdb_cookie_P) f_cookie(*fp)); f_cookie(*fp) = NULL; return ret; } /* ** SM_CDBSETINFO -- 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. ** ** Last code review: 2005-03-17 06:49:15 ** Last code change: */ /* ARGSUSED2 */ static sm_ret_T sm_cdbsetinfo(sm_file_T *fp, int what, void *valp) { int r; const char *path; sm_ret_T ret; switch (what) { case SM_IO_WHAT_COMMIT: SM_ASSERT(is_valid_fd(f_fd(*fp))); r = fsync(f_fd(*fp)); if (-1 == r) return sm_error_perm(SM_EM_CDB, errno); return SM_SUCCESS; case SM_IO_WHAT_ABORT: ret = SM_SUCCESS; r = close(f_fd(*fp)); if (-1 == r) ret = sm_error_perm(SM_EM_CDB, errno); path = cdb_path(fp); r = unlink(path); if (-1 == r && !sm_is_err(ret)) ret = sm_error_perm(SM_EM_CDB, errno); cdb_cookie_free((cdb_cookie_P) f_cookie(*fp)); f_cookie(*fp) = NULL; return ret; default: return sm_error_perm(SM_EM_CDB, EINVAL); } } /* ** SM_CDBGETINFO -- 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. ** ** Last code review: 2005-03-17 06:51:42 ** Last code change: */ static sm_ret_T sm_cdbgetinfo(sm_file_T *fp, int what, void *valp) { switch (what) { 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_CDB, errno); if (valp != NULL) *(off_t *) valp = st.st_size; 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_CDB, EINVAL); } } /* ** CDB_REMOVE -- unlink CDB file ** ** Parameters: ** cdb_ctx -- CDB context ** cdb_id -- cdb id ** ** Returns: ** usual sm_error code; ENOMEM, unlink() errors ** ** Note: this is a hack. ** ** Last code review: 2005-03-17 18:25:19 ** Last code change: */ sm_ret_T cdb_remove(cdb_ctx_P cdb_ctx, const char *cdb_id) { size_t l; sm_ret_T ret; int r; char *cdb_path; SM_IS_CDB_CTX(cdb_ctx); l = strlen(cdb_id) + 3; SM_ASSERT(l >= 5); if (cdb_ctx->cdbx_base != NULL && *cdb_ctx->cdbx_base != '\0') l += strlen(cdb_ctx->cdbx_base); cdb_path = (char *) sm_malloc(l); if (cdb_path == NULL) { ret = sm_error_temp(SM_EM_CDB, ENOMEM); goto error; } ret = cdb_crt_path(cdb_id, cdb_ctx->cdbx_base, cdb_path, l); if (sm_is_err(ret)) goto error; r = unlink(cdb_path); if (r != 0) ret = sm_error_perm(SM_EM_CDB, errno); /* fall through for cleanup */ error: SM_FREE(cdb_path); return ret; }