/*
 * nasd_edrfs_dir.c
 *
 * Basic directory management for NASD EDRFS.
 *
 * Authors: Jim Zelenka, Nat Lanza
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1997,1998,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.
 */


#include <nasd/nasd_options.h>
#include <nasd/nasd_types.h>
#include <nasd/nasd_edrfs_types.h>
#include <nasd/nasd_freelist.h>
#include <nasd/nasd_itypes.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_common.h>
#include <nasd/nasd_pdrive_client.h>
#include <nasd/nasd_edrfs_dir.h>
#include <nasd/nasd_edrfs_server_internal.h>
#include <nasd/nasd_timer.h>

#define DEBUG_LOAD_DIR_DETAIL 0
#define DEBUG_ADD_DIR_DETAIL 0

int nasd_edrfs_max_cached_dirs  = 768; /* max dirs to cache */
int nasd_edrfs_max_cached_pages = 768; /* max dir pages in cache */

NASD_DECLARE_COND(nasd_edrfs_server_dir_clean_cond)

nasd_edrfs_core_dir_t *nasd_edrfs_dirty_dir_head = NULL;
nasd_edrfs_core_dir_t *nasd_edrfs_dirty_dir_tail = NULL;

#define SIGNAL_DIRC() NASD_SIGNAL_COND(nasd_edrfs_server_dir_clean_cond)

nasd_edrfs_core_dirpage_t *nasd_edrfs_free_dirpages = NULL;
int nasd_edrfs_num_free_dirpages = 0;

NASD_DECLARE_MUTEX(nasd_edrfs_dircache_lock)

nasd_edrfs_core_dir_t *nasd_edrfs_free_dirs = NULL;
nasd_edrfs_core_dir_t nasd_edrfs_clean_dirs;

nasd_edrfs_core_dir_t nasd_edrfs_obj_buckets[NASD_EDRFS_OBJHASH_BUCKETS];

#define DIRB_UNHASH(_cd_) { \
  (_cd_)->hprev->hnext = (_cd_)->hnext; \
  (_cd_)->hnext->hprev = (_cd_)->hprev; \
  (_cd_)->hprev = (_cd_)->hnext = NULL; \
}

/*
 * Called to dealloc resources associated with a core dir
 */
void
nasd_edrfs_coredir_disassoc(
  nasd_edrfs_core_dir_t  *cd)
{
  nasd_edrfs_server_dir_hash_check(0);
  NASD_ASSERT(cd->ndirty == 0);
  NASD_ASSERT(cd->busy == 0);
  NASD_ASSERT(cd->refcnt == 0);
  if (cd->lprev) {
    cd->lprev->lnext = cd->lnext;
    cd->lnext->lprev = cd->lprev;
  }
  else {
    NASD_ASSERT(cd->lnext == NULL);
  }
  DIRB_UNHASH(cd);
  /* now get rid of entries associated with this dir */
  NASD_EDRFS_LOCK_DIR(cd,1);
  nasd_edrfs_name_cleandir(cd);
  NASD_EDRFS_UNLOCK_DIR(cd);
  if (cd->first_page) {
    cd->last_page->next = nasd_edrfs_free_dirpages;
    nasd_edrfs_free_dirpages = cd->first_page;
    nasd_edrfs_num_free_dirpages += cd->npages;
    cd->first_page = cd->last_page = NULL;
    cd->npages = 0;
  }
  else {
    NASD_ASSERT(cd->last_page == NULL);
    NASD_ASSERT(cd->npages == 0);
  }
  cd->lprev = cd->lnext = NULL;
  cd->dprev = cd->dnext = NULL;
  if (cd->attr)
    nasd_edrfs_release_attr(cd->attr);
  cd->attr = NULL;
  cd->refcnt = 0;
  cd->lnext = nasd_edrfs_free_dirs;
  nasd_edrfs_free_dirs = cd;
  nasd_edrfs_server_dir_hash_check(0);
}

/*
 * Called at shutdown time- cleans up LRU (which is all
 * clean directories).
 */
void
nasd_edrfs_server_dir_cleanup()
{
  nasd_edrfs_core_dir_t *cd, *next;

  for(cd=nasd_edrfs_clean_dirs.lnext;cd!=&nasd_edrfs_clean_dirs;cd=next) {
    next = cd->lnext;
    nasd_edrfs_coredir_disassoc(cd);
  }
}

/*
 * call with dircache LOCKED
 */
void
nasd_edrfs_server_dir_hash_ins(
  nasd_edrfs_core_dir_t  *cd)
{
  nasd_edrfs_core_dir_t *bucket;
  int h;

  nasd_edrfs_server_dir_hash_check(0);
  h = nasd_edrfs_obj_hash(cd->nid);
  bucket = &nasd_edrfs_obj_buckets[h];
  cd->hnext = bucket->hnext;
  cd->hprev = bucket;
  cd->hprev->hnext = cd;
  cd->hnext->hprev = cd;
  nasd_edrfs_server_dir_hash_check(0);
}

/*
 * DEBUGGING
 */
