/*-
 * Copyright (c) 2013-2016 Varnish Software AS
 * All rights reserved.
 *
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * 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 AUTHOR 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 AUTHOR 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.
 *
 * Lifetime of an objcore:
 *	phase 0	- nonexistent
 *	phase 1	- created, but no stevedore associated
 *	phase 2	- stevedore associated, being filled out
 *	phase 3	- stable, no changes happening
 *	phase 4	- unavailable, being dismantled
 *	phase 5	- stevedore disassociated
 *	phase 6	- nonexistent
 *
 * 0->1	ObjNew()	creates objcore
 *
 * 1->2	STV_NewObject()	associates a stevedore
 *
 * 2	ObjSetState()	sets state
 * 2	ObjWaitState()	waits for particular state
 *			INVALID->REQ_DONE->STREAM->FINISHED->FAILED
 *
 * 2	ObjGetSpace()	allocates space
 * 2	ObjExtend()	commits content
 * 2	ObjWaitExtend()	waits for content - used to implement ObjIterate())
 *
 * 2	ObjSetAttr()
 * 2	  ObjCopyAttr()
 * 2	  ObjSetFlag()
 * 2	  ObjSetDouble()
 * 2	  ObjSetU32()
 * 2	  ObjSetU64()
 *
 * 2->3	ObjBocDone()	Boc removed from OC, clean it up
 *
 * 23	ObjHasAttr()
 * 23	ObjGetAttr()
 * 23	  ObjCheckFlag()
 * 23	  ObjGetDouble()
 * 23	  ObjGetU32()
 * 23	  ObjGetU64()
 * 23	  ObjGetLen()
 * 23	  ObjGetXID()
 *
 * 23	ObjIterate()	... over body
 *
 * 23	ObjTouch()	Signal to LRU(-like) facilities
 *
 * 3->4	HSH_Snipe()	kill if not in use
 * 3->4	HSH_Kill()	make unavailable
 *
 * 234	ObjSlim()	Release body storage (but retain attribute storage)
 *
 * 4->5	ObjFreeObj()	disassociates stevedore
 *
 * 5->6 FREE_OBJ()	...in HSH_DerefObjCore()
 */

#include "config.h"

#include <stdlib.h>

#include "cache_vinyld.h"
#include "cache_obj.h"
#include "cache_objhead.h"
#include "vend.h"
#include "storage/storage.h"

static const struct obj_methods *
obj_getmethods(const struct objcore *oc)
{

	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
	CHECK_OBJ_NOTNULL(oc->stobj->stevedore, STEVEDORE_MAGIC);
	AN(oc->stobj->stevedore->methods);
	return (oc->stobj->stevedore->methods);
}

static struct boc *
obj_newboc(void)
{
	struct boc *boc;

	ALLOC_OBJ(boc, BOC_MAGIC);
	AN(boc);
	Lck_New(&boc->mtx, lck_busyobj);
	PTOK(pthread_cond_init(&boc->cond, NULL));
	boc->refcount = 1;
	return (boc);
}

static void
obj_deleteboc(struct boc **p)
{
	struct boc *boc;

	TAKE_OBJ_NOTNULL(boc, p, BOC_MAGIC);
	Lck_Delete(&boc->mtx);
	PTOK(pthread_cond_destroy(&boc->cond));
	free(boc->vary);
	FREE_OBJ(boc);
}

/*====================================================================
 * ObjNew()
 *
 */

struct objcore *
ObjNew(const struct worker *wrk)
{
	struct objcore *oc;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);

	ALLOC_OBJ(oc, OBJCORE_MAGIC);
	AN(oc);
	wrk->stats->n_objectcore++;
	oc->last_lru = NAN;
	oc->boc = obj_newboc();

	return (oc);
}

/*====================================================================
 * ObjDestroy()
 *
 */

void
ObjDestroy(const struct worker *wrk, struct objcore **p)
{
	struct objcore *oc;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	TAKE_OBJ_NOTNULL(oc, p, OBJCORE_MAGIC);
	if (oc->boc != NULL)
		obj_deleteboc(&oc->boc);
	FREE_OBJ(oc);
	wrk->stats->n_objectcore--;
}

