udf: Fix incorrect final NOT_ALLOCATED (hole) extent length

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: 2c948b3f86 ("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>
This commit is contained in:
Steven J. Magnani 2019-06-30 21:39:35 -05:00 committed by Jan Kara
parent 90f15ac9fa
commit fa33cdbf3e

View File

@ -470,13 +470,15 @@ static struct buffer_head *udf_getblk(struct inode *inode, udf_pblk_t block,
return NULL; 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, static int udf_do_extend_file(struct inode *inode,
struct extent_position *last_pos, struct extent_position *last_pos,
struct kernel_long_ad *last_ext, 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); int count = 0, fake = !(last_ext->extLength & UDF_EXTENT_LENGTH_MASK);
struct super_block *sb = inode->i_sb; struct super_block *sb = inode->i_sb;
struct kernel_lb_addr prealloc_loc = {}; 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 /* The previous extent is fake and we should not extend by anything
* - there's nothing to do... */ * - there's nothing to do... */
if (!blocks && fake) if (!new_block_bytes && fake)
return 0; return 0;
iinfo = UDF_I(inode); iinfo = UDF_I(inode);
@ -517,13 +519,12 @@ static int udf_do_extend_file(struct inode *inode,
/* Can we merge with the previous extent? */ /* Can we merge with the previous extent? */
if ((last_ext->extLength & UDF_EXTENT_FLAG_MASK) == if ((last_ext->extLength & UDF_EXTENT_FLAG_MASK) ==
EXT_NOT_RECORDED_NOT_ALLOCATED) { EXT_NOT_RECORDED_NOT_ALLOCATED) {
add = ((1 << 30) - sb->s_blocksize - add = (1 << 30) - sb->s_blocksize -
(last_ext->extLength & UDF_EXTENT_LENGTH_MASK)) >> (last_ext->extLength & UDF_EXTENT_LENGTH_MASK);
sb->s_blocksize_bits; if (add > new_block_bytes)
if (add > blocks) add = new_block_bytes;
add = blocks; new_block_bytes -= add;
blocks -= add; last_ext->extLength += add;
last_ext->extLength += add << sb->s_blocksize_bits;
} }
if (fake) { if (fake) {
@ -544,28 +545,27 @@ static int udf_do_extend_file(struct inode *inode,
} }
/* Managed to do everything necessary? */ /* Managed to do everything necessary? */
if (!blocks) if (!new_block_bytes)
goto out; goto out;
/* All further extents will be NOT_RECORDED_NOT_ALLOCATED */ /* All further extents will be NOT_RECORDED_NOT_ALLOCATED */
last_ext->extLocation.logicalBlockNum = 0; last_ext->extLocation.logicalBlockNum = 0;
last_ext->extLocation.partitionReferenceNum = 0; last_ext->extLocation.partitionReferenceNum = 0;
add = (1 << (30-sb->s_blocksize_bits)) - 1; add = (1 << 30) - sb->s_blocksize;
last_ext->extLength = EXT_NOT_RECORDED_NOT_ALLOCATED | last_ext->extLength = EXT_NOT_RECORDED_NOT_ALLOCATED | add;
(add << sb->s_blocksize_bits);
/* Create enough extents to cover the whole hole */ /* Create enough extents to cover the whole hole */
while (blocks > add) { while (new_block_bytes > add) {
blocks -= add; new_block_bytes -= add;
err = udf_add_aext(inode, last_pos, &last_ext->extLocation, err = udf_add_aext(inode, last_pos, &last_ext->extLocation,
last_ext->extLength, 1); last_ext->extLength, 1);
if (err) if (err)
return err; return err;
count++; count++;
} }
if (blocks) { if (new_block_bytes) {
last_ext->extLength = EXT_NOT_RECORDED_NOT_ALLOCATED | 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, err = udf_add_aext(inode, last_pos, &last_ext->extLocation,
last_ext->extLength, 1); last_ext->extLength, 1);
if (err) if (err)
@ -596,6 +596,24 @@ static int udf_do_extend_file(struct inode *inode,
return count; 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) 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; int8_t etype;
struct super_block *sb = inode->i_sb; struct super_block *sb = inode->i_sb;
sector_t first_block = newsize >> sb->s_blocksize_bits, offset; sector_t first_block = newsize >> sb->s_blocksize_bits, offset;
unsigned long partial_final_block;
int adsize; int adsize;
struct udf_inode_info *iinfo = UDF_I(inode); struct udf_inode_info *iinfo = UDF_I(inode);
struct kernel_long_ad extent; struct kernel_long_ad extent;
int err; int err = 0;
int within_final_block;
if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT) if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT)
adsize = sizeof(struct short_ad); adsize = sizeof(struct short_ad);
@ -618,18 +638,8 @@ static int udf_extend_file(struct inode *inode, loff_t newsize)
BUG(); BUG();
etype = inode_bmap(inode, first_block, &epos, &eloc, &elen, &offset); 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)) || if ((!epos.bh && epos.offset == udf_file_entry_alloc_offset(inode)) ||
(epos.bh && epos.offset == sizeof(struct allocExtDesc))) { (epos.bh && epos.offset == sizeof(struct allocExtDesc))) {
/* File has no extents at all or has empty last /* 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, 0);
extent.extLength |= etype << 30; 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) if (err < 0)
goto out; goto out;
err = 0; err = 0;
@ -745,6 +770,7 @@ static sector_t inode_getblk(struct inode *inode, sector_t block,
/* Are we beyond EOF? */ /* Are we beyond EOF? */
if (etype == -1) { if (etype == -1) {
int ret; int ret;
loff_t hole_len;
isBeyondEOF = true; isBeyondEOF = true;
if (count) { if (count) {
if (c) if (c)
@ -760,7 +786,8 @@ static sector_t inode_getblk(struct inode *inode, sector_t block,
startnum = (offset > 0); startnum = (offset > 0);
} }
/* Create extents for the hole between EOF and offset */ /* 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) { if (ret < 0) {
*err = ret; *err = ret;
newblock = 0; newblock = 0;