/*	$NetBSD: prop_object.c,v 1.12 2006/10/19 10:10:35 he Exp $	*/

/*-
 * Copyright (c) 2006 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the NetBSD
 *      Foundation, Inc. and its contributors.
 * 4. Neither the name of The NetBSD Foundation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <prop/prop_object.h>
#include "prop_object_impl.h"

#if !defined(_KERNEL) && !defined(_STANDALONE)
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <unistd.h>
#endif

#ifdef _STANDALONE
void *
_prop_standalone_calloc(size_t size)
{
	void *rv;

	rv = alloc(size);
	if (rv != NULL)
		memset(rv, 0, size);

	return (rv);
}

void *
_prop_standalone_realloc(void *v, size_t size)
{
	void *rv;

	rv = alloc(size);
	if (rv != NULL) {
		memcpy(rv, v, size);	/* XXX */
		dealloc(v, 0);		/* XXX */
	}
	
	return (rv);
}
#endif /* _STANDALONE */

/*
 * _prop_object_init --
 *	Initialize an object.  Called when sub-classes create
 *	an instance.
 */
void
_prop_object_init(struct _prop_object *po, const struct _prop_object_type *pot)
{

	po->po_type = pot;
	po->po_refcnt = 1;
}

/*
 * _prop_object_fini --
 *	Finalize an object.  Called when sub-classes destroy
 *	an instance.
 */
/*ARGSUSED*/
void
_prop_object_fini(struct _prop_object *po _PROP_ARG_UNUSED)
{
	/* Nothing to do, currently. */
}

/*
 * _prop_object_externalize_context_alloc --
 *	Allocate an externalize context.
 */
struct _prop_object_externalize_context *
_prop_object_externalize_context_alloc(void)
{
	struct _prop_object_externalize_context *ctx;

	ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
	if (ctx != NULL) {
		ctx->poec_buf = _PROP_MALLOC(_PROP_BUF_EXPAND, M_TEMP);
		if (ctx->poec_buf == NULL) {
			_PROP_FREE(ctx, M_TEMP);
			return (NULL);
		}
		ctx->poec_len = 0;
		ctx->poec_capacity = _PROP_BUF_EXPAND;
		ctx->poec_depth = 0;
	}
	return (ctx);
}

/*
 * _prop_object_externalize_append_char --
 *	Append a single character to the externalize buffer.
 */
boolean_t
_prop_object_externalize_append_char(
    struct _prop_object_externalize_context *ctx, unsigned char c)
{

	_PROP_ASSERT(ctx->poec_capacity != 0);
	_PROP_ASSERT(ctx->poec_buf != NULL);
	_PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity);

	if (ctx->poec_len == ctx->poec_capacity) {
		char *cp = _PROP_REALLOC(ctx->poec_buf,
					 ctx->poec_capacity + _PROP_BUF_EXPAND,
					 M_TEMP);
		if (cp == NULL)
			return (FALSE);
		ctx->poec_capacity = ctx->poec_capacity + _PROP_BUF_EXPAND;
		ctx->poec_buf = cp;
	}

	ctx->poec_buf[ctx->poec_len++] = c;

	return (TRUE);
}

/*
 * _prop_object_externalize_append_cstring --
 *	Append a C string to the externalize buffer.
 */
boolean_t
_prop_object_externalize_append_cstring(
    struct _prop_object_externalize_context *ctx, const char *cp)
{

	while (*cp != '\0') {
		if (_prop_object_externalize_append_char(ctx,
						(unsigned char) *cp) == FALSE)
			return (FALSE);
		cp++;
	}

	return (TRUE);
}

/*
 * _prop_object_externalize_context_free --
 *	Free an externalize context.
 */
void
_prop_object_externalize_context_free(
		struct _prop_object_externalize_context *ctx)
{

	/* Buffer is always freed by the caller. */
	_PROP_FREE(ctx, M_TEMP);
}

#if !defined(_KERNEL) && !defined(_STANDALONE)
/*
 * _prop_object_externalize_file_dirname --
 *	dirname(3), basically.  We have to roll our own because the
 *	system dirname(3) isn't reentrant.
 */