/*====================================================================
 * ObjIterate()
 *
 */

int
ObjIterate(struct worker *wrk, struct objcore *oc,
    void *priv, objiterate_f *func, int final)
{
	const struct obj_methods *om = obj_getmethods(oc);

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	AN(func);
	AN(om->objiterator);
	return (om->objiterator(wrk, oc, priv, func, final));
}

/*====================================================================
 * ObjVAI...(): Asynchronous Iteration
 *
 *
 * ObjVAIinit() returns an opaque handle, or NULL if not supported
 *
 *	A VAI handle must not be used concurrently
 *
 *	the vai_notify_cb(priv) will be called asynchronously by the storage
 *	engine when a -EAGAIN / -ENOBUFS condition is over and ObjVAIlease()
 *	can be called again.
 *
 *	Note:
 *	- the callback gets executed by an arbitrary thread
 *	- WITH the boc mtx held
 *	so it should never block and only do minimal work
 *
 * ObjVAIlease() fills the vscarab with leases. returns:
 *
 *	-EAGAIN:  nothing available at the moment, storage will notify, no use to
 *		  call again until notification
 *	-ENOBUFS: caller needs to return leases, storage will notify
 *	-EPIPE:	  BOS_FAILED for busy object
 *	-(errno): other problem, fatal
 *
 *	>= 0:	  number of viovs added (== scarab->capacity - scarab->used)
 *
 *	struct vscarab:
 *
 *	the leases can be used by the caller until returned with
 *	ObjVAIreturn(). The storage guarantees that the lease member is a
 *	multiple of 8 (that is, the lower three bits are zero). These can be
 *	used by the caller between lease and return, but must be cleared to
 *	zero before returning.
 *
 * ObjVAIbuffer() allocates temporary buffers, returns:
 *
 *	-EAGAIN:  allocation can not be fulfilled immediately, storage will notify,
 *		  no use to call again until notification
 *	-EINVAL:  size larger than UINT_MAX requested
 *	-(errno): other problem, fatal
 *	n:	  n > 0, number of viovs filled
 *
 *	The struct vscarab is used on the way in and out: On the way in, the
 *	iov.iov_len members contain the sizes the caller requests, all other
 *	members of the struct viovs are expected to be zero initialized.
 *
 *	The maximum size to be requested is UINT_MAX.
 *
 *	ObjVAIbuffer() may return sizes larger than requested. The returned n
 *	might be smaller than requested.
 *
 * ObjVAIreturn() returns leases collected in a struct vscaret
 *
 *	it must be called with a vscaret, which holds an array of lease values
 *	received via ObjVAIlease() or ObjVAIbuffer() when the caller can
 *	guarantee that they are no longer accessed.
 *
 *	ObjVAIreturn() may retain leases in the vscaret if the implementation
 *	still requires them, iow, the vscaret might not be empty upon return.
 *
 * ObjVAInotify() notify to resume delivery from a vmod
 *
 *	Not only a storage engine can encounter a "no data ready at the moment"
 *	condition, but also a filter: A filter's .io_lease() function might
 *	return -EAGAIN, but then it needs a way to notify when more data is
 *	available. This is ObjVAInotify().
 *
 * ObjVAIfini() finalized iteration
 *
 *	it must be called when iteration is done, irrespective of error status
 */

vai_hdl
ObjVAIinit(struct worker *wrk, struct objcore *oc, struct ws *ws,
    vai_notify_cb *cb, void *cb_priv)
{
	const struct obj_methods *om = obj_getmethods(oc);

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);

	if (om->vai_init == NULL)
		return (NULL);
	return (om->vai_init(wrk, oc, ws, cb, cb_priv));
}

int
ObjVAIlease(struct worker *wrk, vai_hdl vhdl, struct vscarab *scarab)
{
	struct vai_hdl_preamble *vaip = vhdl;

	AN(vaip);
	assert(vaip->magic2 == VAI_HDL_PREAMBLE_MAGIC2);
	AN(vaip->vai_lease);
	return (vaip->vai_lease(wrk, vhdl, scarab));
}

