diff options
-rw-r--r-- | ext2fs/truncate.c | 408 |
1 files changed, 158 insertions, 250 deletions
diff --git a/ext2fs/truncate.c b/ext2fs/truncate.c index 58725381..01796b27 100644 --- a/ext2fs/truncate.c +++ b/ext2fs/truncate.c @@ -2,7 +2,7 @@ Copyright (C) 1995 Free Software Foundation, Inc. - Converted to work under the hurd by Miles Bader <miles@gnu.ai.mit.edu> + Written by Miles Bader <miles@gnu.ai.mit.edu> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -18,21 +18,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -/* - * linux/fs/ext2/truncate.c - * - * Copyright (C) 1992, 1993, 1994, 1995 - * Remy Card (card@masi.ibp.fr) - * Laboratoire MASI - Institut Blaise Pascal - * Universite Pierre et Marie Curie (Paris VI) - * - * from - * - * linux/fs/minix/truncate.c - * - * Copyright (C) 1991, 1992 Linus Torvalds - */ - #include "ext2fs.h" #ifdef DONT_CACHE_MEMORY_OBJECTS @@ -43,274 +28,188 @@ /* ---------------------------------------------------------------- */ -/* Write something to each page from START to END inclusive of memory - object OBJ, but make sure the data doesns't actually change. */ -static void -poke_pages (memory_object_t obj, - vm_offset_t start, - vm_offset_t end) +/* A sequence of blocks to be freed in NODE. */ +struct free_block_run { - vm_address_t addr, poke; - vm_size_t len; - error_t err; - - while (start < end) - { - len = 8 * vm_page_size; - if (len > end - start) - len = end - start; - addr = 0; - err = vm_map (mach_task_self (), &addr, len, 0, 1, obj, start, 0, - VM_PROT_WRITE|VM_PROT_READ, VM_PROT_READ|VM_PROT_WRITE, 0); - if (!err) - { - for (poke = addr; poke < addr + len; poke += vm_page_size) - *(volatile int *)poke = *(volatile int *)poke; - vm_deallocate (mach_task_self (), addr, len); - } - start += len; - } + block_t first_block; + unsigned long num_blocks; + struct node *node; +}; + +/* Initialize FBR, pointing to NODE. */ +static inline void +free_block_run_init (struct free_block_run *fbr, struct node *node) +{ + fbr->num_blocks = 0; + fbr->node = node; } - -/* ---------------------------------------------------------------- */ - -#define DIRECT_BLOCK(length) \ - ((length + block_size - 1) >> log2_block_size) -#define INDIRECT_BLOCK(length, offset) ((int)DIRECT_BLOCK(length) - offset) -#define DINDIRECT_BLOCK(length, offset) \ - (((int)DIRECT_BLOCK(length) - offset) / addr_per_block) -#define TINDIRECT_BLOCK(length) \ - (((int)DIRECT_BLOCK(length) \ - - (addr_per_block * addr_per_block + addr_per_block + EXT2_NDIR_BLOCKS)) \ - / (addr_per_block * addr_per_block)) -static void -trunc_direct (struct node * node, unsigned long length) +static inline void +_free_block_run_flush (struct free_block_run *fbr, unsigned long count) { - u32 block; - int i; - unsigned long block_to_free = 0; - unsigned long free_count = 0; - int direct_block = DIRECT_BLOCK(length); - - ext2_debug ("truncating direct blocks from %lu, block %d", - length, direct_block); + fbr->node->dn_stat.st_blocks -= count << log2_stat_blocks_per_fs_block; + fbr->node->dn_stat_dirty = 1; + ext2_free_blocks (fbr->first_block, count); +} - for (i = direct_block ; i < EXT2_NDIR_BLOCKS ; i++) +/* Add BLOCK to the list of blocks to be freed in FBR. */ +static inline void +free_block_run_add (struct free_block_run *fbr, block_t block) +{ + unsigned long count = fbr->num_blocks; + if (count == 0) { - block = node->dn->info.i_data[i]; - if (!block) - continue; - - node->dn->info.i_data[i] = 0; - - node->dn_stat.st_blocks -= 1 << log2_stat_blocks_per_fs_block; - node->dn_stat_dirty = 1; + fbr->first_block = block; + fbr->num_blocks++; + } + else if (count > 0 && fbr->first_block == block - count) + fbr->num_blocks++; + else + { + _free_block_run_flush (fbr, count); + fbr->first_block = block; + fbr->num_blocks = 1; + } +} - if (free_count == 0) - { - block_to_free = block; - free_count++; - } - else if (free_count > 0 && block_to_free == block - free_count) - free_count++; - else - { - ext2_free_blocks (block_to_free, free_count); - block_to_free = block; - free_count = 1; - } +/* If *P is non-zero, set it to zero, and add the block it pointed to the + list of blocks to be freed in FBR. */ +static inline void +free_block_run_free_ptr (struct free_block_run *fbr, block_t *p) +{ + block_t block = *p; + if (block) + { + *p = 0; + free_block_run_add (fbr, block); } +} - if (free_count > 0) - ext2_free_blocks (block_to_free, free_count); +/* Free any blocks left in FBR, and cleanup any resources it's using. */ +static inline void +free_block_run_finish (struct free_block_run *fbr) +{ + unsigned long count = fbr->num_blocks; + if (count > 0) + _free_block_run_flush (fbr, count); } /* ---------------------------------------------------------------- */ +/* Free any direct blocks starting with block END. */ static void -trunc_indirect (struct node * node, unsigned long length, int offset, u32 * p) +trunc_direct (struct node *node, block_t end, struct free_block_run *fbr) { - int i, block; - char * ind_bh; - u32 * ind; - int modified = 0; - unsigned long block_to_free = 0; - unsigned long free_count = 0; - int indirect_block = INDIRECT_BLOCK (length, offset); - - if (indirect_block < 0) - indirect_block = 0; - - ext2_debug ("truncating indirect (offs = %d) blocks from %lu, block %d", - offset, length, indirect_block); + block_t *blocks = node->dn->info.i_data; - block = *p; - if (!block) - return; + ext2_debug ("truncating direct blocks from %d", direct_block); - ind_bh = bptr (block); + while (end < EXT2_NDIR_BLOCKS) + free_block_run_free_ptr (fbr, blocks + end++); +} - for (i = indirect_block ; i < addr_per_block ; i++) +/* Free any blocks in NODE greater than or equal to END that are rooted in + the indirect block *P; OFFSET should be the block position that *P + corresponds to. For each block pointer in *P that should be freed, + FREE_BLOCK is called with a pointer to the entry for that block, and the + index of the entry within *P. If every block in *P is freed, then *P is + set to 0, otherwise it is left alone. */ +static void +trunc_indirect (struct node *node, block_t end, + block_t *p, block_t offset, + void (*free_block)(block_t *p, unsigned index), + struct free_block_run *fbr) +{ + if (*p) { - ind = (u32 *)ind_bh + i; - block = *ind; - - if (block) - { - *ind = 0; - - if (free_count == 0) - { - block_to_free = block; - free_count++; - } - else if (free_count > 0 && block_to_free == block - free_count) - free_count++; - else - { - ext2_free_blocks (block_to_free, free_count); - block_to_free = block; - free_count = 1; - } - - node->dn_stat.st_blocks -= 1 << log2_stat_blocks_per_fs_block; - node->dn_stat_dirty = 1; - - modified = 1; - } + unsigned index; + int modified = 0, all_freed = 1; + block_t *ind_bh = (block_t *)bptr (*p); + unsigned first = end < offset ? 0 : end - offset; + + for (index = first; index < addr_per_block; index++) + if (ind_bh[index]) + { + (*free_block)(ind_bh + index, index); + if (ind_bh[index]) + all_freed = 0; /* Some descendent hasn't been freed. */ + modified = 1; + } + + if (first == 0 && all_freed) + free_block_run_free_ptr (fbr, p); + else if (modified) + record_indir_poke (node, ind_bh); } - - if (free_count > 0) - ext2_free_blocks (block_to_free, free_count); - - ind = (u32 *) ind_bh; - for (i = 0; i < addr_per_block; i++) - if (*(ind++)) - break; - - if (i >= addr_per_block) +} + +static void +trunc_single_indirect (struct node *node, block_t end, + block_t *p, block_t offset, + struct free_block_run *fbr) +{ + void free_block (block_t *p, unsigned index) { - block = *p; - *p = 0; - node->dn_stat.st_blocks -= 1 << log2_stat_blocks_per_fs_block; - node->dn_stat_dirty = 1; - ext2_free_blocks (block, 1); + free_block_run_free_ptr (fbr, p); } - else if (modified) - record_indir_poke (node, ind_bh); + trunc_indirect (node, end, p, offset, free_block, fbr); } - -/* ---------------------------------------------------------------- */ static void -trunc_dindirect (struct node * node, unsigned long length, - int offset, u32 * p) +trunc_double_indirect (struct node *node, block_t end, + block_t *p, block_t offset, + struct free_block_run *fbr) { - int i, block; - char * dind_bh; - u32 * dind; - int modified = 0; - int dindirect_block = DINDIRECT_BLOCK (length, offset); - - if (dindirect_block < 0) - dindirect_block = 0; - - ext2_debug ("truncating dindirect (offs = %d) blocks from %lu, block %d", - offset, length, dindirect_block); - - block = *p; - if (!block) - return; - - dind_bh = bptr (block); - - for (i = dindirect_block ; i < addr_per_block ; i++) + void free_block (block_t *p, unsigned index) { - dind = i + (u32 *) dind_bh; - block = *dind; - - if (!block) - { - trunc_indirect (node, length, offset + (i * addr_per_block), dind); - modified = 1; - } + block_t entry_offs = offset + (index * addr_per_block); + trunc_single_indirect (node, end, p, entry_offs, fbr); } + trunc_indirect (node, end, p, offset, free_block, fbr); +} - dind = (u32 *) dind_bh; - for (i = 0; i < addr_per_block; i++) - if (*(dind++)) - break; - - if (i >= addr_per_block) +static void +trunc_triple_indirect (struct node *node, block_t end, + block_t *p, block_t offset, + struct free_block_run *fbr) +{ + void free_block (block_t *p, unsigned index) { - block = *p; - *p = 0; - node->dn_stat.st_blocks -= 1 << log2_stat_blocks_per_fs_block; - node->dn_stat_dirty = 1; - ext2_free_blocks (block, 1); + block_t entry_offs = offset + (index * addr_per_block * addr_per_block); + trunc_double_indirect (node, end, p, entry_offs, fbr); } - else if (modified) - record_indir_poke (node, dind_bh); + trunc_indirect (node, end, p, offset, free_block, fbr); } /* ---------------------------------------------------------------- */ +/* Write something to each page from START to END inclusive of memory + object OBJ, but make sure the data doesns't actually change. */ static void -trunc_tindirect (struct node * node, unsigned long length) +poke_pages (memory_object_t obj, vm_offset_t start, vm_offset_t end) { - int i, block; - char * tind_bh; - u32 * tind, * p; - int modified = 0; - int tindirect_block = TINDIRECT_BLOCK (length); - - if (tindirect_block < 0) - tindirect_block = 0; - - ext2_debug ("truncating tindirect blocks from %lu, block %d", - length, tindirect_block); - - p = node->dn->info.i_data + EXT2_TIND_BLOCK; - if (!(block = *p)) - return; - - tind_bh = bptr (block); - if (!tind_bh) + while (start < end) { - *p = 0; - return; - } + error_t err; + vm_size_t len = 8 * vm_page_size; + vm_address_t addr = 0; - for (i = tindirect_block ; i < addr_per_block ; i++) - { - tind = i + (u32 *) tind_bh; - trunc_dindirect(node, length, - (EXT2_NDIR_BLOCKS - + addr_per_block - + (i + 1) * addr_per_block * addr_per_block), - tind); - modified = 1; - } + if (len > end - start) + len = end - start; - tind = (u32 *) tind_bh; - for (i = 0; i < addr_per_block; i++) - if (*(tind++)) - break; + err = vm_map (mach_task_self (), &addr, len, 0, 1, obj, start, 0, + VM_PROT_WRITE|VM_PROT_READ, VM_PROT_READ|VM_PROT_WRITE, 0); + if (!err) + { + vm_address_t poke; + for (poke = addr; poke < addr + len; poke += vm_page_size) + *(volatile int *)poke = *(volatile int *)poke; + vm_deallocate (mach_task_self (), addr, len); + } - if (i >= addr_per_block) - { - block = *p; - *p = 0; - node->dn_stat.st_blocks -= 1 << log2_stat_blocks_per_fs_block; - node->dn_stat_dirty = 1; - ext2_free_blocks (block, 1); + start += len; } - else if (modified) - record_indir_poke (node, tind_bh); } - -/* ---------------------------------------------------------------- */ /* Flush all the data past the new size from the kernel. Also force any delayed copies of this data to take place immediately. (We are implicitly @@ -349,8 +248,8 @@ enable_delayed_copies (struct node *node) upi = node->dn->fileinfo; if (upi) ports_port_ref (upi->p); - spin_unlock (&node_to_page_lock); + if (upi) { pager_change_attributes (upi->p, MAY_CACHE, MEMORY_OBJECT_COPY_DELAY, 0); @@ -369,7 +268,7 @@ error_t diskfs_truncate (struct node *node, off_t length) { error_t err; - int offset; + off_t offset; assert (!diskfs_readonly); @@ -406,13 +305,22 @@ diskfs_truncate (struct node *node, off_t length) err = diskfs_catch_exception(); if (!err) { - trunc_direct(node, length); - trunc_indirect (node, length, EXT2_IND_BLOCK, - (u32 *) &node->dn->info.i_data[EXT2_IND_BLOCK]); - trunc_dindirect (node, length, EXT2_IND_BLOCK + - EXT2_ADDR_PER_BLOCK(sblock), - (u32 *) &node->dn->info.i_data[EXT2_DIND_BLOCK]); - trunc_tindirect (node, length); + block_t end = boffs_block (round_block (length)), offs; + block_t *bptrs = node->dn->info.i_data; + struct free_block_run fbr; + + free_block_run_init (&fbr, node); + + trunc_direct (node, end, &fbr); + + offs = EXT2_NDIR_BLOCKS; + trunc_single_indirect (node, end, bptrs + EXT2_IND_BLOCK, offs, &fbr); + offs += addr_per_block; + trunc_double_indirect (node, end, bptrs + EXT2_DIND_BLOCK, offs, &fbr); + offs += addr_per_block * addr_per_block; + trunc_triple_indirect (node, end, bptrs + EXT2_TIND_BLOCK, offs, &fbr); + + free_block_run_finish (&fbr); node->allocsize = round_block (length); |