static void
_prop_object_externalize_file_dirname(const char *path, char *result)
{
	const char *lastp;
	size_t len;

	/*
	 * If `path' is a NULL pointer or points to an empty string,
	 * return ".".
	 */
	if (path == NULL || *path == '\0')
		goto singledot;
	
	/* String trailing slashes, if any. */
	lastp = path + strlen(path) - 1;
	while (lastp != path && *lastp == '/')
		lastp--;
	
	/* Terminate path at the last occurrence of '/'. */
	do {
		if (*lastp == '/') {
			/* Strip trailing slashes, if any. */
			while (lastp != path && *lastp == '/')
				lastp--;

			/* ...and copy the result into the result buffer. */
			len = (lastp - path) + 1 /* last char */;
			if (len > (PATH_MAX - 1))
				len = PATH_MAX - 1;

			memcpy(result, path, len);
			result[len] = '\0';
			return;
		}
	} while (--lastp >= path);

 	/* No /'s found, return ".". */
 singledot:
	strcpy(result, ".");
}

/*
 * _prop_object_externalize_write_file --
 *	Write an externalized dictionary to the specified file.
 *	The file is written atomically from the caller's perspective,
 *	and the mode set to 0666 modified by the caller's umask.
 */
boolean_t
_prop_object_externalize_write_file(const char *fname, const char *xml,
    size_t len)
{
	char tname[PATH_MAX];
	int fd;
	int save_errno;

	if (len > SSIZE_MAX) {
		errno = EFBIG;
		return (FALSE);
	}

	/*
	 * Get the directory name where the file is to be written
	 * and create the temporary file.
	 *
	 * We don't use mkstemp() because mkstemp() always creates the
	 * file with mode 0600.  We do, however, use mktemp() safely.
	 */
 again:
	_prop_object_externalize_file_dirname(fname, tname);
	if (strlcat(tname, "/.plistXXXXXX", sizeof(tname)) >= sizeof(tname)) {
		errno = ENAMETOOLONG;
		return (FALSE);
	}
	if (mktemp(tname) == NULL)
		return (FALSE);
	if ((fd = open(tname, O_CREAT|O_RDWR|O_EXCL, 0666)) == -1) {
		if (errno == EEXIST)
			goto again;
		return (FALSE);
	}

	if (write(fd, xml, len) != (ssize_t)len)
		goto bad;

	if (fsync(fd) == -1)
		goto bad;

	(void) close(fd);
	fd = -1;

	if (rename(tname, fname) == -1)
		goto bad;

	return (TRUE);

 bad:
	save_errno = errno;
	if (fd != -1)
		(void) close(fd);
	(void) unlink(tname);
	errno = save_errno;
	return (FALSE);
}

/*
 * _prop_object_internalize_map_file --
 *	Map a file for the purpose of internalizing it.
 */
struct _prop_object_internalize_mapped_file *
_prop_object_internalize_map_file(const char *fname)
{
	struct stat sb;
	struct _prop_object_internalize_mapped_file *mf;
	size_t pgsize = (size_t)sysconf(_SC_PAGESIZE);
	size_t pgmask = pgsize - 1;
	boolean_t need_guard = FALSE;
	int fd;

	mf = _PROP_MALLOC(sizeof(*mf), M_TEMP);
	if (mf == NULL)
		return (NULL);
	
	fd = open(fname, O_RDONLY, 0400);
	if (fd == -1) {
		_PROP_FREE(mf, M_TEMP);
		return (NULL);
	}

	if (fstat(fd, &sb) == -1) {
		(void) close(fd);
		_PROP_FREE(mf, M_TEMP);
		return (NULL);
	}
	mf->poimf_mapsize = ((size_t)sb.st_size + pgmask) & ~pgmask;
	if (mf->poimf_mapsize < sb.st_size) {
		(void) close(fd);
		_PROP_FREE(mf, M_TEMP);
		return (NULL);
	}

	/*
	 * If the file length is an integral number of pages, then we
	 * need to map a guard page at the end in order to provide the
	 * necessary NUL-termination of the buffer.
	 */
	if ((sb.st_size & pgmask) == 0)
		need_guard = TRUE;

	mf->poimf_xml = mmap(NULL, need_guard ? mf->poimf_mapsize + pgsize
			    		      : mf->poimf_mapsize,
			    PROT_READ, MAP_FILE|MAP_SHARED, fd, (off_t)0);
	(void) close(fd);
	if (mf->poimf_xml == MAP_FAILED) {
		_PROP_FREE(mf, M_TEMP);
		return (NULL);
	}
	(void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_SEQUENTIAL);

	if (need_guard) {
		if (mmap(mf->poimf_xml + mf->poimf_mapsize,
			 pgsize, PROT_READ,
			 MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1,
			 (off_t)0) == MAP_FAILED) {
			(void) munmap(mf->poimf_xml, mf->poimf_mapsize);
			_PROP_FREE(mf, M_TEMP);
			return (NULL);
		}
		mf->poimf_mapsize += pgsize;
	}

	return (mf);
}