int
ObjVAIbuffer(struct worker *wrk, vai_hdl vhdl, struct vscarab *scarab)
{
	struct vai_hdl_preamble *vaip = vhdl;

	AN(vaip);
	assert(vaip->magic2 == VAI_HDL_PREAMBLE_MAGIC2);
	AN(vaip->vai_buffer);
	return (vaip->vai_buffer(wrk, vhdl, scarab));
}

void
ObjVAIreturn(struct worker *wrk, vai_hdl vhdl, struct vscaret *scaret)
{
	struct vai_hdl_preamble *vaip = vhdl;

	AN(vaip);
	assert(vaip->magic2 == VAI_HDL_PREAMBLE_MAGIC2);
	AN(vaip->vai_return);
	vaip->vai_return(wrk, vhdl, scaret);
}

void
ObjVAInotify(struct worker *wrk, vai_hdl vhdl)
{
	struct vai_hdl_preamble *vaip = vhdl;

	AN(vaip);
	assert(vaip->magic2 == VAI_HDL_PREAMBLE_MAGIC2);
	AN(vaip->vai_notify);
	vaip->vai_notify(wrk, vhdl);
}

void
ObjVAIfini(struct worker *wrk, vai_hdl *vhdlp)
{
	AN(vhdlp);
	struct vai_hdl_preamble *vaip = *vhdlp;

	AN(vaip);
	assert(vaip->magic2 == VAI_HDL_PREAMBLE_MAGIC2);
	AN(vaip->vai_lease);
	vaip->vai_fini(wrk, vhdlp);
}

/*====================================================================
 * ObjGetSpace()
 *
 * This function returns a pointer and length of free space.  If there
 * is no free space, some will be added first.
 *
 * The "sz" argument is an input hint of how much space is desired.
 * 0 means "unknown", return some default size (maybe fetch_chunksize)
 */

int
ObjGetSpace(struct worker *wrk, struct objcore *oc, ssize_t *sz, uint8_t **ptr)
{
	const struct obj_methods *om = obj_getmethods(oc);

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	CHECK_OBJ_NOTNULL(oc->boc, BOC_MAGIC);
	AN(sz);
	AN(ptr);
	assert(*sz >= 0);

	AN(om->objgetspace);
	return (om->objgetspace(wrk, oc, sz, ptr));
}

/*====================================================================
 * ObjExtend()
 *
 * This function extends the used part of the object a number of bytes
 * into the last space returned by ObjGetSpace()
 *
 * The final flag must be set on the last call, and it will release any
 * surplus space allocated.
 */

static void
obj_extend_condwait(const struct objcore *oc)
{

	if (oc->boc->transit_buffer == 0)
		return;

	assert(oc->flags & OC_F_TRANSIENT);
	while (!(oc->flags & OC_F_CANCEL) && oc->boc->fetched_so_far >
	    oc->boc->delivered_so_far + oc->boc->transit_buffer)
		(void)Lck_CondWait(&oc->boc->cond, &oc->boc->mtx);
}

// notify of an extension of the boc or state change

static void
obj_boc_notify(struct boc *boc)
{
	struct vai_qe *qe, *next;

	PTOK(pthread_cond_broadcast(&boc->cond));
	qe = VSLIST_FIRST(&boc->vai_q_head);
	VSLIST_FIRST(&boc->vai_q_head) = NULL;
	while (qe != NULL) {
		CHECK_OBJ(qe, VAI_Q_MAGIC);
		AN(qe->flags & VAI_QF_INQUEUE);
		qe->flags &= ~VAI_QF_INQUEUE;
		next = VSLIST_NEXT(qe, list);
		VSLIST_NEXT(qe, list) = NULL;
		qe->cb(qe->hdl, qe->priv);
		qe = next;
	}
}