void
_nasd_edrfs_server_dir_hash_check(
  int  verbose)
{
  static nasd_uint64 calls = 0;
  nasd_edrfs_core_dir_t *bucket, *r;
  int i, size, bad, say_size, h;

  say_size = verbose;

  NASD_ATOMIC_INC64(&calls);
  if (calls%50000 == 0) {
    say_size = 1;
    nasd_printf("\n\nnasd_edrfs_server_dir_hash_check calls %" NASD_64u_FMT "\n", calls);
  }

  bad = 0;
  for(i=0;i<NASD_EDRFS_OBJHASH_BUCKETS;i++) {
    bucket = &nasd_edrfs_obj_buckets[i];
    size = 0;
    if (verbose)
      nasd_printf("bucket 0x%lx\n", (unsigned long)bucket);
    for(r=bucket->hnext;r!=bucket;r=r->hnext) {
      size++;
      h = nasd_edrfs_obj_hash(r->nid);
      if (h != i) {
        nasd_printf("\nERROR r=0x%lx r->nid=0x%" NASD_ID_FMT " i=%d h=%d\n",
          (unsigned long)r, r->nid, i, h);
        bad = 1;
        break;
      }
      if (r->visited) {
        nasd_printf("[REPEAT 0x%lx (0x%lx 0x%lx 0x%lx)]\n",
          (unsigned long)r, (unsigned long)r->hprev,
          (unsigned long)r, (unsigned long)r->hnext);
        bad = 1;
        break;
      }
      r->visited = 1;
      if (verbose)
        nasd_printf("[0x%lx 0x%lx 0x%lx] ", (unsigned long)r->hprev,
          (unsigned long)r, (unsigned long)r->hnext);
      if (r->hprev->hnext != r) {
        nasd_printf("\nERROR r->hprev->hnext=0x%lx r=0x%lx\n",
          (unsigned long)r->hprev->hnext, (unsigned long)r);
        bad = 1;
        break;
      }
      if (r->hnext->hprev != r) {
        nasd_printf("\nERROR r->hnext->hprev=0x%lx r=0x%lx\n",
          (unsigned long)r->hnext->hprev, (unsigned long)r);
        bad = 1;
        break;
      }
    }
    if (verbose)
      nasd_printf("\n");
    for(r=bucket->hnext;r!=bucket;r=r->hnext) {
      if (r->visited == 0)
        break;
      r->visited = 0;
    }
    if (bad)
      break;
    if (say_size) {
      nasd_printf("bucket 0x%lx (i=%d) has size %d\n",
        (unsigned long)bucket, i, size);
    }
  }
}

/*
 * call with dircache LOCKED
 * returns with ref on dir
 */
