/*
 * nasd_edrfs_worker.c
 *
 * Actual dispatch functions for NASD EDRFS server.
 *
 * Authors: Jim Zelenka, Nat Lanza, Mathew Monroe
 */
/*
 * 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_edrfs_server_internal.h>
#include <nasd/nasd_edrfs_server.h>
#include <nasd/nasd_pdrive.h>
#include <nasd/nasd_pdrive_client.h>
#include <nasd/nasd_control.h>
#include <nasd/nasd_timer.h>
#include <nasd/nasd_marshall.h>
#include <nasd/nasd_edrfs_rpc.h>
#include <nasd/nasd_timeout.h>

#if defined(DEC_OSF) || (defined(OSF) && defined(MACH))
#include <sys/secdefines.h>
#include <sys/access.h>
#if SEC_BASE > 0
#include <sys/security.h>
#include <sys/audit.h>
#include <sys/secpolicy.h>
#endif /* SEC_BASE */
#endif /* DEC_OSF || (OSF && MACH) */

#define DEBUG_RPC_ERR_DETAIL         0

#define DEBUG_READDIR_DETAIL         0
#define DEBUG_LOOKUP_DETAIL          0
#define DEBUG_INTERNAL_LOOKUP_DETAIL 0
#define DEBUG_CREATE_DETAIL          0
#define DEBUG_REMOVE_DETAIL          0
#define DEBUG_RENAME_DETAIL          0
#define DEBUG_SETATTR_DETAIL         0

#define LOOKUP_FORCE_VALID_ATTR      0

#if 0
#define NASD_EDRFS_DATA_BYTE_START 8192
#define NASD_EDRFS_MAX_DATALEN     65536
#else
#define NASD_EDRFS_DATA_BYTE_START 65536
#define NASD_EDRFS_MAX_DATALEN     1310720
#endif

int
access_check(nasd_edrfs_attributes_t *attr,
             nasd_edrfs_credential_t *cred,
             int access)
{
  int rc = 0;
  NASD_ASSERT(attr);
  NASD_ASSERT(cred);
  if(access & NASD_EDRFS_X_OK)
    rc |= ((attr->mode & NASD_EDRFS_S_IXOTH) || 
           ((attr->mode & NASD_EDRFS_S_IXGRP) && (attr->gid == cred->gid)) ||
           ((attr->mode & NASD_EDRFS_S_IXUSR) && (attr->uid == cred->uid)) ||
           ((cred->uid == NASD_EDRFS_ROOT_UID) && (attr->uid != cred->uid)));
  if(access & NASD_EDRFS_R_OK)
    rc |= ((attr->mode & NASD_EDRFS_S_IROTH) || 
           ((attr->mode & NASD_EDRFS_S_IRGRP) && (attr->gid == cred->gid)) ||
           ((attr->mode & NASD_EDRFS_S_IRUSR) && (attr->uid == cred->uid)) ||
           ((cred->uid == NASD_EDRFS_ROOT_UID) && (attr->uid != cred->uid)));
  if(access & NASD_EDRFS_W_OK)
    rc |= ((attr->mode & NASD_EDRFS_S_IWOTH) || 
           ((attr->mode & NASD_EDRFS_S_IWGRP) && (attr->gid == cred->gid)) ||
           ((attr->mode & NASD_EDRFS_S_IWUSR) && (attr->uid == cred->uid)) ||
           ((cred->uid == NASD_EDRFS_ROOT_UID) && (attr->uid != cred->uid)));
  return rc;
}


void
nasd_dump_edrfs_attributes(nasd_edrfs_attributes_t  *edrfsattrp)
{
  nasd_printf("type ");
  switch(edrfsattrp->type) {
    case NASD_EDRFS_TYPE_REG:
      nasd_printf("NASD_EDRFS_TYPE_REG ");
      break;
    case NASD_EDRFS_TYPE_DIR:
      nasd_printf("NASD_EDRFS_TYPE_DIR ");
      break;
    case NASD_EDRFS_TYPE_LNK:
      nasd_printf("NASD_EDRFS_TYPE_LNK ");
      break;
    default:
      nasd_printf("0x%x ", ((int)edrfsattrp->type)&0xff);
      break;
  }
  nasd_printf("mode 0%o nlink %d uid %d gid %d",
              edrfsattrp->mode, edrfsattrp->nlink, edrfsattrp->uid,
              edrfsattrp->gid);
}

void
nasd_dump_edrfs_attributes_otw(nasd_edrfs_attributes_otw_t edrfsattrp_otw)
{
  nasd_edrfs_attributes_t tmp;
  nasd_edrfs_attributes_t_unmarshall(edrfsattrp_otw, &tmp);
  nasd_dump_edrfs_attributes(&tmp);
}

static nasd_status_t
nasd_create_fieldmask(nasd_fieldmask_t          in_fieldmask,
                      nasd_fieldmask_t         *out_fieldmask,
                      nasd_attribute_t          in_attribute,
                      nasd_attribute_t         *out_attr,
                      nasd_edrfs_attributes_t  *new_edrfsattr,
                      nasd_edrfs_attributes_t  *out_edrfsattr,
                      nasd_int8 type)
{
  if (in_fieldmask & NASD_ATTR_FS_SPECIFIC) {
    in_fieldmask   &= ~NASD_ATTR_FS_SPECIFIC;
    *out_fieldmask |=  NASD_ATTR_FS_SPECIFIC;

    if (in_fieldmask & NASD_EDRFS_ATTR_TYPE) {
      in_fieldmask &= ~NASD_EDRFS_ATTR_TYPE;
      if (new_edrfsattr->type != type) { return NASD_OP_NOT_SUPPORTED; }
    }

    if (in_fieldmask & NASD_EDRFS_ATTR_NLINK) { return NASD_OP_NOT_SUPPORTED; }

    if (in_fieldmask & NASD_EDRFS_ATTR_MODE) {
      in_fieldmask &= ~NASD_EDRFS_ATTR_MODE;
      out_edrfsattr->mode = new_edrfsattr->mode;
    }

    if (in_fieldmask & NASD_EDRFS_ATTR_UID) {
      in_fieldmask &= ~NASD_EDRFS_ATTR_UID;
      out_edrfsattr->uid = new_edrfsattr->uid;
    }

    if (in_fieldmask & NASD_EDRFS_ATTR_GID) {
      in_fieldmask &= ~NASD_EDRFS_ATTR_GID;
      out_edrfsattr->gid = new_edrfsattr->gid;
    }
  }

  if (in_fieldmask & NASD_ATTR_OBJECT_LEN){
    in_fieldmask   &= ~NASD_ATTR_OBJECT_LEN;
    *out_fieldmask |=  NASD_ATTR_OBJECT_LEN;
    out_attr->object_len = in_attribute.object_len;
  }

  if (in_fieldmask & NASD_ATTR_MODIFY_TIME){
    in_fieldmask   &= ~NASD_ATTR_MODIFY_TIME;
    *out_fieldmask |=  NASD_ATTR_MODIFY_TIME;

    out_attr->fs_attr_modify_time = in_attribute.fs_attr_modify_time;
  }

  if (in_fieldmask & NASD_ATTR_ATTR_MODIFY_TIME){
    in_fieldmask   &= ~NASD_ATTR_ATTR_MODIFY_TIME;
    *out_fieldmask |=  NASD_ATTR_ATTR_MODIFY_TIME;

    out_attr->fs_object_modify_time = in_attribute.fs_object_modify_time;
  }

  if (in_fieldmask) { return NASD_BAD_FIELDMASK; }
  return NASD_SUCCESS;
}

/* actual handlers here */

void
nasd_edrfs_real_mount(
  nasd_edrfs_credential_t   in_credential,
  char                     *dirpath,
  nasd_list_len_t           in_listlen,
  nasd_net_address_t        out_drivelist[],
  nasd_list_len_t          *out_listlen,
  nasd_cookie_t            *out_cookie,
  nasd_edrfs_identifier_t  *out_identifier,
  nasd_status_t            *nasd_status)
{
  /* XXX check credential */

  *nasd_status = nasd_edrfs_mount_lookup((char *)dirpath, in_listlen,
                                         out_drivelist, out_listlen,
                                         out_identifier);
  /* @@@ build cookie */
  bzero((char *)out_cookie, sizeof(nasd_cookie_t));
}

void
nasd_edrfs_real_fsstat(
  nasd_cookie_t                 in_cookie,
  nasd_edrfs_identifier_t       in_identifier,
  nasd_edrfs_credential_t       in_credential,
  nasd_edrfs_fsstat_t          *out_stat,    
  nasd_edrfs_post_attribute_t  *post_attribute,
  nasd_status_t                *nasd_status)
{
  nasd_edrfs_infopage_t *edrfs_info_page;
  nasd_ctrl_part_info_t *ptinfo;
  nasd_edrfs_attr_t *dh_attr;
  nasd_edrfs_core_dir_t *cd;
  nasd_security_param_t sp;
  nasd_timespec_t ts;
  nasd_status_t rc;
  nasd_timer_t tm;
  nasd_uint64 n;
  int partnum;

  if (post_attribute) { post_attribute->valid = NASD_EDRFS_ATTRIBUTE_INVALID; }

  edrfs_info_page = nasd_edrfs_get_infopage();
  if (edrfs_info_page == NULL) {
    *nasd_status = NASD_NO_MEM;
    return;
  }

  NASD_EDRFS_LOCK_DIRCACHE();

  rc = nasd_edrfs_server_dir_hash_lookup(in_identifier, 1, &cd, &dh_attr);
  if (rc) {
    if (rc == NASD_EDRFS_NOT_DIR) { nasd_edrfs_release_attr(dh_attr); }
    *nasd_status = rc;
    NASD_EDRFS_UNLOCK_DIRCACHE();
  }
  else {
    NASD_EDRFS_UNLOCK_DIRCACHE();
    NASD_ASSERT(cd);
    if(access_check(&cd->attr->edrfsattr, &in_credential, NASD_EDRFS_X_OK)) {
      partnum = cd->drive->partnum;
      ptinfo = (nasd_ctrl_part_info_t *)edrfs_info_page->info_page;

      /* @@@ check in_cookie, build cookie */
      bzero(&sp, sizeof(nasd_security_param_t));
      sp.actual_protection = NASD_NO_PROTECTION;

      NASD_TM_START(&tm);
      rc = nasd_cl_p_ctrl_get_part_info(cd->drive->handle, NULL, &sp, NULL,
                                        partnum, ptinfo);
      NASD_TM_STOP(&tm);
      NASD_TM_ELAPSED_TS(&tm, &ts);
      NASD_ATOMIC_TIMESPEC_ADD(&nasd_edrfs_cache_stats.drive_time, &ts);
      if (rc) {
        nasd_printf("EDRFS: nasd_cl_p_get_part_info: got 0x%x (%s)\n",
                    rc, nasd_error_string(rc));
        *nasd_status = rc;
      }
      else {
        if (out_stat) {
          out_stat->total_bytes = ptinfo->part_size * cd->drive->blocksize;
          n = ptinfo->part_size - ptinfo->blocks_allocated;
          out_stat->free_bytes = n * cd->drive->blocksize;
          out_stat->user_free_bytes = out_stat->free_bytes;
          out_stat->total_files = ptinfo->max_objs;
          out_stat->free_files = ptinfo->max_objs - ptinfo->num_obj;
          out_stat->user_free_files = out_stat->free_files;
          out_stat->invariant_seconds = 0; /* this is what EDRFS does too */
        }
        *nasd_status = NASD_SUCCESS;
      }
      NASD_EDRFS_LOCK_DIRCACHE();
      nasd_edrfs_server_dir_release_post_attribute(cd, in_identifier,
                                                   post_attribute);
      NASD_EDRFS_UNLOCK_DIRCACHE();
    }
    else
      *nasd_status = NASD_AUTHCHECK_FAILED;
  }

  if (edrfs_info_page) { nasd_edrfs_free_infopage(edrfs_info_page); }
}