void
ObjExtend(struct worker *wrk, struct objcore *oc, ssize_t l, int final)
{
	const struct obj_methods *om = obj_getmethods(oc);

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	CHECK_OBJ_NOTNULL(oc->boc, BOC_MAGIC);
	AN(om->objextend);
	assert(l >= 0);

	if (l > 0) {
		Lck_Lock(&oc->boc->mtx);
		obj_extend_condwait(oc);
		om->objextend(wrk, oc, l);
		oc->boc->fetched_so_far += l;
		obj_boc_notify(oc->boc);
		Lck_Unlock(&oc->boc->mtx);

		if (oc->boc->transit_buffer > 0)
			wrk->stats->transit_buffered += l;
		else if (oc->flags & OC_F_TRANSIENT)
			wrk->stats->transit_stored += l;
	}

	assert(oc->boc->state < BOS_FINISHED);
	if (final && om->objtrimstore != NULL)
		om->objtrimstore(wrk, oc);
}

/*====================================================================
 */

static inline void
objSignalFetchLocked(const struct objcore *oc, uint64_t l)
{
	if (oc->boc->transit_buffer > 0) {
		assert(oc->flags & OC_F_TRANSIENT);
		/* Signal the new client position */
		oc->boc->delivered_so_far = l;
		PTOK(pthread_cond_signal(&oc->boc->cond));
	}
}

uint64_t
ObjWaitExtend(const struct worker *wrk, const struct objcore *oc, uint64_t l,
    enum boc_state_e *statep)
{
	enum boc_state_e state;
	uint64_t rv;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
	CHECK_OBJ_NOTNULL(oc->boc, BOC_MAGIC);
	Lck_Lock(&oc->boc->mtx);
	while (1) {
		rv = oc->boc->fetched_so_far;
		assert(l <= rv || oc->boc->state == BOS_FAILED);
		state = oc->boc->state;
		objSignalFetchLocked(oc, l);
		if (rv > l || state >= BOS_FINISHED)
			break;
		(void)Lck_CondWait(&oc->boc->cond, &oc->boc->mtx);
	}
	Lck_Unlock(&oc->boc->mtx);
	if (statep != NULL)
		*statep = state;
	return (rv);
}

// get a new extension _or_ register a notification
uint64_t
ObjVAIGetExtend(struct worker *wrk, const struct objcore *oc, uint64_t l,
    enum boc_state_e *statep, struct vai_qe *qe)
{
	enum boc_state_e state;
	uint64_t rv;

	(void) wrk;
	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
	CHECK_OBJ_NOTNULL(oc->boc, BOC_MAGIC);
	CHECK_OBJ_NOTNULL(qe, VAI_Q_MAGIC);
	Lck_Lock(&oc->boc->mtx);
	rv = oc->boc->fetched_so_far;
	assert(l <= rv || oc->boc->state == BOS_FAILED);
	state = oc->boc->state;
	objSignalFetchLocked(oc, l);
	if (l == rv && state < BOS_FINISHED &&
	    (qe->flags & VAI_QF_INQUEUE) == 0) {
		qe->flags |= VAI_QF_INQUEUE;
		VSLIST_INSERT_HEAD(&oc->boc->vai_q_head, qe, list);
	}
	Lck_Unlock(&oc->boc->mtx);
	if (statep != NULL)
		*statep = state;
	return (rv);
}

void
ObjVAICancel(struct worker *wrk, struct boc *boc, struct vai_qe *qe)
{

	(void) wrk;
	CHECK_OBJ_NOTNULL(boc, BOC_MAGIC);
	CHECK_OBJ_NOTNULL(qe, VAI_Q_MAGIC);

	Lck_Lock(&boc->mtx);
	// inefficient, but should be rare
	if ((qe->flags & VAI_QF_INQUEUE) != 0)
		VSLIST_REMOVE(&boc->vai_q_head, qe, vai_qe, list);
	qe->flags = 0;
	Lck_Unlock(&boc->mtx);
}

/*====================================================================
 */

void
ObjSetState(struct worker *wrk, struct objcore *oc, enum boc_state_e next,
    unsigned broadcast)
{
	const struct obj_methods *om;

	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
	CHECK_OBJ_NOTNULL(oc->boc, BOC_MAGIC);
	assert(next > oc->boc->state);

	CHECK_OBJ_ORNULL(oc->stobj->stevedore, STEVEDORE_MAGIC);
	assert(next != BOS_FINISHED || (oc->oa_present & (1 << OA_LEN)));

	if (oc->stobj->stevedore != NULL) {
		om = oc->stobj->stevedore->methods;
		if (om->objsetstate != NULL)
			om->objsetstate(wrk, oc, next);
	}

	if (next == BOS_FAILED)
		HSH_Fail(wrk, oc);
	else if (oc->boc->state < BOS_STREAM && next >= BOS_STREAM)
		HSH_Unbusy(wrk, oc);

	Lck_Lock(&oc->boc->mtx);
	oc->boc->state = next;
	if (broadcast)
		obj_boc_notify(oc->boc);
	Lck_Unlock(&oc->boc->mtx);
}

