/*****************************************************************************

Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA

*****************************************************************************/

/******************************************************
Database log

Created 12/9/1995 Heikki Tuuri
*******************************************************/

#include "os0file.h"
#include "mach0data.h"
#include "mtr0mtr.h"

/**********************************************************
Checks by parsing that the catenated log segment for a single mtr is
consistent. */
UNIV_INTERN
ibool
log_check_log_recs(
/*===============*/
	byte*		buf,		/* in: pointer to the start of
					the log segment in the
					log_sys->buf log buffer */
	ulint		len,		/* in: segment length in bytes */
	ib_uint64_t	buf_start_lsn);	/* in: buffer start lsn */

/****************************************************************
Gets a log block flush bit. */
UNIV_INLINE
ibool
log_block_get_flush_bit(
/*====================*/
				/* out: TRUE if this block was the first
				to be written in a log flush */
	byte*	log_block)	/* in: log block */
{
	if (LOG_BLOCK_FLUSH_BIT_MASK
	    & mach_read_from_4(log_block + LOG_BLOCK_HDR_NO)) {

		return(TRUE);
	}

	return(FALSE);
}

/****************************************************************
Sets the log block flush bit. */
UNIV_INLINE
void
log_block_set_flush_bit(
/*====================*/
	byte*	log_block,	/* in: log block */
	ibool	val)		/* in: value to set */
{
	ulint	field;

	field = mach_read_from_4(log_block + LOG_BLOCK_HDR_NO);

	if (val) {
		field = field | LOG_BLOCK_FLUSH_BIT_MASK;
	} else {
		field = field & ~LOG_BLOCK_FLUSH_BIT_MASK;
	}

	mach_write_to_4(log_block + LOG_BLOCK_HDR_NO, field);
}

/****************************************************************
Gets a log block number stored in the header. */
UNIV_INLINE
ulint
log_block_get_hdr_no(
/*=================*/
				/* out: log block number stored in the block
				header */
	byte*	log_block)	/* in: log block */
{
	return(~LOG_BLOCK_FLUSH_BIT_MASK
	       & mach_read_from_4(log_block + LOG_BLOCK_HDR_NO));
}

/****************************************************************
Sets the log block number stored in the header; NOTE that this must be set
before the flush bit! */
UNIV_INLINE
void
log_block_set_hdr_no(
/*=================*/
	byte*	log_block,	/* in: log block */
	ulint	n)		/* in: log block number: must be > 0 and
				< LOG_BLOCK_FLUSH_BIT_MASK */
{
	ut_ad(n > 0);
	ut_ad(n < LOG_BLOCK_FLUSH_BIT_MASK);

	mach_write_to_4(log_block + LOG_BLOCK_HDR_NO, n);
}

/****************************************************************
Gets a log block data length. */
UNIV_INLINE
ulint
log_block_get_data_len(
/*===================*/
				/* out: log block data length measured as a
				byte offset from the block start */
	byte*	log_block)	/* in: log block */
{
	return(mach_read_from_2(log_block + LOG_BLOCK_HDR_DATA_LEN));
}

/****************************************************************
Sets the log block data length. */
UNIV_INLINE
void
log_block_set_data_len(
/*===================*/
	byte*	log_block,	/* in: log block */
	ulint	len)		/* in: data length */
{
	mach_write_to_2(log_block + LOG_BLOCK_HDR_DATA_LEN, len);
}

/****************************************************************
Gets a log block first mtr log record group offset. */
UNIV_INLINE
ulint
log_block_get_first_rec_group(
/*==========================*/
				/* out: first mtr log record group byte offset
				from the block start, 0 if none */
	byte*	log_block)	/* in: log block */
{
	return(mach_read_from_2(log_block + LOG_BLOCK_FIRST_REC_GROUP));
}

/****************************************************************
Sets the log block first mtr log record group offset. */
UNIV_INLINE
void
log_block_set_first_rec_group(
/*==========================*/
	byte*	log_block,	/* in: log block */
	ulint	offset)		/* in: offset, 0 if none */
{
	mach_write_to_2(log_block + LOG_BLOCK_FIRST_REC_GROUP, offset);
}

/****************************************************************
Gets a log block checkpoint number field (4 lowest bytes). */
UNIV_INLINE
ulint
log_block_get_checkpoint_no(
/*========================*/
				/* out: checkpoint no (4 lowest bytes) */
	byte*	log_block)	/* in: log block */
{
	return(mach_read_from_4(log_block + LOG_BLOCK_CHECKPOINT_NO));
}

/****************************************************************
Sets a log block checkpoint number field (4 lowest bytes). */
UNIV_INLINE
void
log_block_set_checkpoint_no(
/*========================*/
	byte*		log_block,	/* in: log block */
	ib_uint64_t	no)		/* in: checkpoint no */
{
	mach_write_to_4(log_block + LOG_BLOCK_CHECKPOINT_NO, (ulint) no);
}

/****************************************************************
Converts a lsn to a log block number. */
UNIV_INLINE
ulint
log_block_convert_lsn_to_no(
/*========================*/
				/* out: log block number,
				it is > 0 and <= 1G */
	ib_uint64_t	lsn)	/* in: lsn of a byte within the block */
{
	return(((ulint) (lsn / OS_FILE_LOG_BLOCK_SIZE) & 0x3FFFFFFFUL) + 1);
}