void
nasd_edrfs_real_fsinfo(
  nasd_cookie_t                 in_cookie,
  nasd_edrfs_identifier_t       in_identifier,
  nasd_edrfs_credential_t       in_credential,
  nasd_edrfs_fsinfo_t          *out_info,    
  nasd_edrfs_post_attribute_t  *post_attribute,
  nasd_status_t                *nasd_status)
{
  /* XXX check credential, what should be checked? */

  if (post_attribute) { post_attribute->valid = NASD_EDRFS_ATTRIBUTE_INVALID; }

  if (out_info) {
    out_info->max_read_size             = NASD_EDRFS_MAX_DATALEN;
    out_info->preferred_read_size       = NASD_EDRFS_DATA_BYTE_START;
    out_info->preferred_read_multiple   = NASD_EDRFS_DATA_BYTE_START;
    out_info->max_write_size            = NASD_EDRFS_MAX_DATALEN;
    out_info->preferred_write_size      = NASD_EDRFS_DATA_BYTE_START;
    out_info->preferred_write_multiple  = NASD_EDRFS_DATA_BYTE_START;
    out_info->preferred_readdir_size    = NASD_EDRFS_DATA_BYTE_START;
    out_info->max_file_size             = INT_MAX;
    out_info->time_delta.ts_sec         = 0;
    out_info->time_delta.ts_nsec        = 10000000;
    out_info->fs_properties             = 0; /* might change if further support added */
  }

  if (post_attribute) { nasd_edrfs_lookup_post_attribute(in_identifier,
                                                         post_attribute); }
  *nasd_status = NASD_SUCCESS;
}

void
nasd_edrfs_real_lookup(
  nasd_cookie_t                 in_cookie,
  nasd_edrfs_identifier_t       in_identifier,
  nasd_edrfs_credential_t       in_credential,
  char                         *in_dirpath,
  nasd_cookie_t                *out_cookie,
  nasd_edrfs_identifier_t      *out_identifier,
  nasd_edrfs_post_attribute_t  *out_attribute,
  nasd_edrfs_post_attribute_t  *post_attribute,
  nasd_status_t                *nasd_status)
{
  nasd_edrfs_diskdirent_t *de;
  nasd_edrfs_attr_t *dh_attr;
  nasd_edrfs_core_dir_t *cd;
  nasd_status_t rc;

#if DEBUG_INTERNAL_LOOKUP_DETAIL > 0
  nasd_printf("internal_lookup: starting for nasdid 0x%" NASD_ID_FMT
              " in_dirpath=\"%s\"\n", in_identifier.nasd_identifier,
              in_dirpath);
#endif /* DEBUG_INTERNAL_LOOKUP_DETAIL > 0 */

  if (post_attribute) { post_attribute->valid = NASD_EDRFS_ATTRIBUTE_INVALID; }
  if (out_attribute)  {  out_attribute->valid = NASD_EDRFS_ATTRIBUTE_INVALID; }

  NASD_EDRFS_LOCK_DIRCACHE();
  rc = nasd_edrfs_server_dir_hash_lookup(in_identifier, 1, &cd, &dh_attr);
  if (rc) {
    if (rc == NASD_EDRFS_NOT_DIR) { nasd_edrfs_release_attr(dh_attr); }
  } else {
    NASD_ASSERT(cd);
    NASD_EDRFS_LOCK_DIR(cd,1);
    /* check permissions on directory */
    NASD_ASSERT(cd->attr);
    if(access_check(&cd->attr->edrfsattr, &in_credential, NASD_EDRFS_X_OK)) {
      de = nasd_edrfs_name_lookup(cd, (char *)in_dirpath);
      if (de == NULL) { rc = NASD_EDRFS_BAD_NAME; }
      else {
        out_identifier->nasd_identifier = de->data->nasdid;
        out_identifier->disk_identifier = cd->di;
        out_identifier->partnum=cd->drive->partnum;
        
        if (out_attribute) {
          if (de->attr) {
            bcopy((char *)&NASD_EDRFS_ATTR_ATTR(de->attr),
                  (char *)&out_attribute->attribute,
                  sizeof(nasd_attribute_t));
            nasd_edrfs_attributes_t_marshall(&de->attr->edrfsattr,
                  (nasd_otw_base_t *) out_attribute->attribute.fs_specific);
            out_attribute->valid = NASD_EDRFS_ATTRIBUTE_VALID;
            rc = NASD_SUCCESS;
          } else {
#if LOOKUP_FORCE_VALID_ATTR > 0
            nasd_p_getattr_dr_res_t ga_res;

            NASD_EDRFS_CSINC(lookup_get_attr);
            rc = nasd_edrfs_do_getattr(cd->drive, de->data->nasdid, &ga_res);
            if (rc == NASD_SUCCESS) {
              bcopy((char *)&ga_res.out_attribute,
                    (char *)&out_attribute->attribute,
                    sizeof(nasd_attribute_t));
              /* Don't need to marshall edrfs attributes as they already are */
              out_attribute->valid = NASD_EDRFS_ATTRIBUTE_VALID;
            }
#else /* LOOKUP_FORCE_VALID_ATTR > 0 */
            out_attribute->valid = NASD_EDRFS_ATTRIBUTE_INVALID;
            rc = NASD_SUCCESS;
#endif /* LOOKUP_FORCE_VALID_ATTR > 0 */
          }
        }
        else { rc = NASD_SUCCESS; }
      }
    } else { rc = NASD_AUTHCHECK_FAILED; }
    
    NASD_EDRFS_UNLOCK_DIR(cd);
    nasd_edrfs_server_dir_release_post_attribute(cd, in_identifier,
                                                 post_attribute);
  }

  NASD_EDRFS_UNLOCK_DIRCACHE();

  /* @@@ build cookie */
  if (rc == NASD_SUCCESS) { bzero((char *)out_cookie, sizeof(nasd_cookie_t)); }

#if DEBUG_INTERNAL_LOOKUP_DETAIL > 0
  nasd_printf("internal_lookup: finished, returning with status 0x%x (%s)\n",
              rc, nasd_error_string(rc));
#endif /* DEBUG_INTERNAL_LOOKUP_DETAIL > 0 */

  *nasd_status = rc;
}

void
nasd_edrfs_real_readdir(
  nasd_cookie_t                 in_cookie,
  nasd_edrfs_identifier_t       in_identifier,
  nasd_edrfs_credential_t       in_credential,
  nasd_edrfs_marker_t           in_marker,    
  nasd_edrfs_markerv_t          in_markerv,
  nasd_len_t                    in_count,
  nasd_edrfs_marker_t          *out_marker,
  nasd_edrfs_markerv_t         *out_markerv,
  nasd_len_t                   *out_count,
  nasd_boolean_t               *out_eof,
  nasd_procpipe_t              *byte_pipe,
  nasd_edrfs_post_attribute_t  *post_attribute,
  nasd_status_t                *nasd_status)
{
  nasd_edrfs_dirent_otw_t entry_otw;
  nasd_edrfs_core_dirpage_t *cdp;
  nasd_edrfs_attr_t *dh_attr;
  int pageind, ui, ub;
  nasd_edrfs_marker_t marker;
  nasd_edrfs_dirent_t entry;
  nasd_edrfs_markerv_t verf;
  nasd_edrfs_core_dir_t *cd;
  nasd_edrfs_dirdata_t *dd;
  nasd_status_t rc;

#if DEBUG_READDIR_DETAIL > 0
  nasd_printf("readdir_fm called on nasdid 0x%" NASD_ID_FMT
              " in_marker 0x%" NASD_64x_FMT " in_markerv 0x%" NASD_64x_FMT
              " in_count %d\n",
              in_identifier.nasd_identifier, in_marker, in_markerv,
              in_count);
#endif /* DEBUG_READDIR_DETAIL > 0 */

  rc = NASD_SUCCESS;

  if (post_attribute) { post_attribute->valid = NASD_EDRFS_ATTRIBUTE_INVALID; }

  *out_count = 0;

  NASD_EDRFS_LOCK_DIRCACHE();

  rc = nasd_edrfs_server_dir_hash_lookup(in_identifier, 1, &cd, &dh_attr);
  if (rc) {
    if (rc == NASD_EDRFS_NOT_DIR) { nasd_edrfs_release_attr(dh_attr); }
    *nasd_status = rc;
  }
  else {
    NASD_ASSERT(cd);
    NASD_EDRFS_LOCK_DIR(cd,1);
    if(!access_check(&cd->attr->edrfsattr, &in_credential, NASD_EDRFS_R_OK)){
      rc = NASD_AUTHCHECK_FAILED;
      goto done_inner_readdir;
    }

    rc = nasd_edrfs_server_dir_markerv(cd, &verf);
    if (rc) {
      *nasd_status = rc;
      goto done_inner_readdir;
    }

    /* We want to let a markv of 0 work for the linux client... ugh. */
    if (in_marker && in_markerv && (in_markerv != verf)) {
      *nasd_status = rc = NASD_EDRFS_BAD_MARKER_VERF;
      goto done_inner_readdir;
    }
    
    cdp = cd->first_page;
    pageind = 0;
    marker = 0;
    
    /* this code is really ugly. good thing we don't use it anymore. */
    while(cdp && ((*out_count) < in_count)) {
      ui = pageind / 8;
      ub = 1 << (pageind % 8);
      if (cdp->page->header.usemap[ui] & ub) {
        /* valid entry */
        marker++;
        if (marker >= in_marker) {
          dd = &cdp->page->data_slots[pageind];
          /* send the entry to the client */
#if DEBUG_READDIR_DETAIL > 0
          nasd_printf("readdir_fm returning entry \"%s\" nasdid "
                      "0x%" NASD_ID_FMT "\n", dd->name, dd->nasdid);
#endif /* DEBUG_READDIR_DETAIL > 0 */

          entry.nd_ino = dd->nasdid;
          entry.nd_namlen = dd->name_len;
          memcpy(entry.nd_name, dd->name, dd->name_len);
          entry.nd_name[dd->name_len] = '\0';
          nasd_edrfs_dirent_t_marshall(&entry, entry_otw);
          rc = byte_pipe->push(byte_pipe->state, entry_otw,
                               sizeof(entry_otw), NULL, NULL, NULL);
          if (rc) { break; }
          pageind += NASD_EDRFS_DIRD_EXLEN(dd);
          (*out_count)++; /* total # dirents sent */
        } else { pageind++; }
      } else { pageind++; }

      if (pageind >= 127) {
        NASD_ASSERT(pageind == 127);
        pageind = 0;
        cdp = cdp->next;
      }
    }
    
#if DEBUG_READDIR_DETAIL > 0
    nasd_printf("readdir_fm out_count=%d in_count=%d\n", *out_count,
                in_count);
#endif /* DEBUG_READDIR_DETAIL > 0 */
    
    if(in_count >= 0) { NASD_ASSERT(*out_count <= in_count); }
    *out_eof = (cdp) ? 0 : 1;
    *out_marker = marker + 1;
    *out_markerv = verf;

  done_inner_readdir:
    NASD_EDRFS_UNLOCK_DIR(cd);
    nasd_edrfs_server_dir_release_post_attribute(cd, in_identifier,
                                                 post_attribute);
  }

  *nasd_status = rc;

  NASD_EDRFS_UNLOCK_DIRCACHE();

#if DEBUG_READDIR_DETAIL > 0
  nasd_printf("readdir_fm called on nasdid 0x%" NASD_ID_FMT
              " in_count %d result 0x%x (%s)\n",
              in_identifier.nasd_identifier, in_count, *nasd_status,
              nasd_error_string(*nasd_status));
  nasd_printf("readdir_fm returning out_marker 0x%" NASD_64x_FMT
              " out_markerv 0x%" NASD_64x_FMT " out_count %d out_eof %d\n",
              *out_marker, *out_markerv, *out_count, *out_eof);
#endif /* DEBUG_READDIR_DETAIL > 0 */
}

