| /* |
| * 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; |
| } |