#include "sm/generic.h"
SM_RCSID("@(#)$Id: cdbbf.c,v 1.3 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 "libmta/io-int.h"
#include "sm/fdset.h"
#include "sm/cdb.h"
#include "cdb.h"
#include "cdbbf.h"

static open_F		sm_cdbf_open;
static read_F		sm_cdbf_read;
static write_F		sm_cdbf_write;
static close_F		sm_cdbf_close;
static getinfo_F	sm_cdbf_getinfo;
static setinfo_F	sm_cdbf_setinfo;
static seek_F		sm_cdbf_seek;

sm_stream_T SmCDBStL =
	SM_STREAM_STRUCT(sm_cdbf_open, sm_cdbf_close,
		sm_cdbf_read, sm_cdbf_write,
		NULL, NULL, sm_cdbf_seek,
		sm_cdbf_getinfo, sm_cdbf_setinfo);

/*
**  CDBF_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
cdbf_open(sm_file_T *fp, const char *path, int oflags)
{
	sm_cdbf_P sm_cdbf;

	SM_IS_FP(fp);
	sm_cdbf = (sm_cdbf_P) f_cookie(*fp);
	SM_REQUIRE(sm_cdbf != NULL);

	if (sm_is_err(sm_cdbf->cdbf_open_status))
		return sm_cdbf->cdbf_open_status;
	if (sm_cdbf->cdbf_ondisk || is_valid_fd(f_fd(*fp)))
		return SM_SUCCESS;

	f_fd(*fp) = open(path, oflags | O_NONBLOCK,
			S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
	if (!is_valid_fd(f_fd(*fp)))
	{
		sm_cdbf->cdbf_open_status = sm_error_perm(SM_EM_IO, errno);
		return sm_cdbf->cdbf_open_status;
	}
	sm_cdbf->cdbf_open_status = SM_SUCCESS;
	sm_cdbf->cdbf_ondisk = true;
	return f_fd(*fp);
}

/*
**  CDB_CDBF_NEW -- create a CDB BF context based on cdb id
**
**	Parameters:
**		cdb_id -- cdb id
**		cdb_ctx -- CDB context
**
**	Returns:
**		CDB BF context (NULL on error [ENOMEM])
**
**	Last code review:
**	Last code change:
*/

static sm_cdbf_P
sm_cdbf_new(const char *cdb_id, cdb_ctx_P cdb_ctx, size_t bufsize)
{
	size_t l;
	sm_cdbf_P sm_cdbf;
	sm_ret_T ret;

	SM_ASSERT(cdb_id != NULL);
	SM_IS_CDB_CTX(cdb_ctx);

	sm_cdbf = (sm_cdbf_P) sm_zalloc(sizeof(*sm_cdbf));
	if (sm_cdbf == 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);
	sm_cdbf->cdbf_path = (char *) sm_malloc(l);
	if (sm_cdbf->cdbf_path == NULL)
		goto error;
	sm_cdbf->cdbf_ctx = cdb_ctx;
	ret = cdb_crt_path(cdb_id, cdb_ctx->cdbx_base, sm_cdbf->cdbf_path, l);
	if (sm_is_err(ret))
		goto error;
	return sm_cdbf;

  error:
	if (sm_cdbf != NULL)
	{
		if (sm_cdbf->cdbf_path != NULL)
			sm_free(sm_cdbf->cdbf_path);
		sm_free_size(sm_cdbf, sizeof(*sm_cdbf));
	}
	return NULL;
}

/*
**  CDB_CDBF_FREE -- free a CDB BF context
**
**	Parameters:
**		sm_cdbf -- CDB BF context
**
**	Returns:
**		none
**
**	Last code review:
**	Last code change:
*/

static void
sm_cdbf_free(sm_cdbf_P sm_cdbf)
{
	if (NULL == sm_cdbf)
		return;
	SM_FREE(sm_cdbf->cdbf_path);
	sm_free_size(sm_cdbf, sizeof(*sm_cdbf));
	return;
}

/*
**  SM_CDBF_OPEN -- the "base" open function called by sm_io_open() for the
**		internal, file-type-specific info setup.
**
**	Parameters:
**		fp -- file pointer being filled-in for file being open'd
**		info -- information about file being opened
**		flags -- indicates type of access methods
**		ap -- further arguments
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sm_cdbf_open(sm_file_T *fp, const void *info, int flags, va_list ap)
{
	sm_ret_T ret;
	int oflags, l;
	size_t bsize;
	const char *path;
	sm_cdbf_P sm_cdbf;
	cdb_ctx_P cdb_ctx;
#if 0
	struct stat st;
#endif

	SM_IS_FP(fp);
	SM_REQUIRE(info != NULL);
	path = (const char *) info;
	bsize = SIZE_T_MAX;
	cdb_ctx = NULL;

#if 0
	/* Sanity checks */
	if (stat(path, &st) == 0)
	{
		/* File already exists on disk */
		return sm_error_perm(SM_EM_IO, EEXIST);
	}
#endif /* 0 */

	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_BSIZE:
			bsize = va_arg(ap, size_t);
			break;
/*
		  case SM_IO_WHAT_FMODE:
			fmode = (mode_t) va_arg(ap, int);
			break;
*/

		  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_cdbf = sm_cdbf_new(path, cdb_ctx, bsize);
	if (sm_cdbf == NULL)
		return sm_error_temp(SM_EM_IO, ENOMEM);

	if (bsize != SIZE_T_MAX)
	{
		ret = sm_io_setvbuf(fp, NULL, SM_IO_FBF, bsize);
		if (sm_is_err(ret))
			goto error;
	}

	sm_cdbf->cdbf_ondisk = false;
	sm_cdbf->cdbf_oflags = oflags;
	f_cookie(*fp) = sm_cdbf;
	return SM_SUCCESS;

  error:
	if (sm_cdbf != NULL)
		sm_cdbf_free(sm_cdbf);
	return ret;
}

