diff options
-rw-r--r-- | ext2fs/truncate.c | 356 |
1 files changed, 207 insertions, 149 deletions
diff --git a/ext2fs/truncate.c b/ext2fs/truncate.c index 1aae239a..0ae368e6 100644 --- a/ext2fs/truncate.c +++ b/ext2fs/truncate.c @@ -1,3 +1,23 @@ +/* File truncation & growth routines. + + Copyright (C) 1995 Free Software Foundation, Inc. + + Converted to work under the hurd 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 + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + /* * linux/fs/ext2/truncate.c * @@ -37,124 +57,113 @@ - (addr_per_block * addr_per_block + addr_per_block + EXT2_NDIR_BLOCKS)) \ / (addr_per_block * addr_per_block)) -static int trunc_direct (struct node * node, unsigned long length) +static void +trunc_direct (struct node * node, unsigned long length) { - u32 * p; - int i, tmp; + u32 block; + int i; char * bh; unsigned long block_to_free = 0; unsigned long free_count = 0; - int retry = 0; int blocks = block_size / 512; int direct_block = DIRECT_BLOCK(length); - repeat: for (i = direct_block ; i < EXT2_NDIR_BLOCKS ; i++) { - p = node->dn.info.i_data + i; - tmp = *p; - if (!tmp) + block = node->dn.info.i_data[i]; + if (!block) continue; - bh = baddr(tmp); + bh = baddr(block); if (i < direct_block) goto repeat; - if ((bh && bh->b_count != 1) || tmp != *p) - { - retry = 1; - continue; - } + node->dn.info.i_data[i] = 0; - *p = 0; node->dn_stat.st_blocks -= blocks; - node->dirty = 1; + node->dn_stat_dirty = 1; if (free_count == 0) { - block_to_free = tmp; + block_to_free = block; free_count++; - } else if (free_count > 0 && block_to_free == tmp - 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 = tmp; + block_to_free = block; free_count = 1; } - /* ext2f_free_blocks (tmp, 1); */ + +#if 0 + ext2f_free_blocks (block, 1); +#endif } if (free_count > 0) ext2_free_blocks (block_to_free, free_count); - - return retry; } -static int trunc_indirect (struct node * node, unsigned long length, - int offset, u32 * p) +static void +trunc_indirect (struct node * node, unsigned long length, + int offset, u32 * p) { - int i, tmp; + int i, block; char * bh; char * ind_bh; u32 * ind; unsigned long block_to_free = 0; unsigned long free_count = 0; - int retry = 0; int blocks = block_size / 512; int indirect_block = INDIRECT_BLOCK (length, offset); - tmp = *p; - if (!tmp) + block = *p; + if (!block) return 0; - ind_bh = baddr (tmp); - if (tmp != *p) - return 1; + + ind_bh = baddr (block); if (!ind_bh) { *p = 0; return 0; } - repeat: for (i = indirect_block ; i < addr_per_block ; i++) { if (i < 0) i = 0; - if (i < indirect_block) - goto repeat; - ind = i + (u32 *) ind_bh; - tmp = *ind; - if (!tmp) + ind = (u32 *)ind_bh + i; + block = *ind; + if (!block) continue; - bh = baddr (tmp); + bh = baddr (block); if (i < indirect_block) goto repeat; - if ((bh && bh->b_count != 1) || tmp != *ind) - { - retry = 1; - continue; - } *ind = 0; - poke_loc (ind, sizeof *ind); + pokel_add (&node->dn.pokel, ind, sizeof *ind); if (free_count == 0) { - block_to_free = tmp; + block_to_free = block; free_count++; } - else if (free_count > 0 && block_to_free == tmp - 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 = tmp; + block_to_free = block; free_count = 1; } - /* ext2_free_blocks (tmp, 1); */ + +#if 0 + ext2_free_blocks (block, 1); +#endif + node->dn_stat.st_blocks -= blocks; - node->dirty = 1; + node->dn_stat_dirty = 1; } if (free_count > 0) @@ -165,59 +174,50 @@ static int trunc_indirect (struct node * node, unsigned long length, if (*(ind++)) break; if (i >= addr_per_block) - if (ind_bh->b_count != 1) - retry = 1; - else - { - tmp = *p; - *p = 0; - node->dn_stat.st_blocks -= blocks; - node->dirty = 1; - ext2_free_blocks (tmp, 1); - } + { + block = *p; + *p = 0; + node->dn_stat.st_blocks -= blocks; + node->dn_stat_dirty = 1; + ext2_free_blocks (block, 1); + } return retry; } -static int trunc_dindirect (struct node * node, unsigned long length, - int offset, u32 * p) +static void +trunc_dindirect (struct node * node, unsigned long length, + int offset, u32 * p) { - int i, tmp; + int i, block; char * dind_bh; u32 * dind; - int retry = 0; int blocks = block_size / 512; int dindirect_block = DINDIRECT_BLOCK (length, offset); - tmp = *p; - if (!tmp) + block = *p; + if (!block) return 0; - dind_bh = baddr (tmp); - if (tmp != *p) - return 1; - + dind_bh = baddr (block); if (!dind_bh) { *p = 0; return 0; } - repeat: for (i = dindirect_block ; i < addr_per_block ; i++) { if (i < 0) i = 0; - if (i < dindirect_block) - goto repeat; dind = i + (u32 *) dind_bh; - tmp = *dind; - if (!tmp) + block = *dind; + if (!block) continue; - retry |= trunc_indirect (node, offset + (i * addr_per_block), dind); + trunc_indirect (node, offset + (i * addr_per_block), dind); - poke_loc (dind_bh, block_size); + pokel_add (&node->dn.pokel, dindh_bh, block_size); } dind = (u32 *) dind_bh; @@ -226,23 +226,21 @@ static int trunc_dindirect (struct node * node, unsigned long length, break; if (i >= addr_per_block) - if (dind_bh->b_count != 1) - retry = 1; - else - { - tmp = *p; - *p = 0; - node->dn_stat.st_blocks -= blocks; - node->dirty = 1; - ext2_free_blocks (tmp, 1); - } + { + block = *p; + *p = 0; + node->dn_stat.st_blocks -= blocks; + node->dn_stat_dirty = 1; + ext2_free_blocks (block, 1); + } return retry; } -static int trunc_tindirect (struct node * node, unsigned long length) +static void +trunc_tindirect (struct node * node, unsigned long length) { - int i, tmp; + int i, block; char * tind_bh; u32 * tind, * p; int retry = 0; @@ -250,35 +248,28 @@ static int trunc_tindirect (struct node * node, unsigned long length) int tindirect_block = TINDIRECT_BLOCK (length); p = node->dn.info.i_data + EXT2_TIND_BLOCK; - if (!(tmp = *p)) + if (!(block = *p)) return 0; - tind_bh = baddr (tmp); - if (tmp != *p) - return 1; - + tind_bh = baddr (block); if (!tind_bh) { *p = 0; return 0; } -repeat: for (i = tindirect_block ; i < addr_per_block ; i++) { if (i < 0) i = 0; - if (i < tindirect_block) - goto repeat; tind = i + (u32 *) tind_bh; - retry |= - trunc_dindirect(node, - (EXT2_NDIR_BLOCKS - + addr_per_block - + (i + 1) * addr_per_block * addr_per_block), - tind); - poke_loc (tind_bh, block_size); + trunc_dindirect(node, + (EXT2_NDIR_BLOCKS + + addr_per_block + + (i + 1) * addr_per_block * addr_per_block), + tind); + pokel_add (&node->dn.pokel, tindh_bh, block_size); } tind = (u32 *) tind_bh; @@ -287,55 +278,82 @@ repeat: break; if (i >= addr_per_block) - if (tind_bh->b_count != 1) - retry = 1; - else - { - tmp = *p; - *p = 0; - node->dn_stat.st_blocks -= blocks; - node->dirty = 1; - ext2_free_blocks (tmp, 1); - } + { + block = *p; + *p = 0; + node->dn_stat.st_blocks -= blocks; + node->dn_stat_dirty = 1; + ext2_free_blocks (block, 1); + } +} + +/* ---------------------------------------------------------------- */ + +/* 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 + changing the data to zeros and doing it without the kernel's immediate + knowledge; accordingl we must help out the kernel thusly.) */ +static void +force_delayed_copies (struct node *node, off_t length) +{ + spin_lock (&node2pagelock); + upi = np->dn->fileinfo; + if (upi) + pager_reference (upi->p); + spin_unlock (&node2pagelock); + + if (upi) + { + mach_port_t obj; + + pager_change_attributes (upi->p, MAY_CACHE, MEMORY_OBJECT_COPY_NONE, 1); + obj = diskfs_get_filemap (np); + poke_pages (obj, round_page (length), round_page (np->allocsize)); + mach_port_deallocate (mach_task_self (), obj); + pager_flush_some (upi->p, round_page(length), np->allocsize - length, 1); + pager_unreference (upi->p); + } +} - return retry; +static void +enable_delayed_copies (struct node *node) +{ + spin_lock (&node2pagelock); + upi = np->dn->fileinfo; + if (upi) + pager_reference (upi->p); + spin_unlock (&node2pagelock); + if (upi) + { + pager_change_attributes (upi->p, MAY_CACHE, MEMORY_OBJECT_COPY_DELAY, 0); + pager_unreference (upi->p); + } } - -void ext2_truncate (struct node * node) + +/* ---------------------------------------------------------------- */ + +/* The user must define this function. Truncate locked node NP to be SIZE + bytes long. (If NP is already less than or equal to SIZE bytes + long, do nothing.) If this is a symlink (and diskfs_shortcut_symlink + is set) then this should clear the symlink, even if + diskfs_create_symlink_hook stores the link target elsewhere. */ +error_t +diskfs_truncate (struct node *np, off_t length) { - int retry; int offset; mode_t mode = node->dn_state.st_mode; - if (!(S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) - return; + if (S_ISDIR(mode)) + return EISDIR; + if (!S_ISREG(mode)) + return EINVAL; if (IS_APPEND(node) || IS_IMMUTABLE(node)) - return; + return EINVAL; - ext2_discard_prealloc(node); + if (length >= np->dn_stat.st_size) + return 0; - while (1) - { - down(&node->i_sem); - retry = trunc_direct(node, length); - retry |= - trunc_indirect (node, length, EXT2_IND_BLOCK, - (u32 *) &node->dn.info.i_data[EXT2_IND_BLOCK]); - retry |= - trunc_dindirect (node, length, EXT2_IND_BLOCK + - EXT2_ADDR_PER_BLOCK(sblock), - (u32 *) &node->dn.info.i_data[EXT2_DIND_BLOCK]); - retry |= trunc_tindirect (node, length); - up(&node->i_sem); - - if (!retry) - break; - - if (IS_SYNC(node) && node->dirty) - ext2_sync_inode (node); - current->counter = 0; - schedule (); - } + assert (!diskfs_readonly); /* * If the file is not being truncated to a block boundary, the @@ -343,15 +361,55 @@ void ext2_truncate (struct node * node) * zeroed in case it ever becomes accessible again because of * subsequent file growth. */ - offset = node->allocsize % block_size; - if (offset) + offset = length % block_size; + if (offset > 0) + { + diskfs_node_rdwr (np, (void *)zeroblock, length, block_size - offset, + 1, 0, 0); + diskfs_file_update (np, 1); + } + + ext2_discard_prealloc(node); + + force_delayed_copies (np, length); + + rwlock_writer_lock (&np->dn->allocptrlock); + + /* Update the size on disk; fsck will finish freeing blocks if necessary + should we crash. */ + np->dn_stat.st_size = length; + np->dn_set_mtime = 1; + np->dn_set_ctime = 1; + diskfs_node_update (np, 1); + + err = diskfs_catch_exception(); + if (!err) { - char * bh = baddr (node->allocsize / block_size); - memset (bh + offset, 0, block_size - offset); - poke_loc (bh + offset, block_size - offset); + 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); } node->dn_set_mtime = 1; node->dn_set_ctime = 1; - node->dirty = 1; + node->dn_stat_dirty = 1; + + /* Now we can permit delayed copies again. */ + enable_delayed_copies (np); +} + +/* The user must define this function. Grow the disk allocated to locked node + NP to be at least SIZE bytes, and set NP->allocsize to the actual + allocated size. (If the allocated size is already SIZE bytes, do + nothing.) CRED identifies the user responsible for the call. */ +error_t +diskfs_grow (struct node *np, off_t size, struct protid *cred) +{ + if (size > np->allocsize) + np->allocsize = size; + return 0; } |