/*
 * nasd_edrfs_common.c
 *
 * Directory operations for EDRFS
 *
 * Authors: Nat Lanza, Ted Wong
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1996,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_marshall.h>
#include <nasd/nasd_general.h>
#include <nasd/nasd_edrfs_internal.h>
#include <nasd/nasd_edrfs_dir.h>

void nasd_edrfs_dir_extract_name(nasd_edrfs_dirpage_t  *page,
				 int                    slot,
				 char                  *name) {
  int i, num_slots;
  char *name_tmp = name;
  
  num_slots = NASD_EDRFS_DIRD_EXLEN(&(page->data_slots[slot]));
  for (i = 0; i < num_slots; i++) {
    memcpy(name_tmp, page->data_slots[slot+i].name,
	   NASD_EDRFS_DIRDATA_EMBEDDED_NSZ - 1);
    name_tmp += (NASD_EDRFS_DIRDATA_EMBEDDED_NSZ - 1);
  }

  *name_tmp = '\0';
}


/* how many slots will a name take up? */
int nasd_edrfs_dir_name_len(char *name) {
  int raw_len, num_slots;

  for (raw_len = strlen(name), num_slots = 0; raw_len > 0;
       raw_len -= (NASD_EDRFS_DIRDATA_EMBEDDED_NSZ-1), num_slots++);
  return num_slots;
}


/* parse a raw disk block into a nasd_edrfs_dirpage_t struct */
void nasd_edrfs_dir_parse_page(void                  *raw_page,
			       nasd_edrfs_dirpage_t  *parsed_page) {
  int i;
  char *cur_ptr = (char *) raw_page;

  nasd_edrfs_dirheader_t_unmarshall((void *) cur_ptr,
				    &parsed_page->header);

  cur_ptr += NASD_EDRFS_DIRSLOT_SIZE;

  for (i = 0; i < NASD_EDRFS_DIRPAGE_NUM_SLOTS; i++) {
    /* is this slot used? */
    if (NASD_EDRFS_DIRMAP_TEST_SLOT(parsed_page->header.usemap, i)) {
      nasd_edrfs_dirdata_t_unmarshall((void *) cur_ptr,
				      &(parsed_page->data_slots[i]));	
    } else {
      /* wipe it. */
      /* XXX -- probably overkill, but will make debugging easier. */
      memset((void *) cur_ptr, 0, NASD_EDRFS_DIRSLOT_SIZE);
    }
    
    /* move to the next slot */
    cur_ptr += NASD_EDRFS_DIRSLOT_SIZE;
  }
}


/* parse a raw disk block into a nasd_edrfs_dirpage_t struct */
void nasd_edrfs_dir_parse_page_parts(void                  *raw_page1,
				     void                  *raw_page2,
				     nasd_edrfs_dirpage_t  *parsed_page) {
  int i;
  char *cur_ptr = (char *) raw_page1;

  nasd_edrfs_dirheader_t_unmarshall((void *) cur_ptr,
				    &parsed_page->header);

  cur_ptr += NASD_EDRFS_DIRSLOT_SIZE;

  for (i = 0; i < 63; i++) {
    if (NASD_EDRFS_DIRMAP_TEST_SLOT(parsed_page->header.usemap, i)) {
      nasd_edrfs_dirdata_t_unmarshall((void *) cur_ptr,
				      &(parsed_page->data_slots[i]));	
    }
    cur_ptr += NASD_EDRFS_DIRSLOT_SIZE;
  }

  cur_ptr = (char *) raw_page2;
  for (i = 63; i < NASD_EDRFS_DIRPAGE_NUM_SLOTS; i++) {
    if (NASD_EDRFS_DIRMAP_TEST_SLOT(parsed_page->header.usemap, i)) {
      nasd_edrfs_dirdata_t_unmarshall((void *) cur_ptr,
				      &(parsed_page->data_slots[i]));	
    }
    cur_ptr += NASD_EDRFS_DIRSLOT_SIZE;
  }
}