/*
**  SM_CDBF_GETINFO -- returns info about an open file pointer
**
**	Parameters:
**		fp -- file pointer to get info about
**		what -- type of info to obtain
**		valp -- thing to return the info in
*/

static sm_ret_T
sm_cdbf_getinfo(sm_file_T *fp, int what, void *valp)
{
	sm_cdbf_P sm_cdbf;

	SM_IS_FP(fp);
	sm_cdbf = (sm_cdbf_P) f_cookie(*fp);
	switch (what)
	{
/*
	  case SM_IO_WHAT_BF_BUFSIZE:
		return sm_cdbf->cdbf_bufsize;
	  case SM_IO_WHAT_SIZE:
		return sm_cdbf->cdbf_size;
*/
	  default:
		return sm_error_perm(SM_EM_IO, EINVAL);
	}
}

/*
**  SM_CDBF_ABORT -- abort file I/O, remove file
**
**	Parameters:
**		fp -- file pointer to get info about
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sm_cdbf_abort(sm_file_T *fp)
{
	sm_cdbf_P sm_cdbf;
	sm_ret_T ret;

	SM_IS_FP(fp);
	sm_cdbf = (sm_cdbf_P) f_cookie(*fp);
	ret = SM_SUCCESS;
	if (sm_cdbf->cdbf_ondisk)
	{
		int r;
		const char *path;

		r = close(f_fd(*fp));
		if (-1 == r)
			ret = sm_error_perm(SM_EM_CDB, errno);

		path = cdbf_path(fp);
		r = unlink(path);
		if (-1 == r && !sm_is_err(ret))
			ret = sm_error_perm(SM_EM_CDB, errno);
	}
	sm_cdbf_free(sm_cdbf);
	f_cookie(*fp) = NULL;
	return ret;
}

/*
**  SM_CDBF_COMMIT -- commit file to disk
**
**	Parameters:
**		fp -- file pointer to get info commit
**		sync -- invoke fsync(2)?
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sm_cdbf_commit(sm_file_T *fp, bool sync)
{
	sm_cdbf_P sm_cdbf;
	sm_ret_T ret;
	int r;

	SM_IS_FP(fp);
	sm_cdbf = (sm_cdbf_P) f_cookie(*fp);
	if (!sm_cdbf->cdbf_ondisk)
	{
		ret = cdbf_open(fp, sm_cdbf->cdbf_path, sm_cdbf->cdbf_oflags);
		if (sm_is_err(ret))
			return ret;
	}
	if (sync)
	{
		if (!is_valid_fd(f_fd(*fp)))
			return sm_error_perm(SM_EM_CDB, EINVAL);
		r = fsync(f_fd(*fp));
		if (-1 == r)
			return sm_error_perm(SM_EM_CDB, errno);
	}
	return SM_SUCCESS;
}

/*
**  SM_CDBF_CLOSE -- close a buffered file
**
**	Parameters:
**		fp -- file to close
**		flags -- flags
**
**	Returns:
**		usual sm_error code
*/