void
nasd_edrfs_real_access(
  nasd_cookie_t                 in_cookie,
  nasd_edrfs_identifier_t       in_identifier,
  nasd_edrfs_credential_t       in_credential,
  nasd_edrfs_access_t           in_access,
  nasd_edrfs_access_t          *out_access,
  nasd_edrfs_post_attribute_t  *post_attribute,
  nasd_status_t                *nasd_status)
{
  nasd_edrfs_attributes_t edrfsattr;
  nasd_p_getattr_dr_res_t ga_res;
  nasd_edrfs_attr_t *dh_attr;
  nasd_edrfs_core_dir_t *cd;
  nasd_attribute_t *attr;
  nasd_identifier_t nid;
  nasd_edrfs_drive_t *dr;
  nasd_status_t rc;
  int need_doit;

  if (post_attribute) { post_attribute->valid = NASD_EDRFS_ATTRIBUTE_INVALID; }

  if (out_access == NULL) {
    *nasd_status = NASD_FAIL;
    return;
  }

  *out_access = 0;
  need_doit = 0;
  nid = NASDID_NULL;

  NASD_EDRFS_LOCK_DIRCACHE();

  cd = NULL;
  rc = nasd_edrfs_server_dir_hash_lookup(in_identifier, 1, &cd, &dh_attr);
  if (rc) {
    cd = NULL;
    NASD_EDRFS_CSINC(access_lookup_fail);
    if (rc == NASD_EDRFS_NOT_DIR) {
      /* the object is valid, but not a directory.
         perform access checks, but then ditch the attribute. */
      NASD_EDRFS_CSINC(access_not_dir);
      attr = &dh_attr->attr_res.out_attribute;
      nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *) attr->fs_specific,
                                         &edrfsattr);
      *nasd_status = NASD_SUCCESS;
      nasd_edrfs_release_attr(dh_attr);
      dh_attr = NULL;
      goto other_attr;
    }

    *nasd_status = rc;

  } else {
    NASD_ASSERT(dh_attr == NULL);
    if (cd) {
      NASD_EDRFS_LOCK_DIR(cd,1);
      rc = NASD_SUCCESS;
      if (cd->attr == NULL) {
        NASD_EDRFS_CSINC(access_fault_attr);
        rc = nasd_edrfs_server_dir_load_attr(cd);
      }
      if (rc == NASD_SUCCESS) {
        attr = &NASD_EDRFS_ATTR_ATTR(cd->attr);
        nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *)
                                           attr->fs_specific,
                                           &edrfsattr);
      }
      NASD_EDRFS_UNLOCK_DIR(cd);
    }
    else {
      rc = nasd_edrfs_get_drive(in_identifier, &dr);
      if (rc == NASD_SUCCESS) {
        need_doit = 1;
        nid = cd->nid;
      }
    }
    *nasd_status = rc;
  }

  if (cd) { nasd_edrfs_server_dir_release_post_attribute(cd, in_identifier,
                                                         post_attribute);   }

other_attr:
  NASD_EDRFS_UNLOCK_DIRCACHE();

  if (((*nasd_status) == NASD_SUCCESS) && need_doit) {
    NASD_ASSERT(dr);
    NASD_EDRFS_CSINC(access_get_attr);
    rc = nasd_edrfs_do_getattr(dr, nid, &ga_res);
    if (rc == NASD_SUCCESS){
      attr = &ga_res.out_attribute;
      nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *) attr->fs_specific,
                                         &edrfsattr);
    }
    *nasd_status = rc;
  }

  if ((*nasd_status) == NASD_SUCCESS) {
    /* check access on the file */

    if ((in_access & NASD_EDRFS_ACCESS_READ) &&
        access_check(&edrfsattr, &in_credential, NASD_EDRFS_R_OK))
      *out_access |= NASD_EDRFS_ACCESS_READ;

    if ((in_access & NASD_EDRFS_ACCESS_EXECUTE) &&
        access_check(&edrfsattr, &in_credential, NASD_EDRFS_X_OK))
      *out_access |= NASD_EDRFS_ACCESS_EXECUTE;

    if ((in_access & NASD_EDRFS_ACCESS_MODIFY) &&
        access_check(&edrfsattr, &in_credential, NASD_EDRFS_W_OK))
      *out_access |= NASD_EDRFS_ACCESS_MODIFY;

    if ((in_access & NASD_EDRFS_ACCESS_EXTEND) &&
        access_check(&edrfsattr, &in_credential, NASD_EDRFS_W_OK))
      *out_access |= NASD_EDRFS_ACCESS_EXTEND;

    /* XXX -- we can't actually check these. */
    if (in_access & NASD_EDRFS_ACCESS_LOOKUP) { 
      *out_access |= NASD_EDRFS_ACCESS_LOOKUP;
    }
    if (in_access & NASD_EDRFS_ACCESS_DELETE) {
      *out_access |= NASD_EDRFS_ACCESS_DELETE;
    }
  }
}


void
nasd_edrfs_real_setattr(
  nasd_cookie_t             in_cookie,
  nasd_edrfs_identifier_t   in_identifier,
  nasd_edrfs_credential_t   in_credential,
  nasd_attribute_t          in_attribute,
  nasd_fieldmask_t          in_fieldmask,
  nasd_guard_t              in_guard,
  nasd_attribute_t         *out_attribute,
  nasd_status_t            *nasd_status)
{
  nasd_attribute_t *attr;
  nasd_edrfs_attributes_t *edrfsattr, *in_edrfsattr;
  nasd_p_setattr_dr_args_t *sa_args;
  nasd_p_setattr_dr_res_t *sa_res;
  nasd_p_getattr_dr_res_t *ga_res;
  nasd_edrfs_setattrpair_t *sapair;
  nasd_edrfs_attr_t *dh_attr;
  nasd_edrfs_core_dir_t *cd; 
  nasd_edrfs_drive_t *dr;
  nasd_status_t rc;

  *nasd_status = NASD_SUCCESS;

#if DEBUG_SETATTR_DETAIL > 0
  nasd_printf("setattr: start for nid 0x%" NASD_ID_FMT
              ", uid/gid=%d/%d, fieldmask=0x%x\n",
              in_identifier.nasd_identifier, in_credential.uid, in_credential.gid,
              in_fieldmask, in_guard);
  nasd_dump_edrfs_attributes_otw((nasd_otw_base_t *) in_attribute.fs_specific);
  nasd_printf("\n");
#endif /* DEBUG_SETATTR_DETAIL > 0 */

  sapair = nasd_edrfs_get_setattrpair();
  if (sapair == NULL) { *nasd_status = NASD_NO_MEM; cd = NULL; goto done_setattr; }

  sa_args      = &sapair->sa_args;
  sa_res       = &sapair->sa_res;
  ga_res       = &sapair->ga_res;
  in_edrfsattr = &sapair->in_edrfsattr;
  edrfsattr    = &sapair->out_edrfsattr;

  NASD_EDRFS_LOCK_DIRCACHE();

  rc = nasd_edrfs_server_dir_hash_lookup(in_identifier, 0, &cd, &dh_attr);
  if (rc) {
    if(rc != NASD_EDRFS_NOT_DIR){
      *nasd_status = rc;

      if (cd) { nasd_edrfs_server_dir_release(cd); }
      NASD_EDRFS_UNLOCK_DIRCACHE();
      return;
    }
    cd = NULL;
  }

  if (cd) { NASD_EDRFS_LOCK_DIR(cd,1); }

  NASD_EDRFS_UNLOCK_DIRCACHE();

  if (cd) {
    if (cd->attr == NULL) {
      rc = nasd_edrfs_server_dir_load_attr(cd);
      if (rc) { *nasd_status = rc; goto done_setattr; }
    }

    attr = &NASD_EDRFS_ATTR_ATTR(cd->attr);
    memcpy((char *)edrfsattr, (char *)&cd->attr->edrfsattr,
           sizeof(nasd_edrfs_attr_t)); 
    dr = cd->drive;

  } else {
    rc = nasd_edrfs_get_drive(in_identifier, &dr);
    if (rc) { *nasd_status = rc; goto done_setattr; }

    NASD_ASSERT(dr != NULL);
    NASD_EDRFS_CSINC(setattr_get_attr);

    rc = nasd_edrfs_do_getattr(dr, in_identifier.nasd_identifier, ga_res);
    if (rc) { *nasd_status = rc; goto done_setattr; }
    
    attr = &ga_res->out_attribute;
    nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *) attr->fs_specific,
                                       edrfsattr);
  }

  /* unmarshall the attributes */
  nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *) in_attribute.fs_specific,
                                     in_edrfsattr);


