/*
 * nasd_capability.c
 *
 * Capability operations for EDRFS.
 *
 * Authors: Nat Lanza, Howard Gobioff, Jim Zelenka
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */
/*
 *  Notes:
 *
 *   1) The first iteration of this code assumes that we statically
 *   allocate a finite number of capabilities in the kernel. 
 *   2) When the freelist is empty, we steal the least recently used
 *   capability table entry from the next non-empty capability table bucket
 *   3) We assume that the user has ONE capability per object and
 *   it expresses the users full access rights. This is not an assumption
 *   of NASD but of the EDRFS on NASD implementation.
 *
 *
 * 12 May 1998 jimz
 *    - partly rewrote
 *    - use freelist for cap entries
 *    - currently, freelist is primed for full-size at create, and
 *      we keep track of how big we'll let ourselves grow
 *    - use an lru for finding entries to replace
 *    - move accessed entries to head-of-bucket for faster lookup next time
 *
 * 16 Apr 1999 magus
 *    - ported to Linux client
 *    - some general cleanup
 *    - switch to Linux kernel conventions (static declarations, inlines, etc)
 */

#include <nasd/nasd_options.h>
#include <nasd/nasd_types.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_threadstuff.h>
#include <nasd/nasd_freelist.h>
#include <nasd/nasd_edrfs_client.h>
#include <nasd/linux/nasd_edrfs_client_linux.h>

#define DEBUG_CLEAN_BUCKETS_DETAIL      0
#define DEBUG_DESTROY_FREELIST_DETAIL   0
#define DEBUG_REAL_INIT_DETAIL          0
#define DEBUG_REAL_SHUTDOWN_DETAIL      0
#define DEBUG_SYS_INIT_DETAIL           0
#define DEBUG_INIT_DETAIL               0
#define DEBUG_SHUTDOWN_DETAIL           0
#define DEBUG_LOOKUP_DETAIL             0
#define DEBUG_INSERT_DETAIL             0
#define DEBUG_REMOVE_DETAIL             1
#define DEBUG_FIND_OR_GET_COOKIE_DETAIL 0

#define NASD_MAX_FREE_EDRFS_CAPS NASD_EDRFS_MAX_ACTIVE_CAPS
#define NASD_EDRFS_CAPS_INC      1
#define NASD_EDRFS_CAPS_INITIAL  NASD_EDRFS_MAX_ACTIVE_CAPS

NASD_DECLARE_ONCE(nasd_edrfs_caps_init_once)
NASD_DECLARE_MUTEX(nasd_edrfs_caps_use_counter_lock)

static int                           nasd_edrfs_caps_use_counter;
static int                           nasd_edrfs_caps_outstanding   = 0;
static nasd_shutdown_list_t         *nasd_edrfs_caps_shutdown_list = NULL;
static nasd_freelist_t              *nasd_edrfs_caps_freelist      = NULL;
static nasd_edrfs_captable_entry_t   nasd_edrfs_cap_lru;
static nasd_edrfs_captable_entry_t   nasd_edrfs_cap_buckets[NASD_EDRFS_CAP_TABLE_SZ];

#define LOCK_CAPS()   NASD_FREELIST_DO_LOCK(nasd_edrfs_caps_freelist)
#define UNLOCK_CAPS() NASD_FREELIST_DO_UNLOCK(nasd_edrfs_caps_freelist)


/* hash functions */

static inline int nasd_edrfs_id_hash_calc(nasd_edrfs_identifier_t *ident) {
  return ( ident->nasd_identifier +
	   ident->disk_identifier +
	  (ident->partnum << 4) % NASD_EDRFS_CAP_TABLE_SZ);
}


static inline int nasd_edrfs_id_hash(nasd_edrfs_identifier_t *ident) {
  return (ident->nasd_identifier > 0 ?
	  (      ident->nasd_identifier  % NASD_EDRFS_CAP_TABLE_SZ) :
	  ((-1 * ident->nasd_identifier) % NASD_EDRFS_CAP_TABLE_SZ));
}


/* queue manipulation. */

