minix3/fs/ext2/write.c

382 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, INDIRECT_BLOCK);
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, INDIRECT_BLOCK);
put_block(bp_tindir, INDIRECT_BLOCK);
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_markclean(bp);
else
lmfs_markdirty(bp);
put_block(bp, INDIRECT_BLOCK);
}
/* 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 we don't have to keep it, don't bother writing it out.
*/
if (b1 == NO_BLOCK && !single && b2 != NO_BLOCK &&
empty_indir(bp_dindir, rip->i_sp)) {
lmfs_markclean(bp_dindir);
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 we don't have to keep it, don't bother writing it out.
*/
if (b2 == NO_BLOCK && triple && b3 != NO_BLOCK &&
empty_indir(bp_tindir, rip->i_sp)) {
lmfs_markclean(bp_tindir);
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, INDIRECT_BLOCK); /* release double indirect blk */
put_block(bp_tindir, INDIRECT_BLOCK); /* 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. */
register 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++;
}
}
bp = lmfs_get_block_ino(rip->i_dev, b, NO_READ, rip->i_num,
rounddown(position, rip->i_sp->s_block_size));
zero_block(bp);
return(bp);
}
/*===========================================================================*
* zero_block *
*===========================================================================*/
void zero_block(bp)
register struct buf *bp; /* pointer to buffer to zero */
{
/* Zero a block. */
ASSERT(lmfs_bytes(bp) > 0);
ASSERT(bp->data);
memset(b_data(bp), 0, (size_t) lmfs_bytes(bp));
lmfs_markdirty(bp);
}