#if DEBUG_SETATTR_DETAIL > 0
  nasd_dump_edrfs_attributes(in_edrfsattr);
  nasd_printf("\n");
  nasd_dump_edrfs_attributes_otw((nasd_otw_base_t *) in_attribute.fs_specific);
  nasd_printf("\nexisting attributes:\n");
  nasd_dump_edrfs_attributes(edrfsattr);
  nasd_printf("\n");
#endif /* DEBUG_SETATTR_DETAIL */

  /* now we have the old object attributes.
     figure out the necessary swizzling and set it into sa_args. */
  sa_args->in_identifier = in_identifier.nasd_identifier;

  memcpy((char *) &sa_args->in_attribute, (char *) attr,
         sizeof(nasd_attribute_t));

  sa_args->in_fieldmask = 0;
  sa_args->in_partnum = dr->partnum;
  sa_args->in_guard = (in_guard ? 1 : 0);
  attr = &sa_args->in_attribute;
  rc = nasd_create_fieldmask(in_fieldmask, &sa_args->in_fieldmask,
                             in_attribute, attr,
                             in_edrfsattr, edrfsattr,
                             edrfsattr->type);
  if (rc) { *nasd_status = rc; goto done_setattr; }

#if DEBUG_SETATTR_DETAIL > 0
  nasd_printf("want to set attributes with fieldmask 0x%x:\n",
              sa_args->in_fieldmask);
  nasd_dump_edrfs_attributes(edrfsattr);
  nasd_printf("\n");
#endif /* DEBUG_SETATTR_DETAIL */

  /* marshall the edrfs attributes */
  nasd_edrfs_attributes_t_marshall(edrfsattr, (nasd_otw_base_t *) attr->fs_specific);

  /* now're we're ready to set some attributes. */
  sapair->otw_buf.args_otw = sapair->sa_args_otw;
  sapair->otw_buf.res_otw = sapair->sa_res_otw;
  rc = nasd_edrfs_do_setattr(dr, sa_args, sa_res, &sapair->otw_buf);
  if (rc == NASD_SUCCESS) {
    /* squirrel away a copy of the attribute */
    if (cd) {
      memcpy((char *) &NASD_EDRFS_ATTR_ATTR(cd->attr),
             (char *) &sa_res->out_attribute,
             sizeof(nasd_attribute_t));
    }
  } else {
    nasd_printf("drive setattr failed!\n");
  }
  
  /* and now return the new attribute */
  memcpy((char *) out_attribute,
         (char *) &sa_res->out_attribute,
         sizeof(nasd_attribute_t));

  *nasd_status = rc;

done_setattr:
  if (cd) {
    NASD_EDRFS_LOCK_DIRCACHE();
    NASD_EDRFS_UNLOCK_DIR(cd);
    nasd_edrfs_server_dir_release(cd);
    NASD_EDRFS_UNLOCK_DIRCACHE();
  }

  if (sapair) { nasd_edrfs_free_setattrpair(sapair); }

#if DEBUG_SETATTR_DETAIL > 0
  nasd_printf("setattr: returning 0x%x (%s), attributes:\n",
              *nasd_status, nasd_error_string(*nasd_status));
  nasd_dump_edrfs_attributes_otw((nasd_otw_base_t *)
                                 out_attribute->fs_specific);
  nasd_printf("\n");
#endif /* DEBUG_SETATTR_DETAIL > 0 */

}

void
nasd_edrfs_real_create(
  nasd_cookie_t                 in_cookie,
  nasd_edrfs_identifier_t       in_directory,
  nasd_edrfs_credential_t       in_credential,
  char                         *in_dirpath,
  nasd_attribute_t              in_attribute,
  nasd_fieldmask_t              in_fieldmask,
  nasd_cookie_t                *out_cookie,
  nasd_edrfs_identifier_t      *out_identifier,
  nasd_attribute_t             *out_attribute,
  nasd_edrfs_post_attribute_t  *post_attribute,
  nasd_status_t                *nasd_status)
{
  nasd_edrfs_attributes_t *edrfsattr, *new_edrfsattr;
  nasd_edrfs_createpair_t *crpair;
  nasd_edrfs_diskdirent_t *de;
  nasd_edrfs_attr_t *dh_attr;
  nasd_edrfs_core_dir_t *cd;
  nasd_attribute_t *attr;
  nasd_status_t rc;
  int lk;

  crpair = NULL;
  lk = 0;
  de = NULL;

  if (post_attribute) { post_attribute->valid = NASD_EDRFS_ATTRIBUTE_INVALID; }

  rc = nasd_edrfs_valid_name((char *)in_dirpath);
  if (rc) {
    *nasd_status = rc;
    goto really_done_create;
  }

  crpair = nasd_edrfs_get_createpair();
  if (crpair == NULL) {
    *nasd_status = NASD_NO_MEM;
    goto really_done_create;
  }

  NASD_EDRFS_LOCK_DIRCACHE();
  lk = 1;
  rc = nasd_edrfs_server_dir_hash_lookup(in_directory, 1, &cd, &dh_attr);
  if (rc) {
    if (rc == NASD_EDRFS_NOT_DIR)
      nasd_edrfs_release_attr(dh_attr);
    *nasd_status = rc;
    goto done_create;
  }

  NASD_ASSERT(cd);
  NASD_EDRFS_LOCK_DIR(cd,1);

  if(!access_check(&cd->attr->edrfsattr, &in_credential, NASD_EDRFS_W_OK)){
    rc = NASD_AUTHCHECK_FAILED;
    goto done_create2;
  }

  NASD_EDRFS_UNLOCK_DIRCACHE();
  lk = 0;

  /* @@@ init cookie */
  attr = &crpair->args.in_attribute;
  edrfsattr = &crpair->edrfsattr;
  new_edrfsattr = &crpair->new_edrfsattr;
  nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *)
                                     attr->fs_specific, edrfsattr);
  nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *)
                                     in_attribute.fs_specific, new_edrfsattr);

  crpair->args.in_fieldmask = NASD_ATTR_FS_SPECIFIC;
  edrfsattr->type = NASD_EDRFS_TYPE_REG;
  edrfsattr->mode = NASD_EDRFS_S_IRUSR |
                    NASD_EDRFS_S_IWUSR |
                    NASD_EDRFS_S_IFREG;
  edrfsattr->nlink = 1;
  edrfsattr->uid = in_credential.uid;
  edrfsattr->gid = in_credential.gid;
  edrfsattr->clean = NASD_EDRFS_CLEAN;

  rc = nasd_create_fieldmask(in_fieldmask, &crpair->args.in_fieldmask,
                             in_attribute, attr, new_edrfsattr,
                             edrfsattr, NASD_EDRFS_TYPE_REG);
  if (rc) {
    *nasd_status = rc;
    goto done_create2;
  }
  crpair->args.in_fieldmask |= NASD_ATTR_AV|NASD_ATTR_LAYOUT_HINT;
  attr->av = NASD_EDRFS_INITIAL_AV;
  attr->layout_hint.lh_nid = cd->nid;
  nasd_edrfs_attributes_t_marshall(edrfsattr,
                                   (nasd_otw_base_t *) attr->fs_specific);
  crpair->otw_buf.args_otw = crpair->args_otw;
  crpair->otw_buf.res_otw = crpair->res_otw;
  rc = nasd_edrfs_do_create(cd->drive, &crpair->args, &crpair->res,
                            &crpair->otw_buf);
  if (rc) {
    *nasd_status = rc;
    goto done_create2;
  }

  NASD_EDRFS_UNLOCK_DIR(cd);
  NASD_EDRFS_LOCK_DIRCACHE();
  lk = 1;
  NASD_EDRFS_LOCK_DIR(cd,1);

  /*
   * Have to re-lookup here because someone else might
   * have created it in the meantime.
   */
  de = nasd_edrfs_name_lookup(cd, (char *)in_dirpath);
  if (de) {
    rc = nasd_edrfs_do_remove(cd->drive, crpair->res.out_identifier);
    if (rc) { NASD_PANIC(); }
    *nasd_status = NASD_EDRFS_ALREADY_EXISTS;
    goto done_create2;
  }
  /*
   * Now the object exists, so we can add it to the directory.
   */
  rc = nasd_edrfs_server_dir_add_entry(cd, (char *)in_dirpath,
                                       crpair->res.out_identifier,
                                       NASD_EDRFS_DIRD_TYPE_DATA, &de);
  if (rc) {
    rc = nasd_edrfs_do_remove(cd->drive, crpair->res.out_identifier);
    if (rc) { NASD_PANIC(); }
    *nasd_status = NASD_FAIL;
    goto done_create2;
  }

  NASD_EDRFS_UNLOCK_DIRCACHE();
  lk = 0;
  /* @@@ generate cookie */
  bzero((char *)out_cookie, sizeof(nasd_cookie_t));
  out_identifier->nasd_identifier = crpair->res.out_identifier;
  out_identifier->disk_identifier = cd->drive->di;
  out_identifier->partnum = cd->drive->partnum;
 
  *out_attribute = crpair->res.out_attribute;
  *nasd_status = NASD_SUCCESS;

done_create2:
  NASD_EDRFS_UNLOCK_DIR(cd);
  if (lk == 0) {
    NASD_EDRFS_LOCK_DIRCACHE();
    lk = 1;
  }
  nasd_edrfs_server_dir_release_post_attribute(cd, *out_identifier,
                                               post_attribute);

done_create:
  if (lk) {
    NASD_EDRFS_UNLOCK_DIRCACHE();
    lk = 0;
  }

really_done_create:

  if (crpair) { nasd_edrfs_free_createpair(crpair); }
  NASD_ASSERT(lk == 0);