void nasd_edrfs_dir_parse_page_inplace(nasd_edrfs_dirpage_t *page) {
  int i;
  char *cur_ptr = (char *) page;
  nasd_edrfs_dirdata_t   dirdata_tmp;
  nasd_edrfs_dirheader_t dirheader_tmp;

  NASD_ASSERT(sizeof(nasd_edrfs_dirpage_t)   == NASD_EDRFS_DIRPAGE_SIZE);
  NASD_ASSERT(sizeof(nasd_edrfs_dirheader_t) == NASD_EDRFS_DIRSLOT_SIZE);
  NASD_ASSERT(sizeof(nasd_edrfs_dirdata_t)   == NASD_EDRFS_DIRSLOT_SIZE);

  nasd_edrfs_dirheader_t_unmarshall((void *) cur_ptr, &dirheader_tmp);
  memcpy(&page->header, &dirheader_tmp, sizeof(nasd_edrfs_dirheader_t));

  cur_ptr += NASD_EDRFS_DIRSLOT_SIZE;

  for (i = 0; i < NASD_EDRFS_DIRPAGE_NUM_SLOTS; i++) {
    /* is this slot used? */
    if (NASD_EDRFS_DIRMAP_TEST_SLOT(page->header.usemap, i)) {
      nasd_edrfs_dirdata_t_unmarshall((void *) cur_ptr, &dirdata_tmp);
      memcpy(&(page->data_slots[i]), &dirdata_tmp,
	     sizeof(nasd_edrfs_dirdata_t));
    }    
    /* move to the next slot */
    cur_ptr += NASD_EDRFS_DIRSLOT_SIZE;
  }

}

/* marshall a nasd_edrfs_dirpage_t struct into a raw disk block ready to
   be written out */
void nasd_edrfs_dir_marshall_page(nasd_edrfs_dirpage_t  *parsed_page,
				  void                  *raw_page) {
  int i;
  char *cur_ptr = (char *) raw_page;

  nasd_edrfs_dirheader_t_marshall(&parsed_page->header,
				  (void *) cur_ptr);

  cur_ptr += NASD_EDRFS_DIRSLOT_SIZE;

  for (i = 0; i < NASD_EDRFS_DIRPAGE_NUM_SLOTS; i++) {
    /* is this slot used? */
    if (NASD_EDRFS_DIRMAP_TEST_SLOT(parsed_page->header.usemap, i)) {
      nasd_edrfs_dirdata_t_marshall(&(parsed_page->data_slots[i]),
				    (void *) cur_ptr);
    } else {
      /* wipe it. */
      /* XXX -- probably overkill, but will make debugging easier. */
      memset((void *) cur_ptr, 0, NASD_EDRFS_DIRSLOT_SIZE);
    }

    /* move to the next slot */
    cur_ptr += NASD_EDRFS_DIRSLOT_SIZE;
  }
}

/* look up an entry by name and return its index in the page.
   returns -1 if the entry isn't found. */
int nasd_edrfs_dir_lookup_entry(nasd_edrfs_dirpage_t  *page,
				char                  *name) {
  int i = 0, j = 0, nslots = 0, possible = 0;
  char ddname[NASD_EDRFS_MAX_NAME_LEN];
  nasd_edrfs_dirdata_t *dd;

  for (i = 0; i < NASD_EDRFS_DIRPAGE_NUM_SLOTS; i++){
    dd = &(page->data_slots[i]);

    /* empty slot, continue */
    if (!NASD_EDRFS_DIRMAP_TEST_SLOT(page->header.usemap, i)) { continue; }

    /* first section of the name isn't equal, continue */
    if (strncmp(name, dd->name, NASD_EDRFS_DIRDATA_EMBEDDED_NSZ-1)) { continue; }

    /* at this point, the first name section is equal.
       this is a likely candidate. */
    
    nslots = NASD_EDRFS_DIRD_EXLEN(dd);

    if (nslots > 1) {
      /* name extraction is expensive, so we want to make sure
	 we don't do it unnecessarily. */
      
      nasd_edrfs_dir_extract_name(page, i, ddname);      
      if (strncmp(name, ddname, NASD_EDRFS_MAX_NAME_LEN)) { continue; }
    }
    
    /* the complete name matches. whee. */
    return i;
  }

  return -1;
}


/* returns the index of the next valid entry in a dirpage. uses
   'marker' to keep track of where it is. returns -1 when there are
   no more valid entries in a page. */
