/* Disk allocation routines
   Copyright (C) 1992 Free Software Foundation

This file is part of the GNU Hurd.

The GNU Hurd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

The GNU Hurd is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with the GNU Hurd; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Modified from UCB by Michael I. Bushnell.  */

/*
 * Copyright (c) 1982, 1986, 1989 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution is only permitted until one year after the first shipment
 * of 4.4BSD by the Regents.  Otherwise, redistribution and use in source and
 * binary forms are permitted provided that: (1) source distributions retain
 * this entire copyright notice and comment, and (2) distributions including
 * binaries display the following acknowledgement:  This product includes
 * software developed by the University of California, Berkeley and its
 * contributors'' in the documentation or other materials provided with the
 * distribution and in all advertising materials mentioning features or use
 * of this software.  Neither the name of the University nor the names of
 * its contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 *	@(#)ufs_alloc.c	7.20 (Berkeley) 6/28/90
 */

#include "ufs.h"
#include "fs.h"
#include "inode.h"
#include "dinode.h"
#include <stdio.h>
#include <errno.h>

static u_long alloccg (int, daddr_t, int);
static u_long ialloccg (int, daddr_t, int);
static u_long hashalloc (int, long, int, u_long(*)(int, daddr_t, int));
static daddr_t fragextend (int, long, int, int);
static daddr_t alloccgblk (struct cg *, daddr_t);
static daddr_t mapsearch (struct cg *, daddr_t, int);

/* These are in ufs_tables.c.  */
extern int inside[], around[];     
extern unsigned char	*fragtbl[];

/*
 * Allocate a block in the file system.
 * 
 * The size of the requested block is given, which must be some
 * multiple of fs_fsize and <= fs_bsize.
 * A preference may be optionally specified. If a preference is given
 * the following hierarchy is used to allocate a block:
 *   1) allocate the requested block.
 *   2) allocate a rotationally optimal block in the same cylinder.
 *   3) allocate a block in the same cylinder group.
 *   4) quadradically rehash into other cylinder groups, until an
 *      available block is located.
 * If no block preference is given the following heirarchy is used
 * to allocate a block:
 *   1) allocate a block in the cylinder group that contains the
 *      inode for the file.
 *   2) quadradically rehash into other cylinder groups, until an
 *      available block is located.
 */
error_t
alloc(struct inode *ip,
      daddr_t lbn, 
      daddr_t bpref,
      int size,
      daddr_t *bnp,
      struct protid *cred)
{
  int cg;
  daddr_t bno;
  error_t error;

  *bnp = 0;
  if ((unsigned)size > sblock->fs_bsize || fragoff(size) != 0)
    {
      printf("bsize = %d, size = %d\n", sblock->fs_bsize, size);
      panic("alloc: bad size");
    }
  if (size == sblock->fs_bsize && sblock->fs_cstotal.cs_nbfree == 0)
    goto nospace;
  if (!isuid (0, cred) && freespace(sblock->fs_minfree) <= 0)
    goto nospace;
  
  if (bpref >= sblock->fs_size)
    bpref = 0;
  if (bpref == 0)
    cg = itog(ip->i_number);
  else
    cg = dtog(bpref);
  bno = (daddr_t)hashalloc(cg, (long)bpref, size, alloccg);
  if (bno > 0)
    {
      if (!(error = catch_exception ()))
	{
	  ip->di->di_blocks += btodb(size);
	  ip->di->di_mtime = time->seconds;
	  ip->di->di_ctime = time->seconds;
	  *bnp = bno;
	  end_catch_exception ();
	  return 0;
	}
      else
	return error;
    }
 nospace:
  printf("file system full\n");
  return (ENOSPC);
}

/*
 * Reallocate a fragment to a bigger size
 *
 * The number and size of the old block is given, and a preference
 * and new size is also specified. The allocator attempts to extend
 * the original block. Failing that, the regular block allocator is
 * invoked to get an appropriate block.
 */
error_t
realloccg(struct inode *ip,
	  daddr_t lbprev,
	  daddr_t bpref,
	  int osize, 
	  int nsize,
	  daddr_t *pbn,
	  struct protid *cred)
{
  int cg, request;
  daddr_t bprev, bno;
  error_t error;
  