#if DEBUG_CREATE_DETAIL > 0
  if (*nasd_status == 0) {
    nasd_printf("create returning success creating \"%s\" in directory with "
                "nasdid 0x%" NASD_ID_FMT " new file has id 0x%" NASD_ID_FMT
                "\n",
                in_dirpath, in_directory.nasd_identifier,
                out_identifier->nasd_identifier);
    if (de) {
      nasd_printf("create of \"%s\" got de %p nasdid 0x%" NASD_ID_FMT
                  " pagename \"%s\" len %d\n",
                  in_dirpath, de, de->data->nasdid, de->data->name,
                  de->data->name_len);
    }
  }
#endif /* DEBUG_CREATE_DETAIL > 0 */
}

void
nasd_edrfs_real_symlink(
  nasd_cookie_t                 in_cookie,
  nasd_edrfs_identifier_t       in_directory,
  nasd_edrfs_credential_t       in_credential,
  char                         *in_dirpath,
  char                         *in_symlink,
  nasd_attribute_t              in_attribute,
  nasd_cookie_t                *out_cookie,
  nasd_edrfs_identifier_t      *out_identifier,
  nasd_attribute_t             *out_attribute,
  nasd_edrfs_post_attribute_t  *post_attribute,
  nasd_status_t                *nasd_status)
{
  nasd_edrfs_attributes_t *edrfsattr, *new_edrfsattr;
  nasd_p_smpl_op_dr_args_t p_write_args;
  nasd_p_fastwrite_dr_res_t p_write_res;
  nasd_security_param_t sp;
  nasd_edrfs_createpair_t *crpair;
  nasd_edrfs_diskdirent_t *de;
  nasd_edrfs_attr_t *dh_attr;
  nasd_rpc_status_t wrstat;
  nasd_edrfs_core_dir_t *cd;
  nasd_attribute_t *attr;
  nasd_timespec_t ts;
  nasd_status_t rc;
  nasd_timer_t tm;
  int lk;

  crpair = NULL;
  lk = 0;
  de = NULL;

  if (post_attribute) { post_attribute->valid = NASD_EDRFS_ATTRIBUTE_INVALID; }

  rc = nasd_edrfs_valid_name((char *)in_dirpath);
  if (rc) {
    *nasd_status = rc;
    goto really_done_symlink;
  }

  if (strlen((const char *) in_symlink) >= NASD_EDRFS_MAX_NAME_LEN) {
    *nasd_status = NASD_EDRFS_BAD_NAME;
    goto really_done_symlink;
  }

  crpair = nasd_edrfs_get_createpair();
  if (crpair == NULL) {
    *nasd_status = NASD_NO_MEM;
    goto really_done_symlink;
  }
  
  NASD_EDRFS_LOCK_DIRCACHE();
  lk = 1;
  rc = nasd_edrfs_server_dir_hash_lookup(in_directory, 1, &cd, &dh_attr);
  if (rc) {
    if (rc == NASD_EDRFS_NOT_DIR) { nasd_edrfs_release_attr(dh_attr); }
    *nasd_status = rc;
    goto done_symlink;
  }

  NASD_ASSERT(cd);
  NASD_EDRFS_LOCK_DIR(cd,1);

  if(!access_check(&cd->attr->edrfsattr, &in_credential, NASD_EDRFS_W_OK)){
    rc = NASD_AUTHCHECK_FAILED;
    goto done_symlink2;
  }

  NASD_EDRFS_UNLOCK_DIRCACHE();
  lk = 0;

  /* @@@ init capability */
  attr = &crpair->args.in_attribute;
  edrfsattr = &crpair->edrfsattr;
  new_edrfsattr = &crpair->new_edrfsattr;

  crpair->args.in_fieldmask = NASD_ATTR_FS_SPECIFIC;

  edrfsattr->type = NASD_EDRFS_TYPE_LNK;
  edrfsattr->mode = NASD_EDRFS_S_IRWXU |
                    NASD_EDRFS_S_IRWXG |
                    NASD_EDRFS_S_IRWXO |
                    NASD_EDRFS_S_IFLNK;
  edrfsattr->nlink = 1;
  edrfsattr->uid = in_credential.uid;
  edrfsattr->gid = in_credential.gid;
  edrfsattr->clean = NASD_EDRFS_CLEAN;

  crpair->args.in_fieldmask |= NASD_ATTR_AV | NASD_ATTR_LAYOUT_HINT;
  crpair->args.in_attribute.av = NASD_EDRFS_INITIAL_AV;
  crpair->args.in_attribute.layout_hint.lh_nid = cd->nid;
  nasd_edrfs_attributes_t_marshall(edrfsattr,
                                   (nasd_otw_base_t *) attr->fs_specific);
  crpair->otw_buf.args_otw = crpair->args_otw;
  crpair->otw_buf.res_otw = crpair->res_otw;
  rc = nasd_edrfs_do_create(cd->drive, &crpair->args, &crpair->res,
                          &crpair->otw_buf);
  if (rc) {
    *nasd_status = NASD_FAIL;
    goto done_symlink2;
  }

  NASD_EDRFS_UNLOCK_DIR(cd);
  NASD_EDRFS_LOCK_DIRCACHE();
  lk = 1;
  NASD_EDRFS_LOCK_DIR(cd,1);

  de = nasd_edrfs_name_lookup(cd, (char *)in_dirpath);
  if (de) {
    rc = nasd_edrfs_do_remove(cd->drive, crpair->res.out_identifier);
    if (rc) { NASD_PANIC(); }
    *nasd_status = NASD_EDRFS_ALREADY_EXISTS;
    goto done_symlink2;
  }

  rc = nasd_edrfs_server_dir_add_entry(cd, (char *)in_dirpath,
                                       crpair->res.out_identifier,
                                       NASD_EDRFS_DIRD_TYPE_LINK, &de);
  if (rc) {
    rc = nasd_edrfs_do_remove(cd->drive, crpair->res.out_identifier);
    if (rc) { NASD_PANIC(); }
    *nasd_status = NASD_FAIL;
    goto done_symlink2;
  }

  NASD_EDRFS_UNLOCK_DIRCACHE();
  lk = 0;

  /* we've created the object. now we need to write to it. */

  sp.type = in_cookie.capability.type;
  sp.partnum = cd->drive->partnum;
  sp.actual_protection = NASD_NO_PROTECTION;

  p_write_args.in_identifier = crpair->res.out_identifier;
  p_write_args.in_offset = 0;
  p_write_args.in_len = strlen((char *) in_symlink);
  p_write_args.in_partnum = cd->drive->partnum;

  NASD_TM_START(&tm);
  nasd_cl_p_write_simple_dr(cd->drive->handle,
                            in_cookie.key, &sp, &in_cookie.capability,
                            &p_write_args,
                            (void *) in_symlink,
                            &p_write_res, &wrstat);
  NASD_TM_STOP(&tm);
  NASD_TM_ELAPSED_TS(&tm, &ts);
  NASD_ATOMIC_TIMESPEC_ADD(&nasd_edrfs_cache_stats.drive_time, &ts);

  rc = p_write_res.nasd_status;
  if (rc) { nasd_printf("uh-oh. had a problem writing: '%s'\n",
                        nasd_error_string(rc));                 }
  if (wrstat) { rc = NASD_FAIL; }

  bzero((char *)out_cookie, sizeof(nasd_cookie_t));
  out_identifier->nasd_identifier = crpair->res.out_identifier;
  out_identifier->disk_identifier = cd->drive->di;
  out_identifier->partnum = cd->drive->partnum;
 
  *out_attribute = crpair->res.out_attribute;
  *nasd_status = rc;

 done_symlink2:
  NASD_EDRFS_UNLOCK_DIR(cd);
  if (lk == 0) {
    NASD_EDRFS_LOCK_DIRCACHE();
    lk = 1;
  }
  nasd_edrfs_server_dir_release_post_attribute(cd, *out_identifier, post_attribute);

 done_symlink:
  if (lk) {
    NASD_EDRFS_UNLOCK_DIRCACHE();
    lk = 0;
  }

 really_done_symlink:

  if (crpair) { nasd_edrfs_free_createpair(crpair); }

  NASD_ASSERT(lk == 0);

#if DEBUG_SYMLINK_DETAIL > 0
  if (*nasd_status == 0) {
    nasd_printf("returning success creating link \"%s\" (nasdid 0x%"
           NASD_ID_FMT ") pointing to \"%s\"\nin directory with nasdid 0x%"
           NASD_ID_FMT "\n",
           in_dirpath, out_identifier->nasd_identifier, in_symlink,
           in_directory.nasd_identifier);
    if (de) {
      nasd_printf("create of \"%s\" got de 0x%lx nasdid 0x%"
             NASD_ID_FMT " pagename \"%s\" len %d\n",
             in_dirpath, de, de->data->nasdid,
             de->data->name, de->data->name_len);
    }
  }
#endif /* DEBUG_SYMLINK_DETAIL > 0 */
}