nasd_status_t
nasd_edrfs_server_dir_hash_lookup(
  nasd_edrfs_identifier_t    edrfs_id,
  int                        force_load,
  nasd_edrfs_core_dir_t    **out_dirp,
  nasd_edrfs_attr_t        **out_attrp)
{
  nasd_edrfs_core_dir_t *bucket, *r, *cd, *parent;
  nasd_edrfs_diskdirent_t *de, *found;
  nasd_edrfs_attributes_t *edrfsattr;
  nasd_edrfs_identifier_t p_edrfs_id;
  nasd_edrfs_attr_t *dh_attr;
  nasd_identifier_t p_nid;
  nasd_edrfs_drive_t *dr;
  nasd_status_t rc;
  int h, lru_take;

  NASD_EDRFS_CSINC(dir_lookup);

  nasd_edrfs_server_dir_hash_check(0);

  *out_dirp = NULL;
  *out_attrp = NULL;
  
  if(edrfs_id.nasd_identifier == NASD_ID_NULL)
    return (NASD_BAD_IDENTIFIER);

  h = nasd_edrfs_obj_hash(edrfs_id.nasd_identifier);
  bucket = &nasd_edrfs_obj_buckets[h];
  for(r=bucket->hnext;r!=bucket;r=r->hnext) {
    if ((r->nid == edrfs_id.nasd_identifier)
      && (r->di == edrfs_id.disk_identifier))
    {
      if (r->deleted) {
        /* Oh, yeah, it's "deleted." */
        nasd_edrfs_server_dir_hash_check(0);
        return(NASD_BAD_IDENTIFIER);
      }
      NASD_EDRFS_LOCK_DIR(r,1);
      r->refcnt++;
      if ((r->refcnt == 1) && (r->isroot == 0)) {
        /* remove from clean LRU */
        r->lprev->lnext = r->lnext;
        r->lnext->lprev = r->lprev;
        lru_take = 1;
      }
      while(r->busy) {
        NASD_WAIT_COND(r->cond,r->lock);
      }
      NASD_EDRFS_UNLOCK_DIR(r);
      if (r->deleted) {
        /* Oh, yeah, it's "deleted." */
        nasd_edrfs_server_dir_release(r);
        nasd_edrfs_server_dir_hash_check(0);
        return(NASD_BAD_IDENTIFIER);
      }
      *out_dirp = r;
      nasd_edrfs_server_dir_hash_check(0);
      return(NASD_SUCCESS);
    }
  }
  if (*out_dirp || (force_load == 0)) {
    nasd_edrfs_server_dir_hash_check(0);
    return(NASD_SUCCESS);
  }
  /*
   * Go retrieve directory from disk
   */
  NASD_EDRFS_CSINC(dir_force);
  rc = nasd_edrfs_get_drive(edrfs_id, &dr);
  if (rc) {
    nasd_edrfs_server_dir_hash_check(0);
    return(rc);
  }
  NASD_ASSERT(dr != NULL);
  cd = nasd_edrfs_server_dir_get_coredir(1);
  NASD_ASSERT(cd != NULL);
  cd->drive = dr;
  cd->nid = edrfs_id.nasd_identifier;
  cd->di = dr->di;
  /* @@@ init cd->cookie here */
  NASD_ASSERT(cd->refcnt == 1);
  cd->busy = 1;
  cd->ndirty = 0;
  cd->attr = nasd_edrfs_get_attr();
  if (cd->attr == NULL) {
    NASD_PANIC();
  }
  cd->isroot = 0;

  nasd_edrfs_server_dir_hash_ins(cd);

  /*
   * It is safe to unlock and relock here because we've left the
   * directory cache in a consistent state. If the object is
   * deleted or moved in the meantime, we'll discover that
   * below when we recheck. The directory we're reading in will
   * now appear correctly in the cache, but will be marked
   * busy, which will prevent other lookups from completing
   * until the read is done.
   */
  NASD_EDRFS_UNLOCK_DIRCACHE();

  NASD_EDRFS_CSINC(dirc_load_attr);
  rc = nasd_edrfs_server_dir_load_attr(cd);
  if (rc != NASD_SUCCESS) {
    cd->busy = 0;
    NASD_BROADCAST_COND(cd->cond);
    cd->deleted = 1;
    nasd_edrfs_server_dir_release(cd);
    nasd_edrfs_server_dir_hash_check(0);
    return(rc);
  }
  edrfsattr = &cd->attr->edrfsattr;
  if (edrfsattr->type != NASD_EDRFS_TYPE_DIR) {
    /*
     * Not a directory- we return NASD_EDRFS_NOT_DIR,
     * and give the caller the attribute, in case it's
     * useful to them (for example, ACCESS will still
     * want to check permission bits). The caller is
     * responsible for calling nasd_edrfs_release_attr()
     * on this attribute.
     */
    NASD_EDRFS_CSINC(dirc_not_dir);
    NASD_EDRFS_LOCK_DIRCACHE();
    cd->attr->refcnt++;
    *out_attrp = cd->attr;
    NASD_EDRFS_UNLOCK_DIRCACHE();
    cd->busy = 0;
    NASD_BROADCAST_COND(cd->cond);
    cd->deleted = 1;
    nasd_edrfs_server_dir_release(cd);
    nasd_edrfs_server_dir_hash_check(0);
    return(NASD_EDRFS_NOT_DIR);
  }

  NASD_EDRFS_CSINC(dirc_load_dir);
  rc = nasd_edrfs_load_dir(cd);
  if (rc != NASD_SUCCESS) {
    /*
     * Someone else may have tried to grab in the
     * meantime, get rid of them.
     */
    cd->busy = 0;
    NASD_BROADCAST_COND(cd->cond);
    cd->deleted = 1;
    nasd_edrfs_server_dir_release(cd);
    nasd_edrfs_server_dir_hash_check(0);
    return(rc);
  }

  NASD_EDRFS_LOCK_DIRCACHE();

  nasd_edrfs_server_dir_hash_check(0);

  /*
   * Now extract the parent ent, and see if it's
   * in-core. If so, link up with it.
   *
   * First, find the parent dir.
   */
  p_nid = cd->first_page->page->data_slots[1].nasdid;
  p_edrfs_id.nasd_identifier = p_nid;
  p_edrfs_id.disk_identifier = dr->di;
  rc = nasd_edrfs_server_dir_hash_lookup(p_edrfs_id, 0, &parent, &dh_attr);
  if (rc) {
    /* this should never happen */
    NASD_PANIC();
  }
  if (parent) {
    /* found ../, now get our entry */
    NASD_EDRFS_LOCK_DIR(parent,1);
    h = nasd_edrfs_obj_hash(cd->nid);
    found = NULL;
    for(de=parent->idbuckets[h];de;de=de->ihnext) {
      if (de->data->nasdid == cd->nid) {
        found = de;
        break;
      }
    }
    if (found) {
      found->dir = cd;
      found->attr = cd->attr;
      cd->attr->refcnt++;
    }
    else {
      if (cd->deleted) {
        /* deleted while loading- how ignominous! */
        cd->busy = 0;
        NASD_BROADCAST_COND(cd->cond);
        nasd_edrfs_server_dir_release(cd);
        nasd_edrfs_server_dir_hash_check(0);
        return(NASD_BAD_IDENTIFIER);
      }
      else {
        /* perhaps we're moving - declare "staleness" and bail */
        cd->busy = 0;
        NASD_BROADCAST_COND(cd->cond);
        nasd_edrfs_server_dir_release(cd);
        nasd_edrfs_server_dir_hash_check(0);
        return(NASD_EDRFS_STALE_ID);
      }
    }
    NASD_EDRFS_UNLOCK_DIR(parent);
    nasd_edrfs_server_dir_release(parent);
  }

  nasd_edrfs_server_dir_hash_check(0);

  cd->busy = 0;
  NASD_BROADCAST_COND(cd->cond);

  *out_dirp = cd;
  return(NASD_SUCCESS);
}

/*
 * call with dircache LOCKED
 * call with dir LOCKED
 */
void
nasd_edrfs_server_dir_dirty_page(
  nasd_edrfs_core_dir_t      *dir,
  nasd_edrfs_core_dirpage_t  *cdp)
{
  NASD_EDRFS_CSINC(dirc_dirty);
  if (cdp->dirty)
    return;
  NASD_EDRFS_CSINC(dirc_new_dirty);
  cdp->dirty = 1;
  dir->ndirty++;
}

/*
 * call with dircache LOCKED
 * call with dir LOCKED
 */
