/*
 * nasd_ref.c
 *
 * block reference-count management for on-disk fs
 *
 * Author: Jim Zelenka
 */
/*
 * 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_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_layout.h>

/*
 * Must hold the partition write lock to change refcounts.
 * Note that refcnt blocks are entirely protected by cache
 * ent lock; we don't bother with the r/w lock.
 */
nasd_status_t
nasd_odc_ref_ranges(
  int                     partnum,
  nasd_odc_exlist_ent_t  *in_exle,
  int                     delta,
  void                   *layout_handle,
  int                     flags)
{
  nasd_blkno_t cur_ref_blk, cur_blk, fb, lb;
  nasd_odc_exlist_ent_t *exle, *fe;
  nasd_status_t rc, rc2;
  nasd_odc_ent_t *ent;
  int i;

  NASD_ASSERT(delta);
  rc = NASD_SUCCESS;
  fe = NULL;

  ent = NULL;
  for(exle=in_exle;exle;exle=exle->next) {
db_printf(("REF exle=%lx (%u..%u) delta=%d\n", (u_long)exle, exle->range.first, exle->range.last, delta));
    cur_blk = exle->range.first;
    while(cur_blk<=exle->range.last) {
      cur_ref_blk = NASD_ODC_REFBLK_OF(cur_blk);
      NASD_ASSERT(cur_ref_blk < nasd_odc_refblocks);
      fb = NASD_ODC_REFBLK_FIRST(cur_ref_blk);
      lb = NASD_ODC_REFBLK_LAST(cur_ref_blk);
      if (ent && (ent->blkno != cur_ref_blk)) {
        NASD_ODC_UNLOCK_BLOCK(ent);
        nasd_odc_block_release(ent);
        ent = NULL;
      }
      if (ent == NULL) {
        rc = nasd_odc_block_get(NULL, cur_ref_blk,
          NASD_ODC_L_FORCE|NASD_ODC_L_BLOCK|NASD_ODC_L_LOAD,
          &ent, NASD_ID_NULL, 0, NASD_ODC_T_REFCNT, NULL);
        if (rc) {
          nasd_printf("DRIVE: fatal error, cur_ref_blk=%u\n", cur_ref_blk);
          NASD_PANIC();
        }
        NASD_ODC_LOCK_BLOCK(ent);
        nasd_odc_wait_not_busy_invalid(ent);
      }
      NASD_ASSERT(!(ent->data_flags&NASD_CD_INVALID));
      NASD_ASSERT(!(ent->data_flags&NASD_CD_BUSY));
      nasd_odc_dirty_ent(ent);
      for(i=NASD_ODC_OFF_IN_REFBLK(cur_blk);
        ((cur_blk<=exle->range.last)&&(cur_blk<=lb));cur_blk++,i++)
      {
        if (ent->data.cnt[i] == 0) {
          NASD_ASSERT(delta > 0);
        }
        if (delta < 0) {
          NASD_ASSERT(ent->data.cnt[i] >= NASD_ABS(delta));
        }
        ent->data.cnt[i] += delta;
        /*
         * Check for zero refcnt here. If so, mark block free, eject
         * block from cache if present.
         */
        if (ent->data.cnt[i] == 0) {
redo_free:
          if (fe == NULL) {
            rc2 = nasd_odc_get_extent_list(&fe);
            if (rc2) {
              /* can't create a list of free blocks, try manually */
              rc2 = nasd_od_layout_release_oneblock(partnum, cur_blk, layout_handle);
              /* XXX this could fail- ugh */
              if (rc2)
                NASD_PANIC();
              fe = NULL;
            }
            else {
              fe->range.first = fe->range.last = cur_blk;
              fe->next = fe->prev = NULL;
            }
          }
          else {
            if ((fe->range.last+1) == cur_blk) {
              fe->range.last = cur_blk;
            }
            else if ((fe->range.first-1) == cur_blk) {
              fe->range.first = cur_blk;
            }
            else {
              rc2 = nasd_od_layout_release_blocks(partnum, fe, layout_handle);
              if (rc2)
                NASD_PANIC();
              fe = NULL;
              goto redo_free;
            }
          }
          /* eject block from cache, if present */
          if (flags&NASD_ODC_REF_EJECT) {
            NASD_ODC_UNLOCK_BLOCK(ent);
            nasd_odc_block_eject_by_num(cur_blk);
            NASD_ODC_LOCK_BLOCK(ent);
          }
        }
      }
      /*
       * At this point, cur_blk is either pointing one past the end
       * of the current range, or one past the end of the current refblk.
       * If past the current range, we'll fall out of the loop here,
       * and we'll start in on the next part of the range. If not, we'll
       * come back around, release the block we've got, and grab the
       * next one we need.
       */
    }
  }
  if (fe) {
    rc = nasd_od_layout_release_blocks(partnum, fe, layout_handle);
    if (rc)
      NASD_PANIC();
  }
  if (ent) {
    NASD_ODC_UNLOCK_BLOCK(ent);
    nasd_odc_block_release(ent);
  }

  return(rc);
}

/*
 * Load in reference counts
 */
nasd_status_t
nasd_odc_load_refs()
{
  nasd_odc_ent_t *ents[NASD_ODC_OPCHUNK];
  nasd_status_t rc;
  int i, j, k;

  for(i=0;i<nasd_odc_refblocks;) {
    for(k=j=0;((j<NASD_ODC_OPCHUNK)&&(i<nasd_odc_refblocks));j++) {
      rc = nasd_odc_block_get(NULL, (nasd_blkno_t)i,
        NASD_ODC_L_LOAD|NASD_ODC_L_FORCE, &ents[k],
        NASD_ID_NULL, 0, NASD_ODC_T_REFCNT, NULL);
      if (rc)
        NASD_PANIC();
      k++;
      i++;
    }
    for(j=0;j<k;j++) {
      NASD_ODC_LOCK_BLOCK(ents[j]);
      nasd_odc_wait_not_busy_invalid(ents[j]);
      NASD_ODC_UNLOCK_BLOCK(ents[j]);
      nasd_odc_block_release(ents[j]);
    }
  }
  return(NASD_SUCCESS);
}

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