  *pbn = 0;
  if ((unsigned)osize > sblock->fs_bsize || fragoff(osize) != 0 ||
      (unsigned)nsize > sblock->fs_bsize || fragoff(nsize) != 0)
    {
      printf("bsize = %d, osize = %d, nsize = %d\n",
	     sblock->fs_bsize, osize, nsize);
      panic("realloccg: bad size");
    }
  if (!isuid (0, cred) && freespace(sblock->fs_minfree) <= 0)
    goto nospace;

  if (error = catch_exception ())
    return error;

  if ((bprev = ip->di->di_db[lbprev]) == 0)
    {
      printf("bsize = %d, bprev = %d\n", sblock->fs_bsize, bprev);
      panic("realloccg: bad bprev");
    }

  /*
   * Check for extension in the existing location.
   */
  cg = dtog(bprev);
  if (bno = fragextend(cg, (long)bprev, osize, nsize))
    {
      if (ip->di->di_db[lbprev] != bno)
	panic("realloc_cg: bad blockno");
      ip->di->di_blocks += btodb(nsize - osize);
      ip->di->di_mtime = time->seconds;
      ip->di->di_ctime = time->seconds;
      end_catch_exception ();
      return (0);
    }
  /*
   * Allocate a new disk location.
   */
  if (bpref >= sblock->fs_size)
    bpref = 0;
  switch ((int)sblock->fs_optim)
    {
    case FS_OPTSPACE:
      /*
       * Allocate an exact sized fragment. Although this makes 
       * best use of space, we will waste time relocating it if 
       * the file continues to grow. If the fragmentation is
       * less than half of the minimum free reserve, we choose
       * to begin optimizing for time.
       */
      request = nsize;
      if (sblock->fs_minfree < 5 ||
	  sblock->fs_cstotal.cs_nffree >
	  sblock->fs_dsize * sblock->fs_minfree / (2 * 100))
	break;
      printf("optimization changed from SPACE to TIME\n");
      sblock->fs_optim = FS_OPTTIME; 
      break;
    case FS_OPTTIME:
      /*
       * At this point we have discovered a file that is trying
       * to grow a small fragment to a larger fragment. To save
       * time, we allocate a full sized block, then free the 
       * unused portion. If the file continues to grow, the 
       * `fragextend' call above will be able to grow it in place
       * without further copying. If aberrant programs cause
       * disk fragmentation to grow within 2% of the free reserve,
       * we choose to begin optimizing for space.
       */
      request = sblock->fs_bsize;
      if (sblock->fs_cstotal.cs_nffree <
	  sblock->fs_dsize * (sblock->fs_minfree - 2) / 100)
	break;
      printf("%s: optimization changed from TIME to SPACE\n",
	     sblock->fs_fsmnt);
      sblock->fs_optim = FS_OPTSPACE;
      break;
    default:
      printf("optim = %d\n", sblock->fs_optim);
      panic("realloccg: bad optim");
    }
  
  bno = (daddr_t)hashalloc(cg, (long)bpref, request, 
			   (u_long (*)())alloccg);
  if (bno > 0) 
    {
      blkfree(bprev, (off_t)osize);
      if (nsize < request)
	blkfree(bno + numfrags(nsize), (off_t)(request - nsize));
      ip->di->di_blocks += btodb(nsize - osize);
      ip->di->di_mtime = time->seconds;
      ip->di->di_ctime = time->seconds;
      *pbn = bno;
      end_catch_exception ();
      return (0);
    }
  end_catch_exception ();
 nospace:
  /*
   * no space available
   */
  printf("file system full\n");
  return (ENOSPC);
}


/*
 * Allocate an inode in the file system.
 * 
 * A preference may be optionally specified. If a preference is given
 * the following hierarchy is used to allocate an inode:
 *   1) allocate the requested inode.
 *   2) allocate an inode in the same cylinder group.
 *   3) quadradically rehash into other cylinder groups, until an
 *      available inode is located.
 * If no inode preference is given the following heirarchy is used
 * to allocate an inode:
 *   1) allocate an inode in cylinder group 0.
 *   2) quadradically rehash into other cylinder groups, until an
 *      available inode is located.
 */
error_t
ialloc(ino_t ipref,
       mode_t mode,
       struct inode **ipp)
{
  int ino;
  struct inode *ip;
  int cg;
  error_t error;
  
  *ipp = 0;