/*====================================================================
 */

enum boc_state_e
ObjWaitState(const struct objcore *oc, enum boc_state_e want)
{
	enum boc_state_e got;

	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
	CHECK_OBJ_NOTNULL(oc->boc, BOC_MAGIC);

	Lck_Lock(&oc->boc->mtx);
	/* wake up obj_extend_condwait() */
	if (oc->flags & OC_F_CANCEL)
		PTOK(pthread_cond_signal(&oc->boc->cond));
	while (1) {
		if (oc->boc->state >= want)
			break;
		(void)Lck_CondWait(&oc->boc->cond, &oc->boc->mtx);
	}
	got = oc->boc->state;
	Lck_Unlock(&oc->boc->mtx);

	return (got);
}

/*====================================================================
 * ObjGetlen()
 *
 * This is a separate function because it may need locking
 */

uint64_t
ObjGetLen(struct worker *wrk, struct objcore *oc)
{
	uint64_t len;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);

	AZ(ObjGetU64(wrk, oc, OA_LEN, &len));
	return (len);
}

/*====================================================================
 * ObjSlim()
 *
 * Free the whatever storage can be freed, without freeing the actual
 * object yet.
 */

void
ObjSlim(struct worker *wrk, struct objcore *oc)
{
	const struct obj_methods *om = obj_getmethods(oc);

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);

	if (om->objslim != NULL)
		om->objslim(wrk, oc);
}

/*====================================================================
 * Called when the boc used to populate the objcore is going away.
 * Useful for releasing any leftovers from Trim.
 */

void
ObjBocDone(struct worker *wrk, struct objcore *oc, struct boc **boc)
{
	const struct obj_methods *m;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
	AN(boc);
	CHECK_OBJ_NOTNULL(*boc, BOC_MAGIC);
	CHECK_OBJ_ORNULL(oc->stobj->stevedore, STEVEDORE_MAGIC);
	if (oc->stobj->stevedore != NULL) {
		m = obj_getmethods(oc);
		if (m->objbocdone != NULL)
			m->objbocdone(wrk, oc, *boc);
	}
	obj_deleteboc(boc);
}

/*====================================================================
 */
void
ObjFreeObj(struct worker *wrk, struct objcore *oc)
{
	const struct obj_methods *m = obj_getmethods(oc);

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);

	AN(m->objfree);
	m->objfree(wrk, oc);
	AZ(oc->stobj->stevedore);
}

/*====================================================================
 * ObjHasAttr()
 *
 * Check if object has this attribute
 */

int
ObjHasAttr(struct worker *wrk, struct objcore *oc, enum obj_attr attr)
{

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);

	if (oc->oa_present)
		return (oc->oa_present & (1 << attr));

	/* resurrected persistent objects don't have oa_present set */
	return (ObjGetAttr(wrk, oc, attr, NULL) != NULL ? 1 : 0);
}

/*====================================================================
 * ObjGetAttr()
 *
 * Get an attribute of the object.
 *
 * Returns NULL on unset or zero length attributes and len set to
 * zero. Returns Non-NULL otherwise and len is updated with the attributes
 * length.
 */

const void *
ObjGetAttr(struct worker *wrk, struct objcore *oc, enum obj_attr attr,
   ssize_t *len)
{
	const struct obj_methods *om = obj_getmethods(oc);

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);

	AN(om->objgetattr);
	return (om->objgetattr(wrk, oc, attr, len));
}