/* remove an entry from the LRU queue */
static inline void
cap_lru_dequeue(nasd_edrfs_captable_entry_t  *captable_entry) {
  captable_entry->l_prev->l_next = captable_entry->l_next;
  captable_entry->l_next->l_prev = captable_entry->l_prev;
  captable_entry->l_next         = captable_entry->l_prev = NULL;
}

/* add an entry to the end of the LRU queue */
static inline void
cap_lru_enqueue_tail(nasd_edrfs_captable_entry_t  *captable_entry) {
  captable_entry->l_next         = &nasd_edrfs_cap_lru;
  captable_entry->l_prev         =  nasd_edrfs_cap_lru.l_prev;
  captable_entry->l_next->l_prev =  captable_entry;
  captable_entry->l_prev->l_next =  captable_entry;
}

/* pull off the front of the LRU queue */
static inline void
cap_lru_dequeue_head(nasd_edrfs_captable_entry_t  **captable_entry_p) {
  if (nasd_edrfs_cap_lru.l_next == &nasd_edrfs_cap_lru) {
    *captable_entry_p = NULL;
  } else {
    *captable_entry_p = nasd_edrfs_cap_lru.l_next;
    cap_lru_dequeue(*captable_entry_p);
  }
}

/* remove an entry from the queue */
static inline void
cap_bucket_dequeue(nasd_edrfs_captable_entry_t  *captable_entry) {
  captable_entry->prev->next = captable_entry->next;
  captable_entry->next->prev = captable_entry->prev;
  captable_entry->next       = captable_entry->prev = NULL;
}

/* add an entry to the queue */
static inline void 
cap_bucket_enqueue(nasd_edrfs_captable_entry_t  *captable_entry,
		   nasd_edrfs_captable_entry_t  *bucket) {
  captable_entry->prev       = bucket;
  captable_entry->next       = bucket->next;
  captable_entry->next->prev = captable_entry;
  captable_entry->prev->next = captable_entry;
}

/* find an entry in the queue
   !!! must be called with the capability lock held. */
static inline nasd_edrfs_captable_entry_t *
cap_lookup(nasd_edrfs_identifier_t    *ident,
	   nasd_edrfs_credential_t    *cred) {
  nasd_edrfs_captable_entry_t *bucket = NULL, *r = NULL;

#if 0
  nasd_printf("cap_lookup(0x%lx, 0x%lx)\n",
	      (unsigned long) ident, (unsigned long) cred);
#endif

  bucket = &nasd_edrfs_cap_buckets[nasd_edrfs_id_hash(ident)];

  for(r = bucket->next; r != bucket; r = r->next) {
    if ((r->identifier.nasd_identifier == ident->nasd_identifier)
     && (r->identifier.disk_identifier == ident->disk_identifier)
     && (r->identifier.partnum         == ident->partnum)
     && (r->cred.gid                   == cred->gid)
     && (r->cred.uid                   == cred->uid)) {
      return(r);
    }
  }
  
  return NULL;
}


static void nasd_edrfs_caps_clean_buckets(void  *ignored) {
  nasd_edrfs_captable_entry_t *r = NULL, *next = NULL;
  int i;

#if DEBUG_CLEAN_BUCKETS_DETAIL
  nasd_printf("nasd_edrfs_caps_clean_buckets()\n");
#endif /* DEBUG_CLEAN_BUCKETS_DETAIL */

  for(r = nasd_edrfs_cap_lru.l_next; r != &nasd_edrfs_cap_lru; r = next) {
    next = r->l_next;

    cap_lru_dequeue(r);
    cap_bucket_dequeue(r);

    NASD_FREELIST_FREE_NOLOCKING(nasd_edrfs_caps_freelist, r, next);
    nasd_edrfs_caps_outstanding--;
  }
  for(i = 0; i < NASD_EDRFS_CAP_TABLE_SZ; i++) {
    NASD_ASSERT(nasd_edrfs_cap_buckets[i].next == &nasd_edrfs_cap_buckets[i]);
    NASD_ASSERT(nasd_edrfs_cap_buckets[i].prev == &nasd_edrfs_cap_buckets[i])
  }

  NASD_ASSERT(nasd_edrfs_cap_lru.l_next == &nasd_edrfs_cap_lru);
  NASD_ASSERT(nasd_edrfs_cap_lru.l_prev == &nasd_edrfs_cap_lru);
  NASD_ASSERT(nasd_edrfs_caps_outstanding == 0);
}


