/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <assert.h>
#include <pthread.h>
#include <stdlib.h>
#include <strings.h>
#include "configd.h"
#include "repcache_protocol.h"

typedef struct snapshot_bucket {
	pthread_mutex_t	sb_lock;
	rc_snapshot_t	*sb_head;

	char		sb_pad[64 - sizeof (pthread_mutex_t) -
			    sizeof (rc_snapshot_t *)];
} snapshot_bucket_t;

#define	SN_HASH_SIZE	64
#define	SN_HASH_MASK	(SN_HASH_SIZE - 1)

#pragma align 64(snapshot_hash)
static snapshot_bucket_t snapshot_hash[SN_HASH_SIZE];

#define	SNAPSHOT_BUCKET(h)	(&snapshot_hash[(h) & SN_HASH_MASK])

static rc_snapshot_t *
snapshot_alloc(void)
{
	rc_snapshot_t *sp;
	sp = uu_zalloc(sizeof (*sp));

	(void) pthread_mutex_init(&sp->rs_lock, NULL);
	(void) pthread_cond_init(&sp->rs_cv, NULL);

	sp->rs_refcnt++;
	return (sp);
}

static void
snapshot_free(rc_snapshot_t *sp)
{
	rc_snaplevel_t *lvl, *next;

	assert(sp->rs_refcnt == 0 && sp->rs_childref == 0);

	(void) pthread_mutex_destroy(&sp->rs_lock);
	(void) pthread_cond_destroy(&sp->rs_cv);

	for (lvl = sp->rs_levels; lvl != NULL; lvl = next) {
		next = lvl->rsl_next;

		assert(lvl->rsl_parent == sp);
		lvl->rsl_parent = NULL;

		if (lvl->rsl_service)
			free((char *)lvl->rsl_service);
		if (lvl->rsl_instance)
			free((char *)lvl->rsl_instance);

		uu_free(lvl);
	}
	uu_free(sp);
}

static void
rc_snapshot_hold(rc_snapshot_t *sp)
{
	(void) pthread_mutex_lock(&sp->rs_lock);
	sp->rs_refcnt++;
	assert(sp->rs_refcnt > 0);
	(void) pthread_mutex_unlock(&sp->rs_lock);
}

void
rc_snapshot_rele(rc_snapshot_t *sp)
{
	int done;
	(void) pthread_mutex_lock(&sp->rs_lock);
	assert(sp->rs_refcnt > 0);
	sp->rs_refcnt--;
	done = ((sp->rs_flags & RC_SNAPSHOT_DEAD) &&
	    sp->rs_refcnt == 0 && sp->rs_childref == 0);
	(void) pthread_mutex_unlock(&sp->rs_lock);

	if (done)
		snapshot_free(sp);
}

void
rc_snaplevel_hold(rc_snaplevel_t *lvl)
{
	rc_snapshot_t *sp = lvl->rsl_parent;
	(void) pthread_mutex_lock(&sp->rs_lock);
	sp->rs_childref++;
	assert(sp->rs_childref > 0);
	(void) pthread_mutex_unlock(&sp->rs_lock);
}

void
rc_snaplevel_rele(rc_snaplevel_t *lvl)
{
	int done;
	rc_snapshot_t *sp = lvl->rsl_parent;
	(void) pthread_mutex_lock(&sp->rs_lock);
	assert(sp->rs_childref > 0);
	sp->rs_childref--;
	done = ((sp->rs_flags & RC_SNAPSHOT_DEAD) &&
	    sp->rs_refcnt == 0 && sp->rs_childref == 0);
	(void) pthread_mutex_unlock(&sp->rs_lock);

	if (done)
		snapshot_free(sp);
}

static snapshot_bucket_t *
snapshot_hold_bucket(uint32_t snap_id)
{
	snapshot_bucket_t *bp = SNAPSHOT_BUCKET(snap_id);
	(void) pthread_mutex_lock(&bp->sb_lock);
	return (bp);
}

static void
snapshot_rele_bucket(snapshot_bucket_t *bp)
{
	assert(MUTEX_HELD(&bp->sb_lock));
	(void) pthread_mutex_unlock(&bp->sb_lock);
}

