/*-------------------------------------------------------------------------
 *
 * smgr.c
 *	  public interface routines to storage manager switch.
 *
 *	  All file system operations in POSTGRES dispatch through these
 *	  routines.
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/storage/smgr/smgr.c,v 1.29 1999/09/02 02:57:49 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "storage/smgr.h"

static void smgrshutdown(int dummy);

typedef struct f_smgr
{
	int			(*smgr_init) ();/* may be NULL */
	int			(*smgr_shutdown) ();	/* may be NULL */
	int			(*smgr_create) ();
	int			(*smgr_unlink) ();
	int			(*smgr_extend) ();
	int			(*smgr_open) ();
	int			(*smgr_close) ();
	int			(*smgr_read) ();
	int			(*smgr_write) ();
	int			(*smgr_flush) ();
	int			(*smgr_blindwrt) ();
	int			(*smgr_nblocks) ();
	int			(*smgr_truncate) ();
	int			(*smgr_commit) ();		/* may be NULL */
	int			(*smgr_abort) ();		/* may be NULL */
} f_smgr;

/*
 *	The weird placement of commas in this init block is to keep the compiler
 *	happy, regardless of what storage managers we have (or don't have).
 */

static f_smgr smgrsw[] = {

	/* magnetic disk */
	{mdinit, NULL, mdcreate, mdunlink, mdextend, mdopen, mdclose,
		mdread, mdwrite, mdflush, mdblindwrt, mdnblocks, mdtruncate,
	mdcommit, mdabort},

#ifdef STABLE_MEMORY_STORAGE
	/* main memory */
	{mminit, mmshutdown, mmcreate, mmunlink, mmextend, mmopen, mmclose,
		mmread, mmwrite, mmflush, mmblindwrt, mmnblocks, NULL,
	mmcommit, mmabort},

#endif
};

/*
 *	This array records which storage managers are write-once, and which
 *	support overwrite.	A 'true' entry means that the storage manager is
 *	write-once.  In the best of all possible worlds, there would be no
 *	write-once storage managers.
 */

#ifdef NOT_USED
static bool smgrwo[] = {
	false,						/* magnetic disk */
#ifdef STABLE_MEMORY_STORAGE
	false,						/* main memory */
#endif
};

#endif

static int	NSmgr = lengthof(smgrsw);

/*
 *	smgrinit(), smgrshutdown() -- Initialize or shut down all storage
 *								  managers.
 *
 */
int
smgrinit()
{
	int			i;

	for (i = 0; i < NSmgr; i++)
	{
		if (smgrsw[i].smgr_init)
		{
			if ((*(smgrsw[i].smgr_init)) () == SM_FAIL)
				elog(FATAL, "initialization failed on %s", smgrout(i));
		}
	}

	/* register the shutdown proc */
	on_proc_exit(smgrshutdown, NULL);

	return SM_SUCCESS;
}

static void
smgrshutdown(int dummy)
{
	int			i;

	for (i = 0; i < NSmgr; i++)
	{
		if (smgrsw[i].smgr_shutdown)
		{
			if ((*(smgrsw[i].smgr_shutdown)) () == SM_FAIL)
				elog(FATAL, "shutdown failed on %s", smgrout(i));
		}
	}
}

/*
 *	smgrcreate() -- Create a new relation.
 *
 *		This routine takes a reldesc, creates the relation on the appropriate
 *		device, and returns a file descriptor for it.
 */
int
smgrcreate(int16 which, Relation reln)
{
	int			fd;

	if ((fd = (*(smgrsw[which].smgr_create)) (reln)) < 0)
		elog(ERROR, "cannot create %s", reln->rd_rel->relname.data);

	return fd;
}

/*
 *	smgrunlink() -- Unlink a relation.
 *
 *		The relation is removed from the store.
 */
int
smgrunlink(int16 which, Relation reln)
{
	int			status;

	if ((status = (*(smgrsw[which].smgr_unlink)) (reln)) == SM_FAIL)
		elog(ERROR, "cannot unlink %s", reln->rd_rel->relname.data);

	return status;
}

/*
 *	smgrextend() -- Add a new block to a file.
 *
 *		Returns SM_SUCCESS on success; aborts the current transaction on
 *		failure.
 */
int
smgrextend(int16 which, Relation reln, char *buffer)
{
	int			status;

	status = (*(smgrsw[which].smgr_extend)) (reln, buffer);

	if (status == SM_FAIL)
		elog(ERROR, "%s: cannot extend.  Check free disk space.", reln->rd_rel->relname.data);

	return status;
}

/*
 *	smgropen() -- Open a relation using a particular storage manager.
 *
 *		Returns the fd for the open relation on success, aborts the
 *		transaction on failure.
 */
int
smgropen(int16 which, Relation reln)
{
	int			fd;

	if ((fd = (*(smgrsw[which].smgr_open)) (reln)) < 0)
		elog(ERROR, "cannot open %s", reln->rd_rel->relname.data);

	return fd;
}

/*
 *	smgrclose() -- Close a relation.
 *
 *		NOTE: underlying manager should allow case where relation is
 *		already closed.  Indeed relation may have been unlinked!
 *		This is currently called only from RelationFlushRelation() when
 *		the relation cache entry is about to be dropped; could be doing
 *		simple relation cache clear, or finishing up DROP TABLE.
 *		
 *		Returns SM_SUCCESS on success, aborts on failure.
 */
