/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2013, 2017 Oracle and/or its affiliates.  All rights reserved.
 */

#include "db_config.h"

#include "db_int.h"
#include "dbinc/db_page.h"
#include "dbinc/db_am.h"
#include "dbinc/fop.h"

/*
 * Blob file data item code.
 *
 * Blob file data entries are stored on linked lists of pages.  The initial
 * reference is a structure with an encoded version of the path where the file
 * is stored. The blob file contains only the users data.
 */

/*
 * __blob_bulk --
 *	Dump blob file into buffer.
 *	The space requirements have already been checked, if the blob is
 *	larger than UINT32MAX then DB_BUFFER_SMALL would have already
 *	been returned.
 * PUBLIC: int __blob_bulk
 * PUBLIC:    __P((DBC *, u_int32_t, db_seq_t, u_int8_t *));
 */
int
__blob_bulk(dbc, len, blob_id, dp)
	DBC *dbc;
	u_int32_t len;
	db_seq_t blob_id;
	u_int8_t *dp;
{
	DBT dbt;
	DB_FH *fhp;
	ENV *env;
	int ret, t_ret;

	env = dbc->dbp->env;
	fhp = NULL;
	memset(&dbt, 0, sizeof(dbt));
	F_SET(&dbt, DB_DBT_USERMEM);
	dbt.ulen = len;
	dbt.data = (void *)dp;

	if ((ret = __blob_file_open(
	    dbc->dbp, &fhp, blob_id, DB_FOP_READONLY, 1)) != 0)
		goto err;

	if ((ret = __blob_file_read(env, fhp, &dbt, 0, len)) != 0)
		goto err;

	/* Close any open file descriptors. */
err:	if (fhp != NULL) {
		t_ret = __blob_file_close(dbc, fhp, 0);
		if (ret == 0)
			ret = t_ret;
	}
	return (ret);
}

/*
 * __blob_get --
 *	Get a blob file item. Analogous to db_overflow.c:__db_goff.
 *
 * PUBLIC: int __blob_get __P((DBC *,
 * PUBLIC:     DBT *, db_seq_t, off_t, void **, u_int32_t *));
 */
int
__blob_get(dbc, dbt, blob_id, file_size, bpp, bpsz)
	DBC *dbc;
	DBT *dbt;
	db_seq_t blob_id;
	off_t file_size;
	void **bpp;
	u_int32_t *bpsz;
{
	DB_FH *fhp;
	ENV *env;
	int ret, t_ret;
	u_int32_t needed, start, tlen;

	env = dbc->dbp->env;
	fhp = NULL;
	ret = 0;

	/*
	 * Blobs larger than UINT32_MAX can only be read using
	 * the DB_STREAM API, or the DB_DBT_PARTIAL API.
	 */
	if (file_size > UINT32_MAX) {
		if (!F_ISSET(dbt, DB_DBT_PARTIAL)) {
			dbt->size = UINT32_MAX;
			ret = DB_BUFFER_SMALL;
			goto err;
		} else
			tlen = UINT32_MAX;
	} else
		tlen = (u_int32_t)file_size;

	if (((ret = __db_alloc_dbt(
	    env, dbt, tlen, &needed, &start, bpp, bpsz)) != 0) || needed == 0)
		goto err;
	dbt->size = needed;

	if ((ret = __blob_file_open(
	    dbc->dbp, &fhp, blob_id, DB_FOP_READONLY, 1)) != 0)
		goto err;

	if ((ret = __blob_file_read(env, fhp, dbt, dbt->doff, needed)) != 0)
		goto err;

	/* Close any open file descriptors. */
err:	if (fhp != NULL) {
		t_ret = __blob_file_close(dbc, fhp, 0);
		if (ret == 0)
			ret = t_ret;
	}
	/* Does the dbt need to be cleaned on error? */
	return (ret);
}

/*
 * __blob_put --
 *	Put a blob file item.
 *
 * PUBLIC: int __blob_put __P((
 * PUBLIC:    DBC *, DBT *, db_seq_t *, off_t *size, DB_LSN *));
 */
