Merge branch 'for-2.6.39/core' of git://git.kernel.dk/linux-2.6-block
[pandora-kernel.git] / fs / udf / inode.c
index fa96fc0..1d1358e 100644 (file)
@@ -73,14 +73,12 @@ void udf_evict_inode(struct inode *inode)
        struct udf_inode_info *iinfo = UDF_I(inode);
        int want_delete = 0;
 
-       truncate_inode_pages(&inode->i_data, 0);
-
        if (!inode->i_nlink && !is_bad_inode(inode)) {
                want_delete = 1;
-               inode->i_size = 0;
-               udf_truncate(inode);
+               udf_setsize(inode, 0);
                udf_update_inode(inode, IS_SYNC(inode));
-       }
+       } else
+               truncate_inode_pages(&inode->i_data, 0);
        invalidate_inode_buffers(inode);
        end_writeback(inode);
        if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB &&
@@ -117,9 +115,18 @@ static int udf_write_begin(struct file *file, struct address_space *mapping,
 
        ret = block_write_begin(mapping, pos, len, flags, pagep, udf_get_block);
        if (unlikely(ret)) {
-               loff_t isize = mapping->host->i_size;
-               if (pos + len > isize)
-                       vmtruncate(mapping->host, isize);
+               struct inode *inode = mapping->host;
+               struct udf_inode_info *iinfo = UDF_I(inode);
+               loff_t isize = inode->i_size;
+
+               if (pos + len > isize) {
+                       truncate_pagecache(inode, pos + len, isize);
+                       if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) {
+                               down_write(&iinfo->i_data_sem);
+                               udf_truncate_extents(inode);
+                               up_write(&iinfo->i_data_sem);
+                       }
+               }
        }
 
        return ret;
@@ -138,30 +145,31 @@ const struct address_space_operations udf_aops = {
        .bmap           = udf_bmap,
 };
 
-void udf_expand_file_adinicb(struct inode *inode, int newsize, int *err)
+int udf_expand_file_adinicb(struct inode *inode)
 {
        struct page *page;
        char *kaddr;
        struct udf_inode_info *iinfo = UDF_I(inode);
+       int err;
        struct writeback_control udf_wbc = {
                .sync_mode = WB_SYNC_NONE,
                .nr_to_write = 1,
        };
 
-       /* from now on we have normal address_space methods */
-       inode->i_data.a_ops = &udf_aops;
-
        if (!iinfo->i_lenAlloc) {
                if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_USE_SHORT_AD))
                        iinfo->i_alloc_type = ICBTAG_FLAG_AD_SHORT;
                else
                        iinfo->i_alloc_type = ICBTAG_FLAG_AD_LONG;
+               /* from now on we have normal address_space methods */
+               inode->i_data.a_ops = &udf_aops;
                mark_inode_dirty(inode);
-               return;
+               return 0;
        }
 