nasd_status_t
nasd_edrfs_server_dir_add_entry(
  nasd_edrfs_core_dir_t      *dir,
  char                       *name,
  nasd_identifier_t           nasdid,
  int                         type,
  nasd_edrfs_diskdirent_t   **dep)
{
  nasd_edrfs_core_dirpage_t *cdp, *found;
  int nentries, slot, len, i, j;
  nasd_edrfs_dirheader_t *dh;
  nasd_edrfs_diskdirent_t *de;

#if DEBUG_ADD_DIR_DETAIL
  nasd_printf("asked to add '%s' to dir 0x%" NASD_ID_FMT "\n", name, dir->nid);
#endif /* DEBUG_ADD_DIR_DETAIL */

  /* compute how many contig entries we need to hold this name */
  len = strlen(name);
  if (len > NASD_EDRFS_MAX_NAME_LEN) { return(NASD_EDRFS_BAD_NAME); }
  if (len < 1) { return(NASD_EDRFS_BAD_NAME); }
  nentries = nasd_edrfs_dir_name_len(name);
  len++; /* hold the NULL */

  found = NULL;
  for(cdp=dir->first_page;cdp;cdp=cdp->next) {
    dh = &cdp->page->header;
    if (dh->freecnt < nentries) { continue; }

    /* page has enough entries - will it fit? */
    slot = nasd_edrfs_dir_add_entry(cdp->page, name, nasdid, type);
    if (slot > 0) {
#if DEBUG_ADD_DIR_DETAIL
      nasd_printf("added entry in slot %d\n", slot);
#endif /* DEBUG_ADD_DIR_DETAIL */
      found = cdp;
      break;
    }
  }

  if (found == NULL) {
    /* no pages with enough available slots, add one */

#if DEBUG_ADD_DIR_DETAIL
    nasd_printf("couldn't fit into our pages; adding one\n");
#endif /* DEBUG_ADD_DIR_DETAIL */

    nasd_edrfs_server_dir_mk_freepages(1);
    cdp = nasd_edrfs_free_dirpages;
    nasd_edrfs_free_dirpages = cdp->next;
    nasd_edrfs_num_free_dirpages--;
    /* cdp is our new page */
    dir->last_page->next = cdp;
    cdp->next = NULL;
    cdp->prev = dir->last_page;
    cdp->offset = dir->last_page->offset + sizeof(nasd_edrfs_dirpage_t);
    dir->last_page = cdp;
    dir->npages++;
    nasd_edrfs_dir_init_page(cdp->page);
    slot = nasd_edrfs_dir_add_entry(cdp->page, name, nasdid, type);

    if (slot < 0) { return NASD_FAIL; } /* this should never ever happen */
  }

  /* we've added the entry. */
  nasd_edrfs_server_dir_dirty_page(dir, cdp);
  de = nasd_edrfs_name_add(dir, cdp, slot); 
  if (de == NULL) { NASD_PANIC(); }

  *dep = de;

  return(NASD_SUCCESS);
}

/*
 * call with dircache LOCKED
 */
void
nasd_edrfs_server_dir_release(
  nasd_edrfs_core_dir_t  *dir)
{
  int ndirty, cleaned;
  nasd_status_t rc;

  cleaned = 0;
  NASD_ASSERT(dir->refcnt > 0);
  dir->refcnt--;
  if ((dir->ndirty) && (dir->refcnt == 0)) {
    nasd_gettime(&dir->dirty_time);
    /*
     * XXX
     *
     * For now, nothing goes into the dirty list.
     * At some point, these should be queued for
     * async writes.
     */
    dir->busy = 1;
    ndirty = dir->ndirty;
    rc = nasd_edrfs_server_dir_write_dirty(dir);
    if (rc != NASD_SUCCESS) {
      nasd_printf("EDRFS: got rc=0x%lx (%s) writing dirty dir pages\n",
        (unsigned long)rc, nasd_error_string(rc));
      NASD_PANIC();
    }
    dir->busy = 0;
    NASD_BROADCAST_COND(dir->cond);
    cleaned = 1;
  }
  if ((dir->refcnt == 0) && (dir->isroot == 0)) {
    dir->lnext = nasd_edrfs_clean_dirs.lnext;
    dir->lprev = &nasd_edrfs_clean_dirs;
    dir->lprev->lnext = dir;
    dir->lnext->lprev = dir;
    if (cleaned) {
      SIGNAL_DIRC();
    }
  }
  NASD_EDRFS_LOCK_DIR(dir,1);
  if ((dir->refcnt == 1) && (dir->deleted)) {
    NASD_SIGNAL_COND(dir->cond);
    if (cleaned) {
      SIGNAL_DIRC();
    }
  }
  if ((dir->refcnt == 0) && (dir->deleted)) {
    NASD_EDRFS_UNLOCK_DIR(dir);
    nasd_edrfs_coredir_disassoc(dir);
    if (cleaned)
      SIGNAL_DIRC();
    return;
  }
  NASD_EDRFS_UNLOCK_DIR(dir);
}

/*
 * call with dircache LOCKED
 */
void
nasd_edrfs_server_dir_release_post_attribute(
  nasd_edrfs_core_dir_t        *cd,
  nasd_edrfs_identifier_t       in_identifier,
  nasd_edrfs_post_attribute_t  *post_attribute)
{
  nasd_edrfs_server_dir_release(cd);
  if (post_attribute)
    nasd_edrfs_post_attribute(cd, in_identifier, post_attribute);
}

void
nasd_edrfs_server_dir_writer_shutdown(
  void  *ignored)
{
  nasd_status_t rc;

  /*
   * Mark all the directories clean
   */
  rc = nasd_edrfs_mark_dirs_clean();
  if (rc) {
    NASD_PANIC();
  }
}

