/* * linux/fs/ext/truncate.c * * (C) 1992 Remy Card (card@masi.ibp.fr) * * from * * linux/fs/minix/truncate.c * * (C) 1991 Linus Torvalds */ #include <linux/sched.h> #include <linux/ext_fs.h> #include <linux/tty.h> #include <linux/stat.h> #include <linux/fcntl.h> #include <errno.h> /* * 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; int result = 0; #define DIRECT_BLOCK ((inode->i_size + 1023) >> 10) repeat: for (i = DIRECT_BLOCK ; i < 9 ; i++) { if (i < DIRECT_BLOCK) goto repeat; if (!inode->i_data[i]) continue; result = 1; if (ext_free_block(inode->i_dev,inode->i_data[i])) inode->i_data[i] = 0; } return result; } static int trunc_indirect(struct inode * inode, int offset, unsigned long * p) { int i; struct buffer_head * bh = NULL; unsigned long * ind; int result = 0; #define INDIRECT_BLOCK (DIRECT_BLOCK-offset) if (*p) bh = bread(inode->i_dev,*p); if (!bh) return 0; repeat: for (i = INDIRECT_BLOCK ; i < 256 ; i++) { if (i < 0) i = 0; if (i < INDIRECT_BLOCK) goto repeat; ind = i+(unsigned long *) bh->b_data; if (!*ind) continue; result = 1; if (ext_free_block(inode->i_dev,*ind)) *ind = 0; } ind = (unsigned long *) bh->b_data; for (i = 0; i < 256; i++) if (*(ind++)) break; brelse(bh); if (i >= 256) { result = 1; if (ext_free_block(inode->i_dev,*p)) *p = 0; } return result; } static int trunc_dindirect(struct inode * inode) { int i; struct buffer_head * bh = NULL; unsigned long * dind; int result = 0; #define DINDIRECT_BLOCK ((DIRECT_BLOCK-(256+9))>>8) if (inode->i_data[10]) bh = bread(inode->i_dev,inode->i_data[10]); if (!bh) return 0; repeat: for (i = DINDIRECT_BLOCK ; i < 256 ; i ++) { if (i < 0) i = 0; if (i < DINDIRECT_BLOCK) goto repeat; dind = i+(unsigned long *) bh->b_data; if (!*dind) continue; result |= trunc_indirect(inode,9+256+(i<<8),dind); } dind = (unsigned long *) bh->b_data; for (i = 0; i < 256; i++) if (*(dind++)) break; brelse(bh); if (i >= 256) { result = 1; if (ext_free_block(inode->i_dev,inode->i_data[10])) inode->i_data[10] = 0; } return result; } void ext_truncate(struct inode * inode) { int flag; if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))) return; /* if (inode->i_data[7] & 0xffff0000) printk("BAD! ext inode has 16 high bits set\n"); */ while (1) { flag = trunc_direct(inode); flag |= trunc_indirect(inode,9,(unsigned long *)&inode->i_data[9]); flag |= trunc_dindirect(inode); if (!flag) break; current->counter = 0; schedule(); } inode->i_mtime = inode->i_ctime = CURRENT_TIME; inode->i_dirt = 1; } /* * Called when a inode is released. Note that this is different * from ext_open: open gets called at every open, but release * gets called only when /all/ the files are closed. */ void ext_release(struct inode * inode, struct file * filp) { printk("ext_release not implemented\n"); }