void
nasd_edrfs_real_remove(
  nasd_cookie_t             in_cookie,
  nasd_edrfs_identifier_t   in_directory,
  nasd_edrfs_credential_t   in_credential,
  char                     *in_dirpath,
  nasd_status_t            *nasd_status)
{
  nasd_edrfs_diskdirent_t *de;
  nasd_identifier_t nasdid;
  nasd_edrfs_attr_t *dh_attr;
  nasd_edrfs_core_dir_t *cd;
  nasd_status_t rc;

  NASD_EDRFS_LOCK_DIRCACHE();
  rc = nasd_edrfs_server_dir_hash_lookup(in_directory, 1, &cd, &dh_attr);
  if (rc) {
    if (rc == NASD_EDRFS_NOT_DIR)
      nasd_edrfs_release_attr(dh_attr);
    *nasd_status = rc;
    NASD_EDRFS_UNLOCK_DIRCACHE();
    return;
  }

  NASD_ASSERT(cd);
  NASD_ASSERT(dh_attr == NULL);

  NASD_EDRFS_LOCK_DIR(cd, 1);

  de = nasd_edrfs_name_lookup(cd, (char *)in_dirpath);

  if (de == NULL) {
    *nasd_status = NASD_EDRFS_BAD_NAME;
    NASD_EDRFS_UNLOCK_DIR(cd);
    nasd_edrfs_server_dir_release(cd);
    NASD_EDRFS_UNLOCK_DIRCACHE();
    return;
  }

#if DEBUG_REMOVE_DETAIL > 0
  nasd_printf("remove found ent %p name \"%s\" for \"%s\" in 0x%"
          NASD_ID_FMT " dataname \"%s\" len %d\n",
          de, de->name, in_dirpath, in_directory.nasd_identifier,
          de->data->name, de->data->name_len);
#endif /* DEBUG_REMOVE_DETAIL > 0 */

  if (NASD_EDRFS_DIRD_IS_DIR(de->data)) {
    *nasd_status = NASD_EDRFS_IS_DIR;
    NASD_EDRFS_UNLOCK_DIR(cd);
    nasd_edrfs_server_dir_release(cd);
    NASD_EDRFS_UNLOCK_DIRCACHE();
    return;
  }

  if(!access_check(&cd->attr->edrfsattr, &in_credential, NASD_EDRFS_W_OK)){
    *nasd_status = NASD_AUTHCHECK_FAILED;
    NASD_EDRFS_UNLOCK_DIR(cd);
    nasd_edrfs_server_dir_release(cd);
    NASD_EDRFS_UNLOCK_DIRCACHE();    
    return;
  }

  nasdid = de->data->nasdid;

  /* remove entry from dir */
  rc = nasd_edrfs_server_dir_remove_entry(cd, de);
  if (rc) { NASD_PANIC(); }

  NASD_EDRFS_UNLOCK_DIR(cd);

  nasd_edrfs_server_dir_release(cd);
  NASD_EDRFS_UNLOCK_DIRCACHE();

  /* remove entry from drive */
  rc = nasd_edrfs_do_remove(cd->drive, nasdid);
  if (rc) {
    nasd_printf("EDRFS: failed to remove id 0x%" NASD_ID_FMT " error 0x%x (%s)\n",
      nasdid, rc, nasd_error_string(rc));
    nasd_printf("     id was for \"%s\" in directory with ID 0x%" NASD_ID_FMT "\n",
      in_dirpath, in_directory.nasd_identifier);
    NASD_PANIC();
    *nasd_status = NASD_FAIL;
  }

  *nasd_status = NASD_SUCCESS;

#if DEBUG_REMOVE_DETAIL > 0
  if (*nasd_status == 0) {
    nasd_printf("remove returning success deleting \"%s\" in directory with nasdid 0x%"
                NASD_ID_FMT "\n",
                in_dirpath, in_directory.nasd_identifier);
  }
#endif /* DEBUG_REMOVE_DETAIL > 0 */
}

void
nasd_edrfs_real_mkdir(
  nasd_cookie_t                 in_cookie,
  nasd_edrfs_identifier_t       in_directory,
  nasd_edrfs_credential_t       in_credential,
  char                         *in_dirpath,
  nasd_attribute_t              in_attribute,
  nasd_fieldmask_t              in_fieldmask,
  nasd_cookie_t                *out_cookie,
  nasd_edrfs_identifier_t      *out_identifier,
  nasd_attribute_t             *out_attribute,
  nasd_edrfs_post_attribute_t  *post_attribute,
  nasd_status_t                *nasd_status)
{
  nasd_edrfs_diskdirent_t *de, *dde_dot, *dde_dot_dot;
  nasd_edrfs_attributes_t *edrfsattr, *new_edrfsattr;
  nasd_edrfs_createpair_t *crpair;
  nasd_edrfs_core_dir_t *cd, *new;
  nasd_edrfs_dirpage_t *dirpage;
  nasd_edrfs_attr_t *dh_attr;
  nasd_attribute_t *attr;
  int dir_valid, i, lk;
  nasd_status_t rc;

  crpair = NULL;
  lk = 0;

  if (post_attribute) { post_attribute->valid = NASD_EDRFS_ATTRIBUTE_INVALID; }

  rc = nasd_edrfs_valid_name((char *)in_dirpath);
  if (rc) {
    *nasd_status = rc;
    goto really_done_mkdir;
  }

  crpair = nasd_edrfs_get_createpair();
  if (crpair == NULL) {
    *nasd_status = NASD_NO_MEM;
    goto really_done_mkdir;
  }

  NASD_EDRFS_LOCK_DIRCACHE();
  lk = 1;

  cd = NULL;
  dir_valid = 0;

  /* get a dir structure for this new directory */
  new = nasd_edrfs_server_dir_get_coredir(1);
  if (new == NULL) {
    *nasd_status = NASD_NO_MEM;
    goto done_mkdir;
  }
  new->attr = nasd_edrfs_get_attr();

  /* now get a page to hold the data */
  nasd_edrfs_server_dir_mk_freepages(1);
  new->first_page = new->last_page = nasd_edrfs_free_dirpages;
  nasd_edrfs_free_dirpages = new->last_page->next;
  nasd_edrfs_num_free_dirpages--;
  new->npages = 1;
  new->first_page->prev = NULL;
  new->last_page->next = NULL;
  new->first_page->offset = 0;
  dirpage = new->first_page->page;

  /* get dir to put entry into */
  rc = nasd_edrfs_server_dir_hash_lookup(in_directory, 1, &cd, &dh_attr);
  if (rc) {
    if (rc == NASD_EDRFS_NOT_DIR)
      nasd_edrfs_release_attr(dh_attr);
    *nasd_status = rc;
    goto done_mkdir;
  }

  NASD_ASSERT(cd);

  new->drive = cd->drive;
  new->di = cd->drive->di;
  new->isroot = 0;

  NASD_EDRFS_LOCK_DIR(cd,1);

  if(!access_check(&cd->attr->edrfsattr, &in_credential, NASD_EDRFS_W_OK)){
    rc = NASD_AUTHCHECK_FAILED;
    goto done_mkdir2;
  }

  NASD_EDRFS_UNLOCK_DIRCACHE();
  lk = 0;

  /* @@@ init cookie */
  attr = &crpair->args.in_attribute;
  edrfsattr = &crpair->edrfsattr;
  new_edrfsattr = &crpair->new_edrfsattr;
  nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *)
                                     in_attribute.fs_specific,
                                     new_edrfsattr);

  crpair->args.in_fieldmask = NASD_ATTR_FS_SPECIFIC;
  edrfsattr->type = NASD_EDRFS_TYPE_DIR;
  edrfsattr->mode = NASD_EDRFS_S_IRWXU |
                    NASD_EDRFS_S_IFDIR;
  edrfsattr->nlink = 2;
  edrfsattr->uid = in_credential.uid;
  edrfsattr->gid = in_credential.gid;
  edrfsattr->clean = NASD_EDRFS_CLEAN;

  rc = nasd_create_fieldmask(in_fieldmask, &crpair->args.in_fieldmask,
                             in_attribute, attr, new_edrfsattr,
                             edrfsattr, NASD_EDRFS_TYPE_DIR);
  if (rc) {
    *nasd_status = rc;
    goto done_mkdir2;
  }
  crpair->args.in_fieldmask |= NASD_ATTR_AV;
  crpair->args.in_attribute.av = NASD_EDRFS_INITIAL_AV;
  nasd_edrfs_attributes_t_marshall(edrfsattr,
                                   (nasd_otw_base_t *) attr->fs_specific);
  crpair->otw_buf.args_otw = crpair->args_otw;
  crpair->otw_buf.res_otw = crpair->res_otw;
  rc = nasd_edrfs_do_create(cd->drive, &crpair->args, &crpair->res,
                          &crpair->otw_buf);
  if (rc) {
    *nasd_status = NASD_FAIL;
    goto done_mkdir2;
  }

  bcopy((char *)&crpair->res.out_attribute,
    (char *)&new->attr->attr_res.out_attribute,
    sizeof(nasd_attribute_t));
  new->attr->attr_res.nasd_status = NASD_SUCCESS;
  new->nid = crpair->res.out_identifier;

  /* the new object exists, turn it into a directory
     by writing out a directory entry (which contains "." and ".."). */

  /* format the page */
  nasd_edrfs_dir_init_page(dirpage);
  nasd_edrfs_dir_add_entry(dirpage, ".", crpair->res.out_identifier,
                           NASD_EDRFS_DIRD_TYPE_DIR);
  nasd_edrfs_dir_add_entry(dirpage, "..", cd->nid,
                           NASD_EDRFS_DIRD_TYPE_DIR);

  /* hash . */
  dde_dot = nasd_edrfs_name_add(new, new->first_page, 0);
  if (dde_dot == NULL) { NASD_PANIC(); }
  NASD_ASSERT(dde_dot->attr == NULL);
  dde_dot->attr = new->attr;
  dde_dot->attr->refcnt++;

  /* hash .. */
  dde_dot_dot = nasd_edrfs_name_add(new, new->first_page, 1);
  if (dde_dot_dot == NULL) { NASD_PANIC(); }
  NASD_ASSERT(dde_dot_dot->attr == NULL);
  dde_dot_dot->attr = cd->attr;
  dde_dot_dot->attr->refcnt++;

  /* write the directory */
  new->first_page->dirty = 1;
  new->ndirty = 1;
  rc = nasd_edrfs_server_dir_write_dirty(new);
  if (rc) {
    *nasd_status = NASD_FAIL;
    goto done_mkdir2;
  }

  NASD_EDRFS_UNLOCK_DIR(cd);
  NASD_EDRFS_LOCK_DIRCACHE();
  lk = 1;
  NASD_EDRFS_LOCK_DIR(cd,1);

  /* have to re-lookup here because someone
     else might have created it in the meantime. */
  de = nasd_edrfs_name_lookup(cd, (char *)in_dirpath);
  if (de) {
    rc = nasd_edrfs_do_remove(cd->drive, crpair->res.out_identifier);
    if (rc) { NASD_PANIC(); }
    *nasd_status = NASD_EDRFS_ALREADY_EXISTS;
    goto done_mkdir2;
  }

  /* now the directory exists, so we can add it to the directory. */
  rc = nasd_edrfs_server_dir_add_entry(cd, (char *)in_dirpath,
                                       crpair->res.out_identifier,
                                       NASD_EDRFS_DIRD_TYPE_DIR, &de);
  if (rc) {
    rc = nasd_edrfs_do_remove(cd->drive, crpair->res.out_identifier);
    if (rc) { NASD_PANIC(); }
    *nasd_status = NASD_FAIL;
    goto done_mkdir2;
  }
  de->attr = new->attr;
  de->attr->refcnt++;
  NASD_ASSERT(de->attr->refcnt == 3);
  dir_valid = 1;

  /* add it to the directory hash */
  NASD_EDRFS_LOCK_DIR(new,1);
  nasd_edrfs_server_dir_hash_ins(new);
  NASD_EDRFS_UNLOCK_DIR(new);

  dir_valid = 2;

  /* @@@ generate cookie */
  bzero((char *)out_cookie, sizeof(nasd_cookie_t));
  out_identifier->nasd_identifier = crpair->res.out_identifier;
  out_identifier->disk_identifier = cd->drive->di;
  out_identifier->partnum = cd->drive->partnum;

  *out_attribute = new->attr->attr_res.out_attribute;
  *nasd_status = NASD_SUCCESS;