nasd_status_t
nasd_edrfs_server_dir_init()
{
  nasd_edrfs_core_dirpage_t *cdp;
  nasd_edrfs_core_dir_t *cd;
  nasd_status_t rc;
  int i;

  for(i=0;i<NASD_EDRFS_OBJHASH_BUCKETS;i++) {
    cd = &nasd_edrfs_obj_buckets[i];
    bzero((char *)cd, sizeof(nasd_edrfs_core_dir_t));
    cd->hnext = cd->hprev = cd;
  }

  nasd_edrfs_free_dirpages = NULL;
  nasd_edrfs_free_dirs = NULL;
  nasd_edrfs_num_free_dirpages = 0;
  nasd_edrfs_dirty_dir_head = NULL;
  nasd_edrfs_dirty_dir_tail = NULL;

  bzero((char *)&nasd_edrfs_clean_dirs, sizeof(nasd_edrfs_clean_dirs));
  nasd_edrfs_clean_dirs.lnext = &nasd_edrfs_clean_dirs;
  nasd_edrfs_clean_dirs.lprev = &nasd_edrfs_clean_dirs;

  rc = nasd_mutex_init(&nasd_edrfs_dircache_lock);
  if (rc)
    return(rc);
  rc = nasd_shutdown_mutex(nasd_edrfs_shutdown, &nasd_edrfs_dircache_lock);
  if (rc) {
    return(rc);
  }

  rc = nasd_cond_init(&nasd_edrfs_server_dir_clean_cond);
  if (rc)
    return(rc);
  rc = nasd_shutdown_cond(nasd_edrfs_shutdown, &nasd_edrfs_server_dir_clean_cond);
  if (rc) {
    return(rc);
  }

  for(i=0;i<nasd_edrfs_max_cached_pages;i++) {
    NASD_Malloc(cdp, sizeof(nasd_edrfs_core_dirpage_t),
                (nasd_edrfs_core_dirpage_t *));
    if(cdp == NULL) {
      return NASD_NO_MEM;
    }
    rc = nasd_shutdown_mem(nasd_edrfs_shutdown, cdp,
                           sizeof(nasd_edrfs_core_dirpage_t));
    if (rc) {
      NASD_Free(cdp, sizeof(nasd_edrfs_core_dirpage_t));
      return(NASD_NO_MEM);
    }

    NASD_Malloc(cdp->page, sizeof(nasd_edrfs_dirpage_t),
                (nasd_edrfs_dirpage_t *));
    if(cdp->page == NULL) {
      return NASD_NO_MEM;
    }
    rc = nasd_shutdown_mem(nasd_edrfs_shutdown, cdp->page,
                           sizeof(nasd_edrfs_dirpage_t));
    if(rc) {
      NASD_Free(cdp->page, sizeof(nasd_edrfs_dirpage_t));
      return NASD_NO_MEM;
    }
    cdp->prev = NULL;
    cdp->dirty = 0;
    cdp->offset = 0;
    cdp->next = nasd_edrfs_free_dirpages;
    nasd_edrfs_free_dirpages = cdp;
    nasd_edrfs_num_free_dirpages++;
  }

  for(i=0;i<nasd_edrfs_max_cached_dirs;i++) {
    NASD_Malloc(cd, sizeof(nasd_edrfs_core_dir_t), (nasd_edrfs_core_dir_t *));
    if(cd == NULL) {
      return NASD_NO_MEM;
    }
    rc = nasd_shutdown_mem(nasd_edrfs_shutdown, cd,
                           sizeof(nasd_edrfs_core_dir_t));
    if(rc) {
      NASD_Free(cd, sizeof(nasd_edrfs_core_dir_t));
      return NASD_NO_MEM;
    }
    rc = nasd_mutex_init(&cd->lock);
    if (rc) {
      return(rc);
    }
    rc = nasd_shutdown_mutex(nasd_edrfs_shutdown, &cd->lock);
    if (rc) {
      nasd_mutex_destroy(&cd->lock);
      return(rc);
    }
    rc = nasd_cond_init(&cd->cond);
    if (rc) {
      return(rc);
    }
    rc = nasd_shutdown_cond(nasd_edrfs_shutdown, &cd->cond);
    if (rc) {
      return(rc);
    }
    cd->refcnt = 0;
    cd->first_page = NULL;
    cd->last_page = NULL;
    cd->lprev = NULL;
    cd->lnext = nasd_edrfs_free_dirs;
    nasd_edrfs_free_dirs = cd;
  }

  return(NASD_SUCCESS);
}

void
nasd_edrfs_dealloc_coredir(
  nasd_edrfs_core_dir_t  *cd,
  int                     dircache_lock_held)
{
  if (dircache_lock_held == 0) {
    NASD_EDRFS_LOCK_DIRCACHE();
  }

  nasd_edrfs_server_dir_hash_check(0);

  cd->last_page->next = nasd_edrfs_free_dirpages;
  nasd_edrfs_free_dirpages = cd->first_page;
  nasd_edrfs_num_free_dirpages += cd->npages;
  cd->npages = 0;
  cd->first_page = cd->last_page = NULL;

  nasd_edrfs_name_cleandir(cd);

  if (cd->attr) {
    nasd_edrfs_release_attr(cd->attr);
    cd->attr = NULL;
  }

  cd->lnext = nasd_edrfs_free_dirs;
  nasd_edrfs_free_dirs = cd;

  nasd_edrfs_server_dir_hash_check(0);

  if (dircache_lock_held == 0) {
    NASD_EDRFS_UNLOCK_DIRCACHE();
  }
  SIGNAL_DIRC();
}