/****************************************************************
Calculates the checksum for a log block. */
UNIV_INLINE
ulint
log_block_calc_checksum(
/*====================*/
				/* out: checksum */
	const byte*	block)	/* in: log block */
{
	ulint	sum;
	ulint	sh;
	ulint	i;

	sum = 1;
	sh = 0;

	for (i = 0; i < OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE; i++) {
		ulint	b = (ulint) block[i];
		sum &= 0x7FFFFFFFUL;
		sum += b;
		sum += b << sh;
		sh++;
		if (sh > 24) {
			sh = 0;
		}
	}

	return(sum);
}

/****************************************************************
Gets a log block checksum field value. */
UNIV_INLINE
ulint
log_block_get_checksum(
/*===================*/
					/* out: checksum */
	const byte*	log_block)	/* in: log block */
{
	return(mach_read_from_4(log_block + OS_FILE_LOG_BLOCK_SIZE
				- LOG_BLOCK_CHECKSUM));
}

/****************************************************************
Sets a log block checksum field value. */
UNIV_INLINE
void
log_block_set_checksum(
/*===================*/
	byte*	log_block,	/* in: log block */
	ulint	checksum)	/* in: checksum */
{
	mach_write_to_4(log_block + OS_FILE_LOG_BLOCK_SIZE
			- LOG_BLOCK_CHECKSUM,
			checksum);
}

/****************************************************************
Initializes a log block in the log buffer. */
UNIV_INLINE
void
log_block_init(
/*===========*/
	byte*		log_block,	/* in: pointer to the log buffer */
	ib_uint64_t	lsn)		/* in: lsn within the log block */
{
	ulint	no;

	ut_ad(mutex_own(&(log_sys->mutex)));

	no = log_block_convert_lsn_to_no(lsn);

	log_block_set_hdr_no(log_block, no);

	log_block_set_data_len(log_block, LOG_BLOCK_HDR_SIZE);
	log_block_set_first_rec_group(log_block, 0);
}

/****************************************************************
Initializes a log block in the log buffer in the old format, where there
was no checksum yet. */
UNIV_INLINE
void
log_block_init_in_old_format(
/*=========================*/
	byte*		log_block,	/* in: pointer to the log buffer */
	ib_uint64_t	lsn)		/* in: lsn within the log block */
{
	ulint	no;

	ut_ad(mutex_own(&(log_sys->mutex)));

	no = log_block_convert_lsn_to_no(lsn);

	log_block_set_hdr_no(log_block, no);
	mach_write_to_4(log_block + OS_FILE_LOG_BLOCK_SIZE
			- LOG_BLOCK_CHECKSUM, no);
	log_block_set_data_len(log_block, LOG_BLOCK_HDR_SIZE);
	log_block_set_first_rec_group(log_block, 0);
}

/****************************************************************
Writes to the log the string given. The log must be released with
log_release. */
UNIV_INLINE
ib_uint64_t
log_reserve_and_write_fast(
/*=======================*/
				/* out: end lsn of the log record,
				zero if did not succeed */
	byte*		str,	/* in: string */
	ulint		len,	/* in: string length */
	ib_uint64_t*	start_lsn,/* out: start lsn of the log record */
	ibool*		success)/* out: TRUE if success */
{
	log_t*		log	= log_sys;
	ulint		data_len;
	ib_uint64_t	lsn;

	*success = TRUE;

	mutex_enter(&(log->mutex));

	data_len = len + log->buf_free % OS_FILE_LOG_BLOCK_SIZE;

	if (data_len >= OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE) {

		/* The string does not fit within the current log block
		or the log block would become full */

		*success = FALSE;

		mutex_exit(&(log->mutex));

		return(0);
	}

	*start_lsn = log->lsn;

	ut_memcpy(log->buf + log->buf_free, str, len);

	log_block_set_data_len((byte*) ut_align_down(log->buf + log->buf_free,
						     OS_FILE_LOG_BLOCK_SIZE),
			       data_len);
#ifdef UNIV_LOG_DEBUG
	log->old_buf_free = log->buf_free;
	log->old_lsn = log->lsn;
#endif
	log->buf_free += len;

	ut_ad(log->buf_free <= log->buf_size);

	lsn = log->lsn += len;

#ifdef UNIV_LOG_DEBUG
	log_check_log_recs(log->buf + log->old_buf_free,
			   log->buf_free - log->old_buf_free, log->old_lsn);
#endif
	return(lsn);
}

/***************************************************************************
Releases the log mutex. */
UNIV_INLINE
void
log_release(void)
/*=============*/
{
	mutex_exit(&(log_sys->mutex));
}

/****************************************************************
Gets the current lsn. */
UNIV_INLINE
ib_uint64_t
log_get_lsn(void)
/*=============*/
			/* out: current lsn */
{
	ib_uint64_t	lsn;

	mutex_enter(&(log_sys->mutex));

	lsn = log_sys->lsn;

	mutex_exit(&(log_sys->mutex));

	return(lsn);
}

/***************************************************************************
Checks if there is need for a log buffer flush or a new checkpoint, and does
this if yes. Any database operation should call this when it has modified
more than about 4 pages. NOTE that this function may only be called when the
OS thread owns no synchronization objects except the dictionary mutex. */
UNIV_INLINE
void
log_free_check(void)
/*================*/
{
	/* ut_ad(sync_thread_levels_empty()); */

	if (log_sys->check_flush_or_checkpoint) {

		log_check_margins();
	}
}