int nasd_edrfs_dir_readdir(nasd_edrfs_dirpage_t  *page,
			   int                   *marker) {
  int i;
  
  for (i = *marker; i < NASD_EDRFS_DIRPAGE_NUM_SLOTS; i++) {
    if (!NASD_EDRFS_DIRMAP_TEST_SLOT(page->header.usemap, i)) { continue; }
    *marker = i;
    return i;
  }

  *marker = -1;
  return -1;
}


/* convert the specified directory slot to a dirent structure.
   fails if the slot is empty. */
nasd_status_t nasd_edrfs_dir_slot_to_dirent(nasd_edrfs_dirpage_t  *page,
					    int                    index,
					    nasd_edrfs_dirent_t   *dirent) {
  int nslots;
  nasd_edrfs_dirdata_t *dd;

  if (!NASD_EDRFS_DIRMAP_TEST_SLOT(page->header.usemap, index)) {
    /* it's not a valid entry. bye! */
    return NASD_FAIL;
  }

  dd = &(page->data_slots[index]);

  dirent->nd_ino    = dd->nasdid;
  dirent->nd_namlen = dd->name_len;
  nasd_edrfs_dir_extract_name(page, index, (char *) dirent->nd_name);

  return NASD_SUCCESS;
}


/* change the name of an entry. the new name _must_ take up no more
   slots than the old. */
nasd_status_t nasd_edrfs_dir_rename_entry(nasd_edrfs_dirpage_t  *page,
					  int                    slot,
					  char                  *newname) {
  int i, nslots, oslots, len;
  char *name_tmp;
  nasd_edrfs_dirdata_t *dd = &(page->data_slots[slot]);

  nslots = nasd_edrfs_dir_name_len(newname);
  oslots = NASD_EDRFS_DIRD_EXLEN(dd);

  if (oslots < nslots) { return NASD_FAIL; } /* no way */

  len = strlen(newname);

  dd->name_len = len;
  NASD_EDRFS_DIRD_SET_EXLEN(dd, nslots);
  name_tmp = newname;
  nslots += slot;
  oslots += slot;

  for (i = slot; i < nslots; i++) {
    dd = &(page->data_slots[i]);
    
    if (len > (NASD_EDRFS_DIRDATA_EMBEDDED_NSZ - 1)) {
      strncpy(dd->name, name_tmp, (NASD_EDRFS_DIRDATA_EMBEDDED_NSZ - 1));
      name_tmp += (NASD_EDRFS_DIRDATA_EMBEDDED_NSZ - 1);
      len      -= (NASD_EDRFS_DIRDATA_EMBEDDED_NSZ - 1);
      dd->name[NASD_EDRFS_DIRDATA_EMBEDDED_NSZ - 1] = '\0';
      NASD_EDRFS_DIRD_SET_SEGLEN(dd, (NASD_EDRFS_DIRDATA_EMBEDDED_NSZ - 1));
    } else {
      strncpy(dd->name, name_tmp, len);
      name_tmp += len;
      dd->name[len] = '\0';
      NASD_EDRFS_DIRD_SET_SEGLEN(dd, len);
    }
  }

  /* if the new name is shorter, free up some space */
  i = nslots;
  while (i++ < oslots) {
    dd = &(page->data_slots[i]);
    NASD_EDRFS_DIRMAP_CLEAR_SLOT(page->header.usemap, i);
    memset(dd, 0, sizeof(nasd_edrfs_dirdata_t)); /* XXX overkill */
  }

  return NASD_SUCCESS;
}


/* add an entry to the given dirpage, returning the slot the entry was
   added to. fails and returns -1 if there's no room. */