done_mkdir2:
  NASD_EDRFS_UNLOCK_DIR(cd);

done_mkdir:
  if (dir_valid == 2) { nasd_edrfs_server_dir_release(new); }

  if (dir_valid == 1) {
    rc = nasd_edrfs_server_dir_remove_entry(cd, de);
    if (rc) { NASD_PANIC(); }
    nasd_edrfs_dealloc_coredir(new, 1);
  } else if (dir_valid == 0) {
    if (new) { nasd_edrfs_dealloc_coredir(new, 1); }
  }

  if (cd) { nasd_edrfs_server_dir_release_post_attribute(cd, *out_identifier,
                                                         post_attribute); }

  if (lk) {
    NASD_EDRFS_UNLOCK_DIRCACHE();
    lk = 0;
  }

really_done_mkdir:

  if (crpair) { nasd_edrfs_free_createpair(crpair); }
  NASD_ASSERT(lk == 0);
}

void
nasd_edrfs_real_rmdir(
  nasd_cookie_t             in_cookie,
  nasd_edrfs_identifier_t   in_directory,
  nasd_edrfs_credential_t   in_credential,
  char                     *in_dirpath,
  nasd_status_t            *nasd_status)
{
  nasd_edrfs_core_dir_t *cd, *rm_dir;
  nasd_edrfs_identifier_t rm_dir_id;
  nasd_edrfs_diskdirent_t *de;
  nasd_identifier_t nasdid;
  nasd_edrfs_attr_t *dh_attr;
  nasd_edrfs_drive_t *drive;
  nasd_status_t rc;

  NASD_EDRFS_LOCK_DIRCACHE();

  rc = nasd_edrfs_server_dir_hash_lookup(in_directory, 1, &cd, &dh_attr);
  if (rc) {
    if (rc == NASD_EDRFS_NOT_DIR) { nasd_edrfs_release_attr(dh_attr); }
    *nasd_status = rc;
    NASD_EDRFS_UNLOCK_DIRCACHE();
    return;
  }
  NASD_ASSERT(cd);
  NASD_ASSERT(dh_attr == NULL);

  NASD_EDRFS_LOCK_DIR(cd, 1);

  de = nasd_edrfs_name_lookup(cd, (char *)in_dirpath);

  if (de == NULL) {
    *nasd_status = NASD_EDRFS_BAD_NAME;
    NASD_EDRFS_UNLOCK_DIR(cd);
    nasd_edrfs_server_dir_release(cd);
    NASD_EDRFS_UNLOCK_DIRCACHE();
    return;
  }

  if (!NASD_EDRFS_DIRD_IS_DIR(de->data)) {
    *nasd_status = NASD_EDRFS_NOT_DIR;
    NASD_EDRFS_UNLOCK_DIR(cd);
    nasd_edrfs_server_dir_release(cd);
    NASD_EDRFS_UNLOCK_DIRCACHE();
    return;
  }

  if(!access_check(&cd->attr->edrfsattr, &in_credential, NASD_EDRFS_W_OK)){
    *nasd_status = NASD_AUTHCHECK_FAILED;
    NASD_EDRFS_UNLOCK_DIR(cd);
    nasd_edrfs_server_dir_release(cd);
    NASD_EDRFS_UNLOCK_DIRCACHE();
    return;
  }

  nasdid = de->data->nasdid;

  NASD_EDRFS_UNLOCK_DIR(cd);

  /* load in child dir so we can flag it for deletion
     (we could probably do something more clever).    */
  rm_dir_id.nasd_identifier = de->data->nasdid;
  rm_dir_id.disk_identifier = cd->drive->di;
  rc = nasd_edrfs_server_dir_hash_lookup(rm_dir_id, 1, &rm_dir, &dh_attr);
  if (rc) {
    if (rc == NASD_EDRFS_NOT_DIR) {
      /* should never happen */
      nasd_edrfs_release_attr(dh_attr);
      NASD_PANIC();
    }
    nasd_edrfs_server_dir_release(cd);
    *nasd_status = rc;
    NASD_PANIC();
    return;
  }
  NASD_ASSERT(rm_dir);
  NASD_ASSERT(dh_attr == NULL);

  if (rm_dir->nentries > 2) {
    nasd_edrfs_server_dir_release(cd);
    nasd_edrfs_server_dir_release(rm_dir);
    rm_dir = NULL;
    NASD_EDRFS_UNLOCK_DIRCACHE();
    *nasd_status = NASD_EDRFS_DIR_NOT_EMPTY;
    return;
  }

  NASD_EDRFS_LOCK_DIR(rm_dir,1);

  rm_dir->deleted = 1;

  NASD_EDRFS_UNLOCK_DIR(rm_dir);

  NASD_EDRFS_LOCK_DIR(cd,1);
  /* remove entry from parent dir */
  rc = nasd_edrfs_server_dir_remove_entry(cd, de);
  if (rc) { NASD_PANIC(); }
  NASD_EDRFS_UNLOCK_DIR(cd);

  while(rm_dir->refcnt > 1) { NASD_WAIT_COND(rm_dir->cond,
                                             nasd_edrfs_dircache_lock); }

  /*
   * We no longer need the dir lock on the dir to remove,
   * because no one else is allowed to be touching this
   * dir, since we set deleted above, and gave everyone
   * their chance to get out (and they did). It also no
   * longer has an entry in the parent dir. Release the
   * (dirty) parent dir for rewrite, then we can get
   * rid of locks and remove the object from the drive.
   */

  drive = cd->drive;

  nasd_edrfs_server_dir_release(cd);
  NASD_EDRFS_UNLOCK_DIRCACHE();

  /* remove entry from drive */
  rc = nasd_edrfs_do_remove(drive, nasdid);
  if (rc) { NASD_PANIC(); }

  NASD_EDRFS_LOCK_DIRCACHE();
  nasd_edrfs_server_dir_release(rm_dir);
  NASD_EDRFS_UNLOCK_DIRCACHE();

  *nasd_status = NASD_SUCCESS;
}

void
nasd_edrfs_real_newcookie(
  nasd_edrfs_identifier_t   in_identifier,
  nasd_edrfs_credential_t   in_credential,
  nasd_cookie_t            *out_cookie,
  nasd_status_t            *nasd_status)
{
  /* @@@ implement! */
  bzero((char *)out_cookie, sizeof(nasd_cookie_t));
  *nasd_status = NASD_SUCCESS;
}