  if (sblock->fs_cstotal.cs_nifree == 0)
    goto noinodes;
  if (ipref >= sblock->fs_ncg * sblock->fs_ipg)
    ipref = 0;
  cg = itog(ipref);
  ino = (int)hashalloc(cg, (long)ipref, mode, ialloccg);
  if (ino == 0)
    goto noinodes;
  if (error = iget(ino, &ip))
    return error;
  *ipp = ip;
  if (error = catch_exception ())
    {
      iput (ip);
      return error;
    }
  if (DI_MODE(ip->di)) 
    {
      printf("mode = 0%o, inum = %d\n",
	     DI_MODE(ip->di), ip->i_number);
      panic("ialloc: dup alloc");
    }
  if (ip->di->di_blocks)
    {
      printf("free inode %d had %d blocks\n", ino, ip->di->di_blocks);
      ip->di->di_blocks = 0;
    }
  /*
   * Set up a new generation number for this inode.
   */
  if (++nextgennumber < (u_long)time->seconds)
    nextgennumber = time->seconds;
  ip->di->di_gen = nextgennumber;
  end_catch_exception ();
  return (0);
 noinodes:
  printf("out of inodes\n");
  return (ENOSPC);
}

/*
 * Find a cylinder to place a directory.
 *
 * The policy implemented by this algorithm is to select from
 * among those cylinder groups with above the average number of
 * free inodes, the one with the smallest number of directories.
 */
ino_t
dirpref()
{
  int cg, minndir, mincg, avgifree;
  
  avgifree = sblock->fs_cstotal.cs_nifree / sblock->fs_ncg;
  minndir = sblock->fs_ipg;
  mincg = 0;
  for (cg = 0; cg < sblock->fs_ncg; cg++)
    if (sblock->fs_cs(cg).cs_ndir < minndir &&
	sblock->fs_cs(cg).cs_nifree >= avgifree)
      {
	mincg = cg;
	minndir = sblock->fs_cs(cg).cs_ndir;
      }
  return ((int)(sblock->fs_ipg * mincg));
}

/*
 * Select the desired position for the next block in a file.  The file is
 * logically divided into sections. The first section is composed of the
 * direct blocks. Each additional section contains fs_maxbpg blocks.
 * 
 * If no blocks have been allocated in the first section, the policy is to
 * request a block in the same cylinder group as the inode that describes
 * the file. If no blocks have been allocated in any other section, the
 * policy is to place the section in a cylinder group with a greater than
 * average number of free blocks.  An appropriate cylinder group is found
 * by using a rotor that sweeps the cylinder groups. When a new group of
 * blocks is needed, the sweep begins in the cylinder group following the
 * cylinder group from which the previous allocation was made. The sweep
 * continues until a cylinder group with greater than the average number
 * of free blocks is found. If the allocation is for the first block in an
 * indirect block, the information on the previous allocation is unavailable;
 * here a best guess is made based upon the logical block number being
 * allocated.
 * 
 * If a section is already partially allocated, the policy is to
 * contiguously allocate fs_maxcontig blocks.  The end of one of these
 * contiguous blocks and the beginning of the next is physically separated
 * so that the disk head will be in transit between them for at least
 * fs_rotdelay milliseconds.  This is to allow time for the processor to
 * schedule another I/O transfer.
 */
