/* * linux/fs/truncate.c * * Copyright (C) 1991, 1992 Linus Torvalds * * Copyright (C) 1996 Gertjan van Wingerde (gertjan@cs.vu.nl) * Minix V2 fs support. */ #include #include #include #include #include #define DIRECT_BLOCK ((inode->i_size + 1023) >> 10) #define INDIRECT_BLOCK(offset) (DIRECT_BLOCK-offset) #define V1_DINDIRECT_BLOCK(offset) ((DIRECT_BLOCK-offset)>>9) #define V2_DINDIRECT_BLOCK(offset) ((DIRECT_BLOCK-offset)>>8) #define TINDIRECT_BLOCK(offset) ((DIRECT_BLOCK-(offset))>>8) /* * 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)) /* * The functions for minix V1 fs truncation. */ static int V1_trunc_direct(struct inode * inode) { unsigned short * p; struct buffer_head * bh; int i, tmp; int retry = 0; repeat: for (i = DIRECT_BLOCK ; i < 7 ; i++) { p = i + inode->u.minix_i.u.i1_data; if (!(tmp = *p)) continue; bh = get_hash_table(inode->i_dev,tmp,BLOCK_SIZE); if (i < DIRECT_BLOCK) { brelse(bh); goto repeat; } if ((bh && DATA_BUFFER_USED(bh)) || tmp != *p) { retry = 1; brelse(bh); continue; } *p = 0; mark_inode_dirty(inode); if (bh) { mark_buffer_clean(bh); brelse(bh); } minix_free_block(inode->i_sb,tmp); } return retry; } static int V1_trunc_indirect(struct inode * inode, int offset, unsigned short * p) { struct buffer_head * bh; int i, tmp; struct buffer_head * ind_bh; unsigned short * ind; int retry = 0; 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(offset) ; i < 512 ; i++) { if (i < 0) i = 0; if (i < INDIRECT_BLOCK(offset)) goto repeat; ind = i+(unsigned short *) ind_bh->b_data; tmp = *ind; if (!tmp) continue; bh = get_hash_table(inode->i_dev,tmp,BLOCK_SIZE); if (i < INDIRECT_BLOCK(offset)) { brelse(bh); goto repeat; } if ((bh && DATA_BUFFER_USED(bh)) || tmp != *ind) { retry = 1; brelse(bh); continue; } *ind = 0; mark_buffer_dirty(ind_bh, 1); brelse(bh); minix_free_block(inode->i_sb,tmp); } ind = (unsigned short *) ind_bh->b_data; for (i = 0; i < 512; i++) if (*(ind++)) break; if (i >= 512) { if (atomic_read(&ind_bh->b_count) != 1) retry = 1; else { tmp = *p; *p = 0; minix_free_block(inode->i_sb,tmp); } } brelse(ind_bh); return retry; } static int V1_trunc_dindirect(struct inode * inode, int offset, unsigned short *p) { int i, tmp; struct buffer_head * dind_bh; unsigned short * dind; int retry = 0; if (!(tmp = *p)) 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 = V1_DINDIRECT_BLOCK(offset) ; i < 512 ; i ++) { if (i < 0) i = 0; if (i < V1_DINDIRECT_BLOCK(offset)) goto repeat; dind = i+(unsigned short *) dind_bh->b_data; retry |= V1_trunc_indirect(inode,offset+(i<<9),dind); mark_buffer_dirty(dind_bh, 1); } dind = (unsigned short *) dind_bh->b_data; for (i = 0; i < 512; i++) if (*(dind++)) break; if (i >= 512) { if (atomic_read(&dind_bh->b_count) != 1) retry = 1; else { tmp = *p; *p = 0; mark_inode_dirty(inode); minix_free_block(inode->i_sb,tmp); } } brelse(dind_bh); return retry; } static void V1_minix_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 = V1_trunc_direct(inode); retry |= V1_trunc_indirect(inode, 7, inode->u.minix_i.u.i1_data + 7); retry |= V1_trunc_dindirect(inode, 7+512, inode->u.minix_i.u.i1_data + 8); if (!retry) break; current->counter = 0; schedule(); } inode->i_mtime = inode->i_ctime = CURRENT_TIME; mark_inode_dirty(inode); } /* * The functions for minix V2 fs truncation. */ static int V2_trunc_direct(struct inode * inode) { unsigned long * p; struct buffer_head * bh; int i, tmp; int retry = 0; repeat: for (i = DIRECT_BLOCK ; i < 7 ; i++) { p = (unsigned long *) inode->u.minix_i.u.i2_data + i; if (!(tmp = *p)) continue; bh = get_hash_table(inode->i_dev,tmp,BLOCK_SIZE); if (i < DIRECT_BLOCK) { brelse(bh); goto repeat; } if ((bh && DATA_BUFFER_USED(bh)) || tmp != *p) { retry = 1; brelse(bh); continue; } *p = 0; mark_inode_dirty(inode); if (bh) { mark_buffer_clean(bh); brelse(bh); } minix_free_block(inode->i_sb,tmp); } return retry; } static int V2_trunc_indirect(struct inode * inode, int offset, unsigned long * p) { struct buffer_head * bh; int i, tmp; struct buffer_head * ind_bh; unsigned long * ind; int retry = 0; 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(offset) ; i < 256 ; i++) { if (i < 0) i = 0; if (i < INDIRECT_BLOCK(offset)) goto repeat; ind = i+(unsigned long *) ind_bh->b_data; tmp = *ind; if (!tmp) continue; bh = get_hash_table(inode->i_dev,tmp,BLOCK_SIZE); if (i < INDIRECT_BLOCK(offset)) { brelse(bh); goto repeat; } if ((bh && DATA_BUFFER_USED(bh)) || tmp != *ind) { retry = 1; brelse(bh); continue; } *ind = 0; mark_buffer_dirty(ind_bh, 1); brelse(bh); minix_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 (atomic_read(&ind_bh->b_count) != 1) retry = 1; else { tmp = *p; *p = 0; minix_free_block(inode->i_sb,tmp); } } brelse(ind_bh); return retry; } static int V2_trunc_dindirect(struct inode * inode, int offset, unsigned long *p) { int i, tmp; struct buffer_head * dind_bh; unsigned long * dind; int retry = 0; if (!(tmp = *p)) 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 = V2_DINDIRECT_BLOCK(offset) ; i < 256 ; i ++) { if (i < 0) i = 0; if (i < V2_DINDIRECT_BLOCK(offset)) goto repeat; dind = i+(unsigned long *) dind_bh->b_data; retry |= V2_trunc_indirect(inode,offset+(i<<8),dind); mark_buffer_dirty(dind_bh, 1); } dind = (unsigned long *) dind_bh->b_data; for (i = 0; i < 256; i++) if (*(dind++)) break; if (i >= 256) { if (atomic_read(&dind_bh->b_count) != 1) retry = 1; else { tmp = *p; *p = 0; mark_inode_dirty(inode); minix_free_block(inode->i_sb,tmp); } } brelse(dind_bh); return retry; } static int V2_trunc_tindirect(struct inode * inode, int offset, unsigned long * p) { int i, tmp; struct buffer_head * tind_bh; unsigned long * tind; int retry = 0; 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(offset) ; i < 256 ; i ++) { if (i < 0) i = 0; if (i < TINDIRECT_BLOCK(offset)) goto repeat; tind = i+(unsigned long *) tind_bh->b_data; retry |= V2_trunc_dindirect(inode,offset+(i<<8),tind); mark_buffer_dirty(tind_bh, 1); } tind = (unsigned long *) tind_bh->b_data; for (i = 0; i < 256; i++) if (*(tind++)) break; if (i >= 256) { if (atomic_read(&tind_bh->b_count) != 1) retry = 1; else { tmp = *p; *p = 0; mark_inode_dirty(inode); minix_free_block(inode->i_sb,tmp); } } brelse(tind_bh); return retry; } static void V2_minix_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 = V2_trunc_direct(inode); retry |= V2_trunc_indirect(inode,7, (unsigned long *) inode->u.minix_i.u.i2_data + 7); retry |= V2_trunc_dindirect(inode, 7+256, (unsigned long *) inode->u.minix_i.u.i2_data + 8); retry |= V2_trunc_tindirect(inode, 7+256+256*256, (unsigned long *) inode->u.minix_i.u.i2_data + 9); if (!retry) break; current->counter = 0; schedule(); } inode->i_mtime = inode->i_ctime = CURRENT_TIME; mark_inode_dirty(inode); } /* * The function that is called for file truncation. */ void minix_truncate(struct inode * inode) { if (INODE_VERSION(inode) == MINIX_V1) V1_minix_truncate(inode); else V2_minix_truncate(inode); }