static void nasd_edrfs_caps_destroy_freelist(void *ignored) {

#if DEBUG_DESTROY_FREELIST_DETAIL
  nasd_printf("nasd_edrfs_caps_destroy_freelist()\n");
#endif /* DEBUG_DESTROY_FREELIST_DETAIL */

  NASD_ASSERT(nasd_edrfs_caps_outstanding == 0);

  NASD_FREELIST_DESTROY(nasd_edrfs_caps_freelist,
			next, (nasd_edrfs_captable_entry_t *));
}


/*
 * nasd_edrfs_caps_real_init
 *
 * Called when the system comes into use after not being
 * used (start-of-day call to nasd_edrfs_caps_init(), or first
 * such call after the last call to nasd_edrfs_caps_shutdown()
 * which actually deactivated the system).
 */
static nasd_status_t nasd_edrfs_caps_real_init(void) {
  nasd_status_t rc;
  int i;

#if DEBUG_REAL_INIT_DETAIL
  nasd_printf("nasd_edrfs_caps_real_init()\n");
#endif /* DEBUG_REAL_INIT_DETAIL */

  if ((rc = nasd_threads_init()))      { return(rc); }
  if ((rc = nasd_mem_init()))          { goto bad_mem; }
  if ((rc = nasd_shutdown_sys_init())) { goto bad_sys; }
  if ((rc = nasd_shutdown_list_init(&nasd_edrfs_caps_shutdown_list))) {
    goto bad_shutdown;
  }

  nasd_edrfs_caps_outstanding = 0;

  NASD_FREELIST_CREATE(nasd_edrfs_caps_freelist,
		       NASD_MAX_FREE_EDRFS_CAPS, NASD_EDRFS_CAPS_INC,
		       sizeof(nasd_edrfs_captable_entry_t));
  if (nasd_edrfs_caps_freelist == NULL) {
    rc = NASD_NO_MEM;
    goto bad_freelist;
  }

  NASD_FREELIST_PRIME(nasd_edrfs_caps_freelist,
		      NASD_EDRFS_CAPS_INITIAL, next,
		      (nasd_edrfs_captable_entry_t *));

  rc = nasd_shutdown_proc(nasd_edrfs_caps_shutdown_list,
			  nasd_edrfs_caps_destroy_freelist, NULL);
  if (rc) {
    nasd_edrfs_caps_destroy_freelist(NULL);
    goto bad_freelist;
  }

  for(i = 0; i < NASD_EDRFS_CAP_TABLE_SZ; i++) {
    memset(&nasd_edrfs_cap_buckets[i], 0, sizeof(nasd_edrfs_captable_entry_t));
    nasd_edrfs_cap_buckets[i].next = &nasd_edrfs_cap_buckets[i];
    nasd_edrfs_cap_buckets[i].prev = &nasd_edrfs_cap_buckets[i];
  }

  memset(&nasd_edrfs_cap_lru, 0, sizeof(nasd_edrfs_captable_entry_t));
  nasd_edrfs_cap_lru.l_next = nasd_edrfs_cap_lru.l_prev = &nasd_edrfs_cap_lru;

  rc = nasd_shutdown_proc(nasd_edrfs_caps_shutdown_list,
			  nasd_edrfs_caps_clean_buckets, NULL);
  if (rc == 0) { return(NASD_SUCCESS); }

  /* error handlers */

bad_freelist:
  rc = nasd_shutdown_list_shutdown(nasd_edrfs_caps_shutdown_list,
				   NASD_SHUTDOWN_ANNOUNCE_NONE);
  if (rc) { NASD_PANIC(); }
bad_shutdown:
  nasd_shutdown_cleanup();
bad_sys:
  nasd_mem_shutdown();
bad_mem:
  nasd_threads_shutdown();
  return(rc);
}


