/*
 * Copyright (c) 2000-2001 Silicon Graphics, Inc.
 * 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.
 *
 * This program is distributed in the hope that it would 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 the Free Software Foundation,
 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <xfs/xfs.h>
#include <xfs/jdm.h>

#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <string.h>

#include "config.h"

#include "types.h"
#include "lock.h"
#include "mlog.h"
#include "namreg.h"
#include "openutil.h"
#include "mmap.h"
#include "global.h"
#include "content.h"
#include "content_inode.h"
#include "dirattr.h"

/* structure definitions used locally ****************************************/

#define NAMREG_AVGLEN	10

/* persistent context for a namreg - placed in first page
 * of the namreg file by namreg_init if not a sync
 */
struct namreg_pers {
	off64_t np_appendoff;
};

typedef struct namreg_pers namreg_pers_t;

#define NAMREG_PERS_SZ	pgsz

/* transient context for a namreg - allocated by namreg_init()
 */

#define	NAMREG_BUFSIZE	32768

struct namreg_tran {
	char *nt_pathname;
	int nt_fd;
	char *nt_map;
	bool_t nt_at_endpr;
	size_t nt_off;
	char nt_buf[NAMREG_BUFSIZE];
};

typedef struct namreg_tran namreg_tran_t;


#ifdef NAMREGCHK

/* macros for manipulating namreg handles when handle consistency
 * checking is enabled.
 */
#define CHKBITCNT		2
#define	CHKBITSHIFT		(NBBY * sizeof(nrh_t) - CHKBITCNT)
#define	CHKBITLOMASK		((1ULL << CHKBITCNT) - 1)
#define	CHKBITMASK		(CHKBITLOMASK << CHKBITSHIFT)
#define CHKHDLCNT		CHKBITSHIFT
#define CHKHDLMASK		((1ULL << CHKHDLCNT) - 1)
#define CHKGETBIT(h)		(((h) >> CHKBITSHIFT) & CHKBITLOMASK)
#define CHKGETHDL(h)		((h) & CHKHDLMASK)
#define CHKMKHDL(c, h)	((((c) << CHKBITSHIFT) & CHKBITMASK)	\
				  |					\
				  ((h) & CHKHDLMASK))
#define HDLMAX			((off64_t)CHKHDLMASK)

#else /* NAMREGCHK */

#define HDLMAX			(NRH_NULL - 1)

#endif /* NAMREGCHK */


/* declarations of externally defined global symbols *************************/

extern size_t pgsz;

/* forward declarations of locally defined static functions ******************/

static rv_t namreg_flush(void);

/* definition of locally defined global variables ****************************/


/* definition of locally defined static variables *****************************/

static char *namregfile = "namreg";
static namreg_tran_t *ntp = 0;
static namreg_pers_t *npp = 0;


/* definition of locally defined global functions ****************************/