void
nasd_edrfs_ditch_coredir(
  nasd_edrfs_core_dir_t  *cd,
  int                     dircache_lock_held)
{
  if (dircache_lock_held == 0) {
    NASD_EDRFS_LOCK_DIRCACHE();
  }
  cd->nid = NASDID_NULL;
  cd->lnext = nasd_edrfs_free_dirs;
  nasd_edrfs_free_dirs = cd;
  if (dircache_lock_held == 0) {
    NASD_EDRFS_UNLOCK_DIRCACHE();
  }
  SIGNAL_DIRC();
}

nasd_edrfs_core_dir_t *
nasd_edrfs_server_dir_get_coredir(
  int  dircache_lock_held)
{
  nasd_edrfs_core_dir_t *cd;

  if (dircache_lock_held == 0) {
    NASD_EDRFS_LOCK_DIRCACHE();
  }
  NASD_EDRFS_CSINC(dir_get);
reget_dir:
  NASD_EDRFS_CSINC(dir_reget);
  cd = nasd_edrfs_free_dirs;
  if (cd) {
    nasd_edrfs_free_dirs = cd->lnext;
    goto got_dir;
  }
  /*
   * No free dir, must pull one off lru
   */
  cd = nasd_edrfs_clean_dirs.lprev;
  if (cd == &nasd_edrfs_clean_dirs) {
    /* no clean dirs - wait for one */
    NASD_WAIT_COND(nasd_edrfs_server_dir_clean_cond, nasd_edrfs_dircache_lock);
    goto reget_dir;
  }
  NASD_EDRFS_CSINC(dir_replace);
  NASD_ASSERT(cd->ndirty == 0);
  NASD_ASSERT(cd->busy == 0);
  cd->lprev->lnext = cd->lnext;
  cd->lnext->lprev = cd->lprev;
  DIRB_UNHASH(cd);
  /*
   * Now get rid of entries associated with this dir
   */
  NASD_EDRFS_LOCK_DIR(cd,1);
  nasd_edrfs_name_cleandir(cd);
  NASD_EDRFS_UNLOCK_DIR(cd);
  cd->last_page->next = nasd_edrfs_free_dirpages;
  nasd_edrfs_free_dirpages = cd->first_page;
  nasd_edrfs_num_free_dirpages += cd->npages;

  if (cd->attr) {
    nasd_edrfs_release_attr(cd->attr);
  }

got_dir:
  cd->npages = 0;
  cd->busy = 0;
  cd->first_page = cd->last_page = NULL;
  cd->lprev = cd->lnext = NULL;
  cd->hprev = cd->hnext = NULL;
  cd->dprev = cd->dnext = NULL;
  cd->attr = NULL;
  cd->refcnt = 1;
  cd->deleted = 0;
  cd->nentries = 0;
  cd->isroot = 0;
  bzero((char *)cd->buckets, sizeof(cd->buckets));
  bzero((char *)cd->idbuckets, sizeof(cd->idbuckets));
  nasd_edrfs_server_dir_hash_check(0);
  if (dircache_lock_held == 0) {
    NASD_EDRFS_UNLOCK_DIRCACHE();
  }
  return(cd);
}

/*
 * call with dircache LOCKED
 */
void
nasd_edrfs_server_dir_mk_freepages(
  int  npages)
{
  nasd_edrfs_core_dir_t *cd_tmp;

  while (nasd_edrfs_num_free_dirpages < npages) {
    NASD_EDRFS_CSINC(dirc_mkfree_loop);
    /*
     * Keep pulling dirs off the LRU until we have enough pages
     */
    cd_tmp = nasd_edrfs_clean_dirs.lprev;
    while (cd_tmp == &nasd_edrfs_clean_dirs) {
      NASD_EDRFS_CSINC(dirc_mkfree_wait);
      NASD_WAIT_COND(nasd_edrfs_server_dir_clean_cond, nasd_edrfs_dircache_lock);
      cd_tmp = nasd_edrfs_clean_dirs.lprev;
    }
    nasd_edrfs_coredir_disassoc(cd_tmp);
    SIGNAL_DIRC();
  }
}

/*
 * call with dircache UNLOCKED
 * call with dir UNLOCKED but BUSY
 */