/*
 * nasd_edrfs_caps_real_shutdown
 *
 * Called when last user of the edrfs_caps system calls nasd_edrfs_caps_shutdown().
 * Clean up and deallocate resources.
 */
static void nasd_edrfs_caps_real_shutdown(void) {
  nasd_status_t rc;

#if DEBUG_REAL_SHUTDOWN_DETAIL
  nasd_printf("nasd_edrfs_caps_real_shutdown()\n");
#endif /* DEBUG_REAL_SHUTDOWN_DETAIL */
  
  rc = nasd_shutdown_list_shutdown(nasd_edrfs_caps_shutdown_list,
				   NASD_SHUTDOWN_ANNOUNCE_NONE);
  if (rc) { NASD_PANIC(); }
  
  nasd_edrfs_caps_shutdown_list = NULL;
  nasd_shutdown_cleanup();
  nasd_mem_shutdown();
  nasd_threads_shutdown();
}


/*
 * nasd_edrfs_caps_sys_init
 *
 * Executed exactly once, the first time nasd_edrfs_caps_init() is
 * called. Initialize counter tracking number of times system
 * is initted.
 */
static void nasd_edrfs_caps_sys_init(void) {
  nasd_status_t rc;

#if DEBUG_SYS_INIT_DETAIL
  nasd_printf("nasd_edrfs_caps_sys_init()\n");
#endif /* DEBUG_SYS_INIT_DETAIL */

  rc = nasd_mutex_init(&nasd_edrfs_caps_use_counter_lock);
  if (rc) { NASD_PANIC(); }

  nasd_edrfs_caps_use_counter = 0;
}


/*
 * nasd_edrfs_caps_init
 *
 * Keep a counter of the number of times we're initted and
 * shutdown. When the last shutdown arrives, really deallocate.
 * This lets multiple subsystems use us without knowing about
 * one another.
 */
nasd_status_t nasd_edrfs_caps_init(void) {
  nasd_status_t rc;

#if DEBUG_INIT_DETAIL
  nasd_printf("nasd_edrfs_caps_init()\n");
#endif /* DEBUG_INIT_DETAIL */

  nasd_once(&nasd_edrfs_caps_init_once, nasd_edrfs_caps_sys_init);

  NASD_LOCK_MUTEX(nasd_edrfs_caps_use_counter_lock);

  nasd_edrfs_caps_use_counter++;

  if (nasd_edrfs_caps_use_counter == 1) {
    if ((rc = nasd_edrfs_caps_real_init())) {
      nasd_edrfs_caps_use_counter = 0;
      nasd_edrfs_caps_real_shutdown();
    }
  } else {
    rc = NASD_SUCCESS;
  }

  NASD_UNLOCK_MUTEX(nasd_edrfs_caps_use_counter_lock);

  return(rc);
}


/*
 * nasd_edrfs_caps_shutdown
 *
 * Previous caller of nasd_edrfs_caps_init() not using the edrfs_caps
 * subsystem any more. Deallocate and cleanup iff necessary.
 */
void nasd_edrfs_caps_shutdown() {

#if DEBUG_SHUTDOWN_DETAIL
  nasd_printf("nasd_edrfs_caps_shutdown()\n");
#endif /* DEBUG_SHUTDOWN_DETAIL */  

  NASD_ASSERT(nasd_edrfs_caps_use_counter != 0);
  NASD_LOCK_MUTEX(nasd_edrfs_caps_use_counter_lock);

  nasd_edrfs_caps_use_counter--;

  if (nasd_edrfs_caps_use_counter == 0) {
    nasd_edrfs_caps_real_shutdown();
  }

  NASD_UNLOCK_MUTEX(nasd_edrfs_caps_use_counter_lock);
}

/* 
 * nasd_edrfs_caps_lookup
 *
 * Get the cookie associated with a capability/identifier pair from the
 * cache.
 *
 * Returns NASD_SUCCESS and copies the cookie into the 'cookie' argument if
 * successful, and returns NASD_FAIL and blanks the 'cookie' argument otherwise.
 */