static int
sm_cdbf_close(sm_file_T *fp, int flags)
{
	sm_ret_T ret;
	sm_cdbf_P sm_cdbf;

	SM_IS_FP(fp);
	sm_cdbf = (sm_cdbf_P) f_cookie(*fp);

	/* abort was called before? */
	if (NULL == sm_cdbf)
		return SM_SUCCESS;

	if (SM_IS_FLAG(flags, SM_IO_CF_RM))
		return sm_cdbf_abort(fp);

	/* need to make sure the file is on disk */
	ret = sm_cdbf_commit(fp, SM_IS_FLAG(flags, SM_IO_CF_SYNC));
	if (is_valid_fd(f_fd(*fp)) && close(f_fd(*fp)) == -1 && !sm_is_err(ret))
		ret = sm_error_perm(SM_EM_IO, errno);
	return ret;
}

/*
**  SM_CDBF_READ -- read a buffered file
**
**	Parameters:
**		cookie -- cookie of file to read
**		buf -- buffer to fill
**		nbytes -- how many bytes to read
**		bytesread -- number of bytes read (output)
**
**	Returns:
**		number of bytes read or -1 indicate failure
**
**	Side Effects:
**		none.
**
*/

static sm_ret_T
sm_cdbf_read(sm_file_T *fp, uchar *buf, size_t nbytes, 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, nbytes);
	} 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_CDBF_SEEK -- seek to a position in a buffered file
**
**	Parameters:
**		fp     -- fp of file to seek
**		offset -- position to seek to
**		whence -- how to seek
**
**	Returns:
**		new file offset or -1 indicate failure
**
*/

static sm_ret_T
sm_cdbf_seek(sm_file_T *fp, off_t offset, int whence)
{
	off_t off;
	sm_cdbf_P sm_cdbf;

	SM_IS_FP(fp);
	sm_cdbf = (sm_cdbf_P) f_cookie(*fp);
	SM_REQUIRE(sm_cdbf != NULL);
	if (sm_cdbf->cdbf_ondisk)
	{
		off = lseek(f_fd(*fp), offset, whence);
		if (off == (off_t) -1)
			return sm_error_perm(SM_EM_IO, errno);
		fp->f_lseekoff = off;
	}
	else
	{
		/* reset pointers in buffer ... */
	}
	return SM_SUCCESS;
}

/*
**  SM_CDBF_WRITE -- write to a buffered file
**
**	Parameters:
**		fp -- fp of file to write
**		buf -- data buffer
**		nbytes -- how many bytes to write
**		byteswritten -- number of bytes written (output)
**
**	Returns:
**		number of bytes written or -1 indicate failure
**
**	Side Effects:
**		may create backing file if over memory limit for file.
**
*/

