/*
 * 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