int
__blob_put(dbc, dbt, blob_id, size, plsn)
	DBC *dbc;
	DBT *dbt;
	db_seq_t *blob_id;
	off_t *size;
	DB_LSN *plsn;
{
	DBT partial;
	DB_FH *fhp;
	ENV *env;
	int ret, t_ret;
	off_t offset;

	env = dbc->dbp->env;
	fhp = NULL;
	offset = 0;
	DB_ASSERT(env, blob_id != NULL);
	DB_ASSERT(env, *blob_id == 0);

	ZERO_LSN(*plsn);

	/* If the id didn't refer to an existing blob generate a new one. */
	if ((ret = __blob_file_create(dbc, &fhp, blob_id)) != 0)
		goto err;

	/*
	 * If doing a partial put with dbt->doff == 0, then treat like
	 * a normal put.  Otherwise write NULLs into the file up to doff, which
	 * is required by the PARTIAL API.  Since the file is being created,
	 * its size is always 0.
	 */
	DB_ASSERT(env, *size == 0);
	if (F_ISSET(dbt, DB_DBT_PARTIAL) && dbt->doff > 0) {
		memset(&partial, 0, sizeof(partial));
		if ((ret = __os_malloc(env, dbt->doff, &partial.data)) != 0)
			goto err;
		memset(partial.data, 0, dbt->doff);
		partial.size = dbt->doff;
		ret = __blob_file_write(
		    dbc, fhp, &partial, 0, *blob_id, size, DB_FOP_CREATE);
		offset = dbt->doff;
		__os_free(env, partial.data);
		if (ret != 0)
			goto err;
	}

	if ((ret = __blob_file_write(
	    dbc, fhp, dbt, offset, *blob_id, size, DB_FOP_CREATE)) != 0)
		goto err;

	/* Close any open file descriptors. */
err:	if (fhp != NULL) {
		t_ret = __blob_file_close(dbc, fhp, DB_FOP_WRITE);
		if (ret == 0)
			ret = t_ret;
	}
	return (ret);
}

/*
 * __blob_repl --
 *	Replace a blob file contents.  It would be nice if this could be done
 *	by truncating the file and writing in the new data, but undoing a
 *	truncate would require a lot of logging, so it is performed by
 *	deleting the old blob file, and creating a new one.
 *
 * PUBLIC: int __blob_repl __P((DBC *, DBT *, db_seq_t, db_seq_t *,off_t *));
 */
