/* This files manages blocks allocation and deallocation.
 *
 * The entry points into this file are:
 *   discard_preallocated_blocks:	Discard preallocated blocks.
 *   alloc_block:	somebody wants to allocate a block; find one.
 *   free_block:	indicate that a block is available for new allocation.
 *
 * Created:
 *   June 2010 (Evgeniy Ivanov)
 */

#include "fs.h"
#include <string.h>
#include <stdlib.h>
#include <minix/com.h>
#include <minix/u64.h>
#include "buf.h"
#include "inode.h"
#include "super.h"
#include "const.h"


FORWARD _PROTOTYPE( block_t alloc_block_bit, (struct super_block *sp,
					      block_t origin,
					      struct inode *rip));

/*===========================================================================*
 *                      discard_preallocated_blocks                          *
 *===========================================================================*/
PUBLIC void discard_preallocated_blocks(struct inode *rip)
{
/* When called for rip, discard (free) blocks preallocated for rip,
 * otherwise discard all preallocated blocks.
 * Normally it should be called in following situations:
 * 1. File is closed.
 * 2. File is truncated.
 * 3. Non-sequential write.
 * 4. inode is "unloaded" from the memory.
 * 5. No free blocks left (discard all preallocated blocks).
 */
  int i;

  if (rip) {
	rip->i_prealloc_count = rip->i_prealloc_index = 0;
	for (i = 0; i < EXT2_PREALLOC_BLOCKS; i++) {
		if (rip->i_prealloc_blocks[i] != NO_BLOCK) {
			free_block(rip->i_sp, rip->i_prealloc_blocks[i]);
			rip->i_prealloc_blocks[i] = NO_BLOCK;
		}
	}
	return;
  }

  /* Discard all allocated blocks.
   * Probably there are just few blocks on the disc, so forbid preallocation.*/
  for(rip = &inode[0]; rip < &inode[NR_INODES]; rip++) {
	rip->i_prealloc_count = rip->i_prealloc_index = 0;
	rip->i_preallocation = 0; /* forbid preallocation */
	for (i = 0; i < EXT2_PREALLOC_BLOCKS; i++) {
		if (rip->i_prealloc_blocks[i] != NO_BLOCK) {
			free_block(rip->i_sp, rip->i_prealloc_blocks[i]);
			rip->i_prealloc_blocks[i] = NO_BLOCK;
		}
	}
  }
}


/*===========================================================================*
 *                              alloc_block                                  *
 *===========================================================================*/
PUBLIC block_t alloc_block(struct inode *rip, block_t block)
{
/* Allocate a block for inode. If block is provided, then use it as a goal:
 * try to allocate this block or his neghbors.
 * If block is not provided then goal is group, where inode lives.
 */
  block_t goal;
  block_t b;
  struct super_block *sp = rip->i_sp;

  if (sp->s_rd_only)
	panic("can't alloc block on read-only filesys.");

  /* Check for free blocks. First time discard preallocation,
   * next time return NO_BLOCK
   */
  if (!opt.use_reserved_blocks &&
      sp->s_free_blocks_count <= sp->s_r_blocks_count) {
	discard_preallocated_blocks(NULL);
  } else if (sp->s_free_blocks_count <= EXT2_PREALLOC_BLOCKS) {
	discard_preallocated_blocks(NULL);
  }

  if (!opt.use_reserved_blocks &&
      sp->s_free_blocks_count <= sp->s_r_blocks_count) {
	return(NO_BLOCK);
  } else if (sp->s_free_blocks_count == 0) {
	return(NO_BLOCK);
  }

  if (block != NO_BLOCK) {
	goal = block;
	if (rip->i_preallocation && rip->i_prealloc_count > 0) {
		/* check if goal is preallocated */
		b = rip->i_prealloc_blocks[rip->i_prealloc_index];
		if (block == b || (block + 1) == b) {
			/* use preallocated block */
			rip->i_prealloc_blocks[rip->i_prealloc_index] = NO_BLOCK;
			rip->i_prealloc_count--;
			rip->i_prealloc_index++;
			if (rip->i_prealloc_index >= EXT2_PREALLOC_BLOCKS) {
				rip->i_prealloc_index = 0;
				ASSERT(rip->i_prealloc_count == 0);
			}
			rip->i_bsearch = b;
			return b;
		} else {
			/* probably non-sequential write operation,
			 * disable preallocation for this inode.
			 */
			rip->i_preallocation = 0;
			discard_preallocated_blocks(rip);
		}
	}
  } else {
	  int group = (rip->i_num - 1) / sp->s_inodes_per_group;
	  goal = sp->s_blocks_per_group*group + sp->s_first_data_block;
  }

  if (rip->i_preallocation && rip->i_prealloc_count) {
	ext2_debug("There're preallocated blocks, but they're\
			neither used or freed!");
  }

  b = alloc_block_bit(sp, goal, rip);

  if (b != NO_BLOCK)
	rip->i_bsearch = b;

  return b;
}