daddr_t
blkpref(struct inode *ip,
	daddr_t lbn,
	int indx,
	daddr_t *bap)
{
  int cg;
  int avgbfree, startcg;
  daddr_t nextblk;
  
  if (indx % sblock->fs_maxbpg == 0 || bap[indx - 1] == 0) 
    {
      if (lbn < NDADDR) 
	{
	  cg = itog(ip->i_number);
	  return (sblock->fs_fpg * cg + sblock->fs_frag);
	}
      /*
       * Find a cylinder with greater than average number of
       * unused data blocks.
       */
      if (indx == 0 || bap[indx - 1] == 0)
	startcg = itog(ip->i_number) + lbn / sblock->fs_maxbpg;
      else
	startcg = dtog(bap[indx - 1]) + 1;
      startcg %= sblock->fs_ncg;
      avgbfree = sblock->fs_cstotal.cs_nbfree / sblock->fs_ncg;
      for (cg = startcg; cg < sblock->fs_ncg; cg++)
	if (sblock->fs_cs(cg).cs_nbfree >= avgbfree) 
	  {
	    sblock->fs_cgrotor = cg;
	    return (sblock->fs_fpg * cg + sblock->fs_frag);
	  }
      for (cg = 0; cg <= startcg; cg++)
	if (sblock->fs_cs(cg).cs_nbfree >= avgbfree) 
	  {
	    sblock->fs_cgrotor = cg;
	    return (sblock->fs_fpg * cg + sblock->fs_frag);
	  }
      return 0;
    }
  /*
   * One or more previous blocks have been laid out. If less
   * than fs_maxcontig previous blocks are contiguous, the
   * next block is requested contiguously, otherwise it is
   * requested rotationally delayed by fs_rotdelay milliseconds.
   */
  nextblk = bap[indx - 1] + sblock->fs_frag;
  if (indx > sblock->fs_maxcontig &&
      bap[indx - sblock->fs_maxcontig] + blkstofrags(sblock->fs_maxcontig)
      != nextblk)
    return (nextblk);
  if (sblock->fs_rotdelay != 0)
    /*
     * Here we convert ms of delay to frags as:
     * (frags) = (ms) * (rev/sec) * (sect/rev) /
     *	((sect/frag) * (ms/sec))
     * then round up to the next block.
     */
    nextblk += roundup(sblock->fs_rotdelay * sblock->fs_rps 
		       * sblock->fs_nsect / (NSPF * 1000), sblock->fs_frag);
  return (nextblk);
}

/*
 * Implement the cylinder overflow algorithm.
 *
 * The policy implemented by this algorithm is:
 *   1) allocate the block in its requested cylinder group.
 *   2) quadradically rehash on the cylinder group number.
 *   3) brute force search for a free block.
 */
/*VARARGS5*/
static u_long
hashalloc(int cg,
	  long pref,
	  int size,		/* size for data blocks, mode for inodes */
	  u_long (*allocator)(int, daddr_t, int))
{
  long result;
  int i, icg = cg;
  
  /*
   * 1: preferred cylinder group
   */
  result = (*allocator)(cg, pref, size);
  if (result)
    return (result);
  /*
   * 2: quadratic rehash
   */
  for (i = 1; i < sblock->fs_ncg; i *= 2) 
    {
      cg += i;
      if (cg >= sblock->fs_ncg)
	cg -= sblock->fs_ncg;
      result = (*allocator)(cg, 0, size);
      if (result)
	return (result);
    }
  /*
   * 3: brute force search
   * Note that we start at i == 2, since 0 was checked initially,
   * and 1 is always checked in the quadratic rehash.
   */
  cg = (icg + 2) % sblock->fs_ncg;
  for (i = 2; i < sblock->fs_ncg; i++)
    {
      result = (*allocator)(cg, 0, size);
      if (result)
	return (result);
      cg++;
      if (cg == sblock->fs_ncg)
	cg = 0;
    }
  return 0;
}

/*
 * Determine whether a fragment can be extended.
 *
 * Check to see if the necessary fragments are available, and 
 * if they are, allocate them.
 */
static daddr_t
fragextend(int cg,
	   long bprev,
	   int osize, 
	   int nsize)
{
  struct cg *cgp;
  long bno;
  int frags, bbase;
  int i;
  
  if (sblock->fs_cs(cg).cs_nffree < numfrags(nsize - osize))
    return 0;
  frags = numfrags(nsize);
  bbase = fragnum(bprev);
  if (bbase > fragnum((bprev + frags - 1)))
    /* cannot extend across a block boundary */
    return 0;

  cgp = (struct cg *) (cgs + sblock->fs_bsize * cg);

  if (catch_exception ())
    return 0;			/* bogus, but that's what BSD does... */
  
  if (!cg_chkmagic(cgp))
    {
      printf ("Cylinder group %d bad magic number: %d/%d\n",
	      cg, cgp->cg_magic, ((struct ocg *)(cgp))->cg_magic);
      end_catch_exception ();
      return 0;
    }
  cgp->cg_time = time->seconds;
  bno = dtogd(bprev);
  for (i = numfrags(osize); i < frags; i++)
    if (isclr(cg_blksfree(cgp), bno + i))
      {
	end_catch_exception ();
	return 0;
      }

  /*
   * the current fragment can be extended
   * deduct the count on fragment being extended into
   * increase the count on the remaining fragment (if any)
   * allocate the extended piece
   */
  for (i = frags; i < sblock->fs_frag - bbase; i++)
    if (isclr(cg_blksfree(cgp), bno + i))
      break;
  cgp->cg_frsum[i - numfrags(osize)]--;
  if (i != frags)
    cgp->cg_frsum[i - frags]++;
  for (i = numfrags(osize); i < frags; i++)
    {
      clrbit(cg_blksfree(cgp), bno + i);
      cgp->cg_cs.cs_nffree--;
      sblock->fs_cstotal.cs_nffree--;
      sblock->fs_cs(cg).cs_nffree--;
    }
  sblock->fs_fmod++;
  end_catch_exception ();
  return (bprev);
}

