summaryrefslogtreecommitdiffstats
path: root/fs/ext2/truncate.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ext2/truncate.c')
-rw-r--r--fs/ext2/truncate.c349
1 files changed, 349 insertions, 0 deletions
diff --git a/fs/ext2/truncate.c b/fs/ext2/truncate.c
new file mode 100644
index 000000000..10a1fd236
--- /dev/null
+++ b/fs/ext2/truncate.c
@@ -0,0 +1,349 @@
+/*
+ * linux/fs/ext2/truncate.c
+ *
+ * Copyright (C) 1992, 1993, 1994 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
+ */
+
+/*
+ * Real random numbers for secure rm added 94/02/18
+ * Idea from Pierre del Perugia <delperug@gla.ecoledoc.ibp.fr>
+ */
+
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/ext2_fs.h>
+#include <linux/fcntl.h>
+#include <linux/sched.h>
+#include <linux/stat.h>
+#include <linux/locks.h>
+#include <linux/string.h>
+
+static int ext2_secrm_seed = 152; /* Random generator base */
+
+#define RANDOM_INT (ext2_secrm_seed = ext2_secrm_seed * 69069l +1)
+
+/*
+ * Truncate has the most races in the whole filesystem: coding it is
+ * a pain in the a**. Especially as I don't do any locking...
+ *
+ * The code may look a bit weird, but that's just because I've tried to
+ * handle things like file-size changes in a somewhat graceful manner.
+ * Anyway, truncating a file at the same time somebody else writes to it
+ * is likely to result in pretty weird behaviour...
+ *
+ * The new code handles normal truncates (size = 0) as well as the more
+ * general case (size = XXX). I hope.
+ */
+
+static int trunc_direct (struct inode * inode)
+{
+ int i, tmp;
+ unsigned long * p;
+ struct buffer_head * bh;
+ unsigned long block_to_free = 0;
+ unsigned long free_count = 0;
+ int retry = 0;
+ int blocks = inode->i_sb->s_blocksize / 512;
+#define DIRECT_BLOCK ((inode->i_size + inode->i_sb->s_blocksize - 1) / \
+ inode->i_sb->s_blocksize)
+ int direct_block = DIRECT_BLOCK;
+
+repeat:
+ for (i = direct_block ; i < EXT2_NDIR_BLOCKS ; i++) {
+ p = inode->u.ext2_i.i_data + i;
+ tmp = *p;
+ if (!tmp)
+ continue;
+ if (inode->u.ext2_i.i_flags & EXT2_SECRM_FL)
+ bh = getblk (inode->i_dev, tmp,
+ inode->i_sb->s_blocksize);
+ else
+ bh = get_hash_table (inode->i_dev, tmp,
+ inode->i_sb->s_blocksize);
+ if (i < direct_block) {
+ brelse (bh);
+ goto repeat;
+ }
+ if ((bh && bh->b_count != 1) || tmp != *p) {
+ retry = 1;
+ brelse (bh);
+ continue;
+ }
+ *p = 0;
+ inode->i_blocks -= blocks;
+ inode->i_dirt = 1;
+ if (inode->u.ext2_i.i_flags & EXT2_SECRM_FL) {
+ memset(bh->b_data, RANDOM_INT, inode->i_sb->s_blocksize);
+ mark_buffer_dirty(bh, 1);
+ }
+ brelse (bh);
+ if (free_count == 0) {
+ block_to_free = tmp;
+ free_count++;
+ } else if (free_count > 0 && block_to_free == tmp - free_count)
+ free_count++;
+ else {
+ ext2_free_blocks (inode->i_sb, block_to_free, free_count);
+ block_to_free = tmp;
+ free_count = 1;
+ }
+/* ext2_free_blocks (inode->i_sb, tmp, 1); */
+ }
+ if (free_count > 0)
+ ext2_free_blocks (inode->i_sb, block_to_free, free_count);
+ return retry;
+}
+
+static int trunc_indirect (struct inode * inode, int offset, unsigned long * p)
+{
+ int i, tmp;
+ struct buffer_head * bh;
+ struct buffer_head * ind_bh;
+ unsigned long * ind;
+ unsigned long block_to_free = 0;
+ unsigned long free_count = 0;
+ int retry = 0;
+ int addr_per_block = EXT2_ADDR_PER_BLOCK(inode->i_sb);
+ int blocks = inode->i_sb->s_blocksize / 512;
+#define INDIRECT_BLOCK ((int)DIRECT_BLOCK - offset)
+ int indirect_block = INDIRECT_BLOCK;
+
+ tmp = *p;
+ if (!tmp)
+ return 0;
+ ind_bh = bread (inode->i_dev, tmp, inode->i_sb->s_blocksize);
+ if (tmp != *p) {
+ brelse (ind_bh);
+ return 1;
+ }
+ 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 + (unsigned long *) ind_bh->b_data;
+ tmp = *ind;
+ if (!tmp)
+ continue;
+ if (inode->u.ext2_i.i_flags & EXT2_SECRM_FL)
+ bh = getblk (inode->i_dev, tmp,
+ inode->i_sb->s_blocksize);
+ else
+ bh = get_hash_table (inode->i_dev, tmp,
+ inode->i_sb->s_blocksize);
+ if (i < indirect_block) {
+ brelse (bh);
+ goto repeat;
+ }
+ if ((bh && bh->b_count != 1) || tmp != *ind) {
+ retry = 1;
+ brelse (bh);
+ continue;
+ }
+ *ind = 0;
+ mark_buffer_dirty(ind_bh, 1);
+ if (inode->u.ext2_i.i_flags & EXT2_SECRM_FL) {
+ memset(bh->b_data, RANDOM_INT, inode->i_sb->s_blocksize);
+ mark_buffer_dirty(bh, 1);
+ }
+ brelse (bh);
+ if (free_count == 0) {
+ block_to_free = tmp;
+ free_count++;
+ } else if (free_count > 0 && block_to_free == tmp - free_count)
+ free_count++;
+ else {
+ ext2_free_blocks (inode->i_sb, block_to_free, free_count);
+ block_to_free = tmp;
+ free_count = 1;
+ }
+/* ext2_free_blocks (inode->i_sb, tmp, 1); */
+ inode->i_blocks -= blocks;
+ inode->i_dirt = 1;
+ }
+ if (free_count > 0)
+ ext2_free_blocks (inode->i_sb, block_to_free, free_count);
+ ind = (unsigned long *) ind_bh->b_data;
+ for (i = 0; i < addr_per_block; i++)
+ if (*(ind++))
+ break;
+ if (i >= addr_per_block)
+ if (ind_bh->b_count != 1)
+ retry = 1;
+ else {
+ tmp = *p;
+ *p = 0;
+ inode->i_blocks -= blocks;
+ inode->i_dirt = 1;
+ ext2_free_blocks (inode->i_sb, tmp, 1);
+ }
+ if (IS_SYNC(inode) && ind_bh->b_dirt) {
+ ll_rw_block (WRITE, 1, &ind_bh);
+ wait_on_buffer (ind_bh);
+ }
+ brelse (ind_bh);
+ return retry;
+}
+
+static int trunc_dindirect (struct inode * inode, int offset,
+ unsigned long * p)
+{
+ int i, tmp;
+ struct buffer_head * dind_bh;
+ unsigned long * dind;
+ int retry = 0;
+ int addr_per_block = EXT2_ADDR_PER_BLOCK(inode->i_sb);
+ int blocks = inode->i_sb->s_blocksize / 512;
+#define DINDIRECT_BLOCK (((int)DIRECT_BLOCK - offset) / addr_per_block)
+ int dindirect_block = DINDIRECT_BLOCK;
+
+ tmp = *p;
+ if (!tmp)
+ return 0;
+ dind_bh = bread (inode->i_dev, tmp, inode->i_sb->s_blocksize);
+ if (tmp != *p) {
+ brelse (dind_bh);
+ return 1;
+ }
+ 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 + (unsigned long *) dind_bh->b_data;
+ tmp = *dind;
+ if (!tmp)
+ continue;
+ retry |= trunc_indirect (inode, offset + (i * addr_per_block),
+ dind);
+ mark_buffer_dirty(dind_bh, 1);
+ }
+ dind = (unsigned long *) dind_bh->b_data;
+ for (i = 0; i < addr_per_block; i++)
+ if (*(dind++))
+ break;
+ if (i >= addr_per_block)
+ if (dind_bh->b_count != 1)
+ retry = 1;
+ else {
+ tmp = *p;
+ *p = 0;
+ inode->i_blocks -= blocks;
+ inode->i_dirt = 1;
+ ext2_free_blocks (inode->i_sb, tmp, 1);
+ }
+ if (IS_SYNC(inode) && dind_bh->b_dirt) {
+ ll_rw_block (WRITE, 1, &dind_bh);
+ wait_on_buffer (dind_bh);
+ }
+ brelse (dind_bh);
+ return retry;
+}
+
+static int trunc_tindirect (struct inode * inode)
+{
+ int i, tmp;
+ struct buffer_head * tind_bh;
+ unsigned long * tind, * p;
+ int retry = 0;
+ int addr_per_block = EXT2_ADDR_PER_BLOCK(inode->i_sb);
+ int blocks = inode->i_sb->s_blocksize / 512;
+#define TINDIRECT_BLOCK (((int)DIRECT_BLOCK - (addr_per_block * addr_per_block + \
+ addr_per_block + EXT2_NDIR_BLOCKS)) / \
+ (addr_per_block * addr_per_block))
+ int tindirect_block = TINDIRECT_BLOCK;
+
+ p = inode->u.ext2_i.i_data + EXT2_TIND_BLOCK;
+ if (!(tmp = *p))
+ return 0;
+ tind_bh = bread (inode->i_dev, tmp, inode->i_sb->s_blocksize);
+ if (tmp != *p) {
+ brelse (tind_bh);
+ return 1;
+ }
+ 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 + (unsigned long *) tind_bh->b_data;
+ retry |= trunc_dindirect(inode, EXT2_NDIR_BLOCKS +
+ addr_per_block + (i + 1) * addr_per_block * addr_per_block,
+ tind);
+ mark_buffer_dirty(tind_bh, 1);
+ }
+ tind = (unsigned long *) tind_bh->b_data;
+ for (i = 0; i < addr_per_block; i++)
+ if (*(tind++))
+ break;
+ if (i >= addr_per_block)
+ if (tind_bh->b_count != 1)
+ retry = 1;
+ else {
+ tmp = *p;
+ *p = 0;
+ inode->i_blocks -= blocks;
+ inode->i_dirt = 1;
+ ext2_free_blocks (inode->i_sb, tmp, 1);
+ }
+ if (IS_SYNC(inode) && tind_bh->b_dirt) {
+ ll_rw_block (WRITE, 1, &tind_bh);
+ wait_on_buffer (tind_bh);
+ }
+ brelse (tind_bh);
+ return retry;
+}
+
+void ext2_truncate (struct inode * inode)
+{
+ int retry;
+
+ if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) ||
+ S_ISLNK(inode->i_mode)))
+ return;
+ if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
+ return;
+ ext2_discard_prealloc(inode);
+ while (1) {
+ down(&inode->i_sem);
+ retry = trunc_direct(inode);
+ retry |= trunc_indirect (inode, EXT2_IND_BLOCK,
+ (unsigned long *) &inode->u.ext2_i.i_data[EXT2_IND_BLOCK]);
+ retry |= trunc_dindirect (inode, EXT2_IND_BLOCK +
+ EXT2_ADDR_PER_BLOCK(inode->i_sb),
+ (unsigned long *) &inode->u.ext2_i.i_data[EXT2_DIND_BLOCK]);
+ retry |= trunc_tindirect (inode);
+ up(&inode->i_sem);
+ if (!retry)
+ break;
+ if (IS_SYNC(inode) && inode->i_dirt)
+ ext2_sync_inode (inode);
+ current->counter = 0;
+ schedule ();
+ }
+ inode->i_mtime = inode->i_ctime = CURRENT_TIME;
+ inode->i_dirt = 1;
+}