nasd_status_t nasd_edrfs_caps_lookup(nasd_edrfs_identifier_t  *ident,
				     nasd_edrfs_credential_t  *cred,
				     nasd_cookie_t          *cookie) {
  nasd_edrfs_captable_entry_t *captable_entry = NULL;
  nasd_status_t rc = NASD_SUCCESS;

  NASD_ASSERT(ident  != NULL);
  NASD_ASSERT(cred   != NULL);
  NASD_ASSERT(cookie != NULL);

#if DEBUG_LOOKUP_DETAIL
  nasd_printf("nasd_edrfs_caps_lookup(0x%lx, 0x%lx, 0x%lx)\n",
	      (unsigned long) ident, (unsigned long) cred,
	      (unsigned long) cookie);
#endif /* DEBUG_LOOKUP_DETAIL */

  LOCK_CAPS();
  
  /* find the entry */
  captable_entry = cap_lookup(ident, cred);
  if (captable_entry == NULL) {
    rc = NASD_FAIL;
    goto out;
  }
  
  /* place it at the end of the LRU queue */
  cap_lru_dequeue(captable_entry);
  cap_lru_enqueue_tail(captable_entry);
  memcpy(cookie, &captable_entry->cookie, sizeof(nasd_cookie_t));

out:
  UNLOCK_CAPS();
  if (rc != NASD_SUCCESS) { memset(cookie, 0, sizeof(nasd_cookie_t)); }
  return(rc);
}

/*
 * nasd_edrfs_caps_insert
 *
 * Insert an entry into the cookie cache.
 */
nasd_status_t nasd_edrfs_caps_insert(nasd_edrfs_identifier_t  *ident,
				     nasd_edrfs_credential_t  *cred,
				     nasd_cookie_t          *cookie) {
  nasd_edrfs_captable_entry_t *captable_entry = NULL, *bucket = NULL;
  int bucket_num;

  NASD_ASSERT(ident  != NULL);
  NASD_ASSERT(cred   != NULL);
  NASD_ASSERT(cookie != NULL);

#if DEBUG_INSERT_DETAIL
  nasd_printf("nasd_edrfs_caps_insert(%0xlx, %0xlx, 0x%lx)\n",
	      (unsigned long) ident,
	      (unsigned long) cred,
	      (unsigned long) cookie);
#endif /* DEBUG_INSERT_DETAIL */

  LOCK_CAPS();
  
  /* if we have too many outstanding capabilities, then we need to
     start recycling them. we do this in LRU order. */
  if (nasd_edrfs_caps_outstanding >= NASD_EDRFS_MAX_ACTIVE_CAPS) {
   
    /* grab the least recently used entry from the lru queue. */
    cap_lru_dequeue_head(&captable_entry);
    if (captable_entry) { cap_bucket_dequeue(captable_entry); }

  } else {

    /* if not, then just get a new table entry from the freelist. */
    NASD_FREELIST_GET_NOLOCKING(nasd_edrfs_caps_freelist, captable_entry, next,
				(nasd_edrfs_captable_entry_t *));
    nasd_edrfs_caps_outstanding++;
  }

  /* we, er, failed to get an entry. oops. */
  if (captable_entry == NULL) {
    UNLOCK_CAPS();
    return(NASD_NO_MEM);
  }

  /* set up the entry */
  captable_entry->identifier.nasd_identifier = ident->nasd_identifier;
  captable_entry->identifier.disk_identifier = ident->disk_identifier;
  captable_entry->identifier.partnum         = ident->partnum;
  captable_entry->cred.uid                   = cred->uid;
  captable_entry->cred.gid                   = cred->gid;

  memcpy(&captable_entry->cookie, cookie, sizeof(nasd_cookie_t));
  
  /* insert it into the queues */
  bucket_num = nasd_edrfs_id_hash(ident);
  bucket = &nasd_edrfs_cap_buckets[bucket_num];

  cap_bucket_enqueue(captable_entry, bucket);
  cap_lru_enqueue_tail(captable_entry);

  UNLOCK_CAPS();
  return(NASD_SUCCESS);
}