/*
 * Determine whether a block can be allocated.
 *
 * Check to see if a block of the apprpriate size is available,
 * and if it is, allocate it.
 */
static u_long
alloccg(int cg,
	daddr_t bpref,
	int size)
{
  struct cg *cgp;
  register int i;
  int bno, frags, allocsiz;
  
  if (sblock->fs_cs(cg).cs_nbfree == 0 && size == sblock->fs_bsize)
    return 0;
  cgp = (struct cg *) (cgs + sblock->fs_bsize * cg);

  if (catch_exception ())
    return 0;
  
  if (!cg_chkmagic(cgp) ||
      (cgp->cg_cs.cs_nbfree == 0 && size == sblock->fs_bsize))
    {
      printf ("Cylinder group %d bad magic number: %d/%d\n",
	      cg, cgp->cg_magic, ((struct ocg *)(cgp))->cg_magic);
      end_catch_exception ();
      return 0;
    }
  cgp->cg_time = time->seconds;
  if (size == sblock->fs_bsize)
    {
      bno = alloccgblk(cgp, bpref);
      end_catch_exception ();
      return (u_long) (bno);
    }
  /*
   * check to see if any fragments are already available
   * allocsiz is the size which will be allocated, hacking
   * it down to a smaller size if necessary
   */
  frags = numfrags(size);
  for (allocsiz = frags; allocsiz < sblock->fs_frag; allocsiz++)
    if (cgp->cg_frsum[allocsiz] != 0)
      break;
  if (allocsiz == sblock->fs_frag)
    {
      /*
       * no fragments were available, so a block will be 
       * allocated, and hacked up
       */
      if (cgp->cg_cs.cs_nbfree == 0)
	{
	  end_catch_exception ();
	  return 0;
	}

      bno = alloccgblk(cgp, bpref);
      bpref = dtogd(bno);
      for (i = frags; i < sblock->fs_frag; i++)
	setbit(cg_blksfree(cgp), bpref + i);
      i = sblock->fs_frag - frags;
      cgp->cg_cs.cs_nffree += i;
      sblock->fs_cstotal.cs_nffree += i;
      sblock->fs_cs(cg).cs_nffree += i;
      sblock->fs_fmod++;
      cgp->cg_frsum[i]++;
      return (u_long)(bno);
    }
  bno = mapsearch(cgp, bpref, allocsiz);
  if (bno < 0)
    {
      end_catch_exception ();
      return 0;
    }

  for (i = 0; i < frags; i++)
    clrbit(cg_blksfree(cgp), bno + i);
  cgp->cg_cs.cs_nffree -= frags;
  sblock->fs_cstotal.cs_nffree -= frags;
  sblock->fs_cs(cg).cs_nffree -= frags;
  sblock->fs_fmod++;
  cgp->cg_frsum[allocsiz]--;
  if (frags != allocsiz)
    cgp->cg_frsum[allocsiz - frags]++;
  end_catch_exception ();
  return (u_long) (cg * sblock->fs_fpg + bno);
}

/*
 * Allocate a block in a cylinder group.
 *
 * This algorithm implements the following policy:
 *   1) allocate the requested block.
 *   2) allocate a rotationally optimal block in the same cylinder.
 *   3) allocate the next available block on the block rotor for the
 *      specified cylinder group.
 * Note that this routine only allocates fs_bsize blocks; these
 * blocks may be fragmented by the routine that allocates them.
 */