int nasd_edrfs_dir_add_entry(nasd_edrfs_dirpage_t  *page,
			     char                  *name,
			     nasd_identifier_t      nasdid,
			     int                    type) {
  int i, j, nslots, space, name_left;
  nasd_edrfs_dirdata_t *dd;
  char *name_tmp;

  nslots = nasd_edrfs_dir_name_len(name);

  for (i = 0; i < NASD_EDRFS_DIRPAGE_NUM_SLOTS; i++) {
    /* skip filled slots */
    if (NASD_EDRFS_DIRMAP_TEST_SLOT(page->header.usemap, i)) { continue; }

    if (nslots == 1) {
      NASD_EDRFS_DIRMAP_SET_SLOT(page->header.usemap, i);
      page->header.freecnt--;

      dd = &(page->data_slots[i]);
      dd->nasdid = nasdid;
      dd->name_len = strlen(name);
      strncpy(dd->name, name, NASD_EDRFS_DIRDATA_EMBEDDED_NSZ);
      NASD_EDRFS_DIRD_SET_TYPE(dd, type);
      NASD_EDRFS_DIRD_SET_EXTYPE(dd, NASD_EDRFS_DIRD_EXTYPE_UNUSED);
      NASD_EDRFS_DIRD_SET_EXLEN(dd, nslots);
      NASD_EDRFS_DIRD_SET_SEGLEN(dd, dd->name_len);
      return i;
    }

    /* is there enough room on the page? */
    nslots = i + nslots - 1;
    if (nslots >= NASD_EDRFS_DIRPAGE_NUM_SLOTS) { return -1; }

    space = 1; j = i;

    while (space && (j <= nslots)) {
      j++;
      if (NASD_EDRFS_DIRMAP_TEST_SLOT(page->header.usemap, j)) { space = 0; }
    }

    if (space == 0) { i = j; continue; } /* skip the bad slots. */

    /* if we're here, we have an empty slot and enough room after it. */
    name_left = strlen(name);
    name_tmp = name;

    for (j = i; j <= nslots; j++) {
      NASD_EDRFS_DIRMAP_SET_SLOT(page->header.usemap, j);
      page->header.freecnt--;

      dd = &(page->data_slots[j]);
      dd->nasdid   = 0;
      dd->name_len = 0;
      dd->flags    = 0;
      NASD_EDRFS_DIRD_SET_TYPE(dd, NASD_EDRFS_DIRD_TYPE_EXNAM);
      NASD_EDRFS_DIRD_SET_EXTYPE(dd, NASD_EDRFS_DIRD_EXTYPE_UNUSED);
      NASD_EDRFS_DIRD_SET_EXLEN(dd, 0);

      if (name_left > (NASD_EDRFS_DIRDATA_EMBEDDED_NSZ-1)) {
	strncpy(dd->name, name_tmp, (NASD_EDRFS_DIRDATA_EMBEDDED_NSZ-1));
	name_tmp  += (NASD_EDRFS_DIRDATA_EMBEDDED_NSZ-1);
	name_left -= (NASD_EDRFS_DIRDATA_EMBEDDED_NSZ-1);
	dd->name[NASD_EDRFS_DIRDATA_EMBEDDED_NSZ-1] = '\0';
	NASD_EDRFS_DIRD_SET_SEGLEN(dd, (NASD_EDRFS_DIRDATA_EMBEDDED_NSZ-1));
      } else {
	strncpy(dd->name, name_tmp, name_left);
	NASD_EDRFS_DIRD_SET_SEGLEN(dd, name_left);
	name_tmp += name_left;
	*name_tmp = '\0';
      }
    }

    dd = &(page->data_slots[i]);
    dd->nasdid   = nasdid;
    dd->name_len = strlen(name);
    NASD_EDRFS_DIRD_SET_TYPE(dd, type);
    NASD_EDRFS_DIRD_SET_EXLEN(dd, nslots-i + 1);

    return i;
  }

  return -1;
}


/* remove an entry from the given page. fails if the entry doesn't exist. */
nasd_status_t
nasd_edrfs_dir_remove_entry(nasd_edrfs_dirpage_t  *page,
			    char                  *name) {
  int i, start, end;
  nasd_edrfs_dirdata_t *dd;

  start = nasd_edrfs_dir_lookup_entry(page, name);

  if (start >= 0) {
    dd = &(page->data_slots[start]);

    end = NASD_EDRFS_DIRD_EXLEN(dd) + start;

    for (i = start; i < end; i++) {
      NASD_EDRFS_DIRMAP_CLEAR_SLOT(page->header.usemap, i);
      page->header.freecnt++;
      /* XXX -- overkill? maybe. makes debugging easier, though. */
      memset(&(page->data_slots[i]), 0, NASD_EDRFS_DIRSLOT_SIZE);
    }
    
    return NASD_SUCCESS;
  }

  return NASD_FAIL;
}

/* compact a page to reduce fragmentation -- moves all entries to the
   start of the page. */
void nasd_edrfs_dir_compact_page(nasd_edrfs_dirpage_t *page) {
  int i, j, k, found, space_size, new_size;
  nasd_edrfs_dirdata_t *dd_empty, *dd_full;

  for (i = 0; i < NASD_EDRFS_DIRPAGE_NUM_SLOTS; i++) {
    /* skip this slot if it's used. we only care about empties at the start. */
    if (NASD_EDRFS_DIRMAP_TEST_SLOT(page->header.usemap, i)) { continue; }

    dd_empty = &(page->data_slots[i]);

    j = i;
    
    /* count up the empty slots */
    while(!NASD_EDRFS_DIRMAP_TEST_SLOT(page->header.usemap, j)
	  && (j < NASD_EDRFS_DIRPAGE_NUM_SLOTS)) { j++; }
    space_size = j - i;

    found = 0;
    j = NASD_EDRFS_DIRPAGE_NUM_SLOTS + 1;
    
    while (!found && (--j > i)) {
      /* skip empties */
      if (NASD_EDRFS_DIRMAP_TEST_SLOT(page->header.usemap, j) &&
	  !NASD_EDRFS_DIRD_IS_EXNAM(&(page->data_slots[j]))) {
	new_size = NASD_EDRFS_DIRD_EXLEN(&(page->data_slots[j]));

	if (new_size > space_size) { continue; }

	for (k = 0; k < new_size; k++) {
	  dd_empty = &(page->data_slots[i + k]);
	  dd_full  = &(page->data_slots[j + k]);

	  memcpy(dd_empty, dd_full, NASD_EDRFS_DIRSLOT_SIZE);
	  memset(dd_full, 0, NASD_EDRFS_DIRSLOT_SIZE);
	  
	  NASD_EDRFS_DIRMAP_SET_SLOT(page->header.usemap,   i + k);
	  NASD_EDRFS_DIRMAP_CLEAR_SLOT(page->header.usemap, j + k);
	}

	found = 1;
      }
    }

      /* we didn't find _any_ full slots after i. this means
	 the directory is already compacted. bail out. */
    if (found == 0) { return; }
  }
}


/* set up an empty directory page */
void nasd_edrfs_dir_init_page(nasd_edrfs_dirpage_t *page) {
  int i;

  /* clear the usemap */
  for (i = 0; i < 16; i++) { page->header.usemap[i] = 0; }

  page->header.freecnt = NASD_EDRFS_DIRPAGE_NUM_SLOTS;
}


void nasd_edrfs_dir_dump_header(nasd_edrfs_dirpage_t *page) {
  int i;

  nasd_printf("usemap:\n");

  for (i = 0; i <  8; i++) { nasd_printf("%02x ", page->header.usemap[i]); }
  nasd_printf("\n");
  for (i = 8; i < 16; i++) { nasd_printf("%02x ", page->header.usemap[i]); }
  nasd_printf("\n");

  nasd_printf("freecnt: %d\n", page->header.freecnt);
}


void nasd_edrfs_dir_dump_dirslot(nasd_edrfs_dirpage_t *page, int slot) {
  char name_seg[NASD_EDRFS_DIRDATA_EMBEDDED_NSZ+1];
  nasd_edrfs_dirdata_t *dd = &(page->data_slots[slot]);
  

  nasd_printf("flags:    0x%016" NASD_64x_FMT "\n", dd->flags);
  nasd_printf("  type:   0x%x\n", NASD_EDRFS_DIRD_FLAGS_TYPEOF(dd->flags));
  nasd_printf("  extype: 0x%x\n", NASD_EDRFS_DIRD_FLAGS_EXTYPEOF(dd->flags));
  nasd_printf("  exlen:  0x%x\n", NASD_EDRFS_DIRD_FLAGS_EXLEN(dd->flags));
  nasd_printf("  seglen: 0x%x\n", NASD_EDRFS_DIRD_FLAGS_SEGLEN(dd->flags));
  nasd_printf("nasdid:   0x%016" NASD_64x_FMT "\n", dd->nasdid);
  nasd_printf("name_len: %d\n", dd->name_len);
  strncpy(name_seg, dd->name, NASD_EDRFS_DIRDATA_EMBEDDED_NSZ);
  name_seg[NASD_EDRFS_DIRDATA_EMBEDDED_NSZ] = '\0';
  nasd_printf("name:     '%s'\n", name_seg);
}