-       page = grab_cache_page(inode->i_mapping, 0);
-       BUG_ON(!PageLocked(page));
+       page = find_or_create_page(inode->i_mapping, 0, GFP_NOFS);
+       if (!page)
+               return -ENOMEM;
 
        if (!PageUptodate(page)) {
                kaddr = kmap(page);
@@ -180,11 +188,24 @@ void udf_expand_file_adinicb(struct inode *inode, int newsize, int *err)
                iinfo->i_alloc_type = ICBTAG_FLAG_AD_SHORT;
        else
                iinfo->i_alloc_type = ICBTAG_FLAG_AD_LONG;
-
-       inode->i_data.a_ops->writepage(page, &udf_wbc);
+       /* from now on we have normal address_space methods */
+       inode->i_data.a_ops = &udf_aops;
+       err = inode->i_data.a_ops->writepage(page, &udf_wbc);
+       if (err) {
+               /* Restore everything back so that we don't lose data... */
+               lock_page(page);
+               kaddr = kmap(page);
+               memcpy(iinfo->i_ext.i_data + iinfo->i_lenEAttr, kaddr,
+                      inode->i_size);
+               kunmap(page);
+               unlock_page(page);
+               iinfo->i_alloc_type = ICBTAG_FLAG_AD_IN_ICB;
+               inode->i_data.a_ops = &udf_adinicb_aops;
+       }
        page_cache_release(page);
-
        mark_inode_dirty(inode);
+
+       return err;
 }
 
 struct buffer_head *udf_expand_dir_adinicb(struct inode *inode, int *block,
@@ -347,8 +368,10 @@ static struct buffer_head *udf_getblk(struct inode *inode, long block,
 }
 
 /* Extend the file by 'blocks' blocks, return the number of extents added */
-int udf_extend_file(struct inode *inode, struct extent_position *last_pos,
-                   struct kernel_long_ad *last_ext, sector_t blocks)
+static int udf_do_extend_file(struct inode *inode,
+                             struct extent_position *last_pos,
+                             struct kernel_long_ad *last_ext,
+                             sector_t blocks)
 {
        sector_t add;
        int count = 0, fake = !(last_ext->extLength & UDF_EXTENT_LENGTH_MASK);
@@ -356,6 +379,7 @@ int udf_extend_file(struct inode *inode, struct extent_position *last_pos,
        struct kernel_lb_addr prealloc_loc = {};
        int prealloc_len = 0;
        struct udf_inode_info *iinfo;
+       int err;
 
        /* The previous extent is fake and we should not extend by anything
         * - there's nothing to do... */
@@ -421,26 +445,29 @@ int udf_extend_file(struct inode *inode, struct extent_position *last_pos,
        /* Create enough extents to cover the whole hole */
        while (blocks > add) {
                blocks -= add;
-               if (udf_add_aext(inode, last_pos, &last_ext->extLocation,
-                                last_ext->extLength, 1) == -1)
-                       return -1;
+               err = udf_add_aext(inode, last_pos, &last_ext->extLocation,
+                                  last_ext->extLength, 1);
+               if (err)
+                       return err;
                count++;
        }
        if (blocks) {
                last_ext->extLength = EXT_NOT_RECORDED_NOT_ALLOCATED |
                        (blocks << sb->s_blocksize_bits);
-               if (udf_add_aext(inode, last_pos, &last_ext->extLocation,
-                                last_ext->extLength, 1) == -1)
-                       return -1;
+               err = udf_add_aext(inode, last_pos, &last_ext->extLocation,
+                                  last_ext->extLength, 1);
+               if (err)
+                       return err;
                count++;
        }
 
 out:
        /* Do we have some preallocated blocks saved? */
        if (prealloc_len) {
-               if (udf_add_aext(inode, last_pos, &prealloc_loc,
-                                prealloc_len, 1) == -1)
-                       return -1;
+               err = udf_add_aext(inode, last_pos, &prealloc_loc,
+                                  prealloc_len, 1);
+               if (err)
+                       return err;
                last_ext->extLocation = prealloc_loc;
                last_ext->extLength = prealloc_len;
                count++;
@@ -452,11 +479,68 @@ out:
        else if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_LONG)
                last_pos->offset -= sizeof(struct long_ad);
        else
-               return -1;
+               return -EIO;
 
        return count;
 }
 
+static int udf_extend_file(struct inode *inode, loff_t newsize)
+{
+
+       struct extent_position epos;
+       struct kernel_lb_addr eloc;
+       uint32_t elen;
+       int8_t etype;
+       struct super_block *sb = inode->i_sb;
+       sector_t first_block = newsize >> sb->s_blocksize_bits, offset;
+       int adsize;
+       struct udf_inode_info *iinfo = UDF_I(inode);
+       struct kernel_long_ad extent;
+       int err;
+
+       if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT)
+               adsize = sizeof(struct short_ad);
+       else if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_LONG)
+               adsize = sizeof(struct long_ad);
+       else
+               BUG();
+
+       etype = inode_bmap(inode, first_block, &epos, &eloc, &elen, &offset);
+
+       /* File has extent covering the new size (could happen when extending
+        * inside a block)? */
+       if (etype != -1)
+               return 0;
+       if (newsize & (sb->s_blocksize - 1))
+               offset++;
+       /* Extended file just to the boundary of the last file block? */
+       if (offset == 0)
+               return 0;
+
+       /* Truncate is extending the file by 'offset' blocks */
+       if ((!epos.bh && epos.offset == udf_file_entry_alloc_offset(inode)) ||
+           (epos.bh && epos.offset == sizeof(struct allocExtDesc))) {
+               /* File has no extents at all or has empty last
+                * indirect extent! Create a fake extent... */
+               extent.extLocation.logicalBlockNum = 0;
+               extent.extLocation.partitionReferenceNum = 0;
+               extent.extLength = EXT_NOT_RECORDED_NOT_ALLOCATED;
+       } else {
+               epos.offset -= adsize;
+               etype = udf_next_aext(inode, &epos, &extent.extLocation,
+                                     &extent.extLength, 0);
+               extent.extLength |= etype << 30;
+       }
+       err = udf_do_extend_file(inode, &epos, &extent, offset);
+       if (err < 0)
+               goto out;
+       err = 0;
+       iinfo->i_lenExtents = newsize;
+out:
+       brelse(epos.bh);
+       return err;
+}
+
 static struct buffer_head *inode_getblk(struct inode *inode, sector_t block,
                                        int *err, sector_t *phys, int *new)
 {
@@ -539,7 +623,7 @@ static struct buffer_head *inode_getblk(struct inode *inode, sector_t block,
                        elen = EXT_RECORDED_ALLOCATED |
                                ((elen + inode->i_sb->s_blocksize - 1) &
                                 ~(inode->i_sb->s_blocksize - 1));
-                       etype = udf_write_aext(inode, &cur_epos, &eloc, elen, 1);
+                       udf_write_aext(inode, &cur_epos, &eloc, elen, 1);
                }
                brelse(prev_epos.bh);
                brelse(cur_epos.bh);
@@ -563,19 +647,17 @@ static struct buffer_head *inode_getblk(struct inode *inode, sector_t block,
                        memset(&laarr[0].extLocation, 0x00,
                                sizeof(struct kernel_lb_addr));
                        laarr[0].extLength = EXT_NOT_RECORDED_NOT_ALLOCATED;
-                       /* Will udf_extend_file() create real extent from
+                       /* Will udf_do_extend_file() create real extent from
                           a fake one? */
                        startnum = (offset > 0);
                }
                /* Create extents for the hole between EOF and offset */
-               ret = udf_extend_file(inode, &prev_epos, laarr, offset);
-               if (ret == -1) {
+               ret = udf_do_extend_file(inode, &prev_epos, laarr, offset);
+               if (ret < 0) {
                        brelse(prev_epos.bh);
                        brelse(cur_epos.bh);
                        brelse(next_epos.bh);
-                       /* We don't really know the error here so we just make
-                        * something up */
-                       *err = -ENOSPC;
+                       *err = ret;
                        return NULL;
                }
                c = 0;
@@ -1004,52 +1086,66 @@ struct buffer_head *udf_bread(struct inode *inode, int block,
        return NULL;
 }
 
-void udf_truncate(struct inode *inode)
+int udf_setsize(struct inode *inode, loff_t newsize)
 {
-       int offset;
        int err;
        struct udf_inode_info *iinfo;
+       int bsize = 1 << inode->i_blkbits;
 
        if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) ||
              S_ISLNK(inode->i_mode)))
-               return;
+               return -EINVAL;
        if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
-               return;
+               return -EPERM;
 
        iinfo = UDF_I(inode);
-       if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
+       if (newsize > inode->i_size) {
                down_write(&iinfo->i_data_sem);
-               if (inode->i_sb->s_blocksize <
-                               (udf_file_entry_alloc_offset(inode) +
-                                inode->i_size)) {
-                       udf_expand_file_adinicb(inode, inode->i_size, &err);
-                       if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
-                               inode->i_size = iinfo->i_lenAlloc;
-                               up_write(&iinfo->i_data_sem);
-                               return;
+               if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
+                       if (bsize <
+                           (udf_file_entry_alloc_offset(inode) + newsize)) {
+                               err = udf_expand_file_adinicb(inode);
+                               if (err) {
+                                       up_write(&iinfo->i_data_sem);
+                                       return err;
+                               }
                        } else
-                               udf_truncate_extents(inode);
-               } else {
-                       offset = inode->i_size & (inode->i_sb->s_blocksize - 1);
-                       memset(iinfo->i_ext.i_data + iinfo->i_lenEAttr + offset,
-                               0x00, inode->i_sb->s_blocksize -
-                               offset - udf_file_entry_alloc_offset(inode));
-                       iinfo->i_lenAlloc = inode->i_size;
+                               iinfo->i_lenAlloc = newsize;
+               }
+               err = udf_extend_file(inode, newsize);
+               if (err) {
+                       up_write(&iinfo->i_data_sem);
+                       return err;
                }
+               truncate_setsize(inode, newsize);
                up_write(&iinfo->i_data_sem);
        } else {
-               block_truncate_page(inode->i_mapping, inode->i_size,
-                                   udf_get_block);
+               if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
+                       down_write(&iinfo->i_data_sem);
+                       memset(iinfo->i_ext.i_data + iinfo->i_lenEAttr + newsize,
+                              0x00, bsize - newsize -
+                              udf_file_entry_alloc_offset(inode));
+                       iinfo->i_lenAlloc = newsize;
+                       truncate_setsize(inode, newsize);
+                       up_write(&iinfo->i_data_sem);
+                       goto update_time;
+               }
+               err = block_truncate_page(inode->i_mapping, newsize,
+                                         udf_get_block);
+               if (err)
+                       return err;
                down_write(&iinfo->i_data_sem);
+               truncate_setsize(inode, newsize);
                udf_truncate_extents(inode);
                up_write(&iinfo->i_data_sem);
        }