static daddr_t
alloccgblk(struct cg *cgp,
	   daddr_t bpref)
{
  daddr_t bno;
  int cylno, pos, delta;
  short *cylbp;
  register int i;
  daddr_t ret;
  
  if (catch_exception ())
    return 0;
  
  if (bpref == 0) 
    {
      bpref = cgp->cg_rotor;
      goto norot;
    }
  bpref = blknum(bpref);
  bpref = dtogd(bpref);
  /*
   * if the requested block is available, use it
   */
  if (isblock(cg_blksfree(cgp), fragstoblks(bpref)))
    {
      bno = bpref;
      goto gotit;
    }
  /*
   * check for a block available on the same cylinder
   */
  cylno = cbtocylno(bpref);
  if (cg_blktot(cgp)[cylno] == 0)
    goto norot;
  if (sblock->fs_cpc == 0)
    {
      /*
       * block layout info is not available, so just have
       * to take any block in this cylinder.
       */
      bpref = howmany(sblock->fs_spc * cylno, NSPF);
      goto norot;
    }
  /*
   * check the summary information to see if a block is 
   * available in the requested cylinder starting at the
   * requested rotational position and proceeding around.
   */
  cylbp = cg_blks(cgp, cylno);
  pos = cbtorpos(bpref);
  for (i = pos; i < sblock->fs_nrpos; i++)
    if (cylbp[i] > 0)
      break;
  if (i == sblock->fs_nrpos)
    for (i = 0; i < pos; i++)
      if (cylbp[i] > 0)
	break;
  if (cylbp[i] > 0)
    {
      /*
       * found a rotational position, now find the actual
       * block. A panic if none is actually there.
       */
      pos = cylno % sblock->fs_cpc;
      bno = (cylno - pos) * sblock->fs_spc / NSPB;
      if (fs_postbl(pos)[i] == -1)
	{
	  printf("pos = %d, i = %d\n", pos, i);
	  panic("alloccgblk: cyl groups corrupted");
	}
      for (i = fs_postbl(pos)[i];; )
	{
	  if (isblock(cg_blksfree(cgp), bno + i))
	    {
	      bno = blkstofrags(bno + i);
	      goto gotit;
	    }
	  delta = fs_rotbl[i];
	  if (delta <= 0 ||
	      delta + i > fragstoblks(sblock->fs_fpg))
	    break;
	  i += delta;
	}
      printf("pos = %d, i = %d\n", pos, i);
      panic("alloccgblk: can't find blk in cyl");
    }
 norot:
  /*
   * no blocks in the requested cylinder, so take next
   * available one in this cylinder group.
   */
  bno = mapsearch(cgp, bpref, (int)sblock->fs_frag);
  if (bno < 0)
    {
      end_catch_exception ();
      return 0;
    }
  cgp->cg_rotor = bno;
 gotit:
  clrblock(cg_blksfree(cgp), (long)fragstoblks(bno));
  cgp->cg_cs.cs_nbfree--;
  sblock->fs_cstotal.cs_nbfree--;
  sblock->fs_cs(cgp->cg_cgx).cs_nbfree--;
  cylno = cbtocylno(bno);
  cg_blks(cgp, cylno)[cbtorpos(bno)]--;
  cg_blktot(cgp)[cylno]--;
  sblock->fs_fmod++;
  ret = cgp->cg_cgx * sblock->fs_fpg + bno;
  end_catch_exception ();
  return ret;
}

/*
 * Determine whether an inode can be allocated.
 *
 * Check to see if an inode is available, and if it is,
 * allocate it using the following policy:
 *   1) allocate the requested inode.
 *   2) allocate the next available inode after the requested
 *      inode in the specified cylinder group.
 */