/*====================================================================
 * ObjSetAttr()
 *
 * Setting fixed size attributes always succeeds.
 *
 * Setting a variable size attribute asserts if the combined size of the
 * variable attributes exceeds the total variable attribute space set at
 * object creation. If there is space it always succeeds.
 *
 * Setting an auxiliary attribute can fail.
 *
 * Resetting any variable asserts if the new length does not match the
 * previous length exactly.
 *
 * If ptr is Non-NULL, it points to the new content which is copied into
 * the attribute.  Otherwise the caller will have to do the copying.
 *
 * Return value is non-NULL on success and NULL on failure. If ptr was
 * non-NULL, it is an error to use the returned pointer to set the
 * attribute data, it is only a success indicator in that case.
 */

void *
ObjSetAttr(struct worker *wrk, struct objcore *oc, enum obj_attr attr,
    ssize_t len, const void *ptr)
{
	const struct obj_methods *om = obj_getmethods(oc);
	void *r;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	CHECK_OBJ_NOTNULL(oc->boc, BOC_MAGIC);

	AN(om->objsetattr);
	assert((int)attr < 16);
	r = om->objsetattr(wrk, oc, attr, len, ptr);
	if (r)
		oc->oa_present |= (1 << attr);
	return (r);
}

/*====================================================================
 * ObjTouch()
 */

void
ObjTouch(struct worker *wrk, struct objcore *oc, vtim_real now)
{
	const struct obj_methods *om = obj_getmethods(oc);

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	if (om->objtouch != NULL)
		om->objtouch(wrk, oc, now);
}

/*====================================================================
 * Utility functions which work on top of the previous ones
 */

int
ObjCopyAttr(struct worker *wrk, struct objcore *oc, struct objcore *ocs,
    enum obj_attr attr)
{
	const void *vps;
	void *vpd;
	ssize_t l;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
	CHECK_OBJ_NOTNULL(oc->boc, BOC_MAGIC);
	CHECK_OBJ_NOTNULL(ocs, OBJCORE_MAGIC);

	vps = ObjGetAttr(wrk, ocs, attr, &l);
	// XXX: later we want to have zero-length OA's too
	if (vps == NULL || l <= 0)
		return (-1);
	vpd = ObjSetAttr(wrk, oc, attr, l, vps);
	if (vpd == NULL)
		return (-1);
	return (0);
}

int
ObjSetXID(struct worker *wrk, struct objcore *oc, vxid_t xid)
{
	uint64_t u;

	u = VXID(xid);
	AZ(ObjSetU64(wrk, oc, OA_VXID, u));
	return (0);
}


vxid_t
ObjGetXID(struct worker *wrk, struct objcore *oc)
{
	vxid_t u;

	AZ(ObjGetU64(wrk, oc, OA_VXID, &u.vxid));
	return (u);
}

/*--------------------------------------------------------------------
 * There is no well-defined byteorder for IEEE-754 double and the
 * correct solution (frexp(3) and manual encoding) is more work
 * than our (weak) goal of being endian-agnostic requires at this point.
 * We give it a shot by memcpy'ing doubles over a uint64_t and then
 * BE encode that.
 */

int
ObjSetDouble(struct worker *wrk, struct objcore *oc, enum obj_attr a, double t)
{
	void *vp;
	uint64_t u;

	assert(sizeof t == sizeof u);
	memcpy(&u, &t, sizeof u);
	vp = ObjSetAttr(wrk, oc, a, sizeof u, NULL);
	if (vp == NULL)
		return (-1);
	vbe64enc(vp, u);
	return (0);
}

int
ObjGetDouble(struct worker *wrk, struct objcore *oc, enum obj_attr a, double *d)
{
	const void *vp;
	uint64_t u;
	ssize_t l;

	assert(sizeof *d == sizeof u);
	vp = ObjGetAttr(wrk, oc, a, &l);
	if (vp == NULL)
		return (-1);
	if (d != NULL) {
		assert(l == sizeof u);
		u = vbe64dec(vp);
		memcpy(d, &u, sizeof *d);
	}
	return (0);
}

/*--------------------------------------------------------------------
 */

int
ObjSetU64(struct worker *wrk, struct objcore *oc, enum obj_attr a, uint64_t t)
{
	void *vp;

	vp = ObjSetAttr(wrk, oc, a, sizeof t, NULL);
	if (vp == NULL)
		return (-1);
	vbe64enc(vp, t);
	return (0);
}

