- The lmfs_get_block*(3) API calls may now return an error. The idea is to encourage a next generation of file system services to do a better job at dealing with block read errors than the MFS-derived implementations do. These existing file systems have been changed to panic immediately upon getting a block read error, in order to let unchecked errors cause corruption. Note that libbdev already retries failing I/O operations a few times first. - The libminixfs block device I/O module (bio.c) now deals properly with end-of-file conditions on block devices. Since a device or partition size may not be a multiple of the root file system's block size, support for partial block retrival has been added, with a new internal lmfs_get_partial_block(3) call. A new test program, test85, tests the new handling of EOF conditions when reading, writing, and memory-mapping a block device. Change-Id: I05e35b6b8851488328a2679da635ebba0c6d08ce
		
			
				
	
	
		
			377 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			377 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* This file is the counterpart of "read.c".  It contains the code for writing
 | 
						|
 * insofar as this is not contained in fs_readwrite().
 | 
						|
 *
 | 
						|
 * The entry points into this file are
 | 
						|
 *   write_map:    write a new block into an inode
 | 
						|
 *   new_block:    acquire a new block
 | 
						|
 *   zero_block:   overwrite a block with zeroes
 | 
						|
 *
 | 
						|
 * Created (MFS based):
 | 
						|
 *   February 2010 (Evgeniy Ivanov)
 | 
						|
 */
 | 
						|
 | 
						|
#include "fs.h"
 | 
						|
#include <string.h>
 | 
						|
#include <assert.h>
 | 
						|
#include <sys/param.h>
 | 
						|
#include "buf.h"
 | 
						|
#include "inode.h"
 | 
						|
#include "super.h"
 | 
						|
 | 
						|
static void wr_indir(struct buf *bp, int index, block_t block);
 | 
						|
static int empty_indir(struct buf *, struct super_block *);
 | 
						|
 | 
						|
/*===========================================================================*
 | 
						|
 *				write_map				     *
 | 
						|
 *===========================================================================*/
 | 
						|
int write_map(rip, position, new_wblock, op)
 | 
						|
struct inode *rip;		/* pointer to inode to be changed */
 | 
						|
off_t position;			/* file address to be mapped */
 | 
						|
block_t new_wblock;		/* block # to be inserted */
 | 
						|