/*
 * _prop_object_internalize_unmap_file --
 *	Unmap a file previously mapped for internalizing.
 */
void
_prop_object_internalize_unmap_file(
    struct _prop_object_internalize_mapped_file *mf)
{

	(void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_DONTNEED);
	(void) munmap(mf->poimf_xml, mf->poimf_mapsize);
	_PROP_FREE(mf, M_TEMP);
}
#endif /* !_KERNEL && !_STANDALONE */

/*
 * Retain / release serialization --
 *
 * Eventually we would like to use atomic operations.  But until we have
 * an MI API for them that is common to userland and the kernel, we will
 * use a lock instead.
 *
 * We use a single global mutex for all serialization.  In the kernel, because
 * we are still under a biglock, this will basically never contend (properties
 * cannot be manipulated at interrupt level).  In userland, this will cost
 * nothing for single-threaded programs.  For multi-threaded programs, there
 * could be contention, but it probably won't cost that much unless the program
 * makes heavy use of property lists.
 */
_PROP_MUTEX_DECL_STATIC(_prop_refcnt_mutex)
#define	_PROP_REFCNT_LOCK()	_PROP_MUTEX_LOCK(_prop_refcnt_mutex)
#define	_PROP_REFCNT_UNLOCK()	_PROP_MUTEX_UNLOCK(_prop_refcnt_mutex)

/*
 * prop_object_retain --
 *	Increment the reference count on an object.
 */
void
prop_object_retain(prop_object_t obj)
{
	struct _prop_object *po = obj;
	uint32_t ocnt;

	_PROP_REFCNT_LOCK();
	ocnt = po->po_refcnt++;
	_PROP_REFCNT_UNLOCK();

	_PROP_ASSERT(ocnt != 0xffffffffU);
}

/*
 * prop_object_release --
 *	Decrement the reference count on an object.
 *
 *	Free the object if we are releasing the final
 *	reference.
 */
void
prop_object_release(prop_object_t obj)
{
	struct _prop_object *po = obj;
	uint32_t ocnt;

	_PROP_REFCNT_LOCK();
	ocnt = po->po_refcnt--;
	_PROP_REFCNT_UNLOCK();

	_PROP_ASSERT(ocnt != 0);
	if (ocnt == 1)
		(*po->po_type->pot_free)(po);
}

/*
 * prop_object_type --
 *	Return the type of an object.
 */
prop_type_t
prop_object_type(prop_object_t obj)
{
	struct _prop_object *po = obj;

	if (obj == NULL)
		return (PROP_TYPE_UNKNOWN);

	return (po->po_type->pot_type);
}

/*
 * prop_object_equals --
 *	Returns TRUE if thw two objects are equivalent.
 */
boolean_t
prop_object_equals(prop_object_t obj1, prop_object_t obj2)
{
	struct _prop_object *po1 = obj1;
	struct _prop_object *po2 = obj2;

	if (po1->po_type != po2->po_type)
		return (FALSE);

	return ((*po1->po_type->pot_equals)(obj1, obj2));
}

/*
 * prop_object_iterator_next --
 *	Return the next item during an iteration.
 */
prop_object_t
prop_object_iterator_next(prop_object_iterator_t pi)
{

	return ((*pi->pi_next_object)(pi));
}

/*
 * prop_object_iterator_reset --
 *	Reset the iterator to the first object so as to restart
 *	iteration.
 */
void
prop_object_iterator_reset(prop_object_iterator_t pi)
{

	(*pi->pi_reset)(pi);
}

/*
 * prop_object_iterator_release --
 *	Release the object iterator.
 */
void
prop_object_iterator_release(prop_object_iterator_t pi)
{

	prop_object_release(pi->pi_obj);
	_PROP_FREE(pi, M_TEMP);
}
