/*
 * Copyright (c) 2018-2021 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */
#include <skywalk/os_skywalk_private.h>

__attribute__((always_inline))
static inline struct sysctl_oid *
_skoid_oid_alloc(void)
{
	return sk_alloc_type(struct sysctl_oid, Z_WAITOK | Z_NOFAIL, skmem_tag_oid);
}

__attribute__((always_inline))
static inline void
_skoid_oid_free(struct sysctl_oid *oid)
{
	sk_free_type(struct sysctl_oid, oid);
}

__attribute__((always_inline))
static inline void
_skoid_oid_init(struct skoid *skoid, struct sysctl_oid *oid,
    struct sysctl_oid_list *parent, int kind, void *arg1, int arg2,
    const char *name, int (*handler)SYSCTL_HANDLER_ARGS, const char *fmt)
{
	const char *__null_terminated sko_name = NULL;
	ASSERT(oid != NULL);
	/*
	 * Note here we use OID2 for current version, which does oid alloc/free
	 * ourselves with mcache
	 */
	oid->oid_link.sle_next = NULL;
	oid->oid_parent = parent;
	oid->oid_number = OID_AUTO;
	oid->oid_kind = CTLFLAG_OID2 | kind;
	oid->oid_arg1 = arg1;
	oid->oid_arg2 = arg2;
	if (&skoid->sko_oid == oid) {
		/* for DNODE, store its name inside skoid */
		sko_name = tsnprintf(skoid->sko_name, sizeof(skoid->sko_name),
		    "%s", name);
		oid->oid_name = sko_name;
	} else {
		/* for leaf property, use static name string */
		oid->oid_name = name;
	}
	oid->oid_handler = handler;
	oid->oid_fmt = fmt;
	oid->oid_descr = "";    /* unused for current SYSCTL_OID_VERSION */
	oid->oid_version = SYSCTL_OID_VERSION;
	oid->oid_refcnt = 0;
}

/*
 * Create the skoid and register its sysctl_oid.
 */
void
skoid_create(struct skoid *skoid, struct sysctl_oid_list *parent,
    const char *name, int kind)
{
	struct sysctl_oid *oid = &skoid->sko_oid;

	_skoid_oid_init(skoid, oid, parent,
	    CTLTYPE_NODE | CTLFLAG_LOCKED | kind,
	    &skoid->sko_oid_list, 0, name, NULL, "N");
	sysctl_register_oid(oid);
}

__attribute__((always_inline))
static inline void
_skoid_add_property(struct skoid *skoid, const char *name, int kind,
    const char *fmt, void *arg1, int arg2, int (*handler)SYSCTL_HANDLER_ARGS)
{
	struct sysctl_oid *oid;

	oid = _skoid_oid_alloc();
	_skoid_oid_init(skoid, oid, &skoid->sko_oid_list, CTLFLAG_LOCKED | kind,
	    arg1, arg2, name, handler, fmt);
	sysctl_register_oid(oid);
}

/*
 * Add int property to skoid.
 */
void
skoid_add_int(struct skoid *skoid, const char *name, int kind,
    int *int_ptr)
{
	_skoid_add_property(skoid, name, CTLTYPE_INT | kind, "I", int_ptr, 0,
	    sysctl_handle_int);
}

/*
 * Add unsigned int property to skoid.
 */
void
skoid_add_uint(struct skoid *skoid, const char *name, int kind,
    unsigned int *uint_ptr)
{
	_skoid_add_property(skoid, name, CTLTYPE_INT | kind, "IU", uint_ptr, 0,
	    sysctl_handle_int);
}

/*
 * Add procedure handler property to skoid.
 */
void
skoid_add_handler(struct skoid *skoid, const char *name, int kind,
    int (*proc)SYSCTL_HANDLER_ARGS, void *proc_arg1, int proc_arg2)
{
	_skoid_add_property(skoid, name, CTLTYPE_INT | kind, "I", proc_arg1,
	    proc_arg2, proc);
}

/*
 * Destroy skoid and its associated properties
 *
 * @discussion This functions only handles properties associated with it and
 * the unregistration of the sysctl_oid. If skoid itself is dynamically
 * allocated, it's the caller who should release skoid object.
 */
void
skoid_destroy(struct skoid *skoid)
{
	/*
	 * first take down parent sysctl node, which internally deletes it from
	 * sko_oid_list, so we don't free it below
	 */
	sysctl_unregister_oid(&skoid->sko_oid);

	/* then destroy all properties sysctl nodes */
	struct sysctl_oid *oid, *oid_tmp;
	SLIST_FOREACH_SAFE(oid, &skoid->sko_oid_list, oid_link, oid_tmp) {
		/* sub dynamic node must be destroyed first */
		if ((oid->oid_kind & CTLTYPE) == CTLTYPE_NODE) {
			panic("leaked skoid sub-node detected %p %s",
			    oid, oid->oid_name);
			__builtin_unreachable();
		}
		sysctl_unregister_oid(oid);
		ASSERT(oid != &skoid->sko_oid);
		_skoid_oid_free(oid);
	}
}