nasd_status_t
nasd_edrfs_load_dir(
  nasd_edrfs_core_dir_t  *cd)
{
  int npages, i, pgbyte, pgbit, pgmask, exl, typ, h;
  nasd_mem_list_t memlist[NASD_EDRFS_MEMLIST_LEN];
  nasd_security_param_t sp;
  nasd_p_smpl_op_dr_args_t rd_args;
  nasd_p_fastread_dr_res_t rd_res;
  nasd_edrfs_core_dir_t *r, *found;
  nasd_edrfs_core_dirpage_t *cdp;
  nasd_error_string_t err_str;
  nasd_status_t nasd_status;
  nasd_edrfs_diskdirent_t *de;
  nasd_rpc_status_t status;
  nasd_offset_t off, loff;
  nasd_len_t out_len, len;
  nasd_edrfs_dirdata_t *dd;
  nasd_timespec_t ts;
  nasd_timer_t tm;

#if DEBUG_LOAD_DIR_DETAIL
  nasd_printf("loading dir 0x%" NASD_ID_FMT "\n", cd->nid);
#endif /* DEBUG_LOAD_DIR_DETAIL */

  NASD_ASSERT((cd->attr->attr_res.out_attribute.object_len
    % sizeof(nasd_edrfs_dirpage_t)) == 0);
  npages = cd->attr->attr_res.out_attribute.object_len
    / sizeof(nasd_edrfs_dirpage_t);

  NASD_EDRFS_CSINC(dir_load);

  NASD_EDRFS_LOCK_DIRCACHE();

  nasd_edrfs_server_dir_hash_check(0);

  nasd_edrfs_server_dir_mk_freepages(npages);

  cd->first_page = cd->last_page = nasd_edrfs_free_dirpages;
  nasd_edrfs_free_dirpages = cd->last_page->next;
  nasd_edrfs_num_free_dirpages--;
  cd->npages = npages;
  cd->first_page->prev = NULL;
  cd->last_page->next = NULL;
  cd->last_page->offset = 0;
  for(i=1;i<npages;i++) {
    cd->last_page->next = nasd_edrfs_free_dirpages;
    nasd_edrfs_free_dirpages = nasd_edrfs_free_dirpages->next;
    nasd_edrfs_num_free_dirpages--;
    cd->last_page->next->prev = cd->last_page;
    cd->last_page = cd->last_page->next;
    cd->last_page->next = NULL;
    cd->last_page->offset = cd->last_page->prev->offset
      + sizeof(nasd_edrfs_dirpage_t);
  }

  NASD_EDRFS_UNLOCK_DIRCACHE();

  /*
   * Now we have enough pages, load the bits
   */
  loff = off = 0;
  len = 0;
  for(i = 0, cdp = cd->first_page; cdp != NULL; cdp = cdp->next) {
    memlist[i].addr = (void *)cdp->raw_page;
    memlist[i].len = sizeof(nasd_edrfs_dirpage_t);
    memlist[i].stride = 0;
    memlist[i].nelem = 1;
    memlist[i].next = &memlist[i+1];
    loff += sizeof(nasd_edrfs_dirpage_t);
    len += sizeof(nasd_edrfs_dirpage_t);
    i++;
    if (i >= NASD_EDRFS_MEMLIST_LEN) {
      i--;
      memlist[i].next = NULL;
      sp.type = cd->cookie.capability.type;
      sp.partnum = cd->drive->partnum;
      sp.actual_protection = cd->cookie.capability.min_protection;
      rd_args.in_identifier = cd->nid;
      rd_args.in_offset = off;
      rd_args.in_len = len;
      rd_args.in_partnum = cd->drive->partnum;
      NASD_TM_START(&tm);

#if DEBUG_LOAD_DIR_DETAIL
      nasd_printf("reading %lu@%lu from 0x%" NASD_ID_FMT "\n",
                  (unsigned long) len, (unsigned long) off, cd->nid);
#endif /* DEBUG_LOAD_DIR_DETAIL */

      nasd_cl_p_range_read_dr(cd->drive->handle, cd->cookie.key, &sp,
                              &cd->cookie.capability, &rd_args,
                              memlist, &rd_res, &status);
      NASD_TM_STOP(&tm);
      NASD_TM_ELAPSED_TS(&tm, &ts);
      NASD_ATOMIC_TIMESPEC_ADD(&nasd_edrfs_cache_stats.drive_time, &ts);
      nasd_status = rd_res.nasd_status;
      out_len = rd_res.out_datalen;
      if (nasd_status || status) {
        nasd_printf("EDRFS: loading directory drive %s partnum %d "
          "nasd_status 0x%x (%s) status 0x%x (%s)\n",
          cd->drive->drname, cd->drive->partnum, nasd_status,
          nasd_error_string(nasd_status), status,
          nasd_cl_error_string(cd->drive->handle, status, err_str));
        NASD_PANIC();
      }
      NASD_ASSERT(out_len == len);
      off = loff;
      len = 0;
      i = 0;
    }
  }
  if (i) {
    i--;
    memlist[i].next = NULL;
    sp.type = cd->cookie.capability.type;
    sp.partnum = cd->drive->partnum;
    sp.actual_protection = cd->cookie.capability.min_protection;
    rd_args.in_identifier = cd->nid;
    rd_args.in_offset = off;
    rd_args.in_len = len;
    rd_args.in_partnum = cd->drive->partnum;
    NASD_TM_START(&tm);

#if DEBUG_LOAD_DIR_DETAIL
    nasd_printf("reading %lu@%lu from 0x%" NASD_ID_FMT "\n",
                (unsigned long) len, (unsigned long) off, cd->nid);
#endif /* DEBUG_LOAD_DIR_DETAIL */

    nasd_cl_p_range_read_dr(cd->drive->handle, cd->cookie.key, &sp,
                            &cd->cookie.capability, &rd_args,
                            memlist, &rd_res, &status);
    NASD_TM_STOP(&tm);
    NASD_TM_ELAPSED_TS(&tm, &ts);
    NASD_ATOMIC_TIMESPEC_ADD(&nasd_edrfs_cache_stats.drive_time, &ts);
    nasd_status = rd_res.nasd_status;
    out_len = rd_res.out_datalen;
    if (nasd_status || status) {
      nasd_printf(
        "EDRFS: loading directory drive %s partnum %d nasd_status 0x%x (%s) status 0x%x (%s)\n",
        cd->drive->drname, cd->drive->partnum, nasd_status,
        nasd_error_string(nasd_status), status,
        nasd_cl_error_string(cd->drive->handle, status, err_str));
      NASD_PANIC();
    }
    NASD_ASSERT(out_len == len);
  }

  NASD_EDRFS_LOCK_DIRCACHE();

  /*
   * Now we have the bits. Parse them.
   */
  for(cdp=cd->first_page;cdp;cdp=cdp->next) {
    nasd_edrfs_dir_parse_page(cdp->raw_page, cdp->page);

#if DEBUG_LOAD_DIR_DETAIL
    nasd_printf("parsing page\n");
#endif /* DEBUG_LOAD_DIR_DETAIL */

    for (i = 0; i < NASD_EDRFS_DIRPAGE_NUM_SLOTS; i++) {
      if (!NASD_EDRFS_DIRMAP_TEST_SLOT(cdp->page->header.usemap, i)) {
        continue;
      }

#if DEBUG_LOAD_DIR_DETAIL
      nasd_printf("slot %d has a valid entry\n", i);
#endif /* DEBUG_LOAD_DIR_DETAIL */

      /* slot contains a valid entry */
      dd = &cdp->page->data_slots[i];
      exl = NASD_EDRFS_DIRD_EXLEN(dd);
      typ = NASD_EDRFS_DIRD_TYPEOF(dd);
      
      de = nasd_edrfs_name_add(cd, cdp, i);
      
      NASD_ASSERT(de != NULL);

      if (typ == NASD_EDRFS_DIRD_TYPE_DIR) {
        /* wire up to subdir, if present */
        h = nasd_edrfs_obj_hash(dd->nasdid);
        found = NULL;
        for(r  = nasd_edrfs_obj_buckets[h].hnext;
            r != &nasd_edrfs_obj_buckets[h];
            r  = r->hnext) {
          if ((r->nid == dd->nasdid) && (r->di == cd->di)) {
            found = r;
            break;
          }
        }
        if (found) {
#if DEBUG_LOAD_DIR_DETAIL
          nasd_printf("subdir for slot %d ('%s') is in core\n", i, de->name);
#endif /* DEBUG_LOAD_DIR_DETAIL */
          /* subdir is in core */
          de->dir = found;
          if (found->attr) {
            de->attr = found->attr;
            de->attr->refcnt++;
          }
        }
      }
      
      /* skip extra name slots */
      i += (exl-1);
    }
  }

  NASD_EDRFS_UNLOCK_DIRCACHE();

  return(NASD_SUCCESS);
}

