252 lines
5.2 KiB
C
252 lines
5.2 KiB
C
/*
|
|
* linux/fs/ext/truncate.c
|
|
*
|
|
* Copyright (C) 1992 Remy Card (card@masi.ibp.fr)
|
|
*
|
|
* from
|
|
*
|
|
* linux/fs/minix/truncate.c
|
|
*
|
|
* Copyright (C) 1991, 1992 Linus Torvalds
|
|
*/
|
|
|
|
#include <linux/sched.h>
|
|
#include <linux/ext_fs.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/fcntl.h>
|
|
#include <linux/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, tmp;
|
|
unsigned long * p;
|
|
struct buffer_head * bh;
|
|
int retry = 0;
|
|
#define DIRECT_BLOCK ((inode->i_size + 1023) >> 10)
|
|
|
|
repeat:
|
|
for (i = DIRECT_BLOCK ; i < 9 ; i++) {
|
|
p = inode->u.ext_i.i_data+i;
|
|
if (!(tmp = *p))
|
|
continue;
|
|
bh = getblk(inode->i_dev,tmp,BLOCK_SIZE);
|
|
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_dirt = 1;
|
|
brelse(bh);
|
|
ext_free_block(inode->i_sb,tmp);
|
|
}
|
|
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;
|
|
int retry = 0;
|
|
#define INDIRECT_BLOCK (DIRECT_BLOCK-offset)
|
|
|
|
tmp = *p;
|
|
if (!tmp)
|
|
return 0;
|
|
ind_bh = bread(inode->i_dev, tmp, BLOCK_SIZE);
|
|
if (tmp != *p) {
|
|
brelse(ind_bh);
|
|
return 1;
|
|
}
|
|
if (!ind_bh) {
|
|
*p = 0;
|
|
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 *) ind_bh->b_data;
|
|
tmp = *ind;
|
|
if (!tmp)
|
|
continue;
|
|
bh = getblk(inode->i_dev,tmp,BLOCK_SIZE);
|
|
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;
|
|
brelse(bh);
|
|
ext_free_block(inode->i_sb,tmp);
|
|
}
|
|
ind = (unsigned long *) ind_bh->b_data;
|
|
for (i = 0; i < 256; i++)
|
|
if (*(ind++))
|
|
break;
|
|
if (i >= 256)
|
|
if (ind_bh->b_count != 1)
|
|
retry = 1;
|
|
else {
|
|
tmp = *p;
|
|
*p = 0;
|
|
inode->i_dirt = 1;
|
|
ext_free_block(inode->i_sb,tmp);
|
|
}
|
|
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;
|
|
#define DINDIRECT_BLOCK ((DIRECT_BLOCK-offset)>>8)
|
|
|
|
tmp = *p;
|
|
if (!tmp)
|
|
return 0;
|
|
dind_bh = bread(inode->i_dev, tmp, BLOCK_SIZE);
|
|
if (tmp != *p) {
|
|
brelse(dind_bh);
|
|
return 1;
|
|
}
|
|
if (!dind_bh) {
|
|
*p = 0;
|
|
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 *) dind_bh->b_data;
|
|
tmp = *dind;
|
|
if (!tmp)
|
|
continue;
|
|
retry |= trunc_indirect(inode,offset+(i<<8),dind);
|
|
dind_bh->b_dirt = 1;
|
|
}
|
|
dind = (unsigned long *) dind_bh->b_data;
|
|
for (i = 0; i < 256; i++)
|
|
if (*(dind++))
|
|
break;
|
|
if (i >= 256)
|
|
if (dind_bh->b_count != 1)
|
|
retry = 1;
|
|
else {
|
|
tmp = *p;
|
|
*p = 0;
|
|
inode->i_dirt = 1;
|
|
ext_free_block(inode->i_sb,tmp);
|
|
}
|
|
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;
|
|
#define TINDIRECT_BLOCK ((DIRECT_BLOCK-(256*256+256+9))>>16)
|
|
|
|
p = inode->u.ext_i.i_data+11;
|
|
if (!(tmp = *p))
|
|
return 0;
|
|
tind_bh = bread(inode->i_dev, tmp, BLOCK_SIZE);
|
|
if (tmp != *p) {
|
|
brelse(tind_bh);
|
|
return 1;
|
|
}
|
|
if (!tind_bh) {
|
|
*p = 0;
|
|
return 0;
|
|
}
|
|
repeat:
|
|
for (i = TINDIRECT_BLOCK ; i < 256 ; i ++) {
|
|
if (i < 0)
|
|
i = 0;
|
|
if (i < TINDIRECT_BLOCK)
|
|
goto repeat;
|
|
tind = i+(unsigned long *) tind_bh->b_data;
|
|
retry |= trunc_dindirect(inode,9+256+256*256+(i<<16),tind);
|
|
tind_bh->b_dirt = 1;
|
|
}
|
|
tind = (unsigned long *) tind_bh->b_data;
|
|
for (i = 0; i < 256; i++)
|
|
if (*(tind++))
|
|
break;
|
|
if (i >= 256)
|
|
if (tind_bh->b_count != 1)
|
|
retry = 1;
|
|
else {
|
|
tmp = *p;
|
|
*p = 0;
|
|
inode->i_dirt = 1;
|
|
ext_free_block(inode->i_sb,tmp);
|
|
}
|
|
brelse(tind_bh);
|
|
return retry;
|
|
}
|
|
|
|
void ext_truncate(struct inode * inode)
|
|
{
|
|
int retry;
|
|
|
|
if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) ||
|
|
S_ISLNK(inode->i_mode)))
|
|
return;
|
|
while (1) {
|
|
retry = trunc_direct(inode);
|
|
retry |= trunc_indirect(inode,9,inode->u.ext_i.i_data+9);
|
|
retry |= trunc_dindirect(inode,9+256,inode->u.ext_i.i_data+10);
|
|
retry |= trunc_tindirect(inode);
|
|
if (!retry)
|
|
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");
|
|
}
|