static u_long
ialloccg(int cg,
	 daddr_t ipref,
	 int modein)
{
  register struct cg *cgp;
  int start, len, loc, map, i;
  mode_t mode = (mode_t) modein;
  
  if (sblock->fs_cs(cg).cs_nifree == 0)
    return 0;

  cgp = (struct cg *)(cgs + sblock->fs_bsize * cg);

  if (catch_exception ())
    return 0;
  
  if (!cg_chkmagic(cgp) || cgp->cg_cs.cs_nifree == 0)
    {
      printf ("Cylinder group %d bad magic number: %d/%d\n",
	      cg, cgp->cg_magic, ((struct ocg *)(cgp))->cg_magic);
      end_catch_exception ();
      return 0;
    }
  cgp->cg_time = time->seconds;
  if (ipref)
    {
      ipref %= sblock->fs_ipg;
      if (isclr(cg_inosused(cgp), ipref))
	goto gotit;
    }
  start = cgp->cg_irotor / NBBY;
  len = howmany(sblock->fs_ipg - cgp->cg_irotor, NBBY);
  loc = skpc(0xff, len, (u_char *) &cg_inosused(cgp)[start]);
  if (loc == 0)
    {
      len = start + 1;
      start = 0;
      loc = skpc(0xff, len, (u_char *) &cg_inosused(cgp)[0]);
      if (loc == 0)
	{
	  printf("cg = %d, irotor = %d\n", cg, cgp->cg_irotor);
	  panic("ialloccg: map corrupted");
	}
    }
  i = start + len - loc;
  map = cg_inosused(cgp)[i];
  ipref = i * NBBY;
  for (i = 1; i < (1 << NBBY); i <<= 1, ipref++)
    {
      if ((map & i) == 0)
	{
	  cgp->cg_irotor = ipref;
	  goto gotit;
	}
    }
  panic("ialloccg: block not in map");
 gotit:
  setbit(cg_inosused(cgp), ipref);
  cgp->cg_cs.cs_nifree--;
  sblock->fs_cstotal.cs_nifree--;
  sblock->fs_cs(cg).cs_nifree--;
  sblock->fs_fmod++;
  if ((mode & IFMT) == IFDIR)
    {
      cgp->cg_cs.cs_ndir++;
      sblock->fs_cstotal.cs_ndir++;
      sblock->fs_cs(cg).cs_ndir++;
    }
  end_catch_exception ();
  return (u_long)(cg * sblock->fs_ipg + ipref);
}

/*
 * Free a block or fragment.
 *
 * The specified block or fragment is placed back in the
 * free map. If a fragment is deallocated, a possible 
 * block reassembly is checked.
 */
void
blkfree(daddr_t bno,
	int size)
{
  register struct cg *cgp;
  int cg, blk, frags, bbase;
  register int i;
  
  if ((unsigned)size > sblock->fs_bsize || fragoff(size) != 0)
    {
      printf("bsize = %d, size = %d\n", sblock->fs_bsize, size);
      panic("blkfree: bad size");
    }
  cg = dtog(bno);
  if ((unsigned)bno >= sblock->fs_size)
    {
      printf("bad block %d\n", bno);
      return;
    }

  cgp = (struct cg *)(cgs + sblock->fs_bsize * cg);

  if (catch_exception ())
    return;

  if (!cg_chkmagic(cgp))
    {
      printf ("Cylinder group %d bad magic number: %d/%d\n",
	      cg, cgp->cg_magic, ((struct ocg *)(cgp))->cg_magic);
      end_catch_exception ();
      return;
    }
  cgp->cg_time = time->seconds;
  bno = dtogd(bno);
  if (size == sblock->fs_bsize)
    {
      if (isblock(cg_blksfree(cgp), fragstoblks(bno)))
	{
	  printf("block = %d\n", bno);
	  panic("blkfree: freeing free block");
	}
      setblock(cg_blksfree(cgp), fragstoblks(bno));
      cgp->cg_cs.cs_nbfree++;
      sblock->fs_cstotal.cs_nbfree++;
      sblock->fs_cs(cg).cs_nbfree++;
      i = cbtocylno(bno);
      cg_blks(cgp, i)[cbtorpos(bno)]++;
      cg_blktot(cgp)[i]++;
    } 
  else
    {
      bbase = bno - fragnum(bno);
      /*
       * decrement the counts associated with the old frags
       */
      blk = blkmap(cg_blksfree(cgp), bbase);
      fragacct(blk, cgp->cg_frsum, -1);
      /*
       * deallocate the fragment
       */
      frags = numfrags(size);
      for (i = 0; i < frags; i++)
	{
	  if (isset(cg_blksfree(cgp), bno + i))
	    {
	      printf("block = %d\n", bno + i);
	      panic("blkfree: freeing free frag");
	    }
	  setbit(cg_blksfree(cgp), bno + i);
	}
      cgp->cg_cs.cs_nffree += i;
      sblock->fs_cstotal.cs_nffree += i;
      sblock->fs_cs(cg).cs_nffree += i;
      /*
       * add back in counts associated with the new frags
       */
      blk = blkmap(cg_blksfree(cgp), bbase);
      fragacct(blk, cgp->cg_frsum, 1);
      /*
       * if a complete block has been reassembled, account for it
       */
      if (isblock(cg_blksfree(cgp), (daddr_t)fragstoblks(bbase)))
	{
	  cgp->cg_cs.cs_nffree -= sblock->fs_frag;
	  sblock->fs_cstotal.cs_nffree -= sblock->fs_frag;
	  sblock->fs_cs(cg).cs_nffree -= sblock->fs_frag;
	  cgp->cg_cs.cs_nbfree++;
	  sblock->fs_cstotal.cs_nbfree++;
	  sblock->fs_cs(cg).cs_nbfree++;
	  i = cbtocylno(bbase);
	  cg_blks(cgp, i)[cbtorpos(bbase)]++;
	  cg_blktot(cgp)[i]++;
	}
    }
  sblock->fs_fmod++;
  end_catch_exception ();
}