void
nasd_edrfs_real_rename(
  nasd_cookie_t                 in_cookie,
  nasd_edrfs_identifier_t       in_from_directory,
  nasd_edrfs_credential_t       in_credential,
  char                         *in_from_path,
  nasd_edrfs_identifier_t       in_to_directory,
  char                         *in_to_path,
  nasd_edrfs_post_attribute_t  *post_from_attribute,
  nasd_edrfs_post_attribute_t  *post_to_attribute,
  nasd_status_t                *nasd_status)
{
  int to_len, to_ents_required, from_ents_required, n, ntype, otype;
  nasd_edrfs_diskdirent_t *old_de, *new_de, *ditch_de;
  nasd_edrfs_core_dir_t *from_cd, *to_cd, *new_cd;
  nasd_identifier_t del_nasdid;
  nasd_edrfs_drive_t *del_drive;
  nasd_edrfs_attr_t *dh_attr;
  nasd_status_t rc;

#if DEBUG_RENAME_DETAIL > 0
  nasd_printf("rename 0x%" NASD_ID_FMT ":%s to 0x%" NASD_ID_FMT ":%s\n",
    in_from_directory.nasd_identifier, in_from_path,
    in_to_directory.nasd_identifier, in_to_path);
#endif /* DEBUG_RENAME_DETAIL > 0 */

  if (post_from_attribute) { post_from_attribute->valid =
                               NASD_EDRFS_ATTRIBUTE_INVALID; }
  if (post_to_attribute)   { post_to_attribute->valid   =
                               NASD_EDRFS_ATTRIBUTE_INVALID; }

  from_cd = NULL;
  to_cd = NULL;
  old_de = NULL;
  new_de = NULL;
  new_cd = NULL;
  del_drive = NULL;
  del_nasdid = NASDID_NULL;
  ditch_de = NULL;
  ntype = NASD_EDRFS_DIRD_TYPE_BOGUS;

  if ((strcmp(in_to_path, ".") == 0) || (strcmp(in_from_path, ".") == 0)) {
    *nasd_status = NASD_EDRFS_BAD_NAME;
    goto really_done_rename;
  }

  to_len = strlen(in_to_path);
  if (to_len > NASD_EDRFS_MAX_NAME_LEN) {
    *nasd_status = NASD_EDRFS_BAD_NAME;
    goto really_done_rename;
  }
  if (to_len < 1) {
    *nasd_status = NASD_EDRFS_BAD_NAME;
    goto really_done_rename;
  }

  to_ents_required = nasd_edrfs_dir_name_len(in_to_path);

  NASD_EDRFS_LOCK_DIRCACHE();
  rc = nasd_edrfs_server_dir_hash_lookup(in_from_directory, 1,
                                         &from_cd, &dh_attr);
  if (rc) {
    if (rc == NASD_EDRFS_NOT_DIR) { nasd_edrfs_release_attr(dh_attr); }
    *nasd_status = rc;
    goto done_rename;
  }

  if (   (in_from_directory.nasd_identifier == in_to_directory.nasd_identifier)
      && (in_from_directory.disk_identifier == in_to_directory.disk_identifier)
      && (in_from_directory.partnum         == in_to_directory.partnum)) {
    to_cd = from_cd;
    to_cd->refcnt++; /* can do this because we hold dircache lock */
  } else {
    rc = nasd_edrfs_server_dir_hash_lookup(in_to_directory, 1,
                                           &to_cd, &dh_attr);
    if (rc) {
      if (rc == NASD_EDRFS_NOT_DIR) { nasd_edrfs_release_attr(dh_attr); }
      *nasd_status = rc;
      goto done_rename;
    }
  }

  NASD_ASSERT(from_cd);
  NASD_ASSERT(to_cd);

  NASD_EDRFS_LOCK_DIR(from_cd,1);

  if(!access_check(&from_cd->attr->edrfsattr, &in_credential,
                   NASD_EDRFS_R_OK)){
    rc = NASD_AUTHCHECK_FAILED;
    goto done_rename2;;
  }

  if (to_cd != from_cd) {
    NASD_EDRFS_LOCK_DIR(to_cd,1);
    if(!access_check(&to_cd->attr->edrfsattr, &in_credential,
                     NASD_EDRFS_W_OK)){
      rc = NASD_AUTHCHECK_FAILED;
      goto done_rename2;;
    }
  }

  old_de = nasd_edrfs_name_lookup(from_cd, (char *)in_from_path);
  if (old_de == NULL) {
    *nasd_status = NASD_EDRFS_BAD_NAME;
    goto done_rename2;
  }

  new_de = nasd_edrfs_name_lookup(to_cd, (char *)in_to_path);
  if (new_de) {
    /* Check to make sure they are not the same */
    if ((from_cd == to_cd) && (old_de->data->nasdid == new_de->data->nasdid)) {
      *nasd_status = NASD_SUCCESS;
      goto done_rename2; 
    }
    
    ntype = NASD_EDRFS_DIRD_TYPEOF(new_de->data);
    otype = NASD_EDRFS_DIRD_TYPEOF(old_de->data);
    if (( (ntype == NASD_EDRFS_DIRD_TYPE_DIR) ||
          (otype == NASD_EDRFS_DIRD_TYPE_DIR)    ) && (ntype != otype)) {
      /* one is a directory, the other is not. client will see EEXIST */
      *nasd_status = NASD_EDRFS_ALREADY_EXISTS;
      goto done_rename2;
    }

    if (ntype == NASD_EDRFS_DIRD_TYPE_DIR) {
      /* both are directories. Is the 'target' directory empty? */

      nasd_edrfs_identifier_t tmp; /* Make a directory id for the to_path */
      tmp.nasd_identifier = new_de->data->nasdid;
      tmp.disk_identifier = in_to_directory.disk_identifier;
      tmp.partnum         = in_to_directory.partnum;

      rc = nasd_edrfs_server_dir_hash_lookup(tmp, 1, &new_cd, &dh_attr);
      if (rc) {
        if (rc == NASD_EDRFS_NOT_DIR) { NASD_PANIC(); }
        new_cd = NULL;
        *nasd_status = rc;
        goto done_rename2;
      }

      NASD_EDRFS_LOCK_DIR(new_cd,1);

      if (new_cd->nentries > 2) { /* dir not empty */
        *nasd_status = NASD_EDRFS_DIR_NOT_EMPTY;
        goto done_rename2;
      }
    }

    ditch_de = new_de;
  }

  /* old_de   is the dirent for the source of the rename
     new_de   is the dirent for the target (if null, the target doesn't exist)
     from_cd  is the directory old_de is in
     to_cd    is the directory new_de is in
     ditch_de is new_de
     new_cd   is dir that is ditch_de (subdir of to_cd)   */
  
  from_ents_required = NASD_EDRFS_DIRD_EXLEN(old_de->data);

  if (ditch_de && (to_cd == from_cd) &&
      (from_ents_required == to_ents_required)) {
    /* Src name pages = dest name pages
       Dest already exists (ditch_de), and may be a directory (new_cd) */
    
#if DEBUG_RENAME_DETAIL > 0
    nasd_printf(  "rename old_de     %p name \"%s\" id 0x%" NASD_ID_FMT "\n",
                  old_de, old_de->name, old_de->data->nasdid);
    if (ditch_de) {
      nasd_printf("rename ditch_de %p name \"%s\" id 0x%" NASD_ID_FMT "\n",
                  ditch_de, ditch_de->name, ditch_de->data->nasdid);
    }
#endif /* DEBUG_RENAME_DETAIL > 0 */
    
    /* get rid of ent for original */
    rc = nasd_edrfs_name_remove(from_cd, old_de, 1);
    if (rc) { NASD_PANIC(); }
    
    /* set up target ent for new name */
    new_de = old_de;
    nasd_edrfs_server_dir_dirty_page(to_cd, new_de->page);

    nasd_edrfs_dir_rename_entry(new_de->page->page, new_de->slot, in_to_path);
    strcpy(new_de->name, in_to_path);

    rc = nasd_edrfs_name_hash_ins(to_cd, new_de);
    if (rc) { NASD_PANIC(); }

    del_nasdid = ditch_de->data->nasdid;
    if (new_cd) { /* old target was empty directory, new_cd is its core */
      new_cd->deleted = 1;
      NASD_EDRFS_UNLOCK_DIR(new_cd);
      while (new_cd->refcnt > 1) { NASD_WAIT_COND(new_cd->cond,
                                                  nasd_edrfs_dircache_lock); }
      del_drive = new_cd->drive;
    } else { /* old target was file */
      del_drive = to_cd->drive;
    }
    
    /* get rid of ent for old target */
    rc = nasd_edrfs_server_dir_remove_entry(to_cd, ditch_de);
    if (rc) { NASD_PANIC(); }
    
  } else if ((to_cd == from_cd) && (from_ents_required == to_ents_required)) {
    /* this works because they're in the same directory, and we don't
       need to allocate more slots to hold the name.
       Remember, in this case, the target does not yet exist.         */

    rc = nasd_edrfs_name_remove(from_cd, old_de, 1);
    if (rc) { NASD_PANIC(); }
    new_de = old_de;
    nasd_edrfs_server_dir_dirty_page(to_cd, new_de->page);

    nasd_edrfs_dir_rename_entry(new_de->page->page, new_de->slot, in_to_path);
    strcpy(new_de->name, in_to_path);

    rc = nasd_edrfs_name_hash_ins(to_cd, new_de);
    if (rc) { NASD_PANIC(); }
  } else {
    /* target directory is not source directory, or target pages
       different from source pages. do it the hard way.          */

#if DEBUG_RENAME_DETAIL > 0
    nasd_printf("rename between dirs\n");
    nasd_printf("rename old_de     %p name \"%s\" id 0x%"
                NASD_ID_FMT "\n",
                old_de, old_de->name, old_de->data->nasdid);
    if (ditch_de) {
      nasd_printf("rename ditch_de %p name \"%s\" id 0x%"
                  NASD_ID_FMT "\n",
                  ditch_de, ditch_de->name, ditch_de->data->nasdid);
    }
#endif /* DEBUG_RENAME_DETAIL > 0 */

    /* if we were actually fault-tolerant, we'd have a better way than
       this. Here we first blow away the target if it exists, then
       create the target ent, then blow away the source.               */

    if (ditch_de) {
      del_nasdid = ditch_de->data->nasdid;
      if (new_cd) {
        /* old target was empty directory, new_cd is its core */
        new_cd->deleted = 1;
        NASD_EDRFS_UNLOCK_DIR(new_cd);
        while(new_cd->refcnt > 1) { NASD_WAIT_COND(new_cd->cond,
                                                   nasd_edrfs_dircache_lock); }
        del_drive = new_cd->drive;
      } else {
        /* old target was file */
        del_drive = to_cd->drive;
      }
      
      /* get rid of ent for old target */
      rc = nasd_edrfs_server_dir_remove_entry(to_cd, ditch_de);
      if (rc) { NASD_PANIC(); }
    }

    /* create target ent from scratch */
    rc = nasd_edrfs_server_dir_add_entry(to_cd, (char *)in_to_path,
                                         old_de->data->nasdid,
                                         NASD_EDRFS_DIRD_TYPEOF(old_de->data),
                                         &new_de);
    if (rc) {
      *nasd_status = NASD_FAIL;
      goto done_rename2;
    }
    
#if DEBUG_RENAME_DETAIL > 0
    nasd_printf("remove entry for \"%s\"\n", old_de->name);
#endif /* DEBUG_RENAME_DETAIL > 0 */
    /* get rid of ent for original */
    rc = nasd_edrfs_server_dir_remove_entry(from_cd, old_de);
    if (rc) { NASD_PANIC(); }
  }
  
  *nasd_status = NASD_SUCCESS;

 done_rename2:
  if (new_cd) {
    NASD_EDRFS_UNLOCK_DIR(new_cd);
    nasd_edrfs_server_dir_release(new_cd);
    new_cd = NULL;
  }
  
  NASD_EDRFS_UNLOCK_DIR(from_cd);
  if (to_cd && (to_cd != from_cd)) { NASD_EDRFS_UNLOCK_DIR(to_cd); }
  
  if (del_drive) {
    rc = nasd_edrfs_do_remove(del_drive, del_nasdid);
    if (rc) { NASD_PANIC(); }
  }

 done_rename:
  if (from_cd) {
    nasd_edrfs_server_dir_release_post_attribute(from_cd, in_to_directory,
                                                 post_to_attribute);
  }
  if (to_cd) {
    nasd_edrfs_server_dir_release_post_attribute(to_cd, in_to_directory,
                                                 post_to_attribute);
  }
  NASD_EDRFS_UNLOCK_DIRCACHE();

 really_done_rename:
  return;
}

void
nasd_edrfs_real_getstats(
  nasd_edrfs_opstats_t       *opstats,
  nasd_edrfs_cachestats_t    *cachestats,
  nasd_edrfs_rpc_opdepths_t  *opdepths,
  nasd_status_t              *nasd_status)
{
  if (cachestats) { *cachestats = nasd_edrfs_cache_stats;  }
  if (opstats)    { *opstats    = nasd_edrfs_opstats;      }
  if (opdepths)   { *opdepths   = nasd_edrfs_rpc_opdepths; }
  *nasd_status = NASD_SUCCESS;
}

void
nasd_edrfs_real_resetstats(
  nasd_status_t  *nasd_status)
{
  bzero((char *)&nasd_edrfs_cache_stats, sizeof(nasd_edrfs_cache_stats));
  bzero((char *)&nasd_edrfs_opstats, sizeof(nasd_edrfs_opstats));
  bzero((char *)&nasd_edrfs_rpc_opdepths, sizeof(nasd_edrfs_rpc_opdepths));

#define DORESET(_x_) \
  nasd_edrfs_opstats._x_.num_ops = 0; \
  nasd_edrfs_opstats._x_.op_nsecs = 0; \
  nasd_edrfs_opstats._x_.min_nsecs = 0; \
  nasd_edrfs_opstats._x_.max_nsecs = 0; \
  nasd_edrfs_opstats._x_.invalid = 0

  DORESET(null);
  DORESET(mount);
  DORESET(fsstat);
  DORESET(fsinfo);
  DORESET(lookup);
  DORESET(readdir);
  DORESET(access);
  DORESET(setattr);
  DORESET(create);
  DORESET(remove);
  DORESET(mkdir);
  DORESET(rmdir);
  DORESET(newcookie);
  DORESET(rename);
  DORESET(symlink);
  DORESET(readlink);
  DORESET(getstats);
  DORESET(resetstats);

#undef DORESET

  *nasd_status = NASD_SUCCESS;
}

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