/*
* 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;
}
syntax highlighted by Code2HTML, v. 0.9.1