udf: Avoid IO in udf_clear_inode
authorJan Kara <jack@suse.cz>
Thu, 3 Dec 2009 12:39:28 +0000 (13:39 +0100)
committerJan Kara <jack@suse.cz>
Mon, 14 Dec 2009 20:40:04 +0000 (21:40 +0100)
It is not very good to do IO in udf_clear_inode. First, VFS does not really
expect inode to become dirty there and thus we have to write it ourselves,
second, memory reclaim gets blocked waiting for IO when it does not really
expect it, third, the IO pattern (e.g. on umount) resulting from writes in
udf_clear_inode is bad and it slows down writing a lot.

The reason why UDF needed to do IO in udf_clear_inode is that UDF standard
mandates extent length to exactly match inode size. But when we allocate
extents to a file or directory, we don't really know what exactly the final
file size will be and thus temporarily set it to block boundary and later
truncate it to exact length in udf_clear_inode. Now, this is changed to
truncate to final file size in udf_release_file for regular files. For
directories and symlinks, we do the truncation at the moment when learn
what the final file size will be.

Signed-off-by: Jan Kara <jack@suse.cz>
fs/udf/file.c
fs/udf/inode.c
fs/udf/namei.c

index b80cbd78833cf7aa99430016b84f29b038d5e098..f311d509b6a3541f5f93f81e785dd820b82f7daf 100644 (file)
@@ -196,6 +196,7 @@ static int udf_release_file(struct inode *inode, struct file *filp)
                mutex_lock(&inode->i_mutex);
                lock_kernel();
                udf_discard_prealloc(inode);
+               udf_truncate_tail_extent(inode);
                unlock_kernel();
                mutex_unlock(&inode->i_mutex);
        }
index 6d24c2c63f9353c7baf7e9c7158360c1ef40a9f4..f90231eb29165785d14369191a00fc458dafbc9c 100644 (file)
@@ -97,15 +97,17 @@ no_delete:
  */
 void udf_clear_inode(struct inode *inode)
 {
-       struct udf_inode_info *iinfo;
-       if (!(inode->i_sb->s_flags & MS_RDONLY)) {
-               lock_kernel();
-               udf_truncate_tail_extent(inode);
-               unlock_kernel();
-               write_inode_now(inode, 0);
-               invalidate_inode_buffers(inode);
+       struct udf_inode_info *iinfo = UDF_I(inode);
+
+       if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB &&
+           inode->i_size != iinfo->i_lenExtents) {
+               printk(KERN_WARNING "UDF-fs (%s): Inode %lu (mode %o) has "
+                       "inode size %llu different from extent lenght %llu. "
+                       "Filesystem need not be standards compliant.\n",
+                       inode->i_sb->s_id, inode->i_ino, inode->i_mode,
+                       (unsigned long long)inode->i_size,
+                       (unsigned long long)iinfo->i_lenExtents);
        }
-       iinfo = UDF_I(inode);
        kfree(iinfo->i_ext.i_data);
        iinfo->i_ext.i_data = NULL;
 }