int
smgrclose(int16 which, Relation reln)
{
	if ((*(smgrsw[which].smgr_close)) (reln) == SM_FAIL)
		elog(ERROR, "cannot close %s", reln->rd_rel->relname.data);

	return SM_SUCCESS;
}

/*
 *	smgrread() -- read a particular block from a relation into the supplied
 *				  buffer.
 *
 *		This routine is called from the buffer manager in order to
 *		instantiate pages in the shared buffer cache.  All storage managers
 *		return pages in the format that POSTGRES expects.  This routine
 *		dispatches the read.  On success, it returns SM_SUCCESS.  On failure,
 *		the current transaction is aborted.
 */
int
smgrread(int16 which, Relation reln, BlockNumber blocknum, char *buffer)
{
	int			status;

	status = (*(smgrsw[which].smgr_read)) (reln, blocknum, buffer);

	if (status == SM_FAIL)
		elog(ERROR, "cannot read block %d of %s",
			 blocknum, reln->rd_rel->relname.data);

	return status;
}

/*
 *	smgrwrite() -- Write the supplied buffer out.
 *
 *		This is not a synchronous write -- the interface for that is
 *		smgrflush().  The buffer is written out via the appropriate
 *		storage manager.  This routine returns SM_SUCCESS or aborts
 *		the current transaction.
 */
int
smgrwrite(int16 which, Relation reln, BlockNumber blocknum, char *buffer)
{
	int			status;

	status = (*(smgrsw[which].smgr_write)) (reln, blocknum, buffer);

	if (status == SM_FAIL)
		elog(ERROR, "cannot write block %d of %s",
			 blocknum, reln->rd_rel->relname.data);

	return status;
}

/*
 *	smgrflush() -- A synchronous smgrwrite().
 */
int
smgrflush(int16 which, Relation reln, BlockNumber blocknum, char *buffer)
{
	int			status;

	status = (*(smgrsw[which].smgr_flush)) (reln, blocknum, buffer);

	if (status == SM_FAIL)
		elog(ERROR, "cannot flush block %d of %s to stable store",
			 blocknum, reln->rd_rel->relname.data);

	return status;
}

/*
 *	smgrblindwrt() -- Write a page out blind.
 *
 *		In some cases, we may find a page in the buffer cache that we
 *		can't make a reldesc for.  This happens, for example, when we
 *		want to reuse a dirty page that was written by a transaction
 *		that has not yet committed, which created a new relation.  In
 *		this case, the buffer manager will call smgrblindwrt() with
 *		the name and OID of the database and the relation to which the
 *		buffer belongs.  Every storage manager must be able to force
 *		this page down to stable storage in this circumstance.
 */
int
smgrblindwrt(int16 which,
			 char *dbname,
			 char *relname,
			 Oid dbid,
			 Oid relid,
			 BlockNumber blkno,
			 char *buffer)
{
	char	   *dbstr;
	char	   *relstr;
	int			status;

	dbstr = pstrdup(dbname);
	relstr = pstrdup(relname);

	status = (*(smgrsw[which].smgr_blindwrt)) (dbstr, relstr, dbid, relid,
											   blkno, buffer);

	if (status == SM_FAIL)
		elog(ERROR, "cannot write block %d of %s [%s] blind",
			 blkno, relstr, dbstr);

	pfree(dbstr);
	pfree(relstr);

	return status;
}

/*
 *	smgrnblocks() -- Calculate the number of POSTGRES blocks in the
 *					 supplied relation.
 *
 *		Returns the number of blocks on success, aborts the current
 *		transaction on failure.
 */
int
smgrnblocks(int16 which, Relation reln)
{
	int			nblocks;

	if ((nblocks = (*(smgrsw[which].smgr_nblocks)) (reln)) < 0)
		elog(ERROR, "cannot count blocks for %s", reln->rd_rel->relname.data);

	return nblocks;
}

/*
 *	smgrtruncate() -- Truncate supplied relation to a specified number
 *						of blocks
 *
 *		Returns the number of blocks on success, aborts the current
 *		transaction on failure.
 */
int
smgrtruncate(int16 which, Relation reln, int nblocks)
{
	int			newblks;

	newblks = nblocks;
	if (smgrsw[which].smgr_truncate)
	{
		if ((newblks = (*(smgrsw[which].smgr_truncate)) (reln, nblocks)) < 0)
			elog(ERROR, "cannot truncate %s to %d blocks",
				 reln->rd_rel->relname.data, nblocks);
	}

	return newblks;
}

/*
 *	smgrcommit(), smgrabort() -- Commit or abort changes made during the
 *								 current transaction.
 */
int
smgrcommit()
{
	int			i;

	for (i = 0; i < NSmgr; i++)
	{
		if (smgrsw[i].smgr_commit)
		{
			if ((*(smgrsw[i].smgr_commit)) () == SM_FAIL)
				elog(FATAL, "transaction commit failed on %s", smgrout(i));
		}
	}

	return SM_SUCCESS;
}

#ifdef NOT_USED
int
smgrabort()
{
	int			i;

	for (i = 0; i < NSmgr; i++)
	{
		if (smgrsw[i].smgr_abort)
		{
			if ((*(smgrsw[i].smgr_abort)) () == SM_FAIL)
				elog(FATAL, "transaction abort failed on %s", smgrout(i));
		}
	}

	return SM_SUCCESS;
}

#endif

#ifdef NOT_USED
bool
smgriswo(int16 smgrno)
{
	if (smgrno < 0 || smgrno >= NSmgr)
		elog(ERROR, "illegal storage manager number %d", smgrno);

	return smgrwo[smgrno];
}

#endif