int
ObjGetU64(struct worker *wrk, struct objcore *oc, enum obj_attr a, uint64_t *d)
{
	const void *vp;
	ssize_t l;

	vp = ObjGetAttr(wrk, oc, a, &l);
	if (vp == NULL || l != sizeof *d)
		return (-1);
	if (d != NULL)
		*d = vbe64dec(vp);
	return (0);
}

/*--------------------------------------------------------------------
 */

int
ObjCheckFlag(struct worker *wrk, struct objcore *oc, enum obj_flags of)
{
	const uint8_t *fp;

	fp = ObjGetAttr(wrk, oc, OA_FLAGS, NULL);
	AN(fp);
	return ((*fp) & of);
}

void
ObjSetFlag(struct worker *wrk, struct objcore *oc, enum obj_flags of, int val)
{
	uint8_t *fp;

	fp = ObjSetAttr(wrk, oc, OA_FLAGS, 1, NULL);
	AN(fp);
	if (val)
		(*fp) |= of;
	else
		(*fp) &= ~of;
}

/*====================================================================
 * Object event subscription mechanism.
 *
 * XXX: it is extremely unclear what the locking circumstances are here.
 */

struct oev_entry {
	unsigned			magic;
#define OEV_MAGIC			0xb0b7c5a1
	unsigned			mask;
	obj_event_f			*func;
	void				*priv;
	VTAILQ_ENTRY(oev_entry)		list;
};

static VTAILQ_HEAD(,oev_entry)		oev_list;
static pthread_rwlock_t			oev_rwl;
static unsigned				oev_mask;

/*
 * NB: ObjSubscribeEvents() is not atomic:
 * oev_mask is checked optimistically in ObjSendEvent()
 */
uintptr_t
ObjSubscribeEvents(obj_event_f *func, void *priv, unsigned mask)
{
	struct oev_entry *oev;

	AN(func);
	AZ(mask & ~OEV_MASK);

	ALLOC_OBJ(oev, OEV_MAGIC);
	AN(oev);
	oev->func = func;
	oev->priv = priv;
	oev->mask = mask;
	PTOK(pthread_rwlock_wrlock(&oev_rwl));
	VTAILQ_INSERT_TAIL(&oev_list, oev, list);
	oev_mask |= mask;
	PTOK(pthread_rwlock_unlock(&oev_rwl));
	return ((uintptr_t)oev);
}

void
ObjUnsubscribeEvents(uintptr_t *handle)
{
	struct oev_entry *oev, *oev2 = NULL;
	unsigned newmask = 0;

	AN(handle);
	AN(*handle);
	PTOK(pthread_rwlock_wrlock(&oev_rwl));
	VTAILQ_FOREACH(oev, &oev_list, list) {
		CHECK_OBJ_NOTNULL(oev, OEV_MAGIC);
		if ((uintptr_t)oev == *handle)
			oev2 = oev;
		else
			newmask |= oev->mask;
	}
	AN(oev2);
	VTAILQ_REMOVE(&oev_list, oev2, list);
	oev_mask = newmask;
	AZ(newmask & ~OEV_MASK);
	PTOK(pthread_rwlock_unlock(&oev_rwl));
	FREE_OBJ(oev2);
	*handle = 0;
}

void
ObjSendEvent(struct worker *wrk, struct objcore *oc, unsigned event)
{
	struct oev_entry *oev;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
	AN(event & OEV_MASK);
	AZ(event & ~OEV_MASK);
	if (!(event & oev_mask))
		return;

	PTOK(pthread_rwlock_rdlock(&oev_rwl));
	VTAILQ_FOREACH(oev, &oev_list, list) {
		CHECK_OBJ_NOTNULL(oev, OEV_MAGIC);
		if (event & oev->mask)
			oev->func(wrk, oev->priv, oc, event);
	}
	PTOK(pthread_rwlock_unlock(&oev_rwl));

}

void
ObjInit(void)
{
	VTAILQ_INIT(&oev_list);
	PTOK(pthread_rwlock_init(&oev_rwl, NULL));
}