bool_t
namreg_init(char *hkdir, bool_t resume, uint64_t inocnt)
{
	if (ntp) {
		return BOOL_TRUE;
	}

	/* sanity checks
	 */
	assert(!ntp);
	assert(!npp);

	assert(sizeof(namreg_pers_t) <= NAMREG_PERS_SZ);

	/* allocate and initialize context
	 */
	ntp = (namreg_tran_t *)calloc(1, sizeof(namreg_tran_t));
	assert(ntp);

	/* generate a string containing the pathname of the namreg file
	 */
	ntp->nt_pathname = open_pathalloc(hkdir, namregfile, 0);

	/* open the namreg file
	 */
	if (resume) {
		/* open existing file
		 */
		ntp->nt_fd = open(ntp->nt_pathname, O_RDWR);
		if (ntp->nt_fd < 0) {
			mlog(MLOG_NORMAL | MLOG_ERROR, _(
			      "could not find name registry file %s: "
			      "%s\n"),
			      ntp->nt_pathname,
			      strerror(errno));
			return BOOL_FALSE;
		}
	} else {
		ntp->nt_fd = create_filled_file(ntp->nt_pathname,
			NAMREG_PERS_SZ + (inocnt * NAMREG_AVGLEN));
		if (ntp->nt_fd < 0) {
			mlog(MLOG_NORMAL | MLOG_ERROR, _(
			      "could not create name registry file %s: "
			      "%s\n"),
			      ntp->nt_pathname,
			      strerror(errno));
			return BOOL_FALSE;
		}
	}

	/* mmap the persistent descriptor
	 */
	assert(!(NAMREG_PERS_SZ % pgsz));
	npp = (namreg_pers_t *) mmap_autogrow(
				        NAMREG_PERS_SZ,
				        ntp->nt_fd,
				        (off_t)0);
	if (npp == (namreg_pers_t *)-1) {
		mlog(MLOG_NORMAL | MLOG_ERROR, _(
		      "unable to map %s: %s\n"),
		      ntp->nt_pathname,
		      strerror(errno));
		return BOOL_FALSE;
	}

	/* initialize persistent state
	 */
	if (!resume) {
		npp->np_appendoff = (off64_t)NAMREG_PERS_SZ;
	}

	/* initialize transient state
	 */
	ntp->nt_at_endpr = BOOL_FALSE;

	return BOOL_TRUE;
}

nrh_t
namreg_add(char *name, size_t namelen)
{
	off64_t oldoff;
	unsigned char c;
	nrh_t nrh;

	/* sanity checks
	 */
	assert(ntp);
	assert(npp);
	assert(!ntp->nt_map);

	/* make sure file pointer is positioned to append
	 */
	if (!ntp->nt_at_endpr) {
		off64_t newoff;
		newoff = lseek64(ntp->nt_fd, npp->np_appendoff, SEEK_SET);
		if (newoff == (off64_t)-1) {
			mlog(MLOG_NORMAL, _(
			      "lseek of namreg failed: %s\n"),
			      strerror(errno));
			assert(0);
			return NRH_NULL;
		}
		assert(npp->np_appendoff == newoff);
		ntp->nt_at_endpr = BOOL_TRUE;
	}

	if (ntp->nt_off + namelen + 1 > sizeof(ntp->nt_buf)) {
		if (namreg_flush() != RV_OK) {
			return NRH_NULL;
		}
	}

	/* save the current offset
	 */
	oldoff = npp->np_appendoff;

	/* write a one byte unsigned string length into the buffer.
	 */
	assert(namelen < 256);
	c = (unsigned char)(namelen & 0xff);
	ntp->nt_buf[ntp->nt_off++] = c;

	/* write the name string into the buffer.
	 */
	memcpy(ntp->nt_buf + ntp->nt_off, name, namelen);
	ntp->nt_off += namelen;

	npp->np_appendoff += (off64_t)(1 + namelen);
	assert(oldoff <= HDLMAX);

#ifdef NAMREGCHK

	/* encode the lsb of the len plus the first character into the handle.
	 */
	nrh = CHKMKHDL((nrh_t)namelen + (nrh_t)*name, (nrh_t)oldoff);

#else /* NAMREGCHK */

	nrh = (nrh_t)oldoff;

#endif /* NAMREGCHK */

	return nrh;
}

/* ARGSUSED */
void
namreg_del(nrh_t nrh)
{
	/* currently not implemented - grows, never shrinks
	 */
}

static rv_t
namreg_flush(void)
{
	ssize_t nwritten;

	/* sanity checks
	*/
	assert(ntp);

	if (ntp->nt_off) {

		/* write the accumulated name strings.
		*/
		nwritten = write(ntp->nt_fd, (void *)ntp->nt_buf, ntp->nt_off);
		if (nwritten != ntp->nt_off) {
			if (nwritten < 0) {
				mlog(MLOG_NORMAL | MLOG_ERROR,
					_("write of namreg buffer failed: %s\n"),
					strerror(errno));
			} else {
				mlog(MLOG_NORMAL | MLOG_ERROR,
					_("write of namreg buffer failed: "
					"expected to write %ld, actually "
					"wrote %ld\n"), ntp->nt_off, nwritten);
			}
			assert(0);
			return RV_UNKNOWN;
		}
		ntp->nt_off = 0;
	}
	return RV_OK;
}