FORWARD _PROTOTYPE( void check_block_number, (block_t block,
					      struct super_block *sp,
					      struct group_desc *gd) );

/*===========================================================================*
 *                              alloc_block_bit                              *
 *===========================================================================*/
PRIVATE block_t alloc_block_bit(sp, goal, rip)
struct super_block *sp;		/* the filesystem to allocate from */
block_t goal;			/* try to allocate near this block */
struct inode *rip;		/* used for preallocation */
{
  block_t block = NO_BLOCK;	/* allocated block */
  int word;			/* word in block bitmap */
  bit_t	bit = -1;
  int group;
  char update_bsearch = FALSE;
  int i;

  if (goal >= sp->s_blocks_count ||
      (goal < sp->s_first_data_block && goal != 0)) {
	goal = sp->s_bsearch;
  }

  if (goal <= sp->s_bsearch) {
	/* No reason to search in a place with no free blocks */
	goal = sp->s_bsearch;
	update_bsearch = TRUE;
  }

  /* Figure out where to start the bit search. */
  word = ((goal - sp->s_first_data_block) % sp->s_blocks_per_group)
			/ FS_BITCHUNK_BITS;

  /* Try to allocate block at any group starting from the goal's group.
   * First time goal's group is checked from the word=goal, after all
   * groups checked, it's checked again from word=0, that's why "i <=".
   */
  group = (goal - sp->s_first_data_block) / sp->s_blocks_per_group;
  for (i = 0; i <= sp->s_groups_count; i++, group++) {
	struct buf *bp;
	struct group_desc *gd;

	if (group >= sp->s_groups_count)
		group = 0;

	gd = get_group_desc(group);
	if (gd == NULL)
		panic("can't get group_desc to alloc block");

	if (gd->free_blocks_count == 0) {
		word = 0;
		continue;
	}

	bp = get_block(sp->s_dev, gd->block_bitmap, NORMAL);

	if (rip->i_preallocation &&
	    gd->free_blocks_count >= (EXT2_PREALLOC_BLOCKS * 4) ) {
		/* Try to preallocate blocks */
		if (rip->i_prealloc_count != 0) {
			/* kind of glitch... */
			discard_preallocated_blocks(rip);
			ext2_debug("warning, discarding previously preallocated\
				    blocks! It had to be done by another code.");
		}
		ASSERT(rip->i_prealloc_count == 0);
		/* we preallocate bytes only */
		ASSERT(EXT2_PREALLOC_BLOCKS == sizeof(char)*CHAR_BIT);

		bit = setbyte(bp->b_bitmap, sp->s_blocks_per_group, word);
		if (bit != -1) {
			block = bit + sp->s_first_data_block +
					group * sp->s_blocks_per_group;
			check_block_number(block, sp, gd);

			/* We preallocate a byte starting from block.
			 * First preallocated block will be returned as
			 * normally allocated block.
			 */
			for (i = 1; i < EXT2_PREALLOC_BLOCKS; i++) {
				check_block_number(block + i, sp, gd);
				rip->i_prealloc_blocks[i-1] = block + i;
			}
			rip->i_prealloc_index = 0;
			rip->i_prealloc_count = EXT2_PREALLOC_BLOCKS - 1;

			bp->b_dirt = DIRTY; /* by setbyte */
			put_block(bp, MAP_BLOCK);

			gd->free_blocks_count -= EXT2_PREALLOC_BLOCKS;
			sp->s_free_blocks_count -= EXT2_PREALLOC_BLOCKS;
			group_descriptors_dirty = DIRTY;
			return block;
		}
	}

        bit = setbit(bp->b_bitmap, sp->s_blocks_per_group, word);
	if (bit == -1) {
		if (word == 0) {
			panic("ext2: allocator failed to allocate a bit in bitmap\
				with free bits.");
		} else {
			word = 0;
			continue;
		}
	}

	block = sp->s_first_data_block + group * sp->s_blocks_per_group + bit;
	check_block_number(block, sp, gd);

        bp->b_dirt = DIRTY; /* Now it's safe to mark it as dirty */
	put_block(bp, MAP_BLOCK);

	gd->free_blocks_count--;
	sp->s_free_blocks_count--;
	group_descriptors_dirty = DIRTY;

	if (update_bsearch && block != -1 && block != NO_BLOCK) {
		/* We searched from the beginning, update bsearch. */
		sp->s_bsearch = block;
	}

	return block;
  }