int
__blob_repl(dbc, nval, blob_id, new_blob_id, size)
	DBC *dbc;
	DBT *nval;
	db_seq_t blob_id;
	db_seq_t *new_blob_id;
	off_t *size;
{
	DBT partial;
	DB_FH *fhp, *new_fhp;
	DB_LSN lsn;
	ENV *env;
	int ret, t_ret;
	off_t current, old_size;

	fhp = new_fhp = NULL;
	*new_blob_id = 0;
	old_size = *size;
	env = dbc->env;
	memset(&partial, 0, sizeof(partial));

	/*
	 * Handling partial replace.
	 * 1. doff > blob file size : Pad the end of the blob file with NULLs
	 *	up to doff, then append the data.
	 * 2. doff == size: Write the data to the existing blob file.
	 * 3. dlen == size: Write the data to the existing blob file.
	 * 4. Create a new blob file.  Copy old blob data up to doff
	 *	to the new file.  Append the new data.  Append data
	 *	from the old file from doff + dlen to the end of the
	 *	old file to the new file.  Delete the old file.
	 */
	if (F_ISSET(nval, DB_DBT_PARTIAL)) {
		if ((nval->doff > *size) ||
		    ((nval->doff == *size) || (nval->dlen == nval->size))) {
			/* Open the file for appending. */
			if ((ret = __blob_file_open(
			    dbc->dbp, &fhp, blob_id, 0, 1)) != 0)
				goto err;
			*new_blob_id = blob_id;

			/* Pad the end of the blob with NULLs. */
			if (nval->doff > *size) {
				partial.size = nval->doff - (u_int32_t)*size;
				if ((ret = __os_malloc(
				    env, partial.size, &partial.data)) != 0)
					goto err;
				memset(partial.data, 0, partial.size);
				if ((ret = __blob_file_write(dbc, fhp,
				    &partial, *size, blob_id, size, 0)) != 0)
					goto err;
			}

			/* Write in the data. */
			if ((ret = __blob_file_write(dbc, fhp,
			    nval, nval->doff, blob_id, size, 0)) != 0)
				goto err;

			/* Close the file */
			ret = __blob_file_close(dbc, fhp, DB_FOP_WRITE);
			fhp = NULL;
			if (ret != 0)
				goto err;
		} else {
			/* Open the old blob file. */
			if ((ret = __blob_file_open(
			    dbc->dbp, &fhp, blob_id, DB_FOP_READONLY, 1)) != 0)
				goto err;
			/* Create the new blob file. */
			if ((ret = __blob_file_create(
			    dbc, &new_fhp, new_blob_id)) != 0)
				goto err;

			*size = 0;
			/* Copy data to the new file up to doff. */
			if (nval->doff != 0) {
				partial.ulen = partial.size = nval->doff;
				if ((ret = __os_malloc(
				    env, partial.ulen, &partial.data)) != 0)
					goto err;
				if ((ret = __blob_file_read(
				    env, fhp, &partial, 0, partial.size)) != 0)
					goto err;
				if ((ret = __blob_file_write(
				    dbc, new_fhp, &partial, 0,
				    *new_blob_id, size, DB_FOP_CREATE)) != 0)
					goto err;
			}

			/* Write the partial data into the new file. */
			if ((ret = __blob_file_write(
			    dbc, new_fhp, nval, nval->doff,
			    *new_blob_id, size, DB_FOP_CREATE)) != 0)
				goto err;

			/* Copy remaining blob data into the new file. */
			current = nval->doff + nval->dlen;
			while (current < old_size) {
				if (partial.ulen < MEGABYTE) {
					if ((ret = __os_realloc(env,
					    MEGABYTE, &partial.data)) != 0)
						goto err;
					partial.size = partial.ulen = MEGABYTE;
				}
				if ((old_size - current) < partial.ulen) {
					partial.size =
					(u_int32_t)(old_size - current);
				} else
					partial.size = MEGABYTE;

				if ((ret = __blob_file_read(env, fhp,
				    &partial, current, partial.size)) != 0)
					goto err;
				if ((ret = __blob_file_write(
				    dbc, new_fhp, &partial, *size,
				    *new_blob_id, size, DB_FOP_CREATE)) != 0)
					goto err;
				current += partial.size;
			}

			/* Close the old file. */
			ret = __blob_file_close(dbc, fhp, 0);
			fhp = NULL;
			if (ret != 0)
				goto err;

			/* Delete the old blob file. */
			if ((ret = __blob_del(dbc, blob_id)) != 0)
				goto err;
		}
		goto err;
	}

	if ((ret = __blob_del(dbc, blob_id)) != 0)
		goto err;

	*size = 0;
	if ((ret = __blob_put(dbc, nval, new_blob_id, size, &lsn)) != 0)
		goto err;

err:	if (fhp != NULL) {
		t_ret = __blob_file_close(dbc, fhp, DB_FOP_WRITE);
		if (ret == 0)
			ret = t_ret;
	}
	if (new_fhp != NULL) {
		t_ret = __blob_file_close(dbc, new_fhp, DB_FOP_WRITE);
		if (ret == 0)
			ret = t_ret;
	}
	if (partial.data != NULL)
		__os_free(env, partial.data);
	return (ret);
}

/*
 * __blob_del --
 *	Delete a blob file. The onpage record is handled separately..
 *
 * PUBLIC: int __blob_del __P((DBC *, db_seq_t));
 */
int
__blob_del(dbc, blob_id)
	DBC *dbc;
	db_seq_t blob_id;
{
	int ret;

	ret = __blob_file_delete(dbc, blob_id);

	return (ret);
}