nasd_status_t nasd_edrfs_caps_remove(nasd_edrfs_identifier_t  *ident,
				     nasd_edrfs_credential_t  *cred) {
  nasd_edrfs_captable_entry_t *captable_entry = NULL;
  nasd_status_t rc = NASD_SUCCESS;

  NASD_ASSERT(ident != NULL);
  NASD_ASSERT(cred  != NULL);

#if DEBUG_REMOVE_DETAIL
  nasd_printf("nasd_edrfs_caps_remove(%0xlx, %0xlx) (0x%" NASD_ID_FMT " x [%d,%d])\n",
	      (unsigned long) ident, (unsigned long) cred,
	      ident->nasd_identifier, cred->uid, cred->gid);
#endif /* DEBUG_REMOVE_DETAIL */

  LOCK_CAPS();

  /* find the entry or die trying */
  captable_entry = cap_lookup(ident, cred);
  if (captable_entry == NULL) {
    rc = NASD_FAIL;
    goto out;
  }

  /* pull it from the queues and toss it back onto the freelist */
  cap_lru_dequeue(captable_entry);
  cap_bucket_dequeue(captable_entry);
  NASD_FREELIST_FREE_NOLOCKING(nasd_edrfs_caps_freelist, captable_entry, next);
  nasd_edrfs_caps_outstanding--;

out:
  UNLOCK_CAPS();
  return(rc);
}

/*
 * nasd_edrfs_find_or_get_cookie
 *
 * Look up a cookie in the cache. If it's not there, try to get a new
 * one from the file manager.
 */
nasd_status_t nasd_edrfs_find_or_get_cookie(nasd_edrfs_handles_t     *handles,
					    nasd_edrfs_identifier_t  *ident,
					    nasd_edrfs_credential_t  *cred,
					    nasd_cookie_t          *cookie) {
  nasd_edrfs_newcookie_args_t  newcookie_args;
  nasd_edrfs_newcookie_res_t   newcookie_res;
  nasd_rpc_status_t          op_status;
  nasd_status_t              rc;

  NASD_ASSERT(handles != NULL);
  NASD_ASSERT(ident   != NULL);
  NASD_ASSERT(cred    != NULL);
  NASD_ASSERT(cookie  != NULL);

#if DEBUG_FIND_OR_GET_COOKIE_DETAIL
  nasd_printf("nasd_edrfs_find_or_get_cookie(%0xlx, %0xlx, %0xlx, %0xlx) (0x%" NASD_ID_FMT " x [%d,%d])\n",
	      (unsigned long) handles, (unsigned long) ident,
	      (unsigned long) cred, (unsigned long) cookie,
	      ident->nasd_identifier, cred->uid, cred->gid);
#endif /* DEBUG_FIND_OR_GET_COOKIE_DETAIL */

  rc = nasd_edrfs_caps_lookup(ident, cred, cookie);
  if (rc == NASD_SUCCESS) { return(rc); }

  /* we don't have a cookie for this file, so we need to get one. */

  if (!(handles->flags & NASD_BINDING_SERVER_VALID)) {
    /* something's wrong with our handles. abort. */
    return(NASD_FAIL);
  }

/* set up RPC arguments */
  newcookie_args.in_identifier.nasd_identifier = ident->nasd_identifier;
  newcookie_args.in_identifier.disk_identifier = ident->disk_identifier;
  newcookie_args.in_identifier.partnum         = ident->partnum;
  newcookie_args.in_credential.uid             = cred->uid;
  newcookie_args.in_credential.gid             = cred->gid;
  
  nasd_edrfscli_newcookie(handles->server_handle,
			&newcookie_args, &newcookie_res, &op_status);
  rc = newcookie_res.nasd_status;

  if ((op_status != 0) || (rc != NASD_SUCCESS)) {
    /* we can't get a new cookie. something's wrong. */
    return(rc);
  }
  
  memcpy(cookie, &newcookie_res.out_cookie, sizeof(nasd_cookie_t));

  rc = nasd_edrfs_caps_insert(ident, cred, cookie);

  return(rc);
}
