udf: Fix incorrect final NOT_ALLOCATED (hole) extent length
authorSteven J. Magnani <steve.magnani@digidescorp.com>
Mon, 1 Jul 2019 02:39:35 +0000 (21:39 -0500)
committerJan Kara <jack@suse.cz>
Wed, 10 Jul 2019 08:11:24 +0000 (10:11 +0200)
In some cases, using the 'truncate' command to extend a UDF file results
in a mismatch between the length of the file's extents (specifically, due
to incorrect length of the final NOT_ALLOCATED extent) and the information
(file) length. The discrepancy can prevent other operating systems
(i.e., Windows 10) from opening the file.

Two particular errors have been observed when extending a file:

1. The final extent is larger than it should be, having been rounded up
   to a multiple of the block size.

B. The final extent is not shorter than it should be, due to not having
   been updated when the file's information length was increased.

[JK: simplified udf_do_extend_final_block(), fixed up some types]

Fixes: 2c948b3f86e5 ("udf: Avoid IO in udf_clear_inode")
CC: stable@vger.kernel.org
Signed-off-by: Steven J. Magnani <steve@digidescorp.com>
Link: https://lore.kernel.org/r/1561948775-5878-1-git-send-email-steve@digidescorp.com
Signed-off-by: Jan Kara <jack@suse.cz>
fs/udf/inode.c

index e7276932e433c9cdc1bdd10f9a35a3d0ef09ece7..9bb18311a22fc1a60bb642c8b68891c2e5a05949 100644 (file)
@@ -470,13 +470,15 @@ static struct buffer_head *udf_getblk(struct inode *inode, udf_pblk_t block,
        return NULL;
 }
 