int
namreg_get(nrh_t nrh,
	    char *bufp,
	    size_t bufsz)
{
	off64_t newoff;
	int nread;
	size_t len;
	char *in_bufp;
	static char read_buf[256];
	/* long enough for the longest allowed name (255), plus 1 for length */
#ifdef NAMREGCHK
	nrh_t chkbit;
#endif /* NAMREGCHK */

	/* sanity checks
	 */
	assert(ntp);
	assert(npp);

	/* make sure we aren't being given a NULL handle
	 */
	assert(nrh != NRH_NULL);

	/* convert the handle into the offset
	 */
#ifdef NAMREGCHK

	newoff = (off64_t)(size64_t)CHKGETHDL(nrh);
	chkbit = CHKGETBIT(nrh);

#else /* NAMREGCHK */

	newoff = (off64_t)(size64_t)nrh;

#endif /* NAMREGCHK */

	/* do sanity check on offset
	 */
	assert(newoff <= HDLMAX);
	assert(newoff < npp->np_appendoff);
	assert(newoff >= (off64_t)NAMREG_PERS_SZ);

	lock();

	if (ntp->nt_map) {

		in_bufp = ntp->nt_map + newoff - NAMREG_PERS_SZ;

	} else {

		if (ntp->nt_at_endpr && ntp->nt_off) {
			if (namreg_flush() != RV_OK) {
				unlock();
				return -3;
			}
		}

		/* seek to the name
		*/
		newoff = lseek64(ntp->nt_fd, newoff, SEEK_SET);
		if (newoff == (off64_t)-1) {
			unlock();
			mlog(MLOG_NORMAL, _(
				"lseek of namreg failed: %s\n"),
				strerror(errno));
			return -3;
		}
		ntp->nt_at_endpr = BOOL_FALSE;

		/* read the name length and the name itself in one call
		 * NOTE: assumes read_buf is big enough for the longest
		 * allowed name (255 chars) plus one byte for length.
		 */
		nread = read(ntp->nt_fd, (void *)read_buf, sizeof(read_buf));
		if (nread <= 0) {
			unlock();
			mlog(MLOG_NORMAL, _(
				"read of namreg failed: %s (nread = %d)\n"),
				strerror(errno),
				nread);
			return -3;
		}

		in_bufp = read_buf;
	}

	/* deal with a short caller-supplied buffer
	 */
	len = (size_t)in_bufp[0];
	if (bufsz < len + 1) {
		unlock();
		return -1;
	}

	/* copy the name into the caller-supplied buffer.
	 */
	strncpy(bufp, in_bufp+1, len);

#ifdef NAMREGCHK

	/* validate the checkbit
	 */
	assert(chkbit
		==
		(((nrh_t)len + (nrh_t)bufp[0]) & CHKBITLOMASK));

#endif /* NAMREGCHK */

	/* null-terminate the string if room
	 */
	bufp[len] = 0;

	unlock();

	return (int)len;
}

rv_t
namreg_map(void)
{
	rv_t rv;

	/* ensure all entries have been written */
	if ((rv = namreg_flush()) != RV_OK) {
		return rv;
	}

	ntp->nt_map = (char *) mmap_autogrow(
					npp->np_appendoff - NAMREG_PERS_SZ,
					ntp->nt_fd,
					NAMREG_PERS_SZ);

	/* it's okay if this fails, just fall back to (the much slower)
	 * seek-and-read lookups.
	 */
	if (ntp->nt_map == (char *)-1) {
		mlog(MLOG_DEBUG, "failed to map namreg: %s\n",
			strerror(errno));
		ntp->nt_map = NULL;
	}

	return RV_OK;
}

/* definition of locally defined static functions ****************************/
