/* * linux/fs/sysv/truncate.c * * minix/truncate.c * Copyright (C) 1991, 1992 Linus Torvalds * * coh/truncate.c * Copyright (C) 1993 Pascal Haible, Bruno Haible * * sysv/truncate.c * Copyright (C) 1993 Bruno Haible */ #include #include #include #include /* Linus' implementation of truncate. * It doesn't need locking because it can tell from looking at bh->b_count * whether a given block is in use elsewhere. */ /* * 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. */ #define DATA_BUFFER_USED(bh) \ (atomic_read(&bh->b_count) || buffer_locked(bh)) /* We throw away any data beyond inode->i_size. */ static int trunc_direct(struct inode * inode) { struct super_block * sb; unsigned int i; u32 * p; u32 block; struct buffer_head * bh; int retry = 0; sb = inode->i_sb; repeat: for (i = ((unsigned long) inode->i_size + sb->sv_block_size_1) >> sb->sv_block_size_bits; i < 10; i++) { p = inode->u.sysv_i.i_data + i; block = *p; if (!block) continue; bh = sv_get_hash_table(sb, inode->i_dev, block); if ((i << sb->sv_block_size_bits) < inode->i_size) { brelse(bh); goto repeat; } if ((bh && DATA_BUFFER_USED(bh)) || (block != *p)) { retry = 1; brelse(bh); continue; } *p = 0; mark_inode_dirty(inode); brelse(bh); sysv_free_block(sb,block); } return retry; } static int trunc_indirect(struct inode * inode, unsigned long offset, sysv_zone_t * p, int convert, unsigned char * dirt) { unsigned long indtmp, indblock; struct super_block * sb; struct buffer_head * indbh; unsigned int i; sysv_zone_t * ind; unsigned long tmp, block; struct buffer_head * bh; int retry = 0; indblock = indtmp = *p; if (convert) indblock = from_coh_ulong(indblock); if (!indblock) return 0; sb = inode->i_sb; indbh = sv_bread(sb, inode->i_dev, indblock); if (indtmp != *p) { brelse(indbh); return 1; } if (!indbh) { *p = 0; *dirt = 1; return 0; } repeat: if (inode->i_size < offset) i = 0; else i = (inode->i_size - offset + sb->sv_block_size_1) >> sb->sv_block_size_bits; for (; i < sb->sv_ind_per_block; i++) { ind = ((sysv_zone_t *) indbh->b_data) + i; block = tmp = *ind; if (sb->sv_convert) block = from_coh_ulong(block); if (!block) continue; bh = sv_get_hash_table(sb, inode->i_dev, block); if ((i << sb->sv_block_size_bits) + offset < inode->i_size) { brelse(bh); goto repeat; } if ((bh && DATA_BUFFER_USED(bh)) || (tmp != *ind)) { retry = 1; brelse(bh); continue; } *ind = 0; mark_buffer_dirty(indbh, 1); brelse(bh); sysv_free_block(sb,block); } for (i = 0; i < sb->sv_ind_per_block; i++) if (((sysv_zone_t *) indbh->b_data)[i]) goto done; if (DATA_BUFFER_USED(indbh) || (indtmp != *p)) { brelse(indbh); return 1; } *p = 0; *dirt = 1; sysv_free_block(sb,indblock); done: brelse(indbh); return retry; } static int trunc_dindirect(struct inode * inode, unsigned long offset, sysv_zone_t * p, int convert, unsigned char * dirt) { u32 indtmp, indblock; struct super_block * sb; struct buffer_head * indbh; unsigned int i; sysv_zone_t * ind; u32 tmp, block; int retry = 0; indblock = indtmp = *p; if (convert) indblock = from_coh_ulong(indblock); if (!indblock) return 0; sb = inode->i_sb; indbh = sv_bread(sb, inode->i_dev, indblock); if (indtmp != *p) { brelse(indbh); return 1; } if (!indbh) { *p = 0; *dirt = 1; return 0; } if (inode->i_size < offset) i = 0; else i = (inode->i_size - offset + sb->sv_ind_per_block_block_size_1) >> sb->sv_ind_per_block_block_size_bits; for (; i < sb->sv_ind_per_block; i++) { unsigned char dirty = 0; ind = ((sysv_zone_t *) indbh->b_data) + i; block = tmp = *ind; if (sb->sv_convert) block = from_coh_ulong(block); if (!block) continue; retry |= trunc_indirect(inode,offset+(i<sv_ind_per_block_bits),ind,sb->sv_convert,&dirty); if (dirty) mark_buffer_dirty(indbh, 1); } for (i = 0; i < sb->sv_ind_per_block; i++) if (((sysv_zone_t *) indbh->b_data)[i]) goto done; if (DATA_BUFFER_USED(indbh) || (indtmp != *p)) { brelse(indbh); return 1; } *p = 0; *dirt = 1; sysv_free_block(sb,indblock); done: brelse(indbh); return retry; } static int trunc_tindirect(struct inode * inode, unsigned long offset, sysv_zone_t * p, int convert, unsigned char * dirt) { u32 indtmp, indblock; struct super_block * sb; struct buffer_head * indbh; unsigned int i; sysv_zone_t * ind; u32 tmp, block; int retry = 0; indblock = indtmp = *p; if (convert) indblock = from_coh_ulong(indblock); if (!indblock) return 0; sb = inode->i_sb; indbh = sv_bread(sb, inode->i_dev, indblock); if (indtmp != *p) { brelse(indbh); return 1; } if (!indbh) { *p = 0; *dirt = 1; return 0; } if (inode->i_size < offset) i = 0; else i = (inode->i_size - offset + sb->sv_ind_per_block_2_block_size_1) >> sb->sv_ind_per_block_2_block_size_bits; for (; i < sb->sv_ind_per_block; i++) { unsigned char dirty = 0; ind = ((sysv_zone_t *) indbh->b_data) + i; block = tmp = *ind; if (sb->sv_convert) block = from_coh_ulong(block); if (!block) continue; retry |= trunc_dindirect(inode,offset+(i<sv_ind_per_block_2_bits),ind,sb->sv_convert,&dirty); if (dirty) mark_buffer_dirty(indbh, 1); } for (i = 0; i < sb->sv_ind_per_block; i++) if (((sysv_zone_t *) indbh->b_data)[i]) goto done; if (DATA_BUFFER_USED(indbh) || (indtmp != *p)) { brelse(indbh); return 1; } *p = 0; *dirt = 1; sysv_free_block(sb,indblock); done: brelse(indbh); return retry; } static int trunc_all(struct inode * inode) { struct super_block * sb; char dirty; sb = inode->i_sb; return trunc_direct(inode) | trunc_indirect(inode,sb->sv_ind0_size,&inode->u.sysv_i.i_data[10],0,&dirty) | trunc_dindirect(inode,sb->sv_ind1_size,&inode->u.sysv_i.i_data[11],0,&dirty) | trunc_tindirect(inode,sb->sv_ind2_size,&inode->u.sysv_i.i_data[12],0,&dirty); } void sysv_truncate(struct inode * inode) { /* If this is called from sysv_put_inode, we needn't worry about * races as we are just losing the last reference to the inode. * If this is called from another place, let's hope it's a regular * file. * Truncating symbolic links is strange. We assume we don't truncate * a directory we are just modifying. We ensure we don't truncate * a regular file we are just writing to, by use of a lock. */ if (S_ISLNK(inode->i_mode)) printk("sysv_truncate: truncating symbolic link\n"); else if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) return; while (trunc_all(inode)) { current->counter = 0; schedule(); } inode->i_mtime = inode->i_ctime = CURRENT_TIME; mark_inode_dirty(inode); }