  return block;
}


/*===========================================================================*
 *                        free_block	                                     *
 *===========================================================================*/
PUBLIC void free_block(struct super_block *sp, bit_t bit_returned)
{
/* Return a block by turning off its bitmap bit. */
  int group;		/* group number of bit_returned */
  int bit;		/* bit_returned number within its group */
  struct buf *bp;
  struct group_desc *gd;

  if (sp->s_rd_only)
	panic("can't free bit on read-only filesys.");

  if (bit_returned >= sp->s_blocks_count ||
      bit_returned < sp->s_first_data_block)
	panic("trying to free block %d beyond blocks scope.",
		bit_returned);

  /* At first search group, to which bit_returned belongs to
   * and figure out in what word bit is stored.
   */
  group = (bit_returned - sp->s_first_data_block) / sp->s_blocks_per_group;
  bit = (bit_returned - sp->s_first_data_block) % sp->s_blocks_per_group;

  gd = get_group_desc(group);
  if (gd == NULL)
	panic("can't get group_desc to alloc block");

  /* We might be buggy (No way! :P), so check if we deallocate
   * data block, but not control (system) block.
   * This should never happen.
   */
  if (bit_returned == gd->inode_bitmap || bit_returned == gd->block_bitmap
      || (bit_returned >= gd->inode_table
          && bit_returned < (gd->inode_table + sp->s_itb_per_group))) {
	ext2_debug("ext2: freeing non-data block %d\n", bit_returned);
	panic("trying to deallocate \
		system/control block, hardly poke author.");
  }

  bp = get_block(sp->s_dev, gd->block_bitmap, NORMAL);

  if (unsetbit(bp->b_bitmap, bit))
	panic("Tried to free unused block", bit_returned);

  bp->b_dirt = DIRTY;
  put_block(bp, MAP_BLOCK);

  gd->free_blocks_count++;
  sp->s_free_blocks_count++;

  group_descriptors_dirty = DIRTY;

  if (bit_returned < sp->s_bsearch)
	sp->s_bsearch = bit_returned;
}


PRIVATE void check_block_number(block_t block, struct super_block *sp,
				struct group_desc *gd)
{

  /* Check if we allocated a data block, but not control (system) block.
   * Only major bug can cause us to allocate wrong block. If it happens,
   * we panic (and don't bloat filesystem's bitmap).
   */
  if (block == gd->inode_bitmap || block == gd->block_bitmap ||
      (block >= gd->inode_table
       && block < (gd->inode_table + sp->s_itb_per_group))) {
	ext2_debug("ext2: allocating non-data block %d\n", block);
	panic("ext2: block allocator tryed to return \
		system/control block, poke author.\n");
  }

  if (block >= sp->s_blocks_count) {
	panic("ext2: allocator returned blocknum greater, than \
			total number of blocks.\n");
  }
}