-
+update_time:
        inode->i_mtime = inode->i_ctime = current_fs_time(inode->i_sb);
        if (IS_SYNC(inode))
                udf_sync_inode(inode);
        else
                mark_inode_dirty(inode);
+       return 0;
 }
 
 static void __udf_read_inode(struct inode *inode)
@@ -1636,14 +1732,13 @@ struct inode *udf_iget(struct super_block *sb, struct kernel_lb_addr *ino)
        return NULL;
 }
 
-int8_t udf_add_aext(struct inode *inode, struct extent_position *epos,
-                   struct kernel_lb_addr *eloc, uint32_t elen, int inc)
+int udf_add_aext(struct inode *inode, struct extent_position *epos,
+                struct kernel_lb_addr *eloc, uint32_t elen, int inc)
 {
        int adsize;
        struct short_ad *sad = NULL;
        struct long_ad *lad = NULL;
        struct allocExtDesc *aed;
-       int8_t etype;
        uint8_t *ptr;
        struct udf_inode_info *iinfo = UDF_I(inode);
 
@@ -1659,7 +1754,7 @@ int8_t udf_add_aext(struct inode *inode, struct extent_position *epos,
        else if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_LONG)
                adsize = sizeof(struct long_ad);
        else
-               return -1;
+               return -EIO;
 
        if (epos->offset + (2 * adsize) > inode->i_sb->s_blocksize) {
                unsigned char *sptr, *dptr;
@@ -1671,12 +1766,12 @@ int8_t udf_add_aext(struct inode *inode, struct extent_position *epos,
                                                obloc.partitionReferenceNum,
                                                obloc.logicalBlockNum, &err);
                if (!epos->block.logicalBlockNum)
-                       return -1;
+                       return -ENOSPC;
                nbh = udf_tgetblk(inode->i_sb, udf_get_lb_pblock(inode->i_sb,
                                                                 &epos->block,
                                                                 0));
                if (!nbh)
-                       return -1;
+                       return -EIO;
                lock_buffer(nbh);
                memset(nbh->b_data, 0x00, inode->i_sb->s_blocksize);
                set_buffer_uptodate(nbh);
@@ -1745,7 +1840,7 @@ int8_t udf_add_aext(struct inode *inode, struct extent_position *epos,
                epos->bh = nbh;
        }
 
-       etype = udf_write_aext(inode, epos, eloc, elen, inc);
+       udf_write_aext(inode, epos, eloc, elen, inc);
 
        if (!epos->bh) {
                iinfo->i_lenAlloc += adsize;
@@ -1763,11 +1858,11 @@ int8_t udf_add_aext(struct inode *inode, struct extent_position *epos,
                mark_buffer_dirty_inode(epos->bh, inode);
        }
 
-       return etype;
+       return 0;
 }
 
-int8_t udf_write_aext(struct inode *inode, struct extent_position *epos,
-                     struct kernel_lb_addr *eloc, uint32_t elen, int inc)
+void udf_write_aext(struct inode *inode, struct extent_position *epos,
+                   struct kernel_lb_addr *eloc, uint32_t elen, int inc)
 {
        int adsize;
        uint8_t *ptr;
@@ -1797,7 +1892,7 @@ int8_t udf_write_aext(struct inode *inode, struct extent_position *epos,
                adsize = sizeof(struct long_ad);
                break;
        default:
-               return -1;
+               return;
        }
 
        if (epos->bh) {
@@ -1816,8 +1911,6 @@ int8_t udf_write_aext(struct inode *inode, struct extent_position *epos,
 
        if (inc)
                epos->offset += adsize;
-
-       return (elen >> 30);
 }
 
 int8_t udf_next_aext(struct inode *inode, struct extent_position *epos,