int op;				/* special actions */
 | 
						|
{
 | 
						|
/* Write a new block into an inode.
 | 
						|
 *
 | 
						|
 * If op includes WMAP_FREE, free the block corresponding to that position
 | 
						|
 * in the inode ('new_wblock' is ignored then). Also free the indirect block
 | 
						|
 * if that was the last entry in the indirect block.
 | 
						|
 * Also free the double/triple indirect block if that was the last entry in
 | 
						|
 * the double/triple indirect block.
 | 
						|
 * It's the only function which should take care about rip->i_blocks counter.
 | 
						|
 */
 | 
						|
  int index1 = 0, index2 = 0, index3 = 0; /* indexes in single..triple indirect blocks */
 | 
						|
  long excess, block_pos;
 | 
						|
  char new_ind = 0, new_dbl = 0, new_triple = 0;
 | 
						|
  int single = 0, triple = 0;
 | 
						|
  block_t old_block = NO_BLOCK, b1 = NO_BLOCK, b2 = NO_BLOCK, b3 = NO_BLOCK;
 | 
						|
  struct buf *bp = NULL,
 | 
						|
             *bp_dindir = NULL,
 | 
						|
             *bp_tindir = NULL;
 | 
						|
  static char first_time = TRUE;
 | 
						|
  static long addr_in_block;
 | 
						|
  static long addr_in_block2;
 | 
						|
  static long doub_ind_s;
 | 
						|
  static long triple_ind_s;
 | 
						|
  static long out_range_s;
 | 
						|
 | 
						|
  if (first_time) {
 | 
						|
	addr_in_block = rip->i_sp->s_block_size / BLOCK_ADDRESS_BYTES;
 | 
						|
	addr_in_block2 = addr_in_block * addr_in_block;
 | 
						|
	doub_ind_s = EXT2_NDIR_BLOCKS + addr_in_block;
 | 
						|
	triple_ind_s = doub_ind_s + addr_in_block2;
 | 
						|
	out_range_s = triple_ind_s + addr_in_block2 * addr_in_block;
 | 
						|
	first_time = FALSE;
 | 
						|
  }
 | 
						|
 | 
						|
  block_pos = position / rip->i_sp->s_block_size; /* relative blk # in file */
 | 
						|
  rip->i_dirt = IN_DIRTY;		/* inode will be changed */
 | 
						|
 | 
						|
  /* Is 'position' to be found in the inode itself? */
 | 
						|
  if (block_pos < EXT2_NDIR_BLOCKS) {
 | 
						|
	if (rip->i_block[block_pos] != NO_BLOCK && (op & WMAP_FREE)) {
 | 
						|
		free_block(rip->i_sp, rip->i_block[block_pos]);
 | 
						|
		rip->i_block[block_pos] = NO_BLOCK;
 | 
						|
		rip->i_blocks -= rip->i_sp->s_sectors_in_block;
 | 
						|
	} else {
 | 
						|
		rip->i_block[block_pos] = new_wblock;
 | 
						|
		rip->i_blocks += rip->i_sp->s_sectors_in_block;
 | 
						|
	}
 | 
						|
	return(OK);
 | 
						|
  }
 | 
						|
 | 
						|
  /* It is not in the inode, so it must be single, double or triple indirect */
 | 
						|
  if (block_pos < doub_ind_s) {
 | 
						|
      b1 = rip->i_block[EXT2_NDIR_BLOCKS]; /* addr of single indirect block */
 | 
						|
      index1 = block_pos - EXT2_NDIR_BLOCKS;
 | 
						|
      single = TRUE;
 | 
						|
  } else if (block_pos >= out_range_s) { /* TODO: do we need it? */
 | 
						|
	return(EFBIG);
 | 
						|
  } else {
 | 
						|
	/* double or triple indirect block. At first if it's triple,
 | 
						|
	 * find double indirect block.
 | 
						|
	 */
 | 
						|
	excess = block_pos - doub_ind_s;
 | 
						|
	b2 = rip->i_block[EXT2_DIND_BLOCK];
 | 
						|
	if (block_pos >= triple_ind_s) {
 | 
						|
		b3 = rip->i_block[EXT2_TIND_BLOCK];
 | 
						|
		if (b3 == NO_BLOCK && !(op & WMAP_FREE)) {
 | 
						|
		/* Create triple indirect block. */
 | 
						|
			if ( (b3 = alloc_block(rip, rip->i_bsearch) ) == NO_BLOCK) {
 | 
						|
				ext2_debug("failed to allocate tblock near %d\n", rip->i_block[0]);
 | 
						|
				return(ENOSPC);
 | 
						|
			}
 | 
						|
			rip->i_block[EXT2_TIND_BLOCK] = b3;
 | 
						|
			rip->i_blocks += rip->i_sp->s_sectors_in_block;
 | 
						|
			new_triple = TRUE;
 | 
						|
		}
 | 
						|
		/* 'b3' is block number for triple indirect block, either old
 | 
						|
		 * or newly created.
 | 
						|
		 * If there wasn't one and WMAP_FREE is set, 'b3' is NO_BLOCK.
 | 
						|
		 */
 | 
						|
		if (b3 == NO_BLOCK && (op & WMAP_FREE)) {
 | 
						|
		/* WMAP_FREE and no triple indirect block - then no
 | 
						|
		 * double and single indirect blocks either.
 | 
						|
		 */
 | 
						|
			b1 = b2 = NO_BLOCK;
 | 
						|
		} else {
 | 
						|
			bp_tindir = get_block(rip->i_dev, b3, (new_triple ? NO_READ : NORMAL));
 | 
						|
			if (new_triple) {
 | 
						|
				zero_block(bp_tindir);
 | 
						|
				lmfs_markdirty(bp_tindir);
 | 
						|
			}
 | 
						|
			excess = block_pos - triple_ind_s;
 | 
						|
			index3 = excess / addr_in_block2;
 | 
						|
			b2 = rd_indir(bp_tindir, index3);
 | 
						|
			excess = excess % addr_in_block2;
 | 
						|
		}
 | 
						|
		triple = TRUE;
 | 
						|
	}
 | 
						|
 | 
						|
	if (b2 == NO_BLOCK && !(op & WMAP_FREE)) {
 | 
						|
	/* Create the double indirect block. */
 | 
						|
		if ( (b2 = alloc_block(rip, rip->i_bsearch) ) == NO_BLOCK) {
 | 
						|
			/* Release triple ind blk. */
 | 
						|
			put_block(bp_tindir);
 | 
						|
			ext2_debug("failed to allocate dblock near %d\n", rip->i_block[0]);
 | 
						|
			return(ENOSPC);
 | 
						|
		}
 | 
						|
		if (triple) {
 | 
						|
			wr_indir(bp_tindir, index3, b2);  /* update triple indir */
 | 
						|
			lmfs_markdirty(bp_tindir);
 | 
						|
		} else {
 | 
						|
			rip->i_block[EXT2_DIND_BLOCK] = b2;
 | 
						|
		}
 | 
						|
		rip->i_blocks += rip->i_sp->s_sectors_in_block;
 | 
						|
		new_dbl = TRUE; /* set flag for later */
 | 
						|
	}
 | 
						|
 | 
						|
	/* 'b2' is block number for double indirect block, either old
 | 
						|
	 * or newly created.
 | 
						|
	 * If there wasn't one and WMAP_FREE is set, 'b2' is NO_BLOCK.
 | 
						|
	 */
 | 
						|
	if (b2 == NO_BLOCK && (op & WMAP_FREE)) {
 | 
						|
	/* WMAP_FREE and no double indirect block - then no
 | 
						|
	 * single indirect block either.
 | 
						|
	 */
 | 
						|
		b1 = NO_BLOCK;
 | 
						|
	} else {
 | 
						|
		bp_dindir = get_block(rip->i_dev, b2, (new_dbl ? NO_READ : NORMAL));
 | 
						|
		if (new_dbl) {
 | 
						|
			zero_block(bp_dindir);
 | 
						|
			lmfs_markdirty(bp_dindir);
 | 
						|
		}
 | 
						|
		index2 = excess / addr_in_block;
 | 
						|
		b1 = rd_indir(bp_dindir, index2);
 | 
						|
		index1 = excess % addr_in_block;
 | 
						|
	}
 | 
						|
	single = FALSE;
 | 
						|
  }
 | 
						|
 | 
						|
  /* b1 is now single indirect block or NO_BLOCK; 'index' is index.
 | 
						|
   * We have to create the indirect block if it's NO_BLOCK. Unless
 | 
						|
   * we're freing (WMAP_FREE).
 | 
						|
   */
 | 
						|
  if (b1 == NO_BLOCK && !(op & WMAP_FREE)) {
 | 
						|
	if ( (b1 = alloc_block(rip, rip->i_bsearch) ) == NO_BLOCK) {
 | 
						|
		/* Release dbl and triple indirect blks. */
 | 
						|
		put_block(bp_dindir);
 | 
						|
		put_block(bp_tindir);
 | 
						|
		ext2_debug("failed to allocate dblock near %d\n", rip->i_block[0]);
 | 
						|
		return(ENOSPC);
 | 
						|
	}
 | 
						|
	if (single) {
 | 
						|
		rip->i_block[EXT2_NDIR_BLOCKS] = b1; /* update inode single indirect */
 | 
						|
	} else {
 | 
						|
		wr_indir(bp_dindir, index2, b1);  /* update dbl indir */
 | 
						|
		lmfs_markdirty(bp_dindir);
 | 
						|
	}
 | 
						|
	rip->i_blocks += rip->i_sp->s_sectors_in_block;
 | 
						|
	new_ind = TRUE;
 | 
						|
  }
 | 
						|
 | 
						|
  /* b1 is indirect block's number (unless it's NO_BLOCK when we're
 | 
						|
   * freeing).
 | 
						|
   */
 | 
						|
  if (b1 != NO_BLOCK) {
 | 
						|
	bp = get_block(rip->i_dev, b1, (new_ind ? NO_READ : NORMAL) );
 | 
						|
	if (new_ind)
 | 
						|
		zero_block(bp);
 | 
						|
	if (op & WMAP_FREE) {
 | 
						|
		if ((old_block = rd_indir(bp, index1)) != NO_BLOCK) {
 | 
						|
			free_block(rip->i_sp, old_block);
 | 
						|
			rip->i_blocks -= rip->i_sp->s_sectors_in_block;
 | 
						|
			wr_indir(bp, index1, NO_BLOCK);
 | 
						|
		}
 | 
						|
 | 
						|
		/* Last reference in the indirect block gone? Then
 | 
						|
		 * free the indirect block.
 | 
						|
		 */
 | 
						|
		if (empty_indir(bp, rip->i_sp)) {
 | 
						|
			free_block(rip->i_sp, b1);
 | 
						|
			rip->i_blocks -= rip->i_sp->s_sectors_in_block;
 | 
						|
			b1 = NO_BLOCK;
 | 
						|
			/* Update the reference to the indirect block to
 | 
						|
			 * NO_BLOCK - in the double indirect block if there
 | 
						|
			 * is one, otherwise in the inode directly.
 | 
						|
			 */
 | 
						|
			if (single) {
 | 
						|
				rip->i_block[EXT2_NDIR_BLOCKS] = b1;
 | 
						|
			} else {
 | 
						|
				wr_indir(bp_dindir, index2, b1);
 | 
						|
				lmfs_markdirty(bp_dindir);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		wr_indir(bp, index1, new_wblock);
 | 
						|
		rip->i_blocks += rip->i_sp->s_sectors_in_block;
 | 
						|
	}
 | 
						|
	/* b1 equals NO_BLOCK only when we are freeing up the indirect block. */
 | 
						|
	if(b1 != NO_BLOCK)
 | 
						|
		lmfs_markdirty(bp);
 | 
						|
	put_block(bp);
 | 
						|
  }
 | 
						|
 | 
						|
  /* If the single indirect block isn't there (or was just freed),
 | 
						|
   * see if we have to keep the double indirect block, if any.
 | 
						|
   */
 | 
						|
  if (b1 == NO_BLOCK && !single && b2 != NO_BLOCK &&
 | 
						|
     empty_indir(bp_dindir, rip->i_sp)) {
 | 
						|
	free_block(rip->i_sp, b2);
 | 
						|
	rip->i_blocks -= rip->i_sp->s_sectors_in_block;
 | 
						|
	b2 = NO_BLOCK;
 | 
						|
	if (triple) {
 | 
						|
		wr_indir(bp_tindir, index3, b2);  /* update triple indir */
 | 
						|
		lmfs_markdirty(bp_tindir);
 | 
						|
	} else {
 | 
						|
		rip->i_block[EXT2_DIND_BLOCK] = b2;
 | 
						|
	}
 | 
						|
  }
 | 
						|
  /* If the double indirect block isn't there (or was just freed),
 | 
						|
   * see if we have to keep the triple indirect block, if any.
 | 
						|
   */
 | 
						|
  if (b2 == NO_BLOCK && triple && b3 != NO_BLOCK &&
 | 
						|
     empty_indir(bp_tindir, rip->i_sp)) {
 | 
						|
	free_block(rip->i_sp, b3);
 | 
						|
	rip->i_blocks -= rip->i_sp->s_sectors_in_block;
 | 
						|
	rip->i_block[EXT2_TIND_BLOCK] = NO_BLOCK;
 | 
						|
  }
 | 
						|
 | 
						|
  put_block(bp_dindir);			/* release double indirect blk */
 | 
						|
  put_block(bp_tindir);			/* release triple indirect blk */
 | 
						|
 | 
						|
  return(OK);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*===========================================================================*
 | 
						|
 *				wr_indir				     *
 | 
						|
 *===========================================================================*/
 | 
						|
static void wr_indir(bp, wrindex, block)
 | 
						|
struct buf *bp;			/* pointer to indirect block */
 | 
						|
int wrindex;			/* index into *bp */
 | 
						|
block_t block;			/* block to write */
 | 
						|
{
 | 
						|
/* Given a pointer to an indirect block, write one entry. */
 | 
						|
 | 
						|
  if(bp == NULL)
 | 
						|
	panic("wr_indir() on NULL");
 | 
						|
 | 
						|
  /* write a block into an indirect block */
 | 
						|
  b_ind(bp)[wrindex] = conv4(le_CPU, block);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*===========================================================================*
 | 
						|
 *				empty_indir				     *
 | 
						|
 *===========================================================================*/
 | 
						|
static int empty_indir(bp, sb)
 | 
						|
struct buf *bp;			/* pointer to indirect block */
 | 
						|
struct super_block *sb;		/* superblock of device block resides on */
 | 
						|
{
 | 
						|
/* Return nonzero if the indirect block pointed to by bp contains
 | 
						|
 * only NO_BLOCK entries.
 | 
						|
 */
 | 
						|
  long addr_in_block = sb->s_block_size/4; /* 4 bytes per addr */
 | 
						|
  int i;
 | 
						|
  for(i = 0; i < addr_in_block; i++)
 | 
						|
	if(b_ind(bp)[i] != NO_BLOCK)
 | 
						|
		return(0);
 | 
						|
  return(1);
 | 
						|
}
 | 
						|
 | 
						|
/*===========================================================================*
 | 
						|
 *				new_block				     *
 | 
						|
 *===========================================================================*/
 | 
						|
struct buf *new_block(rip, position)
 | 
						|
register struct inode *rip;	/* pointer to inode */
 | 
						|
off_t position;			/* file pointer */
 | 
						|
{
 | 
						|
/* Acquire a new block and return a pointer to it. */
 | 
						|
  struct buf *bp;
 | 
						|
  int r;
 | 
						|
  block_t b;
 | 
						|
 | 
						|
  /* Is another block available? */
 | 
						|
  if ( (b = read_map(rip, position, 0)) == NO_BLOCK) {
 | 
						|
	/* Check if this position follows last allocated
 | 
						|
	 * block.
 | 
						|
	 */
 | 
						|
	block_t goal = NO_BLOCK;
 | 
						|
	if (rip->i_last_pos_bl_alloc != 0) {
 | 
						|
		off_t position_diff = position - rip->i_last_pos_bl_alloc;
 | 
						|
		if (rip->i_bsearch == 0) {
 | 
						|
			/* Should never happen, but not critical */
 | 
						|
			ext2_debug("warning, i_bsearch is 0, while\
 | 
						|
					i_last_pos_bl_alloc is not!");
 | 
						|
		}
 | 
						|
		if (position_diff <= rip->i_sp->s_block_size) {
 | 
						|
			goal = rip->i_bsearch + 1;
 | 
						|
		} else {
 | 
						|
			/* Non-sequential write operation,
 | 
						|
			 * disable preallocation
 | 
						|
			 * for this inode.
 | 
						|
			 */
 | 
						|
			rip->i_preallocation = 0;
 | 
						|
			discard_preallocated_blocks(rip);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if ( (b = alloc_block(rip, goal) ) == NO_BLOCK) {
 | 
						|
		err_code = ENOSPC;
 | 
						|
		return(NULL);
 | 
						|
	}
 | 
						|
	if ( (r = write_map(rip, position, b, 0)) != OK) {
 | 
						|
		free_block(rip->i_sp, b);
 | 
						|
		err_code = r;
 | 
						|
		ext2_debug("write_map failed\n");
 | 
						|
		return(NULL);
 | 
						|
	}
 | 
						|
	rip->i_last_pos_bl_alloc = position;
 | 
						|
	if (position == 0) {
 | 
						|
		/* rip->i_last_pos_bl_alloc points to the block position,
 | 
						|
		 * and zero indicates first usage, thus just increment.
 | 
						|
		 */
 | 
						|
		rip->i_last_pos_bl_alloc++;
 | 
						|
	}
 | 
						|
  }
 | 
						|
 | 
						|
  r = lmfs_get_block_ino(&bp, rip->i_dev, b, NO_READ, rip->i_num,
 | 
						|
	rounddown(position, rip->i_sp->s_block_size));
 | 
						|
  if (r != OK)
 | 
						|
	panic("ext2: error getting block (%llu,%u): %d", rip->i_dev, b, r);
 | 
						|
  zero_block(bp);
 | 
						|
  return(bp);
 | 
						|
}
 | 
						|
 | 
						|
/*===========================================================================*
 | 
						|
 *				zero_block				     *
 | 
						|
 *===========================================================================*/
 | 
						|
void zero_block(bp)
 | 
						|
register struct buf *bp;	/* pointer to buffer to zero */
 | 
						|
{
 | 
						|
/* Zero a block. */
 | 
						|
  ASSERT(bp->data);
 | 
						|
  memset(b_data(bp), 0, lmfs_fs_block_size());
 | 
						|
  lmfs_markdirty(bp);
 | 
						|
}
 |