-/* Extend the file by 'blocks' blocks, return the number of extents added */
+/* Extend the file with new blocks totaling 'new_block_bytes',
+ * return the number of extents added
+ */
 static int udf_do_extend_file(struct inode *inode,
                              struct extent_position *last_pos,
                              struct kernel_long_ad *last_ext,
-                             sector_t blocks)
+                             loff_t new_block_bytes)
 {
-       sector_t add;
+       uint32_t add;
        int count = 0, fake = !(last_ext->extLength & UDF_EXTENT_LENGTH_MASK);
        struct super_block *sb = inode->i_sb;
        struct kernel_lb_addr prealloc_loc = {};
@@ -486,7 +488,7 @@ static int udf_do_extend_file(struct inode *inode,
 
        /* The previous extent is fake and we should not extend by anything
         * - there's nothing to do... */
-       if (!blocks && fake)
+       if (!new_block_bytes && fake)
                return 0;
 
        iinfo = UDF_I(inode);
@@ -517,13 +519,12 @@ static int udf_do_extend_file(struct inode *inode,
        /* Can we merge with the previous extent? */
        if ((last_ext->extLength & UDF_EXTENT_FLAG_MASK) ==
                                        EXT_NOT_RECORDED_NOT_ALLOCATED) {
-               add = ((1 << 30) - sb->s_blocksize -
-                       (last_ext->extLength & UDF_EXTENT_LENGTH_MASK)) >>
-                       sb->s_blocksize_bits;
-               if (add > blocks)
-                       add = blocks;
-               blocks -= add;
-               last_ext->extLength += add << sb->s_blocksize_bits;
+               add = (1 << 30) - sb->s_blocksize -
+                       (last_ext->extLength & UDF_EXTENT_LENGTH_MASK);
+               if (add > new_block_bytes)
+                       add = new_block_bytes;
+               new_block_bytes -= add;
+               last_ext->extLength += add;
        }
 
        if (fake) {
@@ -544,28 +545,27 @@ static int udf_do_extend_file(struct inode *inode,
        }
 
        /* Managed to do everything necessary? */
-       if (!blocks)
+       if (!new_block_bytes)
                goto out;
 
        /* All further extents will be NOT_RECORDED_NOT_ALLOCATED */
        last_ext->extLocation.logicalBlockNum = 0;
        last_ext->extLocation.partitionReferenceNum = 0;
-       add = (1 << (30-sb->s_blocksize_bits)) - 1;
-       last_ext->extLength = EXT_NOT_RECORDED_NOT_ALLOCATED |
-                               (add << sb->s_blocksize_bits);
+       add = (1 << 30) - sb->s_blocksize;
+       last_ext->extLength = EXT_NOT_RECORDED_NOT_ALLOCATED | add;
 
        /* Create enough extents to cover the whole hole */
-       while (blocks > add) {
-               blocks -= add;
+       while (new_block_bytes > add) {
+               new_block_bytes -= add;
                err = udf_add_aext(inode, last_pos, &last_ext->extLocation,
                                   last_ext->extLength, 1);
                if (err)
                        return err;
                count++;
        }
-       if (blocks) {
+       if (new_block_bytes) {
                last_ext->extLength = EXT_NOT_RECORDED_NOT_ALLOCATED |
-                       (blocks << sb->s_blocksize_bits);
+                       new_block_bytes;
                err = udf_add_aext(inode, last_pos, &last_ext->extLocation,
                                   last_ext->extLength, 1);
                if (err)
@@ -596,6 +596,24 @@ out:
        return count;
 }
 
+/* Extend the final block of the file to final_block_len bytes */
+static void udf_do_extend_final_block(struct inode *inode,
+                                     struct extent_position *last_pos,
+                                     struct kernel_long_ad *last_ext,
+                                     uint32_t final_block_len)
+{
+       struct super_block *sb = inode->i_sb;
+       uint32_t added_bytes;
+
+       added_bytes = final_block_len -
+                     (last_ext->extLength & (sb->s_blocksize - 1));
+       last_ext->extLength += added_bytes;
+       UDF_I(inode)->i_lenExtents += added_bytes;
+
+       udf_write_aext(inode, last_pos, &last_ext->extLocation,
+                       last_ext->extLength, 1);
+}
+
 static int udf_extend_file(struct inode *inode, loff_t newsize)
 {
 
@@ -605,10 +623,12 @@ static int udf_extend_file(struct inode *inode, loff_t newsize)
        int8_t etype;
        struct super_block *sb = inode->i_sb;
        sector_t first_block = newsize >> sb->s_blocksize_bits, offset;
+       unsigned long partial_final_block;
        int adsize;
        struct udf_inode_info *iinfo = UDF_I(inode);
        struct kernel_long_ad extent;
-       int err;
+       int err = 0;
+       int within_final_block;
 
        if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT)
                adsize = sizeof(struct short_ad);
@@ -618,18 +638,8 @@ static int udf_extend_file(struct inode *inode, loff_t newsize)
                BUG();
 
        etype = inode_bmap(inode, first_block, &epos, &eloc, &elen, &offset);
+       within_final_block = (etype != -1);
 
-       /* 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
@@ -643,7 +653,22 @@ static int udf_extend_file(struct inode *inode, loff_t newsize)
                                      &extent.extLength, 0);
                extent.extLength |= etype << 30;
        }
-       err = udf_do_extend_file(inode, &epos, &extent, offset);
+
+       partial_final_block = newsize & (sb->s_blocksize - 1);
+
+       /* File has extent covering the new size (could happen when extending
+        * inside a block)?
+        */
+       if (within_final_block) {
+               /* Extending file within the last file block */
+               udf_do_extend_final_block(inode, &epos, &extent,
+                                         partial_final_block);
+       } else {
+               loff_t add = ((loff_t)offset << sb->s_blocksize_bits) |
+                            partial_final_block;
+               err = udf_do_extend_file(inode, &epos, &extent, add);
+       }
+
        if (err < 0)
                goto out;
        err = 0;
@@ -745,6 +770,7 @@ static sector_t inode_getblk(struct inode *inode, sector_t block,
        /* Are we beyond EOF? */
        if (etype == -1) {
                int ret;
+               loff_t hole_len;
                isBeyondEOF = true;
                if (count) {
                        if (c)
@@ -760,7 +786,8 @@ static sector_t inode_getblk(struct inode *inode, sector_t block,
                        startnum = (offset > 0);
                }
                /* Create extents for the hole between EOF and offset */
-               ret = udf_do_extend_file(inode, &prev_epos, laarr, offset);
+               hole_len = (loff_t)offset << inode->i_blkbits;
+               ret = udf_do_extend_file(inode, &prev_epos, laarr, hole_len);
                if (ret < 0) {
                        *err = ret;
                        newblock = 0;