static sm_ret_T
sm_cdbf_write(sm_file_T *fp, const uchar *buf, size_t nbytes, ssize_t *byteswritten)
{
	ssize_t ret;
	sm_ret_T res;
	sm_cdbf_P sm_cdbf;

	SM_REQUIRE(fp != NULL);
	sm_cdbf = (sm_cdbf_P) f_cookie(*fp);
	SM_REQUIRE(sm_cdbf != NULL);

	if (!is_valid_fd(f_fd(*fp)))
	{
		ret = cdbf_open(fp, sm_cdbf->cdbf_path, sm_cdbf->cdbf_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, nbytes);
	} while (ret == -1 && errno == EINTR);
	if (ret == -1)
		return sm_error_perm(SM_EM_IO, errno);
	*byteswritten = ret;
	return SM_SUCCESS;
}

#if 0
/*
**  BFREWIND -- rewinds the sm_file_T *
**
**	Parameters:
**		fp -- sm_file_T * to rewind
**
**	Returns:
**		usual sm_error code
**
**	Side Effects:
**		rewinds the sm_file_T * and puts it into read mode. Normally one
**		would cdbfopen() a file, write to it, then cdbfrewind() and
**		fread(). If fp is not a buffered file, this is equivalent to
**		rewind().
*/

static sm_ret_T
cdbfrewind(sm_file_T *fp)
{
	SM_IS_FP(fp);
	(void) sm_io_flush(fp);
	sm_io_clearerr(fp); /* quicker just to do it */
	return sm_io_seek(fp, 0L, SM_IO_SEEK_SET);
}
#endif /* 0 */


#if 0
/*
**  SM_CDBF_TRUNCATE -- rewinds and truncates the sm_file_T *
**
**	Parameters:
**		fp -- sm_file_T * to truncate
**
**	Returns:
**		usual sm_error code
**
**	Side Effects:
**		rewinds the sm_file_T *, truncates it to zero length, and puts
**		it into write mode.
*/

static sm_ret_T
sm_cdbf_truncate(sm_file_T *fp)
{
	sm_ret_T ret;
	sm_cdbf_P sm_cdbf;

	SM_IS_FP(fp);
	ret = cdbfrewind(fp);
	if (sm_is_err(ret))
		return ret;

	/* Get cdbf structure */
	sm_cdbf = (sm_cdbf_P) f_cookie(*fp);
	sm_cdbf->cdbf_buffilled = 0;
	sm_cdbf->cdbf_size = 0;

	if (sm_cdbf->cdbf_ondisk)
	{
#if NOFTRUNCATE
		/* XXX: Not much we can do except rewind it */
		return sm_error_perm(SM_EM_IO, EINVAL);
#else
		if (ftruncate(sm_cdbf->cdbf_disk_fd, (off_t) 0) < 0)
			return sm_error_perm(SM_EM_IO, errno);
#endif
	}
	return SM_SUCCESS;
}
#endif /* 0 */

/*
**  SM_CDBF_SETINFO -- set/change info for an open file pointer
**
**	Parameters:
**		fp -- file pointer to get info about
**		what -- type of info to set/change
**		valp -- thing to set/change the info to
**
*/

static sm_ret_T
sm_cdbf_setinfo(sm_file_T *fp, int what, void *valp)
{
	sm_cdbf_P sm_cdbf;
	sm_ret_T ret;

	SM_IS_FP(fp);
	sm_cdbf = (sm_cdbf_P) f_cookie(*fp);
	switch (what)
	{
	  case SM_IO_WHAT_ABORT:
		ret = sm_cdbf_abort(fp);
		return ret;

	  case SM_IO_WHAT_COMMIT:
		ret = sm_cdbf_commit(fp, true);
		return ret;
#if 0
	  case SM_IO_WHAT_BF_COMMIT:
		return sm_cdbfcommit(fp, false);
	  case SM_IO_WHAT_BF_TRUNCATE:
		return sm_cdbftruncate(fp);
	  case SM_IO_WHAT_BF_TEST:
		return SM_SUCCESS; /* always */
#endif /* 0 */
	  default:
		return sm_error_perm(SM_EM_IO, EINVAL);
	}
	/* NOTREACHED */
}


syntax highlighted by Code2HTML, v. 0.9.1