static rc_snapshot_t *
snapshot_lookup_unlocked(snapshot_bucket_t *bp, uint32_t snap_id)
{
	rc_snapshot_t *sp;

	assert(MUTEX_HELD(&bp->sb_lock));
	assert(bp == SNAPSHOT_BUCKET(snap_id));

	for (sp = bp->sb_head; sp != NULL; sp = sp->rs_hash_next) {
		if (sp->rs_snap_id == snap_id) {
			rc_snapshot_hold(sp);
			return (sp);
		}
	}
	return (NULL);
}

static void
snapshot_insert_unlocked(snapshot_bucket_t *bp, rc_snapshot_t *sp)
{
	assert(MUTEX_HELD(&bp->sb_lock));
	assert(bp == SNAPSHOT_BUCKET(sp->rs_snap_id));

	assert(sp->rs_hash_next == NULL);

	sp->rs_hash_next = bp->sb_head;
	bp->sb_head = sp;
}

static void
snapshot_remove_unlocked(snapshot_bucket_t *bp, rc_snapshot_t *sp)
{
	rc_snapshot_t **spp;

	assert(MUTEX_HELD(&bp->sb_lock));
	assert(bp == SNAPSHOT_BUCKET(sp->rs_snap_id));

	assert(sp->rs_hash_next == NULL);

	for (spp = &bp->sb_head; *spp != NULL; spp = &(*spp)->rs_hash_next)
		if (*spp == sp)
			break;

	assert(*spp == sp);
	*spp = sp->rs_hash_next;
	sp->rs_hash_next = NULL;
}

/*
 * Look up the snapshot with id snap_id in the hash table, or create it
 * & populate it with its snaplevels if it's not in the hash table yet.
 *
 * Fails with
 *   _NO_RESOURCES
 */
int
rc_snapshot_get(uint32_t snap_id, rc_snapshot_t **snpp)
{
	snapshot_bucket_t *bp;
	rc_snapshot_t *sp;
	int r;

	bp = snapshot_hold_bucket(snap_id);
	sp = snapshot_lookup_unlocked(bp, snap_id);
	if (sp != NULL) {
		snapshot_rele_bucket(bp);
		(void) pthread_mutex_lock(&sp->rs_lock);
		while (sp->rs_flags & RC_SNAPSHOT_FILLING)
			(void) pthread_cond_wait(&sp->rs_cv, &sp->rs_lock);

		if (sp->rs_flags & RC_SNAPSHOT_DEAD) {
			(void) pthread_mutex_unlock(&sp->rs_lock);
			rc_snapshot_rele(sp);
			return (REP_PROTOCOL_FAIL_NO_RESOURCES);
		}
		assert(sp->rs_flags & RC_SNAPSHOT_READY);
		(void) pthread_mutex_unlock(&sp->rs_lock);
		*snpp = sp;
		return (REP_PROTOCOL_SUCCESS);
	}
	sp = snapshot_alloc();
	sp->rs_snap_id = snap_id;
	sp->rs_flags |= RC_SNAPSHOT_FILLING;
	snapshot_insert_unlocked(bp, sp);
	snapshot_rele_bucket(bp);

	/*
	 * Now fill in the snapshot tree
	 */
	r = object_fill_snapshot(sp);
	if (r != REP_PROTOCOL_SUCCESS) {
		assert(r == REP_PROTOCOL_FAIL_NO_RESOURCES);

		/*
		 * failed -- first remove it from the hash table, then kill it
		 */
		bp = snapshot_hold_bucket(snap_id);
		snapshot_remove_unlocked(bp, sp);
		snapshot_rele_bucket(bp);

		(void) pthread_mutex_lock(&sp->rs_lock);
		sp->rs_flags &= ~RC_SNAPSHOT_FILLING;
		sp->rs_flags |= RC_SNAPSHOT_DEAD;
		(void) pthread_cond_broadcast(&sp->rs_cv);
		(void) pthread_mutex_unlock(&sp->rs_lock);
		rc_snapshot_rele(sp);		/* may free sp */
		return (r);
	}
	(void) pthread_mutex_lock(&sp->rs_lock);
	sp->rs_flags &= ~RC_SNAPSHOT_FILLING;
	sp->rs_flags |= RC_SNAPSHOT_READY;
	(void) pthread_cond_broadcast(&sp->rs_cv);
	(void) pthread_mutex_unlock(&sp->rs_lock);
	*snpp = sp;
	return (REP_PROTOCOL_SUCCESS);		/* pass on creation reference */
}