/*
 * Free an inode.
 *
 * The specified inode is placed back in the free map.
 */
void
ifree(ino_t ino,
      mode_t mode)
{
  register struct cg *cgp;
  int cg;
  
  if ((unsigned)ino >= sblock->fs_ipg*sblock->fs_ncg)
    {
      printf("ino = %d\n", ino);
      panic("ifree: range");
    }
  cg = itog(ino);
  cgp = (struct cg *)(cgs + sblock->fs_bsize * cg);

  if (catch_exception ())
    return ;

  if (!cg_chkmagic(cgp))
    {
      printf ("Cylinder group %d bad magic number: %d/%d\n",
	      cg, cgp->cg_magic, ((struct ocg *)(cgp))->cg_magic);
      end_catch_exception ();
      return;
    }
  cgp->cg_time = time->seconds;
  ino %= sblock->fs_ipg;
  if (isclr(cg_inosused(cgp), ino))
    {
      printf("ino = %d\n", ino);
      panic("ifree: freeing free inode");
    }
  clrbit(cg_inosused(cgp), ino);
  if (ino < cgp->cg_irotor)
    cgp->cg_irotor = ino;
  cgp->cg_cs.cs_nifree++;
  sblock->fs_cstotal.cs_nifree++;
  sblock->fs_cs(cg).cs_nifree++;
  if ((mode & IFMT) == IFDIR)
    {
      cgp->cg_cs.cs_ndir--;
      sblock->fs_cstotal.cs_ndir--;
      sblock->fs_cs(cg).cs_ndir--;
    }
  sblock->fs_fmod++;
  end_catch_exception ();
}

/*
 * Find a block of the specified size in the specified cylinder group.
 *
 * It is a panic if a request is made to find a block if none are
 * available.
 */
/* This routine expects to be called from inside a catch_exception */
static daddr_t
mapsearch(struct cg *cgp,
	  daddr_t bpref,
	  int allocsiz)
{
  daddr_t bno;
  int start, len, loc, i;
  int blk, field, subfield, pos;
  
  /*
   * find the fragment by searching through the free block
   * map for an appropriate bit pattern
   */
  if (bpref)
    start = dtogd(bpref) / NBBY;
  else
    start = cgp->cg_frotor / NBBY;
  len = howmany(sblock->fs_fpg, NBBY) - start;
  loc = scanc((unsigned)len, (u_char *)&cg_blksfree(cgp)[start],
	      (u_char *)fragtbl[sblock->fs_frag],
	      (u_char)(1 << (allocsiz - 1 + (sblock->fs_frag % NBBY))));
  if (loc == 0)
    {
      len = start + 1;
      start = 0;
      loc = scanc((unsigned)len, (u_char *)&cg_blksfree(cgp)[0],
		  (u_char *)fragtbl[sblock->fs_frag],
		  (u_char)(1 << (allocsiz - 1 + (sblock->fs_frag % NBBY))));
      if (loc == 0)
	{
	  printf("start = %d, len = %d\n", start, len);
	  panic("alloccg: map corrupted");
	}
    }
  bno = (start + len - loc) * NBBY;
  cgp->cg_frotor = bno;
  /*
   * found the byte in the map
   * sift through the bits to find the selected frag
   */
  for (i = bno + NBBY; bno < i; bno += sblock->fs_frag)
    {
      blk = blkmap(cg_blksfree(cgp), bno);
      blk <<= 1;
      field = around[allocsiz];
      subfield = inside[allocsiz];
      for (pos = 0; pos <= sblock->fs_frag - allocsiz; pos++)
	{
	  if ((blk & field) == subfield)
	    return (bno + pos);
	  field <<= 1;
	  subfield <<= 1;
	}
    }
  printf("bno = %d\n", bno);
  panic("alloccg: block not in map");
  return (-1);
}


