home *** CD-ROM | disk | FTP | other *** search
- /*
- * 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>
-
- #define clear_block(addr,size,value) \
- __asm__("cld\n\t" \
- "rep\n\t" \
- "stosl" \
- : \
- :"a" (value), "c" (size / 4), "D" ((long) (addr)) \
- :"cx", "di")
-
- 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) {
- clear_block (bh->b_data, inode->i_sb->s_blocksize,
- RANDOM_INT);
- bh->b_dirt = 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;
- ind_bh->b_dirt = 1;
- if (inode->u.ext2_i.i_flags & EXT2_SECRM_FL) {
- clear_block (bh->b_data, inode->i_sb->s_blocksize,
- RANDOM_INT);
- bh->b_dirt = 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);
- dind_bh->b_dirt = 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);
- tind_bh->b_dirt = 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;
- ext2_discard_prealloc(inode);
- while (1) {
- 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);
- 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;
- }
-