/*      $NetBSD: prop_object.c,v 1.42 2025/05/14 03:25:46 thorpej Exp $ */

/*-
* Copyright (c) 2006, 2007, 2025 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.
*
* 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_object_impl.h"
#include <prop/prop_object.h>

#ifdef _PROP_NEED_REFCNT_MTX
static pthread_mutex_t _prop_refcnt_mtx = PTHREAD_MUTEX_INITIALIZER;
#endif /* _PROP_NEED_REFCNT_MTX */

#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_retain --
*      Increment the reference count on an object.
*/
_PROP_EXPORT void
prop_object_retain(prop_object_t obj)
{
       struct _prop_object *po = obj;
       uint32_t ncnt __unused;

       _PROP_ATOMIC_INC32_NV(&po->po_refcnt, ncnt);
       _PROP_ASSERT(ncnt != 0);
}

/*
* prop_object_release_emergency
*      A direct free with prop_object_release failed.
*      Walk down the tree until a leaf is found and
*      free that. Do not recurse to avoid stack overflows.
*
*      This is a slow edge condition, but necessary to
*      guarantee that an object can always be freed.
*/
static void
prop_object_release_emergency(prop_object_t obj)
{
       struct _prop_object *po;
       void (*unlock)(void);
       prop_object_t parent = NULL;
       uint32_t ocnt;

       for (;;) {
               po = obj;
               _PROP_ASSERT(obj);

               if (po->po_type->pot_lock != NULL)
               po->po_type->pot_lock();

               /* Save pointerto unlock function */
               unlock = po->po_type->pot_unlock;

               /* Dance a bit to make sure we always get the non-racy ocnt */
               _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt);
               ocnt++;
               _PROP_ASSERT(ocnt != 0);

               if (ocnt != 1) {
                       if (unlock != NULL)
                               unlock();
                       break;
               }

               _PROP_ASSERT(po->po_type);
               if ((po->po_type->pot_free)(NULL, &obj) ==
                   _PROP_OBJECT_FREE_DONE) {
                       if (unlock != NULL)
                               unlock();
                       break;
               }

               if (unlock != NULL)
                       unlock();

               parent = po;
               _PROP_ATOMIC_INC32(&po->po_refcnt);
       }
       _PROP_ASSERT(parent);
       /* One object was just freed. */
       po = parent;
       (*po->po_type->pot_emergency_free)(parent);
}

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

       _prop_stack_init(&stack);

       do {
               do {
                       po = obj;
                       _PROP_ASSERT(obj);

                       if (po->po_type->pot_lock != NULL)
                               po->po_type->pot_lock();

                       /* Save pointer to object unlock function */
                       unlock = po->po_type->pot_unlock;

                       _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt);
                       ocnt++;
                       _PROP_ASSERT(ocnt != 0);

                       if (ocnt != 1) {
                               ret = 0;
                               if (unlock != NULL)
                                       unlock();
                               break;
                       }

                       ret = (po->po_type->pot_free)(&stack, &obj);

                       if (unlock != NULL)
                               unlock();

                       if (ret == _PROP_OBJECT_FREE_DONE)
                               break;

                       _PROP_ATOMIC_INC32(&po->po_refcnt);
               } while (ret == _PROP_OBJECT_FREE_RECURSE);
               if (ret == _PROP_OBJECT_FREE_FAILED)
                       prop_object_release_emergency(obj);
       } while (_prop_stack_pop(&stack, &obj, NULL, NULL, NULL));
}

/*
* prop_object_type --
*      Return the type of an object.
*/
_PROP_EXPORT 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.
*/
_PROP_EXPORT bool
prop_object_equals(prop_object_t obj1, prop_object_t obj2)
{
       return (prop_object_equals_with_error(obj1, obj2, NULL));
}

_PROP_EXPORT bool
prop_object_equals_with_error(prop_object_t obj1, prop_object_t obj2,
   bool *error_flag)
{
       struct _prop_object *po1;
       struct _prop_object *po2;
       void *stored_pointer1, *stored_pointer2;
       prop_object_t next_obj1, next_obj2;
       struct _prop_stack stack;
       _prop_object_equals_rv_t ret;

       _prop_stack_init(&stack);
       if (error_flag)
               *error_flag = false;

start_subtree:
       stored_pointer1 = NULL;
       stored_pointer2 = NULL;
       po1 = obj1;
       po2 = obj2;

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

continue_subtree:
       ret = (*po1->po_type->pot_equals)(obj1, obj2,
                                         &stored_pointer1, &stored_pointer2,
                                         &next_obj1, &next_obj2);
       if (ret == _PROP_OBJECT_EQUALS_FALSE)
               goto finish;
       if (ret == _PROP_OBJECT_EQUALS_TRUE) {
               if (!_prop_stack_pop(&stack, &obj1, &obj2,
                                    &stored_pointer1, &stored_pointer2))
                       return true;
               po1 = obj1;
               po2 = obj2;
               goto continue_subtree;
       }
       _PROP_ASSERT(ret == _PROP_OBJECT_EQUALS_RECURSE);

       if (!_prop_stack_push(&stack, obj1, obj2,
                             stored_pointer1, stored_pointer2)) {
               if (error_flag)
                       *error_flag = true;
               goto finish;
       }
       obj1 = next_obj1;
       obj2 = next_obj2;
       goto start_subtree;

finish:
       while (_prop_stack_pop(&stack, &obj1, &obj2, NULL, NULL)) {
               po1 = obj1;
               (*po1->po_type->pot_equals_finish)(obj1, obj2);
       }
       return (false);
}

/*
* prop_object_iterator_next --
*      Return the next item during an iteration.
*/
_PROP_EXPORT 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.
*/
_PROP_EXPORT void
prop_object_iterator_reset(prop_object_iterator_t pi)
{

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

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

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