void
nasd_edrfs_post_attribute(
  nasd_edrfs_core_dir_t        *cd,
  nasd_edrfs_identifier_t       in_identifier,
  nasd_edrfs_post_attribute_t  *post_attribute)
{
  NASD_EDRFS_CSINC(post_attribute);
#if 0
  post_attribute->valid = NASD_EDRFS_ATTRIBUTE_INVALID;
  NASD_EDRFS_CSINC(post_attribute_invalid);
#else
  if (cd->attr) {
    post_attribute->valid = NASD_EDRFS_ATTRIBUTE_VALID;
    post_attribute->attribute = cd->attr->attr_res.out_attribute;
    NASD_EDRFS_CSINC(post_attribute_valid);
  }
  else {
    post_attribute->valid = NASD_EDRFS_ATTRIBUTE_INVALID;
    NASD_EDRFS_CSINC(post_attribute_invalid);
  }
#endif
}

nasd_status_t
nasd_edrfs_lookup_post_attribute(
  nasd_edrfs_identifier_t       in_identifier,
  nasd_edrfs_post_attribute_t  *post_attribute)
{
  /*
   * XXX lookup + set this while we're here
   */
  NASD_EDRFS_CSINC(lookup_post_attribute);
  post_attribute->valid = NASD_EDRFS_ATTRIBUTE_INVALID;
  return(NASD_SUCCESS);
}

/* call with dir LOCKED */
nasd_status_t
nasd_edrfs_server_dir_markerv(
  nasd_edrfs_core_dir_t  *dir,
  nasd_edrfs_markerv_t   *mvp)
{
  nasd_edrfs_markerv_t m;
  nasd_status_t rc;

  m = 0;
  if (dir->attr == NULL) {
    rc = nasd_edrfs_server_dir_load_attr(dir);
    if (rc)
      return(rc);
  }
  /* XXX eventually, this should be the disk-maintained stamp, not fs */
  m = NASD_EDRFS_ATTR_ATTR(dir->attr).object_modify_time.ts_sec;
  m <<= 32;
  m |=
    ((nasd_edrfs_markerv_t)
      NASD_EDRFS_ATTR_ATTR(dir->attr).object_modify_time.ts_nsec)
     &0xffffffff;
  *mvp = m;
  return(NASD_SUCCESS);
}

/*
 * call with dircache LOCKED
 * call with dir LOCKED
 *
 * deallocates de
 */
nasd_status_t
nasd_edrfs_server_dir_remove_entry(
  nasd_edrfs_core_dir_t    *dir,
  nasd_edrfs_diskdirent_t  *de)
{
  nasd_edrfs_core_dirpage_t  *cdp;
  char                        name[NASD_EDRFS_MAX_NAME_LEN];
  nasd_status_t               rc;

  cdp = de->page;
  strncpy(name, de->name, NASD_EDRFS_MAX_NAME_LEN);

  /* remove the name from the in-core hash */
  rc = nasd_edrfs_name_remove(dir, de, 0);
  if (rc) { NASD_PANIC(); }

  /* remove the entry from the on-disk structure */
  nasd_edrfs_server_dir_dirty_page(dir, cdp);
  rc = nasd_edrfs_dir_remove_entry(cdp->page, name);
  if (rc) { NASD_PANIC(); }

  return(NASD_SUCCESS);
}

/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */
