/*
 * nasd_layout_seq.c
 *
 * Sequential (naiive) layout for NASD embedded filesystem.
 *
 * Author: Jim Zelenka
 */
/*
 * Copyright (c) of Carnegie Mellon University, 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_drive_options.h>
#include <nasd/nasd_types.h>
#include <nasd/nasd_freelist.h>
#include <nasd/nasd_itypes.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_cache.h>
#include <nasd/nasd_common.h>
#include <nasd/nasd_timer.h>
#include <nasd/nasd_security.h>
#include <nasd/nasd_sys.h>
#include <nasd/nasd_layout.h>

#if NASD_DRIVE_LAYOUT_SEQUENTIAL_INCLUDE > 0

int nasd_nl_seq_clustered = 0;

/*
 * Given a block number, returns the first block
 * in that cluster. If blkno is 0, eval to 0.
 */
#define nasd_nl_seq_cluster_root(_blkno_) \
 ((_blkno_) ? ((_blkno_) - (((_blkno_) - 1) % nasd_od_region_blocks)) : 0)

/*
 * Given a block number, return the block that should be
 * used as a first block arg for a logically-sequential
 * allocation.
 */
#define nasd_nl_seq_first_from(_blkno_) \
 (nasd_nl_seq_clustered ? nasd_nl_seq_cluster_root(_blkno_) : (_blkno_))

/*
 * Nothing much to do here, just be sure
 * clustering is off.
 */
nasd_status_t
nasd_nl_seq_init(
  nasd_od_config_t *config)
{
  nasd_odc_read_regions = 0;
  nasd_nl_seq_clustered = 0;

  return(NASD_SUCCESS);
}

/*
 * Nothing much to do here, just be sure
 * clustering is on.
 */
nasd_status_t
nasd_nl_seq_cl_init(
  nasd_od_config_t *config)
{
  nasd_odc_read_regions = 1;
  nasd_nl_seq_clustered = 1;

  return(NASD_SUCCESS);
}

/*
 * The first available block on the disk becomes the node block.
 * The prealloc range starts with the next available block after
 * the node block, and runs for the maximum length it can before
 * reaching an allocated block or satisfying the preallocation.
 */
nasd_status_t
nasd_nl_seq_get_node_block(
  int                      partnum,
  nasd_blkcnt_t            prealloc_blocks,
  nasd_layout_hint_t      *layout_hint,
  nasd_odc_exlist_ent_t  **exle_p,
  nasd_odc_exlist_ent_t  **pre_exle_p)
{
  nasd_odc_exlist_ent_t *exle, *pre_exle;
  nasd_blkno_t fb, co_blk, co_lvl2_hint;
  nasd_blkcnt_t blocks_got, rel_cnt;
  nasd_nodenum_t co_nodenum;
  nasd_generation_t co_gen;
  nasd_identifier_t co_id;
  nasd_status_t rc, rc2;
  int co_pn;

  co_nodenum = 0;
  co_blk = 1;
  *exle_p = *pre_exle_p = NULL;

  if (layout_hint) {
    co_id = layout_hint->lh_nid;
    /* get location of the hinted object on disk */
    rc = nasd_od_decompose_id(co_id, &co_pn, &co_nodenum, &co_lvl2_hint,
      &co_gen);
    if (rc)
      co_nodenum = 0;
  }

  if (co_nodenum) {
    /*
     * Note that the dance we do here allows us to colocate with an
     * object in another partition.
     */
    rc = nasd_odc_nodenum_to_blknum(co_pn, co_nodenum, co_lvl2_hint, &co_blk);
    if (rc)
      co_blk = 1;
  }

  /*
   * Get a block to be the node block.
   * If we have a hint, we get the first block from the hint.
   * Otherwise, we just get the first block.
   */
  rc = nasd_odc_free_get_range(1, nasd_nl_seq_first_from(co_blk),
    &exle, &blocks_got);
  if (rc) {
    return(rc);
  }

  NASD_ASSERT(blocks_got == 1);
  fb = exle->range.first;

  /*
   * Now get blocks for preallocation.
   */
  if (prealloc_blocks) {
    rc = nasd_odc_free_get_partial_range(prealloc_blocks,
      nasd_nl_seq_first_from(fb), &pre_exle);
    if (rc) {
      /*
       * We allow partial ranges here, so this call failing really
       * means there's not enough space in the partition to satisfy
       * the preallocation.
       *
       * No on-disk reference to node block, yet, so just hand it
       * back to free block tracker.
       */
      rc2 = nasd_odc_free_release_blocks(exle, &rel_cnt);
      if (rc2 != NASD_SUCCESS) {
        NASD_PANIC();
      }
      NASD_ASSERT(rel_cnt == 1);
      return(rc);
    }
    *pre_exle_p = pre_exle;
  }

  *exle_p = exle;

  return(NASD_SUCCESS);
}

/*
 * Just free them up. No state to update.
 */
nasd_status_t
nasd_nl_seq_node_fail_create(
  int                     partnum,
  nasd_odc_exlist_ent_t  *exle,
  nasd_odc_exlist_ent_t  *pre_exle)
{
  nasd_blkcnt_t rel_cnt;
  nasd_status_t rc;

  rc = nasd_odc_free_release_blocks(exle, &rel_cnt);
  if (rc != NASD_SUCCESS)
    return(rc);
  if (pre_exle) {
    rc = nasd_odc_free_release_blocks(pre_exle, &rel_cnt);
    if (rc != NASD_SUCCESS)
      NASD_PANIC();
  }
  return(rc);
}

nasd_status_t
nasd_nl_seq_release_oneblock(
  int            partnum,
  nasd_blkno_t   blknum,
  void          *layout_handle)
{
  nasd_status_t rc;

  NASD_ASSERT(layout_handle == NULL);
  rc = nasd_odc_free_release_oneblock(blknum);
  return(rc);
}

nasd_status_t
nasd_nl_seq_release_blocks(
  int                     partnum,
  nasd_odc_exlist_ent_t  *exle,
  nasd_blkcnt_t          *blocks_released_p,
  void                   *layout_handle)
{
  nasd_status_t rc;

  NASD_ASSERT(layout_handle == NULL);
  rc = nasd_odc_free_release_blocks(exle, blocks_released_p);
  return(rc);
}

/*
 * Call with partition write lock held.
 *
 * Adjust layout of preallocated range as necessary (increase
 * by pb_need, which may be negative (logical decrease)).
 * If object length has changed, release our blocks and
 * re-do layout decisions.
 */
nasd_status_t
nasd_nl_seq_adj_prealloc(
  int                              partnum,
  nasd_odc_ent_t                  *ne,
  nasd_odc_prealloc_adj_handle_t  *pah,
  int                              len_changed)
{
  nasd_odc_exlist_ent_t *fe, *pre_exle;
  nasd_blkno_t old_last, fb, needmore;
  nasd_odc_icpart_t *icp;
  nasd_blkcnt_t rel_cnt;
  nasd_od_part_t *part;
  nasd_od_node_t *np;
  nasd_status_t rc;

  part = &PART(partnum);
  icp = &nasd_odc_state->parts[partnum];

  np = ne->data.node;

  if (len_changed && np->prealloc_ex.first) {
    /*
     * Throw out old prealloc blocks, redo layout decision.
     */
    rc = nasd_odc_get_extent_list(&fe);
    if (rc)
      NASD_PANIC();
    NASD_ASSERT(np->prealloc_ex.last >= np->prealloc_ex.first);
    fe->range = np->prealloc_ex;
    np->prealloc_ex.first = np->prealloc_ex.last = 0;
    rc = nasd_odc_free_release_blocks(fe, &rel_cnt);
    if (rc)
      NASD_PANIC();
  }
  if ((len_changed == 0) && (pah->pb_need < 0)) {
    /*
     * Ditch (trailing) part of an existing range.
     */
    rc = nasd_odc_get_extent_list(&fe);
    if (rc)
      NASD_PANIC();
    NASD_ASSERT(np->prealloc_ex.last >= np->prealloc_ex.first);
    old_last = np->prealloc_ex.last;
    np->prealloc_ex.last += pah->pb_need;
    fe->range.first = np->prealloc_ex.last + 1;
    fe->range.last = old_last;
    rc = nasd_odc_free_release_blocks(fe, &rel_cnt);
    if (rc)
      NASD_PANIC();
  }
  if (((len_changed == 0) && (pah->pb_need > 0))
    || (len_changed && np->blocks_preallocated))
  {
    /*
     * Try to lay out more blocks. Maybe want to add more to an existing
     * range, maybe want a new range.
     */
    if (np->prealloc_ex.first) {
      fb = np->prealloc_ex.last + 1;
      needmore = np->blocks_preallocated -
        (np->prealloc_ex.last - np->prealloc_ex.first + 1);
    }
    else {
      needmore = np->blocks_preallocated;
      fb = np->last_block + 1;
    }
    /* indicate mandatory starting point */
    rc = nasd_odc_free_get_partial_range(needmore,
      nasd_nl_seq_first_from(fb), &pre_exle);
    if (rc) {
      /*
       * So sad. No blocks for us. Don't worry about it, though.
       */
      return(NASD_SUCCESS);
    }
    NASD_ASSERT(pre_exle->next == NULL);
    NASD_ASSERT(pre_exle->prev == NULL);
    if (np->prealloc_ex.first) {
      NASD_ASSERT(pre_exle->range.first == (np->prealloc_ex.last + 1));
      np->prealloc_ex.last = pre_exle->range.last;
    }
    else {
      np->prealloc_ex = pre_exle->range;
    }
    rc = nasd_odc_ref_ranges(partnum, pre_exle, 1, NULL, NASD_ODC_REF_NOFLAGS);
    if (rc != NASD_SUCCESS)
      NASD_PANIC();
    nasd_odc_release_extent_list(pre_exle);
  }

  return(NASD_SUCCESS);
}

/*
 * Give up the range of blocks assigned as the
 * preallocation range.
 */
nasd_status_t
nasd_nl_seq_surrender_prealloc(
  int              partnum,
  nasd_odc_ent_t  *ne)
{
  nasd_odc_exlist_ent_t *fe;
  nasd_blkcnt_t rel_cnt;
  nasd_od_node_t *np;
  nasd_status_t rc;

  np = ne->data.node;

  if (np->prealloc_ex.first) {
    NASD_ASSERT(np->prealloc_ex.last >= np->prealloc_ex.first);
    rc = nasd_odc_get_extent_list(&fe);
    if (rc)
      return(rc);
    fe->range = np->prealloc_ex;
    np->prealloc_ex.first = np->prealloc_ex.last = 0;
    rc = nasd_odc_free_release_blocks(fe, &rel_cnt);
    if (rc)
      NASD_PANIC();
  }
  else {
    NASD_ASSERT(np->prealloc_ex.last == 0);
  }

  return(NASD_SUCCESS);
}

/*
 * Delete the inode occupying block nb.
 */
nasd_status_t
nasd_nl_seq_node_deleting(
  int              partnum,
  nasd_odc_ent_t  *ne)
{
  nasd_odc_exlist_ent_t *fe;
  nasd_odc_icpart_t *icp;
  nasd_od_part_t *part;
  nasd_status_t rc;
  nasd_blkno_t nb;

  nb = ne->blkno;

  part = &PART(partnum);
  icp = &nasd_odc_state->parts[partnum];

  rc = nasd_odc_get_extent_list(&fe);
  if (rc)
    return(rc);

  nasd_odc_block_eject(ne);
  nasd_odc_block_release(ne);

  fe->range.first = fe->range.last = nb;
  fe->next = NULL;

  NASD_ODC_ICPART_LOCK_WRITE(icp);
  rc = nasd_odc_ref_ranges(partnum, fe, -1, NULL, NASD_ODC_REF_NOFLAGS);
  NASD_ODC_ICPART_UNLOCK_WRITE(icp);

  if (rc)
    NASD_PANIC();

  nasd_odc_release_extent_list(fe);

  return(NASD_SUCCESS);
}

/*
 * Call with partition write lock held.
 * Call with object lock held.
 */
nasd_status_t
nasd_nl_seq_alloc_blocks(
  int                      partnum,
  nasd_odc_ent_t          *ne,
  nasd_blkcnt_t            needblks,
  nasd_blkno_t             blk_hint,
  nasd_odc_exlist_ent_t  **exle_p,
  nasd_blkcnt_t           *blocks_allocated_p)
{
  nasd_odc_exlist_ent_t *exle;
  nasd_odc_icpart_t *icp;
  nasd_od_part_t *part;
  nasd_od_node_t *np;
  nasd_status_t rc;

  np = ne->data.node;

  part = &PART(partnum);
  icp = &nasd_odc_state->parts[partnum];

  rc = nasd_odc_free_get_range(needblks, nasd_nl_seq_first_from(blk_hint),
    &exle, blocks_allocated_p);
  if (rc) {
    *exle_p = NULL;
    return(rc);
  }

  *exle_p = exle;
  return(NASD_SUCCESS);
}

/*
 * Do any work necessary for format.
 * There isn't any.
 */
nasd_status_t
nasd_nl_seq_format(
  nasd_od_config_t  *config,
  nasd_blkno_t       first_real_data_blk)
{
  return(NASD_SUCCESS);
}

/*
 * Init any diskstate-dependent state.
 * There isn't any.
 */
nasd_status_t
nasd_nl_seq_init_dynamic(
  nasd_blkno_t       first_real_data_blk)
{
  return(NASD_SUCCESS);
}

#endif /* NASD_DRIVE_LAYOUT_SEQUENTIAL_INCLUDE > 0 */

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