/*      $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);
}