summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext2fs/truncate.c356
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;
}