@@ -198,7 +200,6 @@ struct buffer_head *udf_expand_dir_adinicb(struct inode *inode, int *block,
        int newblock;
        struct buffer_head *dbh = NULL;
        struct kernel_lb_addr eloc;
-       uint32_t elen;
        uint8_t alloctype;
        struct extent_position epos;
 
@@ -273,12 +274,11 @@ struct buffer_head *udf_expand_dir_adinicb(struct inode *inode, int *block,
        eloc.logicalBlockNum = *block;
        eloc.partitionReferenceNum =
                                iinfo->i_location.partitionReferenceNum;
-       elen = inode->i_sb->s_blocksize;
-       iinfo->i_lenExtents = elen;
+       iinfo->i_lenExtents = inode->i_size;
        epos.bh = NULL;
        epos.block = iinfo->i_location;
        epos.offset = udf_file_entry_alloc_offset(inode);
-       udf_add_aext(inode, &epos, &eloc, elen, 0);
+       udf_add_aext(inode, &epos, &eloc, inode->i_size, 0);
        /* UniqueID stuff */
 
        brelse(epos.bh);
index 21dad8c608f955bfac8933e7fea665f0e67a8491..cd2115060fdcc728aeefc7a21e80f6fc6af9c28c 100644 (file)
@@ -408,15 +408,6 @@ static struct fileIdentDesc *udf_add_entry(struct inode *dir,
        }
 
 add:
-       /* Is there any extent whose size we need to round up? */
-       if (dinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB && elen) {
-               elen = (elen + sb->s_blocksize - 1) & ~(sb->s_blocksize - 1);
-               if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT)
-                       epos.offset -= sizeof(struct short_ad);
-               else if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_LONG)
-                       epos.offset -= sizeof(struct long_ad);
-               udf_write_aext(dir, &epos, &eloc, elen, 1);
-       }
        f_pos += nfidlen;
 
        if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB &&
@@ -439,6 +430,7 @@ add:
                udf_current_aext(dir, &epos, &eloc, &elen, 1);
        }
 
+       /* Entry fits into current block? */
        if (sb->s_blocksize - fibh->eoffset >= nfidlen) {
                fibh->soffset = fibh->eoffset;
                fibh->eoffset += nfidlen;
@@ -462,6 +454,16 @@ add:
                                (fibh->sbh->b_data + fibh->soffset);
                }
        } else {
+               /* Round up last extent in the file */
+               elen = (elen + sb->s_blocksize - 1) & ~(sb->s_blocksize - 1);
+               if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT)
+                       epos.offset -= sizeof(struct short_ad);
+               else if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_LONG)
+                       epos.offset -= sizeof(struct long_ad);
+               udf_write_aext(dir, &epos, &eloc, elen, 1);
+               dinfo->i_lenExtents = (dinfo->i_lenExtents + sb->s_blocksize
+                                       - 1) & ~(sb->s_blocksize - 1);
+
                fibh->soffset = fibh->eoffset - sb->s_blocksize;
                fibh->eoffset += nfidlen - sb->s_blocksize;
                if (fibh->sbh != fibh->ebh) {
@@ -508,6 +510,20 @@ add:
                dir->i_size += nfidlen;
                if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB)
                        dinfo->i_lenAlloc += nfidlen;
+               else {
+                       /* Find the last extent and truncate it to proper size */
+                       while (udf_next_aext(dir, &epos, &eloc, &elen, 1) ==
+                               (EXT_RECORDED_ALLOCATED >> 30))
+                               ;
+                       elen -= dinfo->i_lenExtents - dir->i_size;
+                       if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT)
+                               epos.offset -= sizeof(struct short_ad);
+                       else if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_LONG)
+                               epos.offset -= sizeof(struct long_ad);
+                       udf_write_aext(dir, &epos, &eloc, elen, 1);
+                       dinfo->i_lenExtents = dir->i_size;
+               }
+
                mark_inode_dirty(dir);
                goto out_ok;
        } else {
@@ -922,7 +938,7 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry,
                block = udf_get_pblock(inode->i_sb, block,
                                iinfo->i_location.partitionReferenceNum,
                                0);
-               epos.bh = udf_tread(inode->i_sb, block);
+               epos.bh = udf_tgetblk(inode->i_sb, block);
                lock_buffer(epos.bh);
                memset(epos.bh->b_data, 0x00, inode->i_sb->s_blocksize);
                set_buffer_uptodate(epos.bh);
@@ -999,6 +1015,8 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry,
        inode->i_size = elen;
        if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB)
                iinfo->i_lenAlloc = inode->i_size;
+       else
+               udf_truncate_tail_extent(inode);
        mark_inode_dirty(inode);
 
        fi = udf_add_entry(dir, dentry, &fibh, &cfi, &err);