diff options
Diffstat (limited to 'fs/udf')
| -rw-r--r-- | fs/udf/Kconfig | 19 | ||||
| -rw-r--r-- | fs/udf/Makefile | 1 | ||||
| -rw-r--r-- | fs/udf/balloc.c | 330 | ||||
| -rw-r--r-- | fs/udf/dir.c | 199 | ||||
| -rw-r--r-- | fs/udf/directory.c | 593 | ||||
| -rw-r--r-- | fs/udf/ecma_167.h | 174 | ||||
| -rw-r--r-- | fs/udf/file.c | 299 | ||||
| -rw-r--r-- | fs/udf/ialloc.c | 96 | ||||
| -rw-r--r-- | fs/udf/inode.c | 1814 | ||||
| -rw-r--r-- | fs/udf/lowlevel.c | 45 | ||||
| -rw-r--r-- | fs/udf/misc.c | 50 | ||||
| -rw-r--r-- | fs/udf/namei.c | 1345 | ||||
| -rw-r--r-- | fs/udf/osta_udf.h | 152 | ||||
| -rw-r--r-- | fs/udf/partition.c | 41 | ||||
| -rw-r--r-- | fs/udf/super.c | 2112 | ||||
| -rw-r--r-- | fs/udf/symlink.c | 128 | ||||
| -rw-r--r-- | fs/udf/truncate.c | 110 | ||||
| -rw-r--r-- | fs/udf/udf_i.h | 18 | ||||
| -rw-r--r-- | fs/udf/udf_sb.h | 53 | ||||
| -rw-r--r-- | fs/udf/udfdecl.h | 146 | ||||
| -rw-r--r-- | fs/udf/udfend.h | 1 | ||||
| -rw-r--r-- | fs/udf/udftime.c | 141 | ||||
| -rw-r--r-- | fs/udf/unicode.c | 705 |
23 files changed, 4357 insertions, 4215 deletions
diff --git a/fs/udf/Kconfig b/fs/udf/Kconfig index 0e0e99bd6bce..8f7ce30d47fd 100644 --- a/fs/udf/Kconfig +++ b/fs/udf/Kconfig @@ -1,18 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only config UDF_FS tristate "UDF file system support" + select BUFFER_HEAD select CRC_ITU_T + select NLS + select LEGACY_DIRECT_IO help - This is the new file system used on some CD-ROMs and DVDs. Say Y if - you intend to mount DVD discs or CDRW's written in packet mode, or - if written to by other UDF utilities, such as DirectCD. - Please read <file:Documentation/filesystems/udf.txt>. + This is a file system used on some CD-ROMs and DVDs. Since the + file system is supported by multiple operating systems and is more + compatible with standard unix file systems, it is also suitable for + removable USB disks. Say Y if you intend to mount DVD discs or CDRW's + written in packet mode, or if you want to use UDF for removable USB + disks. Please read <file:Documentation/filesystems/udf.rst>. To compile this file system support as a module, choose M here: the module will be called udf. If unsure, say N. - -config UDF_NLS - bool - default y - depends on (UDF_FS=m && NLS) || (UDF_FS=y && NLS=y) diff --git a/fs/udf/Makefile b/fs/udf/Makefile index eb880f66c23a..63981cd333e3 100644 --- a/fs/udf/Makefile +++ b/fs/udf/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only # # Makefile for the linux udf-filesystem routines. # diff --git a/fs/udf/balloc.c b/fs/udf/balloc.c index 1ba2baaf4367..807c493ed0cd 100644 --- a/fs/udf/balloc.c +++ b/fs/udf/balloc.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * balloc.c * @@ -5,11 +6,6 @@ * Block allocation handling routines for the OSTA-UDF(tm) filesystem. * * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. - * * (C) 1999-2001 Ben Fennema * (C) 1999 Stelias Computing Inc * @@ -21,8 +17,8 @@ #include "udfdecl.h" -#include <linux/buffer_head.h> #include <linux/bitops.h> +#include <linux/overflow.h> #include "udf_i.h" #include "udf_sb.h" @@ -37,58 +33,74 @@ static int read_block_bitmap(struct super_block *sb, unsigned long bitmap_nr) { struct buffer_head *bh = NULL; - int retval = 0; + int i; + int max_bits, off, count; struct kernel_lb_addr loc; loc.logicalBlockNum = bitmap->s_extPosition; loc.partitionReferenceNum = UDF_SB(sb)->s_partition; - bh = udf_tread(sb, udf_get_lb_pblock(sb, &loc, block)); + bh = sb_bread(sb, udf_get_lb_pblock(sb, &loc, block)); + bitmap->s_block_bitmap[bitmap_nr] = bh; if (!bh) - retval = -EIO; + return -EIO; - bitmap->s_block_bitmap[bitmap_nr] = bh; - return retval; + /* Check consistency of Space Bitmap buffer. */ + max_bits = sb->s_blocksize * 8; + if (!bitmap_nr) { + off = sizeof(struct spaceBitmapDesc) << 3; + count = min(max_bits - off, bitmap->s_nr_groups); + } else { + /* + * Rough check if bitmap number is too big to have any bitmap + * blocks reserved. + */ + if (bitmap_nr > + (bitmap->s_nr_groups >> (sb->s_blocksize_bits + 3)) + 2) + return 0; + off = 0; + count = bitmap->s_nr_groups - bitmap_nr * max_bits + + (sizeof(struct spaceBitmapDesc) << 3); + count = min(count, max_bits); + } + + for (i = 0; i < count; i++) + if (udf_test_bit(i + off, bh->b_data)) { + bitmap->s_block_bitmap[bitmap_nr] = + ERR_PTR(-EFSCORRUPTED); + brelse(bh); + return -EFSCORRUPTED; + } + return 0; } -static int __load_block_bitmap(struct super_block *sb, - struct udf_bitmap *bitmap, - unsigned int block_group) +static int load_block_bitmap(struct super_block *sb, + struct udf_bitmap *bitmap, + unsigned int block_group) { int retval = 0; int nr_groups = bitmap->s_nr_groups; if (block_group >= nr_groups) { - udf_debug("block_group (%d) > nr_groups (%d)\n", + udf_debug("block_group (%u) > nr_groups (%d)\n", block_group, nr_groups); } if (bitmap->s_block_bitmap[block_group]) { - return block_group; - } else { - retval = read_block_bitmap(sb, bitmap, block_group, - block_group); - if (retval < 0) - return retval; + /* + * The bitmap failed verification in the past. No point in + * trying again. + */ + if (IS_ERR(bitmap->s_block_bitmap[block_group])) + return PTR_ERR(bitmap->s_block_bitmap[block_group]); return block_group; } -} - -static inline int load_block_bitmap(struct super_block *sb, - struct udf_bitmap *bitmap, - unsigned int block_group) -{ - int slot; - - slot = __load_block_bitmap(sb, bitmap, block_group); - if (slot < 0) - return slot; + retval = read_block_bitmap(sb, bitmap, block_group, block_group); + if (retval < 0) + return retval; - if (!bitmap->s_block_bitmap[slot]) - return -EIO; - - return slot; + return block_group; } static void udf_add_free_space(struct super_block *sb, u16 partition, u32 cnt) @@ -112,7 +124,6 @@ static void udf_bitmap_free_blocks(struct super_block *sb, { struct udf_sb_info *sbi = UDF_SB(sb); struct buffer_head *bh = NULL; - struct udf_part_map *partmap; unsigned long block; unsigned long block_group; unsigned long bit; @@ -121,19 +132,9 @@ static void udf_bitmap_free_blocks(struct super_block *sb, unsigned long overflow; mutex_lock(&sbi->s_alloc_mutex); - partmap = &sbi->s_partmaps[bloc->partitionReferenceNum]; - if (bloc->logicalBlockNum + count < count || - (bloc->logicalBlockNum + count) > partmap->s_partition_len) { - udf_debug("%d < %d || %d + %d > %d\n", - bloc->logicalBlockNum, 0, - bloc->logicalBlockNum, count, - partmap->s_partition_len); - goto error_return; - } - + /* We make sure this cannot overflow when mounting the filesystem */ block = bloc->logicalBlockNum + offset + (sizeof(struct spaceBitmapDesc) << 3); - do { overflow = 0; block_group = block >> (sb->s_blocksize_bits + 3); @@ -153,9 +154,9 @@ static void udf_bitmap_free_blocks(struct super_block *sb, bh = bitmap->s_block_bitmap[bitmap_nr]; for (i = 0; i < count; i++) { if (udf_set_bit(bit + i, bh->b_data)) { - udf_debug("bit %ld already set\n", bit + i); + udf_debug("bit %lu already set\n", bit + i); udf_debug("byte=%2x\n", - ((char *)bh->b_data)[(bit + i) >> 3]); + ((__u8 *)bh->b_data)[(bit + i) >> 3]); } } udf_add_free_space(sb, sbi->s_partition, count); @@ -177,8 +178,8 @@ static int udf_bitmap_prealloc_blocks(struct super_block *sb, { struct udf_sb_info *sbi = UDF_SB(sb); int alloc_count = 0; - int bit, block, block_group, group_start; - int nr_groups, bitmap_nr; + int bit, block, block_group; + int bitmap_nr; struct buffer_head *bh; __u32 part_len; @@ -191,10 +192,8 @@ static int udf_bitmap_prealloc_blocks(struct super_block *sb, block_count = part_len - first_block; do { - nr_groups = udf_compute_nr_groups(sb, partition); block = first_block + (sizeof(struct spaceBitmapDesc) << 3); block_group = block >> (sb->s_blocksize_bits + 3); - group_start = block_group ? 0 : sizeof(struct spaceBitmapDesc); bitmap_nr = load_block_bitmap(sb, bitmap, block_group); if (bitmap_nr < 0) @@ -220,16 +219,18 @@ out: return alloc_count; } -static int udf_bitmap_new_block(struct super_block *sb, +static udf_pblk_t udf_bitmap_new_block(struct super_block *sb, struct udf_bitmap *bitmap, uint16_t partition, uint32_t goal, int *err) { struct udf_sb_info *sbi = UDF_SB(sb); - int newbit, bit = 0, block, block_group, group_start; + int newbit, bit = 0; + udf_pblk_t block; + int block_group, group_start; int end_goal, nr_groups, bitmap_nr, i; struct buffer_head *bh = NULL; char *ptr; - int newblock = 0; + udf_pblk_t newblock = 0; *err = -ENOSPC; mutex_lock(&sbi->s_alloc_mutex); @@ -327,6 +328,17 @@ got_block: newblock = bit + (block_group << (sb->s_blocksize_bits + 3)) - (sizeof(struct spaceBitmapDesc) << 3); + if (newblock >= sbi->s_partmaps[partition].s_partition_len) { + /* + * Ran off the end of the bitmap, and bits following are + * non-compliant (not all zero) + */ + udf_err(sb, "bitmap for partition %d corrupted (block %u marked" + " as free, partition length is %u)\n", partition, + newblock, sbi->s_partmaps[partition].s_partition_len); + goto error_return; + } + if (!udf_clear_bit(bit, bh->b_data)) { udf_debug("bit already cleared for block %d\n", bit); goto repeat; @@ -352,26 +364,15 @@ static void udf_table_free_blocks(struct super_block *sb, uint32_t count) { struct udf_sb_info *sbi = UDF_SB(sb); - struct udf_part_map *partmap; uint32_t start, end; uint32_t elen; struct kernel_lb_addr eloc; struct extent_position oepos, epos; int8_t etype; - int i; struct udf_inode_info *iinfo; + int ret = 0; mutex_lock(&sbi->s_alloc_mutex); - partmap = &sbi->s_partmaps[bloc->partitionReferenceNum]; - if (bloc->logicalBlockNum + count < count || - (bloc->logicalBlockNum + count) > partmap->s_partition_len) { - udf_debug("%d < %d || %d + %d > %d\n", - bloc->logicalBlockNum, 0, - bloc->logicalBlockNum, count, - partmap->s_partition_len); - goto error_return; - } - iinfo = UDF_I(table); udf_add_free_space(sb, sbi->s_partition, count); @@ -383,8 +384,12 @@ static void udf_table_free_blocks(struct super_block *sb, epos.block = oepos.block = iinfo->i_location; epos.bh = oepos.bh = NULL; - while (count && - (etype = udf_next_aext(table, &epos, &eloc, &elen, 1)) != -1) { + while (count) { + ret = udf_next_aext(table, &epos, &eloc, &elen, &etype, 1); + if (ret < 0) + goto error_return; + if (ret == 0) + break; if (((eloc.logicalBlockNum + (elen >> sb->s_blocksize_bits)) == start)) { if ((0x3FFFFFFF - elen) < @@ -425,7 +430,6 @@ static void udf_table_free_blocks(struct super_block *sb, } if (epos.bh != oepos.bh) { - i = -1; oepos.block = epos.block; brelse(oepos.bh); get_bh(epos.bh); @@ -451,9 +455,6 @@ static void udf_table_free_blocks(struct super_block *sb, */ int adsize; - struct short_ad *sad = NULL; - struct long_ad *lad = NULL; - struct allocExtDesc *aed; eloc.logicalBlockNum = start; elen = EXT_RECORDED_ALLOCATED | @@ -463,115 +464,27 @@ static void udf_table_free_blocks(struct super_block *sb, adsize = sizeof(struct short_ad); else if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_LONG) adsize = sizeof(struct long_ad); - else { - brelse(oepos.bh); - brelse(epos.bh); + else goto error_return; - } if (epos.offset + (2 * adsize) > sb->s_blocksize) { - unsigned char *sptr, *dptr; - int loffset; - - brelse(oepos.bh); - oepos = epos; - /* Steal a block from the extent being free'd */ - epos.block.logicalBlockNum = eloc.logicalBlockNum; + udf_setup_indirect_aext(table, eloc.logicalBlockNum, + &epos); + eloc.logicalBlockNum++; elen -= sb->s_blocksize; - - epos.bh = udf_tread(sb, - udf_get_lb_pblock(sb, &epos.block, 0)); - if (!epos.bh) { - brelse(oepos.bh); - goto error_return; - } - aed = (struct allocExtDesc *)(epos.bh->b_data); - aed->previousAllocExtLocation = - cpu_to_le32(oepos.block.logicalBlockNum); - if (epos.offset + adsize > sb->s_blocksize) { - loffset = epos.offset; - aed->lengthAllocDescs = cpu_to_le32(adsize); - sptr = iinfo->i_ext.i_data + epos.offset - - adsize; - dptr = epos.bh->b_data + - sizeof(struct allocExtDesc); - memcpy(dptr, sptr, adsize); - epos.offset = sizeof(struct allocExtDesc) + - adsize; - } else { - loffset = epos.offset + adsize; - aed->lengthAllocDescs = cpu_to_le32(0); - if (oepos.bh) { - sptr = oepos.bh->b_data + epos.offset; - aed = (struct allocExtDesc *) - oepos.bh->b_data; - le32_add_cpu(&aed->lengthAllocDescs, - adsize); - } else { - sptr = iinfo->i_ext.i_data + - epos.offset; - iinfo->i_lenAlloc += adsize; - mark_inode_dirty(table); - } - epos.offset = sizeof(struct allocExtDesc); - } - if (sbi->s_udfrev >= 0x0200) - udf_new_tag(epos.bh->b_data, TAG_IDENT_AED, - 3, 1, epos.block.logicalBlockNum, - sizeof(struct tag)); - else - udf_new_tag(epos.bh->b_data, TAG_IDENT_AED, - 2, 1, epos.block.logicalBlockNum, - sizeof(struct tag)); - - switch (iinfo->i_alloc_type) { - case ICBTAG_FLAG_AD_SHORT: - sad = (struct short_ad *)sptr; - sad->extLength = cpu_to_le32( - EXT_NEXT_EXTENT_ALLOCDECS | - sb->s_blocksize); - sad->extPosition = - cpu_to_le32(epos.block.logicalBlockNum); - break; - case ICBTAG_FLAG_AD_LONG: - lad = (struct long_ad *)sptr; - lad->extLength = cpu_to_le32( - EXT_NEXT_EXTENT_ALLOCDECS | - sb->s_blocksize); - lad->extLocation = - cpu_to_lelb(epos.block); - break; - } - if (oepos.bh) { - udf_update_tag(oepos.bh->b_data, loffset); - mark_buffer_dirty(oepos.bh); - } else { - mark_inode_dirty(table); - } } /* It's possible that stealing the block emptied the extent */ - if (elen) { - udf_write_aext(table, &epos, &eloc, elen, 1); - - if (!epos.bh) { - iinfo->i_lenAlloc += adsize; - mark_inode_dirty(table); - } else { - aed = (struct allocExtDesc *)epos.bh->b_data; - le32_add_cpu(&aed->lengthAllocDescs, adsize); - udf_update_tag(epos.bh->b_data, epos.offset); - mark_buffer_dirty(epos.bh); - } - } + if (elen) + __udf_add_aext(table, &epos, &eloc, elen, 1); } +error_return: brelse(epos.bh); brelse(oepos.bh); -error_return: mutex_unlock(&sbi->s_alloc_mutex); return; } @@ -587,6 +500,7 @@ static int udf_table_prealloc_blocks(struct super_block *sb, struct extent_position epos; int8_t etype = -1; struct udf_inode_info *iinfo; + int ret = 0; if (first_block >= sbi->s_partmaps[partition].s_partition_len) return 0; @@ -605,11 +519,14 @@ static int udf_table_prealloc_blocks(struct super_block *sb, epos.bh = NULL; eloc.logicalBlockNum = 0xFFFFFFFF; - while (first_block != eloc.logicalBlockNum && - (etype = udf_next_aext(table, &epos, &eloc, &elen, 1)) != -1) { - udf_debug("eloc=%d, elen=%d, first_block=%d\n", + while (first_block != eloc.logicalBlockNum) { + ret = udf_next_aext(table, &epos, &eloc, &elen, &etype, 1); + if (ret < 0) + goto err_out; + if (ret == 0) + break; + udf_debug("eloc=%u, elen=%u, first_block=%u\n", eloc.logicalBlockNum, elen, first_block); - ; /* empty loop body */ } if (first_block == eloc.logicalBlockNum) { @@ -623,12 +540,12 @@ static int udf_table_prealloc_blocks(struct super_block *sb, udf_write_aext(table, &epos, &eloc, (etype << 30) | elen, 1); } else - udf_delete_aext(table, epos, eloc, - (etype << 30) | elen); + udf_delete_aext(table, epos); } else { alloc_count = 0; } +err_out: brelse(epos.bh); if (alloc_count) @@ -637,18 +554,20 @@ static int udf_table_prealloc_blocks(struct super_block *sb, return alloc_count; } -static int udf_table_new_block(struct super_block *sb, +static udf_pblk_t udf_table_new_block(struct super_block *sb, struct inode *table, uint16_t partition, uint32_t goal, int *err) { struct udf_sb_info *sbi = UDF_SB(sb); uint32_t spread = 0xFFFFFFFF, nspread = 0xFFFFFFFF; - uint32_t newblock = 0, adsize; + udf_pblk_t newblock = 0; + uint32_t adsize; uint32_t elen, goal_elen = 0; - struct kernel_lb_addr eloc, uninitialized_var(goal_eloc); + struct kernel_lb_addr eloc, goal_eloc; struct extent_position epos, goal_epos; int8_t etype; struct udf_inode_info *iinfo = UDF_I(table); + int ret = 0; *err = -ENOSPC; @@ -672,8 +591,10 @@ static int udf_table_new_block(struct super_block *sb, epos.block = iinfo->i_location; epos.bh = goal_epos.bh = NULL; - while (spread && - (etype = udf_next_aext(table, &epos, &eloc, &elen, 1)) != -1) { + while (spread) { + ret = udf_next_aext(table, &epos, &eloc, &elen, &etype, 1); + if (ret <= 0) + break; if (goal >= eloc.logicalBlockNum) { if (goal < eloc.logicalBlockNum + (elen >> sb->s_blocksize_bits)) @@ -701,9 +622,11 @@ static int udf_table_new_block(struct super_block *sb, brelse(epos.bh); - if (spread == 0xFFFFFFFF) { + if (ret < 0 || spread == 0xFFFFFFFF) { brelse(goal_epos.bh); mutex_unlock(&sbi->s_alloc_mutex); + if (ret < 0) + *err = ret; return 0; } @@ -719,7 +642,7 @@ static int udf_table_new_block(struct super_block *sb, if (goal_elen) udf_write_aext(table, &goal_epos, &goal_eloc, goal_elen, 1); else - udf_delete_aext(table, goal_epos, goal_eloc, goal_elen); + udf_delete_aext(table, goal_epos); brelse(goal_epos.bh); udf_add_free_space(sb, partition, -1); @@ -735,6 +658,17 @@ void udf_free_blocks(struct super_block *sb, struct inode *inode, { uint16_t partition = bloc->partitionReferenceNum; struct udf_part_map *map = &UDF_SB(sb)->s_partmaps[partition]; + uint32_t blk; + + if (check_add_overflow(bloc->logicalBlockNum, offset, &blk) || + check_add_overflow(blk, count, &blk) || + bloc->logicalBlockNum + count > map->s_partition_len) { + udf_debug("Invalid request to free blocks: (%d, %u), off %u, " + "len %u, partition len %u\n", + partition, bloc->logicalBlockNum, offset, count, + map->s_partition_len); + return; + } if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_BITMAP) { udf_bitmap_free_blocks(sb, map->s_uspace.s_bitmap, @@ -742,12 +676,6 @@ void udf_free_blocks(struct super_block *sb, struct inode *inode, } else if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_TABLE) { udf_table_free_blocks(sb, map->s_uspace.s_table, bloc, offset, count); - } else if (map->s_partition_flags & UDF_PART_FLAG_FREED_BITMAP) { - udf_bitmap_free_blocks(sb, map->s_fspace.s_bitmap, - bloc, offset, count); - } else if (map->s_partition_flags & UDF_PART_FLAG_FREED_TABLE) { - udf_table_free_blocks(sb, map->s_fspace.s_table, - bloc, offset, count); } if (inode) { @@ -762,7 +690,7 @@ inline int udf_prealloc_blocks(struct super_block *sb, uint32_t block_count) { struct udf_part_map *map = &UDF_SB(sb)->s_partmaps[partition]; - sector_t allocated; + int allocated; if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_BITMAP) allocated = udf_bitmap_prealloc_blocks(sb, @@ -774,16 +702,6 @@ inline int udf_prealloc_blocks(struct super_block *sb, map->s_uspace.s_table, partition, first_block, block_count); - else if (map->s_partition_flags & UDF_PART_FLAG_FREED_BITMAP) - allocated = udf_bitmap_prealloc_blocks(sb, - map->s_fspace.s_bitmap, - partition, first_block, - block_count); - else if (map->s_partition_flags & UDF_PART_FLAG_FREED_TABLE) - allocated = udf_table_prealloc_blocks(sb, - map->s_fspace.s_table, - partition, first_block, - block_count); else return 0; @@ -792,12 +710,12 @@ inline int udf_prealloc_blocks(struct super_block *sb, return allocated; } -inline int udf_new_block(struct super_block *sb, +inline udf_pblk_t udf_new_block(struct super_block *sb, struct inode *inode, uint16_t partition, uint32_t goal, int *err) { struct udf_part_map *map = &UDF_SB(sb)->s_partmaps[partition]; - int block; + udf_pblk_t block; if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_BITMAP) block = udf_bitmap_new_block(sb, @@ -807,14 +725,6 @@ inline int udf_new_block(struct super_block *sb, block = udf_table_new_block(sb, map->s_uspace.s_table, partition, goal, err); - else if (map->s_partition_flags & UDF_PART_FLAG_FREED_BITMAP) - block = udf_bitmap_new_block(sb, - map->s_fspace.s_bitmap, - partition, goal, err); - else if (map->s_partition_flags & UDF_PART_FLAG_FREED_TABLE) - block = udf_table_new_block(sb, - map->s_fspace.s_table, - partition, goal, err); else { *err = -EIO; return 0; diff --git a/fs/udf/dir.c b/fs/udf/dir.c index a012c51caffd..5023dfe191e8 100644 --- a/fs/udf/dir.c +++ b/fs/udf/dir.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * dir.c * @@ -5,11 +6,6 @@ * Directory handling routines for the OSTA-UDF(tm) filesystem. * * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. - * * (C) 1998-2004 Ben Fennema * * HISTORY @@ -30,33 +26,22 @@ #include <linux/errno.h> #include <linux/mm.h> #include <linux/slab.h> -#include <linux/buffer_head.h> +#include <linux/bio.h> +#include <linux/iversion.h> #include "udf_i.h" #include "udf_sb.h" - static int udf_readdir(struct file *file, struct dir_context *ctx) { struct inode *dir = file_inode(file); - struct udf_inode_info *iinfo = UDF_I(dir); - struct udf_fileident_bh fibh = { .sbh = NULL, .ebh = NULL}; - struct fileIdentDesc *fi = NULL; - struct fileIdentDesc cfi; - int block, iblock; - loff_t nf_pos; + loff_t nf_pos, emit_pos = 0; int flen; unsigned char *fname = NULL; - unsigned char *nameptr; - uint16_t liu; - uint8_t lfi; - loff_t size = udf_ext0_offset(dir) + dir->i_size; - struct buffer_head *tmp, *bha[16]; - struct kernel_lb_addr eloc; - uint32_t elen; - sector_t offset; - int i, num, ret = 0; - struct extent_position epos = { NULL, 0, {0, 0} }; + int ret = 0; + struct super_block *sb = dir->i_sb; + bool pos_valid = false; + struct udf_fileident_iter iter; if (ctx->pos == 0) { if (!dir_emit_dot(file, ctx)) @@ -64,136 +49,112 @@ static int udf_readdir(struct file *file, struct dir_context *ctx) ctx->pos = 1; } nf_pos = (ctx->pos - 1) << 2; - if (nf_pos >= size) + if (nf_pos >= dir->i_size) goto out; - fname = kmalloc(UDF_NAME_LEN, GFP_NOFS); + /* + * Something changed since last readdir (either lseek was called or dir + * changed)? We need to verify the position correctly points at the + * beginning of some dir entry so that the directory parsing code does + * not get confused. Since UDF does not have any reliable way of + * identifying beginning of dir entry (names are under user control), + * we need to scan the directory from the beginning. + */ + if (!inode_eq_iversion(dir, *(u64 *)file->private_data)) { + emit_pos = nf_pos; + nf_pos = 0; + } else { + pos_valid = true; + } + + fname = kmalloc(UDF_NAME_LEN, GFP_KERNEL); if (!fname) { ret = -ENOMEM; goto out; } - if (nf_pos == 0) - nf_pos = udf_ext0_offset(dir); - - fibh.soffset = fibh.eoffset = nf_pos & (dir->i_sb->s_blocksize - 1); - if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) { - if (inode_bmap(dir, nf_pos >> dir->i_sb->s_blocksize_bits, - &epos, &eloc, &elen, &offset) - != (EXT_RECORDED_ALLOCATED >> 30)) { - ret = -ENOENT; - goto out; - } - block = udf_get_lb_pblock(dir->i_sb, &eloc, offset); - if ((++offset << dir->i_sb->s_blocksize_bits) < elen) { - if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT) - epos.offset -= sizeof(struct short_ad); - else if (iinfo->i_alloc_type == - ICBTAG_FLAG_AD_LONG) - epos.offset -= sizeof(struct long_ad); - } else { - offset = 0; - } - - if (!(fibh.sbh = fibh.ebh = udf_tread(dir->i_sb, block))) { - ret = -EIO; - goto out; - } - - if (!(offset & ((16 >> (dir->i_sb->s_blocksize_bits - 9)) - 1))) { - i = 16 >> (dir->i_sb->s_blocksize_bits - 9); - if (i + offset > (elen >> dir->i_sb->s_blocksize_bits)) - i = (elen >> dir->i_sb->s_blocksize_bits) - offset; - for (num = 0; i > 0; i--) { - block = udf_get_lb_pblock(dir->i_sb, &eloc, offset + i); - tmp = udf_tgetblk(dir->i_sb, block); - if (tmp && !buffer_uptodate(tmp) && !buffer_locked(tmp)) - bha[num++] = tmp; - else - brelse(tmp); - } - if (num) { - ll_rw_block(READA, num, bha); - for (i = 0; i < num; i++) - brelse(bha[i]); - } - } - } - - while (nf_pos < size) { + for (ret = udf_fiiter_init(&iter, dir, nf_pos); + !ret && iter.pos < dir->i_size; + ret = udf_fiiter_advance(&iter)) { struct kernel_lb_addr tloc; + udf_pblk_t iblock; - ctx->pos = (nf_pos >> 2) + 1; - - fi = udf_fileident_read(dir, &nf_pos, &fibh, &cfi, &epos, &eloc, - &elen, &offset); - if (!fi) - goto out; - - liu = le16_to_cpu(cfi.lengthOfImpUse); - lfi = cfi.lengthFileIdent; - - if (fibh.sbh == fibh.ebh) { - nameptr = fi->fileIdent + liu; - } else { - int poffset; /* Unpaded ending offset */ - - poffset = fibh.soffset + sizeof(struct fileIdentDesc) + liu + lfi; + /* Still not at offset where user asked us to read from? */ + if (iter.pos < emit_pos) + continue; - if (poffset >= lfi) { - nameptr = (char *)(fibh.ebh->b_data + poffset - lfi); - } else { - nameptr = fname; - memcpy(nameptr, fi->fileIdent + liu, - lfi - poffset); - memcpy(nameptr + lfi - poffset, - fibh.ebh->b_data, poffset); - } - } + /* Update file position only if we got past the current one */ + pos_valid = true; + ctx->pos = (iter.pos >> 2) + 1; - if ((cfi.fileCharacteristics & FID_FILE_CHAR_DELETED) != 0) { - if (!UDF_QUERY_FLAG(dir->i_sb, UDF_FLAG_UNDELETE)) + if (iter.fi.fileCharacteristics & FID_FILE_CHAR_DELETED) { + if (!UDF_QUERY_FLAG(sb, UDF_FLAG_UNDELETE)) continue; } - if ((cfi.fileCharacteristics & FID_FILE_CHAR_HIDDEN) != 0) { - if (!UDF_QUERY_FLAG(dir->i_sb, UDF_FLAG_UNHIDE)) + if (iter.fi.fileCharacteristics & FID_FILE_CHAR_HIDDEN) { + if (!UDF_QUERY_FLAG(sb, UDF_FLAG_UNHIDE)) continue; } - if (cfi.fileCharacteristics & FID_FILE_CHAR_PARENT) { + if (iter.fi.fileCharacteristics & FID_FILE_CHAR_PARENT) { if (!dir_emit_dotdot(file, ctx)) - goto out; + goto out_iter; continue; } - flen = udf_get_filename(dir->i_sb, nameptr, fname, lfi); - if (!flen) + flen = udf_get_filename(sb, iter.name, + iter.fi.lengthFileIdent, fname, UDF_NAME_LEN); + if (flen < 0) continue; - tloc = lelb_to_cpu(cfi.icb.extLocation); - iblock = udf_get_lb_pblock(dir->i_sb, &tloc, 0); + tloc = lelb_to_cpu(iter.fi.icb.extLocation); + iblock = udf_get_lb_pblock(sb, &tloc, 0); if (!dir_emit(ctx, fname, flen, iblock, DT_UNKNOWN)) - goto out; - } /* end while */ - - ctx->pos = (nf_pos >> 2) + 1; + goto out_iter; + } + if (!ret) { + ctx->pos = (iter.pos >> 2) + 1; + pos_valid = true; + } +out_iter: + udf_fiiter_release(&iter); out: - if (fibh.sbh != fibh.ebh) - brelse(fibh.ebh); - brelse(fibh.sbh); - brelse(epos.bh); + if (pos_valid) + *(u64 *)file->private_data = inode_query_iversion(dir); kfree(fname); return ret; } +static int udf_dir_open(struct inode *inode, struct file *file) +{ + file->private_data = kzalloc(sizeof(u64), GFP_KERNEL); + if (!file->private_data) + return -ENOMEM; + return 0; +} + +static int udf_dir_release(struct inode *inode, struct file *file) +{ + kfree(file->private_data); + return 0; +} + +static loff_t udf_dir_llseek(struct file *file, loff_t offset, int whence) +{ + return generic_llseek_cookie(file, offset, whence, + (u64 *)file->private_data); +} + /* readdir and lookup functions */ const struct file_operations udf_dir_operations = { - .llseek = generic_file_llseek, + .open = udf_dir_open, + .release = udf_dir_release, + .llseek = udf_dir_llseek, .read = generic_read_dir, - .iterate = udf_readdir, + .iterate_shared = udf_readdir, .unlocked_ioctl = udf_ioctl, .fsync = generic_file_fsync, }; diff --git a/fs/udf/directory.c b/fs/udf/directory.c index 3e44f575fb9c..632453aa3893 100644 --- a/fs/udf/directory.c +++ b/fs/udf/directory.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * directory.c * * PURPOSE * Directory related functions * - * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. */ #include "udfdecl.h" @@ -16,183 +12,488 @@ #include <linux/fs.h> #include <linux/string.h> -#include <linux/buffer_head.h> +#include <linux/bio.h> +#include <linux/crc-itu-t.h> +#include <linux/iversion.h> -struct fileIdentDesc *udf_fileident_read(struct inode *dir, loff_t *nf_pos, - struct udf_fileident_bh *fibh, - struct fileIdentDesc *cfi, - struct extent_position *epos, - struct kernel_lb_addr *eloc, uint32_t *elen, - sector_t *offset) +static int udf_verify_fi(struct udf_fileident_iter *iter) { - struct fileIdentDesc *fi; - int i, num, block; - struct buffer_head *tmp, *bha[16]; - struct udf_inode_info *iinfo = UDF_I(dir); - - fibh->soffset = fibh->eoffset; + unsigned int len; + + if (iter->fi.descTag.tagIdent != cpu_to_le16(TAG_IDENT_FID)) { + udf_err(iter->dir->i_sb, + "directory (ino %lu) has entry at pos %llu with incorrect tag %x\n", + iter->dir->i_ino, (unsigned long long)iter->pos, + le16_to_cpu(iter->fi.descTag.tagIdent)); + return -EFSCORRUPTED; + } + len = udf_dir_entry_len(&iter->fi); + if (le16_to_cpu(iter->fi.lengthOfImpUse) & 3) { + udf_err(iter->dir->i_sb, + "directory (ino %lu) has entry at pos %llu with unaligned length of impUse field\n", + iter->dir->i_ino, (unsigned long long)iter->pos); + return -EFSCORRUPTED; + } + /* + * This is in fact allowed by the spec due to long impUse field but + * we don't support it. If there is real media with this large impUse + * field, support can be added. + */ + if (len > 1 << iter->dir->i_blkbits) { + udf_err(iter->dir->i_sb, + "directory (ino %lu) has too big (%u) entry at pos %llu\n", + iter->dir->i_ino, len, (unsigned long long)iter->pos); + return -EFSCORRUPTED; + } + if (iter->pos + len > iter->dir->i_size) { + udf_err(iter->dir->i_sb, + "directory (ino %lu) has entry past directory size at pos %llu\n", + iter->dir->i_ino, (unsigned long long)iter->pos); + return -EFSCORRUPTED; + } + if (udf_dir_entry_len(&iter->fi) != + sizeof(struct tag) + le16_to_cpu(iter->fi.descTag.descCRCLength)) { + udf_err(iter->dir->i_sb, + "directory (ino %lu) has entry where CRC length (%u) does not match entry length (%u)\n", + iter->dir->i_ino, + (unsigned)le16_to_cpu(iter->fi.descTag.descCRCLength), + (unsigned)(udf_dir_entry_len(&iter->fi) - + sizeof(struct tag))); + return -EFSCORRUPTED; + } + return 0; +} +static int udf_copy_fi(struct udf_fileident_iter *iter) +{ + struct udf_inode_info *iinfo = UDF_I(iter->dir); + u32 blksize = 1 << iter->dir->i_blkbits; + u32 off, len, nameoff; + int err; + + /* Skip copying when we are at EOF */ + if (iter->pos >= iter->dir->i_size) { + iter->name = NULL; + return 0; + } + if (iter->dir->i_size < iter->pos + sizeof(struct fileIdentDesc)) { + udf_err(iter->dir->i_sb, + "directory (ino %lu) has entry straddling EOF\n", + iter->dir->i_ino); + return -EFSCORRUPTED; + } if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { - fi = udf_get_fileident(iinfo->i_ext.i_data - - (iinfo->i_efe ? - sizeof(struct extendedFileEntry) : - sizeof(struct fileEntry)), - dir->i_sb->s_blocksize, - &(fibh->eoffset)); - if (!fi) - return NULL; - - *nf_pos += fibh->eoffset - fibh->soffset; - - memcpy((uint8_t *)cfi, (uint8_t *)fi, + memcpy(&iter->fi, iinfo->i_data + iinfo->i_lenEAttr + iter->pos, sizeof(struct fileIdentDesc)); + err = udf_verify_fi(iter); + if (err < 0) + return err; + iter->name = iinfo->i_data + iinfo->i_lenEAttr + iter->pos + + sizeof(struct fileIdentDesc) + + le16_to_cpu(iter->fi.lengthOfImpUse); + return 0; + } - return fi; + off = iter->pos & (blksize - 1); + len = min_t(u32, sizeof(struct fileIdentDesc), blksize - off); + memcpy(&iter->fi, iter->bh[0]->b_data + off, len); + if (len < sizeof(struct fileIdentDesc)) + memcpy((char *)(&iter->fi) + len, iter->bh[1]->b_data, + sizeof(struct fileIdentDesc) - len); + err = udf_verify_fi(iter); + if (err < 0) + return err; + + /* Handle directory entry name */ + nameoff = off + sizeof(struct fileIdentDesc) + + le16_to_cpu(iter->fi.lengthOfImpUse); + if (off + udf_dir_entry_len(&iter->fi) <= blksize) { + iter->name = iter->bh[0]->b_data + nameoff; + } else if (nameoff >= blksize) { + iter->name = iter->bh[1]->b_data + (nameoff - blksize); + } else { + iter->name = iter->namebuf; + len = blksize - nameoff; + memcpy(iter->name, iter->bh[0]->b_data + nameoff, len); + memcpy(iter->name + len, iter->bh[1]->b_data, + iter->fi.lengthFileIdent - len); } + return 0; +} - if (fibh->eoffset == dir->i_sb->s_blocksize) { - int lextoffset = epos->offset; - unsigned char blocksize_bits = dir->i_sb->s_blocksize_bits; +/* Readahead 8k once we are at 8k boundary */ +static void udf_readahead_dir(struct udf_fileident_iter *iter) +{ + unsigned int ralen = 16 >> (iter->dir->i_blkbits - 9); + struct buffer_head *tmp, *bha[16]; + int i, num; + udf_pblk_t blk; + + if (iter->loffset & (ralen - 1)) + return; + + if (iter->loffset + ralen > (iter->elen >> iter->dir->i_blkbits)) + ralen = (iter->elen >> iter->dir->i_blkbits) - iter->loffset; + num = 0; + for (i = 0; i < ralen; i++) { + blk = udf_get_lb_pblock(iter->dir->i_sb, &iter->eloc, + iter->loffset + i); + tmp = sb_getblk(iter->dir->i_sb, blk); + if (tmp && !buffer_uptodate(tmp) && !buffer_locked(tmp)) + bha[num++] = tmp; + else + brelse(tmp); + } + if (num) { + bh_readahead_batch(num, bha, REQ_RAHEAD); + for (i = 0; i < num; i++) + brelse(bha[i]); + } +} - if (udf_next_aext(dir, epos, eloc, elen, 1) != - (EXT_RECORDED_ALLOCATED >> 30)) - return NULL; +static struct buffer_head *udf_fiiter_bread_blk(struct udf_fileident_iter *iter) +{ + udf_pblk_t blk; - block = udf_get_lb_pblock(dir->i_sb, eloc, *offset); + udf_readahead_dir(iter); + blk = udf_get_lb_pblock(iter->dir->i_sb, &iter->eloc, iter->loffset); + return sb_bread(iter->dir->i_sb, blk); +} - (*offset)++; +/* + * Updates loffset to point to next directory block; eloc, elen & epos are + * updated if we need to traverse to the next extent as well. + */ +static int udf_fiiter_advance_blk(struct udf_fileident_iter *iter) +{ + int8_t etype = -1; + int err = 0; + + iter->loffset++; + if (iter->loffset < DIV_ROUND_UP(iter->elen, 1<<iter->dir->i_blkbits)) + return 0; + + iter->loffset = 0; + err = udf_next_aext(iter->dir, &iter->epos, &iter->eloc, + &iter->elen, &etype, 1); + if (err < 0) + return err; + else if (err == 0 || etype != (EXT_RECORDED_ALLOCATED >> 30)) { + if (iter->pos == iter->dir->i_size) { + iter->elen = 0; + return 0; + } + udf_err(iter->dir->i_sb, + "extent after position %llu not allocated in directory (ino %lu)\n", + (unsigned long long)iter->pos, iter->dir->i_ino); + return -EFSCORRUPTED; + } + return 0; +} - if ((*offset << blocksize_bits) >= *elen) - *offset = 0; - else - epos->offset = lextoffset; +static int udf_fiiter_load_bhs(struct udf_fileident_iter *iter) +{ + int blksize = 1 << iter->dir->i_blkbits; + int off = iter->pos & (blksize - 1); + int err; + struct fileIdentDesc *fi; - brelse(fibh->sbh); - fibh->sbh = fibh->ebh = udf_tread(dir->i_sb, block); - if (!fibh->sbh) - return NULL; - fibh->soffset = fibh->eoffset = 0; - - if (!(*offset & ((16 >> (blocksize_bits - 9)) - 1))) { - i = 16 >> (blocksize_bits - 9); - if (i + *offset > (*elen >> blocksize_bits)) - i = (*elen >> blocksize_bits)-*offset; - for (num = 0; i > 0; i--) { - block = udf_get_lb_pblock(dir->i_sb, eloc, - *offset + i); - tmp = udf_tgetblk(dir->i_sb, block); - if (tmp && !buffer_uptodate(tmp) && - !buffer_locked(tmp)) - bha[num++] = tmp; - else - brelse(tmp); - } - if (num) { - ll_rw_block(READA, num, bha); - for (i = 0; i < num; i++) - brelse(bha[i]); - } + /* Is there any further extent we can map from? */ + if (!iter->bh[0] && iter->elen) { + iter->bh[0] = udf_fiiter_bread_blk(iter); + if (!iter->bh[0]) { + err = -ENOMEM; + goto out_brelse; + } + if (!buffer_uptodate(iter->bh[0])) { + err = -EIO; + goto out_brelse; } - } else if (fibh->sbh != fibh->ebh) { - brelse(fibh->sbh); - fibh->sbh = fibh->ebh; } + /* There's no next block so we are done */ + if (iter->pos >= iter->dir->i_size) + return 0; + /* Need to fetch next block as well? */ + if (off + sizeof(struct fileIdentDesc) > blksize) + goto fetch_next; + fi = (struct fileIdentDesc *)(iter->bh[0]->b_data + off); + /* Need to fetch next block to get name? */ + if (off + udf_dir_entry_len(fi) > blksize) { +fetch_next: + err = udf_fiiter_advance_blk(iter); + if (err) + goto out_brelse; + iter->bh[1] = udf_fiiter_bread_blk(iter); + if (!iter->bh[1]) { + err = -ENOMEM; + goto out_brelse; + } + if (!buffer_uptodate(iter->bh[1])) { + err = -EIO; + goto out_brelse; + } + } + return 0; +out_brelse: + brelse(iter->bh[0]); + brelse(iter->bh[1]); + iter->bh[0] = iter->bh[1] = NULL; + return err; +} - fi = udf_get_fileident(fibh->sbh->b_data, dir->i_sb->s_blocksize, - &(fibh->eoffset)); +int udf_fiiter_init(struct udf_fileident_iter *iter, struct inode *dir, + loff_t pos) +{ + struct udf_inode_info *iinfo = UDF_I(dir); + int err = 0; + int8_t etype; + + iter->dir = dir; + iter->bh[0] = iter->bh[1] = NULL; + iter->pos = pos; + iter->elen = 0; + iter->epos.bh = NULL; + iter->name = NULL; + /* + * When directory is verified, we don't expect directory iteration to + * fail and it can be difficult to undo without corrupting filesystem. + * So just do not allow memory allocation failures here. + */ + iter->namebuf = kmalloc(UDF_NAME_LEN_CS0, GFP_KERNEL | __GFP_NOFAIL); - if (!fi) - return NULL; + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { + err = udf_copy_fi(iter); + goto out; + } - *nf_pos += fibh->eoffset - fibh->soffset; + err = inode_bmap(dir, iter->pos >> dir->i_blkbits, &iter->epos, + &iter->eloc, &iter->elen, &iter->loffset, &etype); + if (err <= 0 || etype != (EXT_RECORDED_ALLOCATED >> 30)) { + if (pos == dir->i_size) + return 0; + udf_err(dir->i_sb, + "position %llu not allocated in directory (ino %lu)\n", + (unsigned long long)pos, dir->i_ino); + err = -EFSCORRUPTED; + goto out; + } + err = udf_fiiter_load_bhs(iter); + if (err < 0) + goto out; + err = udf_copy_fi(iter); +out: + if (err < 0) + udf_fiiter_release(iter); + return err; +} - if (fibh->eoffset <= dir->i_sb->s_blocksize) { - memcpy((uint8_t *)cfi, (uint8_t *)fi, - sizeof(struct fileIdentDesc)); - } else if (fibh->eoffset > dir->i_sb->s_blocksize) { - int lextoffset = epos->offset; +int udf_fiiter_advance(struct udf_fileident_iter *iter) +{ + unsigned int oldoff, len; + int blksize = 1 << iter->dir->i_blkbits; + int err; + + oldoff = iter->pos & (blksize - 1); + len = udf_dir_entry_len(&iter->fi); + iter->pos += len; + if (UDF_I(iter->dir)->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) { + if (oldoff + len >= blksize) { + brelse(iter->bh[0]); + iter->bh[0] = NULL; + /* Next block already loaded? */ + if (iter->bh[1]) { + iter->bh[0] = iter->bh[1]; + iter->bh[1] = NULL; + } else { + err = udf_fiiter_advance_blk(iter); + if (err < 0) + return err; + } + } + err = udf_fiiter_load_bhs(iter); + if (err < 0) + return err; + } + return udf_copy_fi(iter); +} - if (udf_next_aext(dir, epos, eloc, elen, 1) != - (EXT_RECORDED_ALLOCATED >> 30)) - return NULL; +void udf_fiiter_release(struct udf_fileident_iter *iter) +{ + iter->dir = NULL; + brelse(iter->bh[0]); + brelse(iter->bh[1]); + iter->bh[0] = iter->bh[1] = NULL; + kfree(iter->namebuf); + iter->namebuf = NULL; +} - block = udf_get_lb_pblock(dir->i_sb, eloc, *offset); +static void udf_copy_to_bufs(void *buf1, int len1, void *buf2, int len2, + int off, void *src, int len) +{ + int copy; + + if (off >= len1) { + off -= len1; + } else { + copy = min(off + len, len1) - off; + memcpy(buf1 + off, src, copy); + src += copy; + len -= copy; + off = 0; + } + if (len > 0) { + if (WARN_ON_ONCE(off + len > len2 || !buf2)) + return; + memcpy(buf2 + off, src, len); + } +} - (*offset)++; +static uint16_t udf_crc_fi_bufs(void *buf1, int len1, void *buf2, int len2, + int off, int len) +{ + int copy; + uint16_t crc = 0; + + if (off >= len1) { + off -= len1; + } else { + copy = min(off + len, len1) - off; + crc = crc_itu_t(crc, buf1 + off, copy); + len -= copy; + off = 0; + } + if (len > 0) { + if (WARN_ON_ONCE(off + len > len2 || !buf2)) + return 0; + crc = crc_itu_t(crc, buf2 + off, len); + } + return crc; +} - if ((*offset << dir->i_sb->s_blocksize_bits) >= *elen) - *offset = 0; - else - epos->offset = lextoffset; +static void udf_copy_fi_to_bufs(char *buf1, int len1, char *buf2, int len2, + int off, struct fileIdentDesc *fi, + uint8_t *impuse, uint8_t *name) +{ + uint16_t crc; + int fioff = off; + int crcoff = off + sizeof(struct tag); + unsigned int crclen = udf_dir_entry_len(fi) - sizeof(struct tag); + char zeros[UDF_NAME_PAD] = {}; + int endoff = off + udf_dir_entry_len(fi); + + udf_copy_to_bufs(buf1, len1, buf2, len2, off, fi, + sizeof(struct fileIdentDesc)); + off += sizeof(struct fileIdentDesc); + if (impuse) + udf_copy_to_bufs(buf1, len1, buf2, len2, off, impuse, + le16_to_cpu(fi->lengthOfImpUse)); + off += le16_to_cpu(fi->lengthOfImpUse); + if (name) { + udf_copy_to_bufs(buf1, len1, buf2, len2, off, name, + fi->lengthFileIdent); + off += fi->lengthFileIdent; + udf_copy_to_bufs(buf1, len1, buf2, len2, off, zeros, + endoff - off); + } - fibh->soffset -= dir->i_sb->s_blocksize; - fibh->eoffset -= dir->i_sb->s_blocksize; + crc = udf_crc_fi_bufs(buf1, len1, buf2, len2, crcoff, crclen); + fi->descTag.descCRC = cpu_to_le16(crc); + fi->descTag.descCRCLength = cpu_to_le16(crclen); + fi->descTag.tagChecksum = udf_tag_checksum(&fi->descTag); - fibh->ebh = udf_tread(dir->i_sb, block); - if (!fibh->ebh) - return NULL; + udf_copy_to_bufs(buf1, len1, buf2, len2, fioff, fi, sizeof(struct tag)); +} - if (sizeof(struct fileIdentDesc) > -fibh->soffset) { - int fi_len; +void udf_fiiter_write_fi(struct udf_fileident_iter *iter, uint8_t *impuse) +{ + struct udf_inode_info *iinfo = UDF_I(iter->dir); + void *buf1, *buf2 = NULL; + int len1, len2 = 0, off; + int blksize = 1 << iter->dir->i_blkbits; - memcpy((uint8_t *)cfi, (uint8_t *)fi, -fibh->soffset); - memcpy((uint8_t *)cfi - fibh->soffset, - fibh->ebh->b_data, - sizeof(struct fileIdentDesc) + fibh->soffset); + off = iter->pos & (blksize - 1); + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { + buf1 = iinfo->i_data + iinfo->i_lenEAttr; + len1 = iter->dir->i_size; + } else { + buf1 = iter->bh[0]->b_data; + len1 = blksize; + if (iter->bh[1]) { + buf2 = iter->bh[1]->b_data; + len2 = blksize; + } + } - fi_len = (sizeof(struct fileIdentDesc) + - cfi->lengthFileIdent + - le16_to_cpu(cfi->lengthOfImpUse) + 3) & ~3; + udf_copy_fi_to_bufs(buf1, len1, buf2, len2, off, &iter->fi, impuse, + iter->name == iter->namebuf ? iter->name : NULL); - *nf_pos += fi_len - (fibh->eoffset - fibh->soffset); - fibh->eoffset = fibh->soffset + fi_len; - } else { - memcpy((uint8_t *)cfi, (uint8_t *)fi, - sizeof(struct fileIdentDesc)); - } + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { + mark_inode_dirty(iter->dir); + } else { + mark_buffer_dirty_inode(iter->bh[0], iter->dir); + if (iter->bh[1]) + mark_buffer_dirty_inode(iter->bh[1], iter->dir); } - return fi; + inode_inc_iversion(iter->dir); } -struct fileIdentDesc *udf_get_fileident(void *buffer, int bufsize, int *offset) +void udf_fiiter_update_elen(struct udf_fileident_iter *iter, uint32_t new_elen) { - struct fileIdentDesc *fi; - int lengthThisIdent; - uint8_t *ptr; - int padlen; + struct udf_inode_info *iinfo = UDF_I(iter->dir); + int diff = new_elen - iter->elen; + + /* Skip update when we already went past the last extent */ + if (!iter->elen) + return; + iter->elen = new_elen; + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT) + iter->epos.offset -= sizeof(struct short_ad); + else if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_LONG) + iter->epos.offset -= sizeof(struct long_ad); + udf_write_aext(iter->dir, &iter->epos, &iter->eloc, iter->elen, 1); + iinfo->i_lenExtents += diff; + mark_inode_dirty(iter->dir); +} - if ((!buffer) || (!offset)) { - udf_debug("invalidparms, buffer=%p, offset=%p\n", - buffer, offset); - return NULL; +/* Append new block to directory. @iter is expected to point at EOF */ +int udf_fiiter_append_blk(struct udf_fileident_iter *iter) +{ + struct udf_inode_info *iinfo = UDF_I(iter->dir); + int blksize = 1 << iter->dir->i_blkbits; + struct buffer_head *bh; + sector_t block; + uint32_t old_elen = iter->elen; + int err; + int8_t etype; + + if (WARN_ON_ONCE(iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB)) + return -EINVAL; + + /* Round up last extent in the file */ + udf_fiiter_update_elen(iter, ALIGN(iter->elen, blksize)); + + /* Allocate new block and refresh mapping information */ + block = iinfo->i_lenExtents >> iter->dir->i_blkbits; + bh = udf_bread(iter->dir, block, 1, &err); + if (!bh) { + udf_fiiter_update_elen(iter, old_elen); + return err; } - - ptr = buffer; - - if ((*offset > 0) && (*offset < bufsize)) - ptr += *offset; - fi = (struct fileIdentDesc *)ptr; - if (fi->descTag.tagIdent != cpu_to_le16(TAG_IDENT_FID)) { - udf_debug("0x%x != TAG_IDENT_FID\n", - le16_to_cpu(fi->descTag.tagIdent)); - udf_debug("offset: %u sizeof: %lu bufsize: %u\n", - *offset, (unsigned long)sizeof(struct fileIdentDesc), - bufsize); - return NULL; + err = inode_bmap(iter->dir, block, &iter->epos, &iter->eloc, &iter->elen, + &iter->loffset, &etype); + if (err <= 0 || etype != (EXT_RECORDED_ALLOCATED >> 30)) { + udf_err(iter->dir->i_sb, + "block %llu not allocated in directory (ino %lu)\n", + (unsigned long long)block, iter->dir->i_ino); + return -EFSCORRUPTED; } - if ((*offset + sizeof(struct fileIdentDesc)) > bufsize) - lengthThisIdent = sizeof(struct fileIdentDesc); - else - lengthThisIdent = sizeof(struct fileIdentDesc) + - fi->lengthFileIdent + le16_to_cpu(fi->lengthOfImpUse); - - /* we need to figure padding, too! */ - padlen = lengthThisIdent % UDF_NAME_PAD; - if (padlen) - lengthThisIdent += (UDF_NAME_PAD - padlen); - *offset = *offset + lengthThisIdent; - - return fi; + if (!(iter->pos & (blksize - 1))) { + brelse(iter->bh[0]); + iter->bh[0] = bh; + } else { + iter->bh[1] = bh; + } + return 0; } struct short_ad *udf_get_fileshortad(uint8_t *ptr, int maxoffset, uint32_t *offset, diff --git a/fs/udf/ecma_167.h b/fs/udf/ecma_167.h index 4792b771aa80..415b050b977d 100644 --- a/fs/udf/ecma_167.h +++ b/fs/udf/ecma_167.h @@ -2,9 +2,10 @@ * ecma_167.h * * This file is based on ECMA-167 3rd edition (June 1997) - * http://www.ecma.ch + * https://www.ecma.ch * - * Copyright (c) 2001-2002 Ben Fennema <bfennema@falcon.csc.calpoly.edu> + * Copyright (c) 2001-2002 Ben Fennema + * Copyright (c) 2017-2019 Pali Rohár <pali@kernel.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,16 +33,24 @@ * SUCH DAMAGE. */ +/** + * @file + * ECMA-167r3 defines and structure definitions + */ + #include <linux/types.h> #ifndef _ECMA_167_H #define _ECMA_167_H 1 +/* Character sets and coding - d-characters (ECMA 167r3 1/7.2) */ +typedef uint8_t dchars; + /* Character set specification (ECMA 167r3 1/7.2.1) */ struct charspec { uint8_t charSetType; uint8_t charSetInfo[63]; -} __attribute__ ((packed)); +} __packed; /* Character Set Type (ECMA 167r3 1/7.2.1.1) */ #define CHARSPEC_TYPE_CS0 0x00 /* (1/7.2.2) */ @@ -54,6 +63,7 @@ struct charspec { #define CHARSPEC_TYPE_CS7 0x07 /* (1/7.2.9) */ #define CHARSPEC_TYPE_CS8 0x08 /* (1/7.2.10) */ +/* Fixed-length character fields - d-string (EMCA 167r3 1/7.2.12) */ typedef uint8_t dstring; /* Timestamp (ECMA 167r3 1/7.3) */ @@ -68,7 +78,7 @@ struct timestamp { uint8_t centiseconds; uint8_t hundredsOfMicroseconds; uint8_t microseconds; -} __attribute__ ((packed)); +} __packed; /* Type and Time Zone (ECMA 167r3 1/7.3.1) */ #define TIMESTAMP_TYPE_MASK 0xF000 @@ -82,11 +92,11 @@ struct regid { uint8_t flags; uint8_t ident[23]; uint8_t identSuffix[8]; -} __attribute__ ((packed)); +} __packed; /* Flags (ECMA 167r3 1/7.4.1) */ -#define ENTITYID_FLAGS_DIRTY 0x00 -#define ENTITYID_FLAGS_PROTECTED 0x01 +#define ENTITYID_FLAGS_DIRTY 0x01 +#define ENTITYID_FLAGS_PROTECTED 0x02 /* Volume Structure Descriptor (ECMA 167r3 2/9.1) */ #define VSD_STD_ID_LEN 5 @@ -95,7 +105,7 @@ struct volStructDesc { uint8_t stdIdent[VSD_STD_ID_LEN]; uint8_t structVersion; uint8_t structData[2041]; -} __attribute__ ((packed)); +} __packed; /* Standard Identifier (EMCA 167r2 2/9.1.2) */ #define VSD_STD_ID_NSR02 "NSR02" /* (3/9.1) */ @@ -114,7 +124,7 @@ struct beginningExtendedAreaDesc { uint8_t stdIdent[VSD_STD_ID_LEN]; uint8_t structVersion; uint8_t structData[2041]; -} __attribute__ ((packed)); +} __packed; /* Terminating Extended Area Descriptor (ECMA 167r3 2/9.3) */ struct terminatingExtendedAreaDesc { @@ -122,7 +132,7 @@ struct terminatingExtendedAreaDesc { uint8_t stdIdent[VSD_STD_ID_LEN]; uint8_t structVersion; uint8_t structData[2041]; -} __attribute__ ((packed)); +} __packed; /* Boot Descriptor (ECMA 167r3 2/9.4) */ struct bootDesc { @@ -140,7 +150,7 @@ struct bootDesc { __le16 flags; uint8_t reserved2[32]; uint8_t bootUse[1906]; -} __attribute__ ((packed)); +} __packed; /* Flags (ECMA 167r3 2/9.4.12) */ #define BOOT_FLAGS_ERASE 0x01 @@ -149,7 +159,7 @@ struct bootDesc { struct extent_ad { __le32 extLength; __le32 extLocation; -} __attribute__ ((packed)); +} __packed; struct kernel_extent_ad { uint32_t extLength; @@ -166,7 +176,7 @@ struct tag { __le16 descCRC; __le16 descCRCLength; __le32 tagLocation; -} __attribute__ ((packed)); +} __packed; /* Tag Identifier (ECMA 167r3 3/7.2.1) */ #define TAG_IDENT_PVD 0x0001 @@ -186,7 +196,14 @@ struct NSRDesc { uint8_t structVersion; uint8_t reserved; uint8_t structData[2040]; -} __attribute__ ((packed)); +} __packed; + +/* Generic Descriptor */ +struct genericDesc { + struct tag descTag; + __le32 volDescSeqNum; + uint8_t reserved[492]; +} __packed; /* Primary Volume Descriptor (ECMA 167r3 3/10.1) */ struct primaryVolDesc { @@ -212,7 +229,7 @@ struct primaryVolDesc { __le32 predecessorVolDescSeqLocation; __le16 flags; uint8_t reserved[22]; -} __attribute__ ((packed)); +} __packed; /* Flags (ECMA 167r3 3/10.1.21) */ #define PVD_FLAGS_VSID_COMMON 0x0001 @@ -223,7 +240,7 @@ struct anchorVolDescPtr { struct extent_ad mainVolDescSeqExt; struct extent_ad reserveVolDescSeqExt; uint8_t reserved[480]; -} __attribute__ ((packed)); +} __packed; /* Volume Descriptor Pointer (ECMA 167r3 3/10.3) */ struct volDescPtr { @@ -231,7 +248,7 @@ struct volDescPtr { __le32 volDescSeqNum; struct extent_ad nextVolDescSeqExt; uint8_t reserved[484]; -} __attribute__ ((packed)); +} __packed; /* Implementation Use Volume Descriptor (ECMA 167r3 3/10.4) */ struct impUseVolDesc { @@ -239,7 +256,7 @@ struct impUseVolDesc { __le32 volDescSeqNum; struct regid impIdent; uint8_t impUse[460]; -} __attribute__ ((packed)); +} __packed; /* Partition Descriptor (ECMA 167r3 3/10.5) */ struct partitionDesc { @@ -255,7 +272,7 @@ struct partitionDesc { struct regid impIdent; uint8_t impUse[128]; uint8_t reserved[156]; -} __attribute__ ((packed)); +} __packed; /* Partition Flags (ECMA 167r3 3/10.5.3) */ #define PD_PARTITION_FLAGS_ALLOC 0x0001 @@ -290,19 +307,19 @@ struct logicalVolDesc { struct regid impIdent; uint8_t impUse[128]; struct extent_ad integritySeqExt; - uint8_t partitionMaps[0]; -} __attribute__ ((packed)); + uint8_t partitionMaps[]; +} __packed; /* Generic Partition Map (ECMA 167r3 3/10.7.1) */ struct genericPartitionMap { uint8_t partitionMapType; uint8_t partitionMapLength; - uint8_t partitionMapping[0]; -} __attribute__ ((packed)); + uint8_t partitionMapping[]; +} __packed; /* Partition Map Type (ECMA 167r3 3/10.7.1.1) */ #define GP_PARTITION_MAP_TYPE_UNDEF 0x00 -#define GP_PARTIITON_MAP_TYPE_1 0x01 +#define GP_PARTITION_MAP_TYPE_1 0x01 #define GP_PARTITION_MAP_TYPE_2 0x02 /* Type 1 Partition Map (ECMA 167r3 3/10.7.2) */ @@ -311,28 +328,28 @@ struct genericPartitionMap1 { uint8_t partitionMapLength; __le16 volSeqNum; __le16 partitionNum; -} __attribute__ ((packed)); +} __packed; /* Type 2 Partition Map (ECMA 167r3 3/10.7.3) */ struct genericPartitionMap2 { uint8_t partitionMapType; uint8_t partitionMapLength; uint8_t partitionIdent[62]; -} __attribute__ ((packed)); +} __packed; /* Unallocated Space Descriptor (ECMA 167r3 3/10.8) */ struct unallocSpaceDesc { struct tag descTag; __le32 volDescSeqNum; __le32 numAllocDescs; - struct extent_ad allocDescs[0]; -} __attribute__ ((packed)); + struct extent_ad allocDescs[]; +} __packed; /* Terminating Descriptor (ECMA 167r3 3/10.9) */ struct terminatingDesc { struct tag descTag; uint8_t reserved[496]; -} __attribute__ ((packed)); +} __packed; /* Logical Volume Integrity Descriptor (ECMA 167r3 3/10.10) */ struct logicalVolIntegrityDesc { @@ -343,10 +360,10 @@ struct logicalVolIntegrityDesc { uint8_t logicalVolContentsUse[32]; __le32 numOfPartitions; __le32 lengthOfImpUse; - __le32 freeSpaceTable[0]; - __le32 sizeTable[0]; - uint8_t impUse[0]; -} __attribute__ ((packed)); + __le32 freeSpaceTable[]; + /* __le32 sizeTable[]; */ + /* uint8_t impUse[]; */ +} __packed; /* Integrity Type (ECMA 167r3 3/10.10.3) */ #define LVID_INTEGRITY_TYPE_OPEN 0x00000000 @@ -356,7 +373,7 @@ struct logicalVolIntegrityDesc { struct lb_addr { __le32 logicalBlockNum; __le16 partitionReferenceNum; -} __attribute__ ((packed)); +} __packed; /* ... and its in-core analog */ struct kernel_lb_addr { @@ -368,14 +385,14 @@ struct kernel_lb_addr { struct short_ad { __le32 extLength; __le32 extPosition; -} __attribute__ ((packed)); +} __packed; /* Long Allocation Descriptor (ECMA 167r3 4/14.14.2) */ struct long_ad { __le32 extLength; struct lb_addr extLocation; uint8_t impUse[6]; -} __attribute__ ((packed)); +} __packed; struct kernel_long_ad { uint32_t extLength; @@ -389,7 +406,7 @@ struct ext_ad { __le32 recordedLength; __le32 informationLength; struct lb_addr extLocation; -} __attribute__ ((packed)); +} __packed; struct kernel_ext_ad { uint32_t extLength; @@ -434,7 +451,7 @@ struct fileSetDesc { struct long_ad nextExt; struct long_ad streamDirectoryICB; uint8_t reserved[32]; -} __attribute__ ((packed)); +} __packed; /* Partition Header Descriptor (ECMA 167r3 4/14.3) */ struct partitionHeaderDesc { @@ -444,7 +461,7 @@ struct partitionHeaderDesc { struct short_ad freedSpaceTable; struct short_ad freedSpaceBitmap; uint8_t reserved[88]; -} __attribute__ ((packed)); +} __packed; /* File Identifier Descriptor (ECMA 167r3 4/14.4) */ struct fileIdentDesc { @@ -454,10 +471,10 @@ struct fileIdentDesc { uint8_t lengthFileIdent; struct long_ad icb; __le16 lengthOfImpUse; - uint8_t impUse[0]; - uint8_t fileIdent[0]; - uint8_t padding[0]; -} __attribute__ ((packed)); + /* uint8_t impUse[]; */ + /* uint8_t fileIdent[]; */ + /* uint8_t padding[]; */ +} __packed; /* File Characteristics (ECMA 167r3 4/14.4.3) */ #define FID_FILE_CHAR_HIDDEN 0x01 @@ -471,7 +488,7 @@ struct allocExtDesc { struct tag descTag; __le32 previousAllocExtLocation; __le32 lengthAllocDescs; -} __attribute__ ((packed)); +} __packed; /* ICB Tag (ECMA 167r3 4/14.6) */ struct icbtag { @@ -483,7 +500,7 @@ struct icbtag { uint8_t fileType; struct lb_addr parentICBLocation; __le16 flags; -} __attribute__ ((packed)); +} __packed; /* Strategy Type (ECMA 167r3 4/14.6.2) */ #define ICBTAG_STRATEGY_TYPE_UNDEF 0x0000 @@ -531,13 +548,13 @@ struct indirectEntry { struct tag descTag; struct icbtag icbTag; struct long_ad indirectICB; -} __attribute__ ((packed)); +} __packed; /* Terminal Entry (ECMA 167r3 4/14.8) */ struct terminalEntry { struct tag descTag; struct icbtag icbTag; -} __attribute__ ((packed)); +} __packed; /* File Entry (ECMA 167r3 4/14.9) */ struct fileEntry { @@ -561,9 +578,9 @@ struct fileEntry { __le64 uniqueID; __le32 lengthExtendedAttr; __le32 lengthAllocDescs; - uint8_t extendedAttr[0]; - uint8_t allocDescs[0]; -} __attribute__ ((packed)); + uint8_t extendedAttr[]; + /* uint8_t allocDescs[]; */ +} __packed; /* Permissions (ECMA 167r3 4/14.9.5) */ #define FE_PERM_O_EXEC 0x00000001U @@ -607,7 +624,7 @@ struct extendedAttrHeaderDesc { struct tag descTag; __le32 impAttrLocation; __le32 appAttrLocation; -} __attribute__ ((packed)); +} __packed; /* Generic Format (ECMA 167r3 4/14.10.2) */ struct genericFormat { @@ -615,8 +632,8 @@ struct genericFormat { uint8_t attrSubtype; uint8_t reserved[3]; __le32 attrLength; - uint8_t attrData[0]; -} __attribute__ ((packed)); + uint8_t attrData[]; +} __packed; /* Character Set Information (ECMA 167r3 4/14.10.3) */ struct charSetInfo { @@ -626,8 +643,8 @@ struct charSetInfo { __le32 attrLength; __le32 escapeSeqLength; uint8_t charSetType; - uint8_t escapeSeq[0]; -} __attribute__ ((packed)); + uint8_t escapeSeq[]; +} __packed; /* Alternate Permissions (ECMA 167r3 4/14.10.4) */ struct altPerms { @@ -638,7 +655,7 @@ struct altPerms { __le16 ownerIdent; __le16 groupIdent; __le16 permission; -} __attribute__ ((packed)); +} __packed; /* File Times Extended Attribute (ECMA 167r3 4/14.10.5) */ struct fileTimesExtAttr { @@ -649,7 +666,7 @@ struct fileTimesExtAttr { __le32 dataLength; __le32 fileTimeExistence; uint8_t fileTimes; -} __attribute__ ((packed)); +} __packed; /* FileTimeExistence (ECMA 167r3 4/14.10.5.6) */ #define FTE_CREATION 0x00000001 @@ -665,8 +682,8 @@ struct infoTimesExtAttr { __le32 attrLength; __le32 dataLength; __le32 infoTimeExistence; - uint8_t infoTimes[0]; -} __attribute__ ((packed)); + uint8_t infoTimes[]; +} __packed; /* Device Specification (ECMA 167r3 4/14.10.7) */ struct deviceSpec { @@ -677,8 +694,8 @@ struct deviceSpec { __le32 impUseLength; __le32 majorDeviceIdent; __le32 minorDeviceIdent; - uint8_t impUse[0]; -} __attribute__ ((packed)); + uint8_t impUse[]; +} __packed; /* Implementation Use Extended Attr (ECMA 167r3 4/14.10.8) */ struct impUseExtAttr { @@ -688,8 +705,8 @@ struct impUseExtAttr { __le32 attrLength; __le32 impUseLength; struct regid impIdent; - uint8_t impUse[0]; -} __attribute__ ((packed)); + uint8_t impUse[]; +} __packed; /* Application Use Extended Attribute (ECMA 167r3 4/14.10.9) */ struct appUseExtAttr { @@ -699,8 +716,8 @@ struct appUseExtAttr { __le32 attrLength; __le32 appUseLength; struct regid appIdent; - uint8_t appUse[0]; -} __attribute__ ((packed)); + uint8_t appUse[]; +} __packed; #define EXTATTR_CHAR_SET 1 #define EXTATTR_ALT_PERMS 3 @@ -709,22 +726,23 @@ struct appUseExtAttr { #define EXTATTR_DEV_SPEC 12 #define EXTATTR_IMP_USE 2048 #define EXTATTR_APP_USE 65536 +#define EXTATTR_SUBTYPE 1 /* Unallocated Space Entry (ECMA 167r3 4/14.11) */ struct unallocSpaceEntry { struct tag descTag; struct icbtag icbTag; __le32 lengthAllocDescs; - uint8_t allocDescs[0]; -} __attribute__ ((packed)); + uint8_t allocDescs[]; +} __packed; /* Space Bitmap Descriptor (ECMA 167r3 4/14.12) */ struct spaceBitmapDesc { struct tag descTag; __le32 numOfBits; __le32 numOfBytes; - uint8_t bitmap[0]; -} __attribute__ ((packed)); + uint8_t bitmap[]; +} __packed; /* Partition Integrity Entry (ECMA 167r3 4/14.13) */ struct partitionIntegrityEntry { @@ -735,15 +753,17 @@ struct partitionIntegrityEntry { uint8_t reserved[175]; struct regid impIdent; uint8_t impUse[256]; -} __attribute__ ((packed)); +} __packed; /* Short Allocation Descriptor (ECMA 167r3 4/14.14.1) */ /* Extent Length (ECMA 167r3 4/14.14.1.1) */ +#define EXT_LENGTH_MASK 0x3FFFFFFF +#define EXT_TYPE_MASK 0xC0000000 #define EXT_RECORDED_ALLOCATED 0x00000000 #define EXT_NOT_RECORDED_ALLOCATED 0x40000000 #define EXT_NOT_RECORDED_NOT_ALLOCATED 0x80000000 -#define EXT_NEXT_EXTENT_ALLOCDECS 0xC0000000 +#define EXT_NEXT_EXTENT_ALLOCDESCS 0xC0000000 /* Long Allocation Descriptor (ECMA 167r3 4/14.14.2) */ @@ -753,15 +773,15 @@ struct partitionIntegrityEntry { struct logicalVolHeaderDesc { __le64 uniqueID; uint8_t reserved[24]; -} __attribute__ ((packed)); +} __packed; /* Path Component (ECMA 167r3 4/14.16.1) */ struct pathComponent { uint8_t componentType; uint8_t lengthComponentIdent; __le16 componentFileVersionNum; - dstring componentIdent[0]; -} __attribute__ ((packed)); + dchars componentIdent[]; +} __packed; /* File Entry (ECMA 167r3 4/14.17) */ struct extendedFileEntry { @@ -789,8 +809,8 @@ struct extendedFileEntry { __le64 uniqueID; __le32 lengthExtendedAttr; __le32 lengthAllocDescs; - uint8_t extendedAttr[0]; - uint8_t allocDescs[0]; -} __attribute__ ((packed)); + uint8_t extendedAttr[]; + /* uint8_t allocDescs[]; */ +} __packed; #endif /* _ECMA_167_H */ diff --git a/fs/udf/file.c b/fs/udf/file.c index 29569dd08168..0d76c4f37b3e 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * file.c * @@ -5,11 +6,6 @@ * File handling routines for the OSTA-UDF(tm) filesystem. * * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. - * * (C) 1998-1999 Dave Boynton * (C) 1998-2004 Ben Fennema * (C) 1999-2000 Stelias Computing Inc @@ -27,151 +23,104 @@ #include "udfdecl.h" #include <linux/fs.h> -#include <asm/uaccess.h> +#include <linux/uaccess.h> #include <linux/kernel.h> #include <linux/string.h> /* memset */ #include <linux/capability.h> #include <linux/errno.h> #include <linux/pagemap.h> -#include <linux/buffer_head.h> -#include <linux/aio.h> +#include <linux/uio.h> #include "udf_i.h" #include "udf_sb.h" -static void __udf_adinicb_readpage(struct page *page) -{ - struct inode *inode = page->mapping->host; - char *kaddr; - struct udf_inode_info *iinfo = UDF_I(inode); - - kaddr = kmap(page); - memcpy(kaddr, iinfo->i_ext.i_data + iinfo->i_lenEAttr, inode->i_size); - memset(kaddr + inode->i_size, 0, PAGE_CACHE_SIZE - inode->i_size); - flush_dcache_page(page); - SetPageUptodate(page); - kunmap(page); -} - -static int udf_adinicb_readpage(struct file *file, struct page *page) -{ - BUG_ON(!PageLocked(page)); - __udf_adinicb_readpage(page); - unlock_page(page); - - return 0; -} - -static int udf_adinicb_writepage(struct page *page, - struct writeback_control *wbc) -{ - struct inode *inode = page->mapping->host; - char *kaddr; - struct udf_inode_info *iinfo = UDF_I(inode); - - BUG_ON(!PageLocked(page)); - - kaddr = kmap(page); - memcpy(iinfo->i_ext.i_data + iinfo->i_lenEAttr, kaddr, inode->i_size); - mark_inode_dirty(inode); - SetPageUptodate(page); - kunmap(page); - unlock_page(page); - - return 0; -} - -static int udf_adinicb_write_begin(struct file *file, - struct address_space *mapping, loff_t pos, - unsigned len, unsigned flags, struct page **pagep, - void **fsdata) +static vm_fault_t udf_page_mkwrite(struct vm_fault *vmf) { - struct page *page; - - if (WARN_ON_ONCE(pos >= PAGE_CACHE_SIZE)) - return -EIO; - page = grab_cache_page_write_begin(mapping, 0, flags); - if (!page) - return -ENOMEM; - *pagep = page; - - if (!PageUptodate(page) && len != PAGE_CACHE_SIZE) - __udf_adinicb_readpage(page); - return 0; -} - -static int udf_adinicb_write_end(struct file *file, - struct address_space *mapping, - loff_t pos, unsigned len, unsigned copied, - struct page *page, void *fsdata) -{ - struct inode *inode = mapping->host; - unsigned offset = pos & (PAGE_CACHE_SIZE - 1); - char *kaddr; - struct udf_inode_info *iinfo = UDF_I(inode); - - kaddr = kmap_atomic(page); - memcpy(iinfo->i_ext.i_data + iinfo->i_lenEAttr + offset, - kaddr + offset, copied); - kunmap_atomic(kaddr); - - return simple_write_end(file, mapping, pos, len, copied, page, fsdata); -} + struct vm_area_struct *vma = vmf->vma; + struct inode *inode = file_inode(vma->vm_file); + struct address_space *mapping = inode->i_mapping; + struct folio *folio = page_folio(vmf->page); + loff_t size; + unsigned int end; + vm_fault_t ret = VM_FAULT_LOCKED; + int err; + + sb_start_pagefault(inode->i_sb); + file_update_time(vma->vm_file); + filemap_invalidate_lock_shared(mapping); + folio_lock(folio); + size = i_size_read(inode); + if (folio->mapping != inode->i_mapping || folio_pos(folio) >= size) { + folio_unlock(folio); + ret = VM_FAULT_NOPAGE; + goto out_unlock; + } + /* Space is already allocated for in-ICB file */ + if (UDF_I(inode)->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) + goto out_dirty; + if (folio->index == size >> PAGE_SHIFT) + end = size & ~PAGE_MASK; + else + end = PAGE_SIZE; + err = __block_write_begin(folio, 0, end, udf_get_block); + if (err) { + folio_unlock(folio); + ret = vmf_fs_error(err); + goto out_unlock; + } -static ssize_t udf_adinicb_direct_IO(int rw, struct kiocb *iocb, - const struct iovec *iov, - loff_t offset, unsigned long nr_segs) -{ - /* Fallback to buffered I/O. */ - return 0; + block_commit_write(folio, 0, end); +out_dirty: + folio_mark_dirty(folio); + folio_wait_stable(folio); +out_unlock: + filemap_invalidate_unlock_shared(mapping); + sb_end_pagefault(inode->i_sb); + return ret; } -const struct address_space_operations udf_adinicb_aops = { - .readpage = udf_adinicb_readpage, - .writepage = udf_adinicb_writepage, - .write_begin = udf_adinicb_write_begin, - .write_end = udf_adinicb_write_end, - .direct_IO = udf_adinicb_direct_IO, +static const struct vm_operations_struct udf_file_vm_ops = { + .fault = filemap_fault, + .map_pages = filemap_map_pages, + .page_mkwrite = udf_page_mkwrite, }; -static ssize_t udf_file_aio_write(struct kiocb *iocb, const struct iovec *iov, - unsigned long nr_segs, loff_t ppos) +static ssize_t udf_file_write_iter(struct kiocb *iocb, struct iov_iter *from) { ssize_t retval; struct file *file = iocb->ki_filp; struct inode *inode = file_inode(file); - int err, pos; - size_t count = iocb->ki_left; struct udf_inode_info *iinfo = UDF_I(inode); - down_write(&iinfo->i_data_sem); - if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { - if (file->f_flags & O_APPEND) - pos = inode->i_size; - else - pos = ppos; + inode_lock(inode); - if (inode->i_sb->s_blocksize < - (udf_file_entry_alloc_offset(inode) + - pos + count)) { - err = udf_expand_file_adinicb(inode); - if (err) { - udf_debug("udf_expand_adinicb: err=%d\n", err); - return err; - } - } else { - if (pos + count > inode->i_size) - iinfo->i_lenAlloc = pos + count; - else - iinfo->i_lenAlloc = inode->i_size; - up_write(&iinfo->i_data_sem); - } - } else + retval = generic_write_checks(iocb, from); + if (retval <= 0) + goto out; + + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB && + inode->i_sb->s_blocksize < (udf_file_entry_alloc_offset(inode) + + iocb->ki_pos + iov_iter_count(from))) { + filemap_invalidate_lock(inode->i_mapping); + retval = udf_expand_file_adinicb(inode); + filemap_invalidate_unlock(inode->i_mapping); + if (retval) + goto out; + } + + retval = __generic_file_write_iter(iocb, from); +out: + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB && retval > 0) { + down_write(&iinfo->i_data_sem); + iinfo->i_lenAlloc = inode->i_size; up_write(&iinfo->i_data_sem); + } + inode_unlock(inode); - retval = generic_file_aio_write(iocb, iov, nr_segs, ppos); - if (retval > 0) + if (retval > 0) { mark_inode_dirty(inode); + retval = generic_write_sync(iocb, retval); + } return retval; } @@ -180,98 +129,120 @@ long udf_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct inode *inode = file_inode(filp); long old_block, new_block; - int result = -EINVAL; + int result; - if (inode_permission(inode, MAY_READ) != 0) { + if (file_permission(filp, MAY_READ) != 0) { udf_debug("no permission to access inode %lu\n", inode->i_ino); - result = -EPERM; - goto out; + return -EPERM; } - if (!arg) { + if (!arg && ((cmd == UDF_GETVOLIDENT) || (cmd == UDF_GETEASIZE) || + (cmd == UDF_RELOCATE_BLOCKS) || (cmd == UDF_GETEABLOCK))) { udf_debug("invalid argument to udf_ioctl\n"); - result = -EINVAL; - goto out; + return -EINVAL; } switch (cmd) { case UDF_GETVOLIDENT: if (copy_to_user((char __user *)arg, UDF_SB(inode->i_sb)->s_volume_ident, 32)) - result = -EFAULT; - else - result = 0; - goto out; + return -EFAULT; + return 0; case UDF_RELOCATE_BLOCKS: - if (!capable(CAP_SYS_ADMIN)) { - result = -EPERM; - goto out; - } - if (get_user(old_block, (long __user *)arg)) { - result = -EFAULT; - goto out; - } + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (get_user(old_block, (long __user *)arg)) + return -EFAULT; result = udf_relocate_blocks(inode->i_sb, old_block, &new_block); if (result == 0) result = put_user(new_block, (long __user *)arg); - goto out; + return result; case UDF_GETEASIZE: - result = put_user(UDF_I(inode)->i_lenEAttr, (int __user *)arg); - goto out; + return put_user(UDF_I(inode)->i_lenEAttr, (int __user *)arg); case UDF_GETEABLOCK: - result = copy_to_user((char __user *)arg, - UDF_I(inode)->i_ext.i_data, - UDF_I(inode)->i_lenEAttr) ? -EFAULT : 0; - goto out; + return copy_to_user((char __user *)arg, + UDF_I(inode)->i_data, + UDF_I(inode)->i_lenEAttr) ? -EFAULT : 0; + default: + return -ENOIOCTLCMD; } -out: - return result; + return 0; } static int udf_release_file(struct inode *inode, struct file *filp) { - if (filp->f_mode & FMODE_WRITE) { + if (filp->f_mode & FMODE_WRITE && + atomic_read(&inode->i_writecount) == 1) { + /* + * Grab i_mutex to avoid races with writes changing i_size + * while we are running. + */ + inode_lock(inode); down_write(&UDF_I(inode)->i_data_sem); udf_discard_prealloc(inode); udf_truncate_tail_extent(inode); up_write(&UDF_I(inode)->i_data_sem); + inode_unlock(inode); } return 0; } +static int udf_file_mmap(struct file *file, struct vm_area_struct *vma) +{ + file_accessed(file); + vma->vm_ops = &udf_file_vm_ops; + + return 0; +} + const struct file_operations udf_file_operations = { - .read = do_sync_read, - .aio_read = generic_file_aio_read, + .read_iter = generic_file_read_iter, .unlocked_ioctl = udf_ioctl, .open = generic_file_open, - .mmap = generic_file_mmap, - .write = do_sync_write, - .aio_write = udf_file_aio_write, + .mmap = udf_file_mmap, + .write_iter = udf_file_write_iter, .release = udf_release_file, .fsync = generic_file_fsync, - .splice_read = generic_file_splice_read, + .splice_read = filemap_splice_read, + .splice_write = iter_file_splice_write, .llseek = generic_file_llseek, }; -static int udf_setattr(struct dentry *dentry, struct iattr *attr) +static int udf_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + struct iattr *attr) { - struct inode *inode = dentry->d_inode; + struct inode *inode = d_inode(dentry); + struct super_block *sb = inode->i_sb; int error; - error = inode_change_ok(inode, attr); + error = setattr_prepare(&nop_mnt_idmap, dentry, attr); if (error) return error; + if ((attr->ia_valid & ATTR_UID) && + UDF_QUERY_FLAG(sb, UDF_FLAG_UID_SET) && + !uid_eq(attr->ia_uid, UDF_SB(sb)->s_uid)) + return -EPERM; + if ((attr->ia_valid & ATTR_GID) && + UDF_QUERY_FLAG(sb, UDF_FLAG_GID_SET) && + !gid_eq(attr->ia_gid, UDF_SB(sb)->s_gid)) + return -EPERM; + if ((attr->ia_valid & ATTR_SIZE) && attr->ia_size != i_size_read(inode)) { + filemap_invalidate_lock(inode->i_mapping); error = udf_setsize(inode, attr->ia_size); + filemap_invalidate_unlock(inode->i_mapping); if (error) return error; } - setattr_copy(inode, attr); + if (attr->ia_valid & ATTR_MODE) + udf_update_extra_perms(inode, attr->ia_mode); + + setattr_copy(&nop_mnt_idmap, inode, attr); mark_inode_dirty(inode); return 0; } diff --git a/fs/udf/ialloc.c b/fs/udf/ialloc.c index 7e5aae4bf46f..5f1f969f4134 100644 --- a/fs/udf/ialloc.c +++ b/fs/udf/ialloc.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * ialloc.c * @@ -5,11 +6,6 @@ * Inode allocation handling routines for the OSTA-UDF(tm) filesystem. * * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. - * * (C) 1998-2001 Ben Fennema * * HISTORY @@ -28,85 +24,63 @@ void udf_free_inode(struct inode *inode) { - struct super_block *sb = inode->i_sb; - struct udf_sb_info *sbi = UDF_SB(sb); - - mutex_lock(&sbi->s_alloc_mutex); - if (sbi->s_lvid_bh) { - struct logicalVolIntegrityDescImpUse *lvidiu = - udf_sb_lvidiu(sbi); - if (S_ISDIR(inode->i_mode)) - le32_add_cpu(&lvidiu->numDirs, -1); - else - le32_add_cpu(&lvidiu->numFiles, -1); - udf_updated_lvid(sb); - } - mutex_unlock(&sbi->s_alloc_mutex); - - udf_free_blocks(sb, NULL, &UDF_I(inode)->i_location, 0, 1); + udf_free_blocks(inode->i_sb, NULL, &UDF_I(inode)->i_location, 0, 1); } -struct inode *udf_new_inode(struct inode *dir, umode_t mode, int *err) +struct inode *udf_new_inode(struct inode *dir, umode_t mode) { struct super_block *sb = dir->i_sb; struct udf_sb_info *sbi = UDF_SB(sb); struct inode *inode; - int block; + udf_pblk_t block; uint32_t start = UDF_I(dir)->i_location.logicalBlockNum; struct udf_inode_info *iinfo; struct udf_inode_info *dinfo = UDF_I(dir); + int err; inode = new_inode(sb); - if (!inode) { - *err = -ENOMEM; - return NULL; - } - *err = -ENOSPC; + if (!inode) + return ERR_PTR(-ENOMEM); iinfo = UDF_I(inode); if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_USE_EXTENDED_FE)) { iinfo->i_efe = 1; if (UDF_VERS_USE_EXTENDED_FE > sbi->s_udfrev) sbi->s_udfrev = UDF_VERS_USE_EXTENDED_FE; - iinfo->i_ext.i_data = kzalloc(inode->i_sb->s_blocksize - - sizeof(struct extendedFileEntry), - GFP_KERNEL); + iinfo->i_data = kzalloc(inode->i_sb->s_blocksize - + sizeof(struct extendedFileEntry), + GFP_KERNEL); } else { iinfo->i_efe = 0; - iinfo->i_ext.i_data = kzalloc(inode->i_sb->s_blocksize - - sizeof(struct fileEntry), - GFP_KERNEL); + iinfo->i_data = kzalloc(inode->i_sb->s_blocksize - + sizeof(struct fileEntry), + GFP_KERNEL); } - if (!iinfo->i_ext.i_data) { + if (!iinfo->i_data) { + make_bad_inode(inode); iput(inode); - *err = -ENOMEM; - return NULL; + return ERR_PTR(-ENOMEM); } + err = -ENOSPC; block = udf_new_block(dir->i_sb, NULL, dinfo->i_location.partitionReferenceNum, - start, err); - if (*err) { + start, &err); + if (err) { + make_bad_inode(inode); iput(inode); - return NULL; + return ERR_PTR(err); } - if (sbi->s_lvid_bh) { - struct logicalVolIntegrityDescImpUse *lvidiu; + iinfo->i_unique = lvid_get_unique_id(sb); + inode->i_generation = iinfo->i_unique; - iinfo->i_unique = lvid_get_unique_id(sb); - mutex_lock(&sbi->s_alloc_mutex); - lvidiu = udf_sb_lvidiu(sbi); - if (S_ISDIR(mode)) - le32_add_cpu(&lvidiu->numDirs, 1); - else - le32_add_cpu(&lvidiu->numFiles, 1); - udf_updated_lvid(sb); - mutex_unlock(&sbi->s_alloc_mutex); - } - - inode_init_owner(inode, dir, mode); + inode_init_owner(&nop_mnt_idmap, inode, dir, mode); + if (UDF_QUERY_FLAG(sb, UDF_FLAG_UID_SET)) + inode->i_uid = sbi->s_uid; + if (UDF_QUERY_FLAG(sb, UDF_FLAG_GID_SET)) + inode->i_gid = sbi->s_gid; iinfo->i_location.logicalBlockNum = block; iinfo->i_location.partitionReferenceNum = @@ -117,17 +91,23 @@ struct inode *udf_new_inode(struct inode *dir, umode_t mode, int *err) iinfo->i_lenAlloc = 0; iinfo->i_use = 0; iinfo->i_checkpoint = 1; + iinfo->i_extraPerms = FE_PERM_U_CHATTR; + udf_update_extra_perms(inode, mode); + if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_USE_AD_IN_ICB)) iinfo->i_alloc_type = ICBTAG_FLAG_AD_IN_ICB; else 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; - inode->i_mtime = inode->i_atime = inode->i_ctime = - iinfo->i_crtime = current_fs_time(inode->i_sb); - insert_inode_hash(inode); + simple_inode_init_ts(inode); + iinfo->i_crtime = inode_get_mtime(inode); + if (unlikely(insert_inode_locked(inode) < 0)) { + make_bad_inode(inode); + iput(inode); + return ERR_PTR(-EIO); + } mark_inode_dirty(inode); - *err = 0; return inode; } diff --git a/fs/udf/inode.c b/fs/udf/inode.c index b6d15d349810..7fae8002344a 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * inode.c * @@ -5,11 +6,6 @@ * Inode handling routines for the OSTA-UDF(tm) filesystem. * * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. - * * (C) 1998 Dave Boynton * (C) 1998-2004 Ben Fennema * (C) 1999-2000 Stelias Computing Inc @@ -33,40 +29,43 @@ #include <linux/mm.h> #include <linux/module.h> #include <linux/pagemap.h> -#include <linux/buffer_head.h> #include <linux/writeback.h> #include <linux/slab.h> #include <linux/crc-itu-t.h> #include <linux/mpage.h> -#include <linux/aio.h> +#include <linux/uio.h> +#include <linux/bio.h> #include "udf_i.h" #include "udf_sb.h" -MODULE_AUTHOR("Ben Fennema"); -MODULE_DESCRIPTION("Universal Disk Format Filesystem"); -MODULE_LICENSE("GPL"); - #define EXTENT_MERGE_SIZE 5 +#define FE_MAPPED_PERMS (FE_PERM_U_READ | FE_PERM_U_WRITE | FE_PERM_U_EXEC | \ + FE_PERM_G_READ | FE_PERM_G_WRITE | FE_PERM_G_EXEC | \ + FE_PERM_O_READ | FE_PERM_O_WRITE | FE_PERM_O_EXEC) + +#define FE_DELETE_PERMS (FE_PERM_U_DELETE | FE_PERM_G_DELETE | \ + FE_PERM_O_DELETE) + +struct udf_map_rq; + static umode_t udf_convert_permissions(struct fileEntry *); static int udf_update_inode(struct inode *, int); -static void udf_fill_inode(struct inode *, struct buffer_head *); static int udf_sync_inode(struct inode *inode); static int udf_alloc_i_data(struct inode *inode, size_t size); -static sector_t inode_getblk(struct inode *, sector_t, int *, int *); -static int8_t udf_insert_aext(struct inode *, struct extent_position, - struct kernel_lb_addr, uint32_t); -static void udf_split_extents(struct inode *, int *, int, int, - struct kernel_long_ad[EXTENT_MERGE_SIZE], int *); +static int inode_getblk(struct inode *inode, struct udf_map_rq *map); +static int udf_insert_aext(struct inode *, struct extent_position, + struct kernel_lb_addr, uint32_t); +static void udf_split_extents(struct inode *, int *, int, udf_pblk_t, + struct kernel_long_ad *, int *); static void udf_prealloc_extents(struct inode *, int, int, - struct kernel_long_ad[EXTENT_MERGE_SIZE], int *); -static void udf_merge_extents(struct inode *, - struct kernel_long_ad[EXTENT_MERGE_SIZE], int *); -static void udf_update_extents(struct inode *, - struct kernel_long_ad[EXTENT_MERGE_SIZE], int, int, - struct extent_position *); -static int udf_get_block(struct inode *, sector_t, struct buffer_head *, int); + struct kernel_long_ad *, int *); +static void udf_merge_extents(struct inode *, struct kernel_long_ad *, int *); +static int udf_update_extents(struct inode *, struct kernel_long_ad *, int, + int, struct extent_position *); +static int udf_get_block_wb(struct inode *inode, sector_t block, + struct buffer_head *bh_result, int create); static void __udf_clear_extent_cache(struct inode *inode) { @@ -112,7 +111,7 @@ static int udf_read_extent_cache(struct inode *inode, loff_t bcount, /* Add extent to extent cache */ static void udf_update_extent_cache(struct inode *inode, loff_t estart, - struct extent_position *pos, int next_epos) + struct extent_position *pos) { struct udf_inode_info *iinfo = UDF_I(inode); @@ -121,19 +120,16 @@ static void udf_update_extent_cache(struct inode *inode, loff_t estart, __udf_clear_extent_cache(inode); if (pos->bh) get_bh(pos->bh); - memcpy(&iinfo->cached_extent.epos, pos, - sizeof(struct extent_position)); + memcpy(&iinfo->cached_extent.epos, pos, sizeof(*pos)); iinfo->cached_extent.lstart = estart; - if (next_epos) - switch (iinfo->i_alloc_type) { - case ICBTAG_FLAG_AD_SHORT: - iinfo->cached_extent.epos.offset -= - sizeof(struct short_ad); - break; - case ICBTAG_FLAG_AD_LONG: - iinfo->cached_extent.epos.offset -= - sizeof(struct long_ad); - } + switch (iinfo->i_alloc_type) { + case ICBTAG_FLAG_AD_SHORT: + iinfo->cached_extent.epos.offset -= sizeof(struct short_ad); + break; + case ICBTAG_FLAG_AD_LONG: + iinfo->cached_extent.epos.offset -= sizeof(struct long_ad); + break; + } spin_unlock(&iinfo->i_extent_cache_lock); } @@ -142,23 +138,26 @@ void udf_evict_inode(struct inode *inode) struct udf_inode_info *iinfo = UDF_I(inode); int want_delete = 0; - if (!inode->i_nlink && !is_bad_inode(inode)) { - want_delete = 1; - udf_setsize(inode, 0); - udf_update_inode(inode, IS_SYNC(inode)); - } else - truncate_inode_pages(&inode->i_data, 0); + if (!is_bad_inode(inode)) { + if (!inode->i_nlink) { + want_delete = 1; + udf_setsize(inode, 0); + udf_update_inode(inode, IS_SYNC(inode)); + } + if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB && + inode->i_size != iinfo->i_lenExtents) { + udf_warn(inode->i_sb, + "Inode %lu (mode %o) has inode size %llu different from extent length %llu. Filesystem need not be standards compliant.\n", + inode->i_ino, inode->i_mode, + (unsigned long long)inode->i_size, + (unsigned long long)iinfo->i_lenExtents); + } + } + truncate_inode_pages_final(&inode->i_data); invalidate_inode_buffers(inode); clear_inode(inode); - if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB && - inode->i_size != iinfo->i_lenExtents) { - udf_warn(inode->i_sb, "Inode %lu (mode %o) has inode size %llu different from extent length %llu. Filesystem need not be standards compliant.\n", - inode->i_ino, inode->i_mode, - (unsigned long long)inode->i_size, - (unsigned long long)iinfo->i_lenExtents); - } - kfree(iinfo->i_ext.i_data); - iinfo->i_ext.i_data = NULL; + kfree(iinfo->i_data); + iinfo->i_data = NULL; udf_clear_extent_cache(inode); if (want_delete) { udf_free_inode(inode); @@ -172,7 +171,7 @@ static void udf_write_failed(struct address_space *mapping, loff_t to) loff_t isize = inode->i_size; if (to > isize) { - truncate_pagecache(inode, to, isize); + truncate_pagecache(inode, isize); if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) { down_write(&iinfo->i_data_sem); udf_clear_extent_cache(inode); @@ -182,322 +181,346 @@ static void udf_write_failed(struct address_space *mapping, loff_t to) } } -static int udf_writepage(struct page *page, struct writeback_control *wbc) +static int udf_adinicb_writepages(struct address_space *mapping, + struct writeback_control *wbc) { - return block_write_full_page(page, udf_get_block, wbc); + struct inode *inode = mapping->host; + struct udf_inode_info *iinfo = UDF_I(inode); + struct folio *folio = NULL; + int error = 0; + + while ((folio = writeback_iter(mapping, wbc, folio, &error))) { + BUG_ON(!folio_test_locked(folio)); + BUG_ON(folio->index != 0); + memcpy_from_file_folio(iinfo->i_data + iinfo->i_lenEAttr, folio, + 0, i_size_read(inode)); + folio_unlock(folio); + } + + mark_inode_dirty(inode); + return 0; } static int udf_writepages(struct address_space *mapping, - struct writeback_control *wbc) + struct writeback_control *wbc) { - return mpage_writepages(mapping, wbc, udf_get_block); + struct inode *inode = mapping->host; + struct udf_inode_info *iinfo = UDF_I(inode); + + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) + return udf_adinicb_writepages(mapping, wbc); + return mpage_writepages(mapping, wbc, udf_get_block_wb); +} + +static void udf_adinicb_read_folio(struct folio *folio) +{ + struct inode *inode = folio->mapping->host; + struct udf_inode_info *iinfo = UDF_I(inode); + loff_t isize = i_size_read(inode); + + folio_fill_tail(folio, 0, iinfo->i_data + iinfo->i_lenEAttr, isize); + folio_mark_uptodate(folio); } -static int udf_readpage(struct file *file, struct page *page) +static int udf_read_folio(struct file *file, struct folio *folio) { - return mpage_readpage(page, udf_get_block); + struct udf_inode_info *iinfo = UDF_I(file_inode(file)); + + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { + udf_adinicb_read_folio(folio); + folio_unlock(folio); + return 0; + } + return mpage_read_folio(folio, udf_get_block); } -static int udf_readpages(struct file *file, struct address_space *mapping, - struct list_head *pages, unsigned nr_pages) +static void udf_readahead(struct readahead_control *rac) { - return mpage_readpages(mapping, pages, nr_pages, udf_get_block); + struct udf_inode_info *iinfo = UDF_I(rac->mapping->host); + + /* + * No readahead needed for in-ICB files and udf_get_block() would get + * confused for such file anyway. + */ + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) + return; + + mpage_readahead(rac, udf_get_block); } -static int udf_write_begin(struct file *file, struct address_space *mapping, - loff_t pos, unsigned len, unsigned flags, - struct page **pagep, void **fsdata) +static int udf_write_begin(const struct kiocb *iocb, + struct address_space *mapping, + loff_t pos, unsigned len, + struct folio **foliop, void **fsdata) { + struct file *file = iocb->ki_filp; + struct udf_inode_info *iinfo = UDF_I(file_inode(file)); + struct folio *folio; int ret; - ret = block_write_begin(mapping, pos, len, flags, pagep, udf_get_block); - if (unlikely(ret)) - udf_write_failed(mapping, pos + len); - return ret; + if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) { + ret = block_write_begin(mapping, pos, len, foliop, + udf_get_block); + if (unlikely(ret)) + udf_write_failed(mapping, pos + len); + return ret; + } + if (WARN_ON_ONCE(pos >= PAGE_SIZE)) + return -EIO; + folio = __filemap_get_folio(mapping, 0, FGP_WRITEBEGIN, + mapping_gfp_mask(mapping)); + if (IS_ERR(folio)) + return PTR_ERR(folio); + *foliop = folio; + if (!folio_test_uptodate(folio)) + udf_adinicb_read_folio(folio); + return 0; } -static ssize_t udf_direct_IO(int rw, struct kiocb *iocb, - const struct iovec *iov, - loff_t offset, unsigned long nr_segs) +static int udf_write_end(const struct kiocb *iocb, + struct address_space *mapping, + loff_t pos, unsigned len, unsigned copied, + struct folio *folio, void *fsdata) +{ + struct inode *inode = file_inode(iocb->ki_filp); + loff_t last_pos; + + if (UDF_I(inode)->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) + return generic_write_end(iocb, mapping, pos, len, copied, folio, + fsdata); + last_pos = pos + copied; + if (last_pos > inode->i_size) + i_size_write(inode, last_pos); + folio_mark_dirty(folio); + folio_unlock(folio); + folio_put(folio); + + return copied; +} + +static ssize_t udf_direct_IO(struct kiocb *iocb, struct iov_iter *iter) { struct file *file = iocb->ki_filp; struct address_space *mapping = file->f_mapping; struct inode *inode = mapping->host; + size_t count = iov_iter_count(iter); ssize_t ret; - ret = blockdev_direct_IO(rw, iocb, inode, iov, offset, nr_segs, - udf_get_block); - if (unlikely(ret < 0 && (rw & WRITE))) - udf_write_failed(mapping, offset + iov_length(iov, nr_segs)); + /* Fallback to buffered IO for in-ICB files */ + if (UDF_I(inode)->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) + return 0; + ret = blockdev_direct_IO(iocb, inode, iter, udf_get_block); + if (unlikely(ret < 0 && iov_iter_rw(iter) == WRITE)) + udf_write_failed(mapping, iocb->ki_pos + count); return ret; } static sector_t udf_bmap(struct address_space *mapping, sector_t block) { + struct udf_inode_info *iinfo = UDF_I(mapping->host); + + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) + return -EINVAL; return generic_block_bmap(mapping, block, udf_get_block); } const struct address_space_operations udf_aops = { - .readpage = udf_readpage, - .readpages = udf_readpages, - .writepage = udf_writepage, + .dirty_folio = block_dirty_folio, + .invalidate_folio = block_invalidate_folio, + .read_folio = udf_read_folio, + .readahead = udf_readahead, .writepages = udf_writepages, .write_begin = udf_write_begin, - .write_end = generic_write_end, + .write_end = udf_write_end, .direct_IO = udf_direct_IO, .bmap = udf_bmap, + .migrate_folio = buffer_migrate_folio, }; /* * Expand file stored in ICB to a normal one-block-file * - * This function requires i_data_sem for writing and releases it. * This function requires i_mutex held */ int udf_expand_file_adinicb(struct inode *inode) { - struct page *page; - char *kaddr; + struct folio *folio; struct udf_inode_info *iinfo = UDF_I(inode); int err; - struct writeback_control udf_wbc = { - .sync_mode = WB_SYNC_NONE, - .nr_to_write = 1, - }; + WARN_ON_ONCE(!inode_is_locked(inode)); if (!iinfo->i_lenAlloc) { + down_write(&iinfo->i_data_sem); 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; up_write(&iinfo->i_data_sem); mark_inode_dirty(inode); return 0; } - /* - * Release i_data_sem so that we can lock a page - page lock ranks - * above i_data_sem. i_mutex still protects us against file changes. - */ - up_write(&iinfo->i_data_sem); - page = find_or_create_page(inode->i_mapping, 0, GFP_NOFS); - if (!page) - return -ENOMEM; + folio = __filemap_get_folio(inode->i_mapping, 0, + FGP_LOCK | FGP_ACCESSED | FGP_CREAT, GFP_KERNEL); + if (IS_ERR(folio)) + return PTR_ERR(folio); - if (!PageUptodate(page)) { - kaddr = kmap(page); - memset(kaddr + iinfo->i_lenAlloc, 0x00, - PAGE_CACHE_SIZE - iinfo->i_lenAlloc); - memcpy(kaddr, iinfo->i_ext.i_data + iinfo->i_lenEAttr, - iinfo->i_lenAlloc); - flush_dcache_page(page); - SetPageUptodate(page); - kunmap(page); - } + if (!folio_test_uptodate(folio)) + udf_adinicb_read_folio(folio); down_write(&iinfo->i_data_sem); - memset(iinfo->i_ext.i_data + iinfo->i_lenEAttr, 0x00, + memset(iinfo->i_data + iinfo->i_lenEAttr, 0x00, iinfo->i_lenAlloc); iinfo->i_lenAlloc = 0; 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; + folio_mark_dirty(folio); + folio_unlock(folio); up_write(&iinfo->i_data_sem); - err = inode->i_data.a_ops->writepage(page, &udf_wbc); + err = filemap_fdatawrite(inode->i_mapping); if (err) { /* Restore everything back so that we don't lose data... */ - lock_page(page); - kaddr = kmap(page); + folio_lock(folio); down_write(&iinfo->i_data_sem); - memcpy(iinfo->i_ext.i_data + iinfo->i_lenEAttr, kaddr, - inode->i_size); - kunmap(page); - unlock_page(page); + memcpy_from_folio(iinfo->i_data + iinfo->i_lenEAttr, + folio, 0, inode->i_size); + folio_unlock(folio); iinfo->i_alloc_type = ICBTAG_FLAG_AD_IN_ICB; - inode->i_data.a_ops = &udf_adinicb_aops; + iinfo->i_lenAlloc = inode->i_size; up_write(&iinfo->i_data_sem); } - page_cache_release(page); + folio_put(folio); mark_inode_dirty(inode); return err; } -struct buffer_head *udf_expand_dir_adinicb(struct inode *inode, int *block, - int *err) -{ - int newblock; - struct buffer_head *dbh = NULL; - struct kernel_lb_addr eloc; - uint8_t alloctype; - struct extent_position epos; +#define UDF_MAP_CREATE 0x01 /* Mapping can allocate new blocks */ +#define UDF_MAP_NOPREALLOC 0x02 /* Do not preallocate blocks */ - struct udf_fileident_bh sfibh, dfibh; - loff_t f_pos = udf_ext0_offset(inode); - int size = udf_ext0_offset(inode) + inode->i_size; - struct fileIdentDesc cfi, *sfi, *dfi; - struct udf_inode_info *iinfo = UDF_I(inode); +#define UDF_BLK_MAPPED 0x01 /* Block was successfully mapped */ +#define UDF_BLK_NEW 0x02 /* Block was freshly allocated */ - if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_USE_SHORT_AD)) - alloctype = ICBTAG_FLAG_AD_SHORT; - else - alloctype = ICBTAG_FLAG_AD_LONG; +struct udf_map_rq { + sector_t lblk; + udf_pblk_t pblk; + int iflags; /* UDF_MAP_ flags determining behavior */ + int oflags; /* UDF_BLK_ flags reporting results */ +}; - if (!inode->i_size) { - iinfo->i_alloc_type = alloctype; - mark_inode_dirty(inode); - return NULL; - } +static int udf_map_block(struct inode *inode, struct udf_map_rq *map) +{ + int ret; + struct udf_inode_info *iinfo = UDF_I(inode); - /* alloc block, and copy data to it */ - *block = udf_new_block(inode->i_sb, inode, - iinfo->i_location.partitionReferenceNum, - iinfo->i_location.logicalBlockNum, err); - if (!(*block)) - return NULL; - newblock = udf_get_pblock(inode->i_sb, *block, - iinfo->i_location.partitionReferenceNum, - 0); - if (!newblock) - return NULL; - dbh = udf_tgetblk(inode->i_sb, newblock); - if (!dbh) - return NULL; - lock_buffer(dbh); - memset(dbh->b_data, 0x00, inode->i_sb->s_blocksize); - set_buffer_uptodate(dbh); - unlock_buffer(dbh); - mark_buffer_dirty_inode(dbh, inode); - - sfibh.soffset = sfibh.eoffset = - f_pos & (inode->i_sb->s_blocksize - 1); - sfibh.sbh = sfibh.ebh = NULL; - dfibh.soffset = dfibh.eoffset = 0; - dfibh.sbh = dfibh.ebh = dbh; - while (f_pos < size) { - iinfo->i_alloc_type = ICBTAG_FLAG_AD_IN_ICB; - sfi = udf_fileident_read(inode, &f_pos, &sfibh, &cfi, NULL, - NULL, NULL, NULL); - if (!sfi) { - brelse(dbh); - return NULL; + if (WARN_ON_ONCE(iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB)) + return -EFSCORRUPTED; + + map->oflags = 0; + if (!(map->iflags & UDF_MAP_CREATE)) { + struct kernel_lb_addr eloc; + uint32_t elen; + sector_t offset; + struct extent_position epos = {}; + int8_t etype; + + down_read(&iinfo->i_data_sem); + ret = inode_bmap(inode, map->lblk, &epos, &eloc, &elen, &offset, + &etype); + if (ret < 0) + goto out_read; + if (ret > 0 && etype == (EXT_RECORDED_ALLOCATED >> 30)) { + map->pblk = udf_get_lb_pblock(inode->i_sb, &eloc, + offset); + map->oflags |= UDF_BLK_MAPPED; + ret = 0; } - iinfo->i_alloc_type = alloctype; - sfi->descTag.tagLocation = cpu_to_le32(*block); - dfibh.soffset = dfibh.eoffset; - dfibh.eoffset += (sfibh.eoffset - sfibh.soffset); - dfi = (struct fileIdentDesc *)(dbh->b_data + dfibh.soffset); - if (udf_write_fi(inode, sfi, dfi, &dfibh, sfi->impUse, - sfi->fileIdent + - le16_to_cpu(sfi->lengthOfImpUse))) { - iinfo->i_alloc_type = ICBTAG_FLAG_AD_IN_ICB; - brelse(dbh); - return NULL; - } - } - mark_buffer_dirty_inode(dbh, inode); +out_read: + up_read(&iinfo->i_data_sem); + brelse(epos.bh); - memset(iinfo->i_ext.i_data + iinfo->i_lenEAttr, 0, - iinfo->i_lenAlloc); - iinfo->i_lenAlloc = 0; - eloc.logicalBlockNum = *block; - eloc.partitionReferenceNum = - iinfo->i_location.partitionReferenceNum; - 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, inode->i_size, 0); - /* UniqueID stuff */ + return ret; + } - brelse(epos.bh); - mark_inode_dirty(inode); - return dbh; + down_write(&iinfo->i_data_sem); + /* + * Block beyond EOF and prealloc extents? Just discard preallocation + * as it is not useful and complicates things. + */ + if (((loff_t)map->lblk) << inode->i_blkbits >= iinfo->i_lenExtents) + udf_discard_prealloc(inode); + udf_clear_extent_cache(inode); + ret = inode_getblk(inode, map); + up_write(&iinfo->i_data_sem); + return ret; } -static int udf_get_block(struct inode *inode, sector_t block, - struct buffer_head *bh_result, int create) +static int __udf_get_block(struct inode *inode, sector_t block, + struct buffer_head *bh_result, int flags) { - int err, new; - sector_t phys = 0; - struct udf_inode_info *iinfo; - - if (!create) { - phys = udf_block_map(inode, block); - if (phys) - map_bh(bh_result, inode->i_sb, phys); - return 0; - } - - err = -EIO; - new = 0; - iinfo = UDF_I(inode); + int err; + struct udf_map_rq map = { + .lblk = block, + .iflags = flags, + }; - down_write(&iinfo->i_data_sem); - if (block == iinfo->i_next_alloc_block + 1) { - iinfo->i_next_alloc_block++; - iinfo->i_next_alloc_goal++; + err = udf_map_block(inode, &map); + if (err < 0) + return err; + if (map.oflags & UDF_BLK_MAPPED) { + map_bh(bh_result, inode->i_sb, map.pblk); + if (map.oflags & UDF_BLK_NEW) + set_buffer_new(bh_result); } + return 0; +} - udf_clear_extent_cache(inode); - phys = inode_getblk(inode, block, &err, &new); - if (!phys) - goto abort; - - if (new) - set_buffer_new(bh_result); - map_bh(bh_result, inode->i_sb, phys); +int udf_get_block(struct inode *inode, sector_t block, + struct buffer_head *bh_result, int create) +{ + int flags = create ? UDF_MAP_CREATE : 0; -abort: - up_write(&iinfo->i_data_sem); - return err; + /* + * We preallocate blocks only for regular files. It also makes sense + * for directories but there's a problem when to drop the + * preallocation. We might use some delayed work for that but I feel + * it's overengineering for a filesystem like UDF. + */ + if (!S_ISREG(inode->i_mode)) + flags |= UDF_MAP_NOPREALLOC; + return __udf_get_block(inode, block, bh_result, flags); } -static struct buffer_head *udf_getblk(struct inode *inode, long block, - int create, int *err) +/* + * We shouldn't be allocating blocks on page writeback since we allocate them + * on page fault. We can spot dirty buffers without allocated blocks though + * when truncate expands file. These however don't have valid data so we can + * safely ignore them. So never allocate blocks from page writeback. + */ +static int udf_get_block_wb(struct inode *inode, sector_t block, + struct buffer_head *bh_result, int create) { - struct buffer_head *bh; - struct buffer_head dummy; - - dummy.b_state = 0; - dummy.b_blocknr = -1000; - *err = udf_get_block(inode, block, &dummy, create); - if (!*err && buffer_mapped(&dummy)) { - bh = sb_getblk(inode->i_sb, dummy.b_blocknr); - if (buffer_new(&dummy)) { - lock_buffer(bh); - memset(bh->b_data, 0x00, inode->i_sb->s_blocksize); - set_buffer_uptodate(bh); - unlock_buffer(bh); - mark_buffer_dirty_inode(bh, inode); - } - return bh; - } - - return NULL; + return __udf_get_block(inode, block, bh_result, 0); } -/* 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 = {}; - 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... */ - if (!blocks && fake) + if (!new_block_bytes && fake) return 0; iinfo = UDF_I(inode); @@ -512,81 +535,78 @@ static int udf_do_extend_file(struct inode *inode, ~(sb->s_blocksize - 1); } - /* Last extent are just preallocated blocks? */ - if ((last_ext->extLength & UDF_EXTENT_FLAG_MASK) == - EXT_NOT_RECORDED_ALLOCATED) { - /* Save the extent so that we can reattach it to the end */ - prealloc_loc = last_ext->extLocation; - prealloc_len = last_ext->extLength; - /* Mark the extent as a hole */ - last_ext->extLength = EXT_NOT_RECORDED_NOT_ALLOCATED | - (last_ext->extLength & UDF_EXTENT_LENGTH_MASK); - last_ext->extLocation.logicalBlockNum = 0; - last_ext->extLocation.partitionReferenceNum = 0; - } - + add = 0; /* 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) { - udf_add_aext(inode, last_pos, &last_ext->extLocation, - last_ext->extLength, 1); + err = udf_add_aext(inode, last_pos, &last_ext->extLocation, + last_ext->extLength, 1); + if (err < 0) + goto out_err; count++; - } else + } else { + struct kernel_lb_addr tmploc; + uint32_t tmplen; + int8_t tmptype; + udf_write_aext(inode, last_pos, &last_ext->extLocation, last_ext->extLength, 1); + /* + * We've rewritten the last extent. If we are going to add + * more extents, we may need to enter possible following + * empty indirect extent. + */ + if (new_block_bytes) { + err = udf_next_aext(inode, last_pos, &tmploc, &tmplen, + &tmptype, 0); + if (err < 0) + goto out_err; + } + } + iinfo->i_lenExtents += add; + /* 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; + goto out_err; + iinfo->i_lenExtents += add; 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) - return err; + goto out_err; + iinfo->i_lenExtents += new_block_bytes; count++; } out: - /* Do we have some preallocated blocks saved? */ - if (prealloc_len) { - 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++; - } - /* last_pos should point to the last written extent... */ if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT) last_pos->offset -= sizeof(struct short_ad); @@ -596,6 +616,33 @@ out: return -EIO; return count; +out_err: + /* Remove extents we've created so far */ + udf_clear_extent_cache(inode); + udf_truncate_extents(inode); + return err; +} + +/* 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 new_elen) +{ + uint32_t added_bytes; + + /* + * Extent already large enough? It may be already rounded up to block + * size... + */ + if (new_elen <= (last_ext->extLength & UDF_EXTENT_LENGTH_MASK)) + return; + added_bytes = new_elen - (last_ext->extLength & UDF_EXTENT_LENGTH_MASK); + 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) @@ -607,10 +654,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; + loff_t new_elen; int adsize; struct udf_inode_info *iinfo = UDF_I(inode); struct kernel_long_ad extent; - int err; + int err = 0; + bool within_last_ext; if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT) adsize = sizeof(struct short_ad); @@ -619,19 +668,21 @@ static int udf_extend_file(struct inode *inode, loff_t newsize) else BUG(); - etype = inode_bmap(inode, first_block, &epos, &eloc, &elen, &offset); + down_write(&iinfo->i_data_sem); + /* + * When creating hole in file, just don't bother with preserving + * preallocation. It likely won't be very useful anyway. + */ + udf_discard_prealloc(inode); - /* 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; + err = inode_bmap(inode, first_block, &epos, &eloc, &elen, &offset, &etype); + if (err < 0) + goto out; + within_last_ext = (err == 1); + /* We don't expect extents past EOF... */ + WARN_ON_ONCE(within_last_ext && + elen > ((loff_t)offset + 1) << inode->i_blkbits); - /* 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 @@ -641,22 +692,36 @@ static int udf_extend_file(struct inode *inode, loff_t newsize) extent.extLength = EXT_NOT_RECORDED_NOT_ALLOCATED; } else { epos.offset -= adsize; - etype = udf_next_aext(inode, &epos, &extent.extLocation, - &extent.extLength, 0); + err = udf_next_aext(inode, &epos, &extent.extLocation, + &extent.extLength, &etype, 0); + if (err <= 0) + goto out; extent.extLength |= etype << 30; } - err = udf_do_extend_file(inode, &epos, &extent, offset); + + new_elen = ((loff_t)offset << inode->i_blkbits) | + (newsize & (sb->s_blocksize - 1)); + + /* File has extent covering the new size (could happen when extending + * inside a block)? + */ + if (within_last_ext) { + /* Extending file within the last file block */ + udf_do_extend_final_block(inode, &epos, &extent, new_elen); + } else { + err = udf_do_extend_file(inode, &epos, &extent, new_elen); + } + if (err < 0) goto out; err = 0; - iinfo->i_lenExtents = newsize; out: brelse(epos.bh); + up_write(&iinfo->i_data_sem); return err; } -static sector_t inode_getblk(struct inode *inode, sector_t block, - int *err, int *new) +static int inode_getblk(struct inode *inode, struct udf_map_rq *map) { struct kernel_long_ad laarr[EXTENT_MERGE_SIZE]; struct extent_position prev_epos, cur_epos, next_epos; @@ -665,21 +730,20 @@ static sector_t inode_getblk(struct inode *inode, sector_t block, struct kernel_lb_addr eloc, tmpeloc; int c = 1; loff_t lbcount = 0, b_off = 0; - uint32_t newblocknum, newblock; + udf_pblk_t newblocknum; sector_t offset = 0; - int8_t etype; + int8_t etype, tmpetype; struct udf_inode_info *iinfo = UDF_I(inode); - int goal = 0, pgoal = iinfo->i_location.logicalBlockNum; + udf_pblk_t goal = 0, pgoal = iinfo->i_location.logicalBlockNum; int lastblock = 0; - bool isBeyondEOF; + bool isBeyondEOF = false; + int ret = 0; - *err = 0; - *new = 0; prev_epos.offset = udf_file_entry_alloc_offset(inode); prev_epos.block = iinfo->i_location; prev_epos.bh = NULL; cur_epos = next_epos = prev_epos; - b_off = (loff_t)block << inode->i_sb->s_blocksize_bits; + b_off = (loff_t)map->lblk << inode->i_sb->s_blocksize_bits; /* find the extent which contains the block we are looking for. alternate between laarr[0] and laarr[1] for locations of the @@ -704,9 +768,13 @@ static sector_t inode_getblk(struct inode *inode, sector_t block, prev_epos.offset = cur_epos.offset; cur_epos.offset = next_epos.offset; - etype = udf_next_aext(inode, &next_epos, &eloc, &elen, 1); - if (etype == -1) + ret = udf_next_aext(inode, &next_epos, &eloc, &elen, &etype, 1); + if (ret < 0) { + goto out_free; + } else if (ret == 0) { + isBeyondEOF = true; break; + } c = !c; @@ -727,30 +795,36 @@ static sector_t inode_getblk(struct inode *inode, sector_t block, * Move prev_epos and cur_epos into indirect extent if we are at * the pointer to it */ - udf_next_aext(inode, &prev_epos, &tmpeloc, &tmpelen, 0); - udf_next_aext(inode, &cur_epos, &tmpeloc, &tmpelen, 0); + ret = udf_next_aext(inode, &prev_epos, &tmpeloc, &tmpelen, &tmpetype, 0); + if (ret < 0) + goto out_free; + ret = udf_next_aext(inode, &cur_epos, &tmpeloc, &tmpelen, &tmpetype, 0); + if (ret < 0) + goto out_free; /* if the extent is allocated and recorded, return the block if the extent is not a multiple of the blocksize, round up */ - if (etype == (EXT_RECORDED_ALLOCATED >> 30)) { + if (!isBeyondEOF && etype == (EXT_RECORDED_ALLOCATED >> 30)) { if (elen & (inode->i_sb->s_blocksize - 1)) { elen = EXT_RECORDED_ALLOCATED | ((elen + inode->i_sb->s_blocksize - 1) & ~(inode->i_sb->s_blocksize - 1)); + iinfo->i_lenExtents = + ALIGN(iinfo->i_lenExtents, + inode->i_sb->s_blocksize); udf_write_aext(inode, &cur_epos, &eloc, elen, 1); } - brelse(prev_epos.bh); - brelse(cur_epos.bh); - brelse(next_epos.bh); - newblock = udf_get_lb_pblock(inode->i_sb, &eloc, offset); - return newblock; + map->oflags = UDF_BLK_MAPPED; + map->pblk = udf_get_lb_pblock(inode->i_sb, &eloc, offset); + ret = 0; + goto out_free; } - /* Are we beyond EOF? */ - if (etype == -1) { - int ret; - isBeyondEOF = 1; + /* Are we beyond EOF and preallocated extent? */ + if (isBeyondEOF) { + loff_t hole_len; + if (count) { if (c) laarr[0] = laarr[1]; @@ -765,34 +839,27 @@ 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); - if (ret < 0) { - brelse(prev_epos.bh); - brelse(cur_epos.bh); - brelse(next_epos.bh); - *err = ret; - return 0; - } + hole_len = (loff_t)offset << inode->i_blkbits; + ret = udf_do_extend_file(inode, &prev_epos, laarr, hole_len); + if (ret < 0) + goto out_free; c = 0; offset = 0; count += ret; - /* We are not covered by a preallocated extent? */ - if ((laarr[0].extLength & UDF_EXTENT_FLAG_MASK) != - EXT_NOT_RECORDED_ALLOCATED) { - /* Is there any real extent? - otherwise we overwrite - * the fake one... */ - if (count) - c = !c; - laarr[c].extLength = EXT_NOT_RECORDED_NOT_ALLOCATED | - inode->i_sb->s_blocksize; - memset(&laarr[c].extLocation, 0x00, - sizeof(struct kernel_lb_addr)); - count++; - } + /* + * Is there any real extent? - otherwise we overwrite the fake + * one... + */ + if (count) + c = !c; + laarr[c].extLength = EXT_NOT_RECORDED_NOT_ALLOCATED | + inode->i_sb->s_blocksize; + memset(&laarr[c].extLocation, 0x00, + sizeof(struct kernel_lb_addr)); + count++; endnum = c + 1; lastblock = 1; } else { - isBeyondEOF = 0; endnum = startnum = ((count > 2) ? 2 : count); /* if the current extent is in position 0, @@ -806,15 +873,17 @@ static sector_t inode_getblk(struct inode *inode, sector_t block, /* if the current block is located in an extent, read the next extent */ - etype = udf_next_aext(inode, &next_epos, &eloc, &elen, 0); - if (etype != -1) { + ret = udf_next_aext(inode, &next_epos, &eloc, &elen, &etype, 0); + if (ret > 0) { laarr[c + 1].extLength = (etype << 30) | elen; laarr[c + 1].extLocation = eloc; count++; startnum++; endnum++; - } else + } else if (ret == 0) lastblock = 1; + else + goto out_free; } /* if the current extent is not recorded but allocated, get the @@ -822,7 +891,7 @@ static sector_t inode_getblk(struct inode *inode, sector_t block, if ((laarr[c].extLength >> 30) == (EXT_NOT_RECORDED_ALLOCATED >> 30)) newblocknum = laarr[c].extLocation.logicalBlockNum + offset; else { /* otherwise, allocate a new block */ - if (iinfo->i_next_alloc_block == block) + if (iinfo->i_next_alloc_block == map->lblk) goal = iinfo->i_next_alloc_goal; if (!goal) { @@ -832,14 +901,9 @@ static sector_t inode_getblk(struct inode *inode, sector_t block, newblocknum = udf_new_block(inode->i_sb, inode, iinfo->i_location.partitionReferenceNum, - goal, err); - if (!newblocknum) { - brelse(prev_epos.bh); - brelse(cur_epos.bh); - brelse(next_epos.bh); - *err = -ENOSPC; - return 0; - } + goal, &ret); + if (!newblocknum) + goto out_free; if (isBeyondEOF) iinfo->i_lenExtents += inode->i_sb->s_blocksize; } @@ -850,14 +914,8 @@ static sector_t inode_getblk(struct inode *inode, sector_t block, * block */ udf_split_extents(inode, &c, offset, newblocknum, laarr, &endnum); -#ifdef UDF_PREALLOCATE - /* We preallocate blocks only for regular files. It also makes sense - * for directories but there's a problem when to drop the - * preallocation. We might use some delayed work for that but I feel - * it's overengineering for a filesystem like UDF. */ - if (S_ISREG(inode->i_mode)) + if (!(map->iflags & UDF_MAP_NOPREALLOC)) udf_prealloc_extents(inode, c, lastblock, laarr, &endnum); -#endif /* merge any continuous blocks in laarr */ udf_merge_extents(inode, laarr, &endnum); @@ -865,35 +923,36 @@ static sector_t inode_getblk(struct inode *inode, sector_t block, /* write back the new extents, inserting new extents if the new number * of extents is greater than the old number, and deleting extents if * the new number of extents is less than the old number */ - udf_update_extents(inode, laarr, startnum, endnum, &prev_epos); - - brelse(prev_epos.bh); - brelse(cur_epos.bh); - brelse(next_epos.bh); + ret = udf_update_extents(inode, laarr, startnum, endnum, &prev_epos); + if (ret < 0) + goto out_free; - newblock = udf_get_pblock(inode->i_sb, newblocknum, + map->pblk = udf_get_pblock(inode->i_sb, newblocknum, iinfo->i_location.partitionReferenceNum, 0); - if (!newblock) { - *err = -EIO; - return 0; + if (!map->pblk) { + ret = -EFSCORRUPTED; + goto out_free; } - *new = 1; - iinfo->i_next_alloc_block = block; - iinfo->i_next_alloc_goal = newblocknum; - inode->i_ctime = current_fs_time(inode->i_sb); + map->oflags = UDF_BLK_NEW | UDF_BLK_MAPPED; + iinfo->i_next_alloc_block = map->lblk + 1; + iinfo->i_next_alloc_goal = newblocknum + 1; + inode_set_ctime_current(inode); if (IS_SYNC(inode)) udf_sync_inode(inode); else mark_inode_dirty(inode); - - return newblock; + ret = 0; +out_free: + brelse(prev_epos.bh); + brelse(cur_epos.bh); + brelse(next_epos.bh); + return ret; } static void udf_split_extents(struct inode *inode, int *c, int offset, - int newblocknum, - struct kernel_long_ad laarr[EXTENT_MERGE_SIZE], - int *endnum) + udf_pblk_t newblocknum, + struct kernel_long_ad *laarr, int *endnum) { unsigned long blocksize = inode->i_sb->s_blocksize; unsigned char blocksize_bits = inode->i_sb->s_blocksize_bits; @@ -956,7 +1015,7 @@ static void udf_split_extents(struct inode *inode, int *c, int offset, } static void udf_prealloc_extents(struct inode *inode, int c, int lastblock, - struct kernel_long_ad laarr[EXTENT_MERGE_SIZE], + struct kernel_long_ad *laarr, int *endnum) { int start, length = 0, currlength = 0, i; @@ -1051,8 +1110,7 @@ static void udf_prealloc_extents(struct inode *inode, int c, int lastblock, } } -static void udf_merge_extents(struct inode *inode, - struct kernel_long_ad laarr[EXTENT_MERGE_SIZE], +static void udf_merge_extents(struct inode *inode, struct kernel_long_ad *laarr, int *endnum) { int i; @@ -1072,23 +1130,8 @@ static void udf_merge_extents(struct inode *inode, blocksize - 1) >> blocksize_bits)))) { if (((li->extLength & UDF_EXTENT_LENGTH_MASK) + - (lip1->extLength & UDF_EXTENT_LENGTH_MASK) + - blocksize - 1) & ~UDF_EXTENT_LENGTH_MASK) { - lip1->extLength = (lip1->extLength - - (li->extLength & - UDF_EXTENT_LENGTH_MASK) + - UDF_EXTENT_LENGTH_MASK) & - ~(blocksize - 1); - li->extLength = (li->extLength & - UDF_EXTENT_FLAG_MASK) + - (UDF_EXTENT_LENGTH_MASK + 1) - - blocksize; - lip1->extLocation.logicalBlockNum = - li->extLocation.logicalBlockNum + - ((li->extLength & - UDF_EXTENT_LENGTH_MASK) >> - blocksize_bits); - } else { + (lip1->extLength & UDF_EXTENT_LENGTH_MASK) + + blocksize - 1) <= UDF_EXTENT_LENGTH_MASK) { li->extLength = lip1->extLength + (((li->extLength & UDF_EXTENT_LENGTH_MASK) + @@ -1151,52 +1194,78 @@ static void udf_merge_extents(struct inode *inode, } } -static void udf_update_extents(struct inode *inode, - struct kernel_long_ad laarr[EXTENT_MERGE_SIZE], - int startnum, int endnum, - struct extent_position *epos) +static int udf_update_extents(struct inode *inode, struct kernel_long_ad *laarr, + int startnum, int endnum, + struct extent_position *epos) { int start = 0, i; struct kernel_lb_addr tmploc; uint32_t tmplen; + int8_t tmpetype; + int err; if (startnum > endnum) { for (i = 0; i < (startnum - endnum); i++) - udf_delete_aext(inode, *epos, laarr[i].extLocation, - laarr[i].extLength); + udf_delete_aext(inode, *epos); } else if (startnum < endnum) { for (i = 0; i < (endnum - startnum); i++) { - udf_insert_aext(inode, *epos, laarr[i].extLocation, - laarr[i].extLength); - udf_next_aext(inode, epos, &laarr[i].extLocation, - &laarr[i].extLength, 1); + err = udf_insert_aext(inode, *epos, + laarr[i].extLocation, + laarr[i].extLength); + /* + * If we fail here, we are likely corrupting the extent + * list and leaking blocks. At least stop early to + * limit the damage. + */ + if (err < 0) + return err; + err = udf_next_aext(inode, epos, &laarr[i].extLocation, + &laarr[i].extLength, &tmpetype, 1); + if (err < 0) + return err; start++; } } for (i = start; i < endnum; i++) { - udf_next_aext(inode, epos, &tmploc, &tmplen, 0); + err = udf_next_aext(inode, epos, &tmploc, &tmplen, &tmpetype, 0); + if (err < 0) + return err; + udf_write_aext(inode, epos, &laarr[i].extLocation, laarr[i].extLength, 1); } + return 0; } -struct buffer_head *udf_bread(struct inode *inode, int block, +struct buffer_head *udf_bread(struct inode *inode, udf_pblk_t block, int create, int *err) { struct buffer_head *bh = NULL; + struct udf_map_rq map = { + .lblk = block, + .iflags = UDF_MAP_NOPREALLOC | (create ? UDF_MAP_CREATE : 0), + }; - bh = udf_getblk(inode, block, create, err); - if (!bh) + *err = udf_map_block(inode, &map); + if (*err || !(map.oflags & UDF_BLK_MAPPED)) return NULL; - if (buffer_uptodate(bh)) + bh = sb_getblk(inode->i_sb, map.pblk); + if (!bh) { + *err = -ENOMEM; + return NULL; + } + if (map.oflags & UDF_BLK_NEW) { + lock_buffer(bh); + memset(bh->b_data, 0x00, inode->i_sb->s_blocksize); + set_buffer_uptodate(bh); + unlock_buffer(bh); + mark_buffer_dirty_inode(bh, inode); return bh; + } - ll_rw_block(READ, 1, &bh); - - wait_on_buffer(bh); - if (buffer_uptodate(bh)) + if (bh_read(bh, 0) >= 0) return bh; brelse(bh); @@ -1206,44 +1275,38 @@ struct buffer_head *udf_bread(struct inode *inode, int block, int udf_setsize(struct inode *inode, loff_t newsize) { - int err; + int err = 0; struct udf_inode_info *iinfo; - int bsize = 1 << inode->i_blkbits; + unsigned int bsize = i_blocksize(inode); if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))) return -EINVAL; - if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) - return -EPERM; iinfo = UDF_I(inode); if (newsize > inode->i_size) { - down_write(&iinfo->i_data_sem); if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { - if (bsize < + if (bsize >= (udf_file_entry_alloc_offset(inode) + newsize)) { - err = udf_expand_file_adinicb(inode); - if (err) - return err; down_write(&iinfo->i_data_sem); - } else { iinfo->i_lenAlloc = newsize; + up_write(&iinfo->i_data_sem); goto set_size; } + err = udf_expand_file_adinicb(inode); + if (err) + return err; } err = udf_extend_file(inode, newsize); - if (err) { - up_write(&iinfo->i_data_sem); + if (err) return err; - } set_size: truncate_setsize(inode, newsize); - up_write(&iinfo->i_data_sem); } else { if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { down_write(&iinfo->i_data_sem); udf_clear_extent_cache(inode); - memset(iinfo->i_ext.i_data + iinfo->i_lenEAttr + newsize, + memset(iinfo->i_data + iinfo->i_lenEAttr + newsize, 0x00, bsize - newsize - udf_file_entry_alloc_offset(inode)); iinfo->i_lenAlloc = newsize; @@ -1255,27 +1318,59 @@ set_size: udf_get_block); if (err) return err; + truncate_setsize(inode, newsize); down_write(&iinfo->i_data_sem); udf_clear_extent_cache(inode); - truncate_setsize(inode, newsize); - udf_truncate_extents(inode); + err = udf_truncate_extents(inode); up_write(&iinfo->i_data_sem); + if (err) + return err; } update_time: - inode->i_mtime = inode->i_ctime = current_fs_time(inode->i_sb); + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); if (IS_SYNC(inode)) udf_sync_inode(inode); else mark_inode_dirty(inode); - return 0; + return err; } -static void __udf_read_inode(struct inode *inode) +/* + * Maximum length of linked list formed by ICB hierarchy. The chosen number is + * arbitrary - just that we hopefully don't limit any real use of rewritten + * inode on write-once media but avoid looping for too long on corrupted media. + */ +#define UDF_MAX_ICB_NESTING 1024 + +static int udf_read_inode(struct inode *inode, bool hidden_inode) { struct buffer_head *bh = NULL; struct fileEntry *fe; + struct extendedFileEntry *efe; uint16_t ident; struct udf_inode_info *iinfo = UDF_I(inode); + struct udf_sb_info *sbi = UDF_SB(inode->i_sb); + struct kernel_lb_addr *iloc = &iinfo->i_location; + unsigned int link_count; + unsigned int indirections = 0; + int bs = inode->i_sb->s_blocksize; + int ret = -EIO; + uint32_t uid, gid; + struct timespec64 ts; + +reread: + if (iloc->partitionReferenceNum >= sbi->s_partitions) { + udf_debug("partition reference: %u > logical volume partitions: %u\n", + iloc->partitionReferenceNum, sbi->s_partitions); + return -EIO; + } + + if (iloc->logicalBlockNum >= + sbi->s_partmaps[iloc->partitionReferenceNum].s_partition_len) { + udf_debug("block=%u, partition=%u out of range\n", + iloc->logicalBlockNum, iloc->partitionReferenceNum); + return -EIO; + } /* * Set defaults, but the inode is still incomplete! @@ -1289,78 +1384,54 @@ static void __udf_read_inode(struct inode *inode) * i_nlink = 1 * i_op = NULL; */ - bh = udf_read_ptagged(inode->i_sb, &iinfo->i_location, 0, &ident); + bh = udf_read_ptagged(inode->i_sb, iloc, 0, &ident); if (!bh) { - udf_err(inode->i_sb, "(ino %ld) failed !bh\n", inode->i_ino); - make_bad_inode(inode); - return; + udf_err(inode->i_sb, "(ino %lu) failed !bh\n", inode->i_ino); + return -EIO; } if (ident != TAG_IDENT_FE && ident != TAG_IDENT_EFE && ident != TAG_IDENT_USE) { - udf_err(inode->i_sb, "(ino %ld) failed ident=%d\n", + udf_err(inode->i_sb, "(ino %lu) failed ident=%u\n", inode->i_ino, ident); - brelse(bh); - make_bad_inode(inode); - return; + goto out; } fe = (struct fileEntry *)bh->b_data; + efe = (struct extendedFileEntry *)bh->b_data; if (fe->icbTag.strategyType == cpu_to_le16(4096)) { struct buffer_head *ibh; - ibh = udf_read_ptagged(inode->i_sb, &iinfo->i_location, 1, - &ident); + ibh = udf_read_ptagged(inode->i_sb, iloc, 1, &ident); if (ident == TAG_IDENT_IE && ibh) { - struct buffer_head *nbh = NULL; struct kernel_lb_addr loc; struct indirectEntry *ie; ie = (struct indirectEntry *)ibh->b_data; loc = lelb_to_cpu(ie->indirectICB.extLocation); - if (ie->indirectICB.extLength && - (nbh = udf_read_ptagged(inode->i_sb, &loc, 0, - &ident))) { - if (ident == TAG_IDENT_FE || - ident == TAG_IDENT_EFE) { - memcpy(&iinfo->i_location, - &loc, - sizeof(struct kernel_lb_addr)); - brelse(bh); - brelse(ibh); - brelse(nbh); - __udf_read_inode(inode); - return; + if (ie->indirectICB.extLength) { + brelse(ibh); + memcpy(&iinfo->i_location, &loc, + sizeof(struct kernel_lb_addr)); + if (++indirections > UDF_MAX_ICB_NESTING) { + udf_err(inode->i_sb, + "too many ICBs in ICB hierarchy" + " (max %d supported)\n", + UDF_MAX_ICB_NESTING); + goto out; } - brelse(nbh); + brelse(bh); + goto reread; } } brelse(ibh); } else if (fe->icbTag.strategyType != cpu_to_le16(4)) { - udf_err(inode->i_sb, "unsupported strategy type: %d\n", + udf_err(inode->i_sb, "unsupported strategy type: %u\n", le16_to_cpu(fe->icbTag.strategyType)); - brelse(bh); - make_bad_inode(inode); - return; + goto out; } - udf_fill_inode(inode, bh); - - brelse(bh); -} - -static void udf_fill_inode(struct inode *inode, struct buffer_head *bh) -{ - struct fileEntry *fe; - struct extendedFileEntry *efe; - struct udf_sb_info *sbi = UDF_SB(inode->i_sb); - struct udf_inode_info *iinfo = UDF_I(inode); - unsigned int link_count; - - fe = (struct fileEntry *)bh->b_data; - efe = (struct extendedFileEntry *)bh->b_data; - if (fe->icbTag.strategyType == cpu_to_le16(4)) iinfo->i_strat4096 = 0; else /* if (fe->icbTag.strategyType == cpu_to_le16(4096)) */ @@ -1368,6 +1439,13 @@ static void udf_fill_inode(struct inode *inode, struct buffer_head *bh) iinfo->i_alloc_type = le16_to_cpu(fe->icbTag.flags) & ICBTAG_FLAG_AD_MASK; + if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_SHORT && + iinfo->i_alloc_type != ICBTAG_FLAG_AD_LONG && + iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) { + ret = -EIO; + goto out; + } + iinfo->i_hidden = hidden_inode; iinfo->i_unique = 0; iinfo->i_lenEAttr = 0; iinfo->i_lenExtents = 0; @@ -1377,56 +1455,53 @@ static void udf_fill_inode(struct inode *inode, struct buffer_head *bh) if (fe->descTag.tagIdent == cpu_to_le16(TAG_IDENT_EFE)) { iinfo->i_efe = 1; iinfo->i_use = 0; - if (udf_alloc_i_data(inode, inode->i_sb->s_blocksize - - sizeof(struct extendedFileEntry))) { - make_bad_inode(inode); - return; - } - memcpy(iinfo->i_ext.i_data, - bh->b_data + sizeof(struct extendedFileEntry), - inode->i_sb->s_blocksize - + ret = udf_alloc_i_data(inode, bs - sizeof(struct extendedFileEntry)); + if (ret) + goto out; + memcpy(iinfo->i_data, + bh->b_data + sizeof(struct extendedFileEntry), + bs - sizeof(struct extendedFileEntry)); } else if (fe->descTag.tagIdent == cpu_to_le16(TAG_IDENT_FE)) { iinfo->i_efe = 0; iinfo->i_use = 0; - if (udf_alloc_i_data(inode, inode->i_sb->s_blocksize - - sizeof(struct fileEntry))) { - make_bad_inode(inode); - return; - } - memcpy(iinfo->i_ext.i_data, + ret = udf_alloc_i_data(inode, bs - sizeof(struct fileEntry)); + if (ret) + goto out; + memcpy(iinfo->i_data, bh->b_data + sizeof(struct fileEntry), - inode->i_sb->s_blocksize - sizeof(struct fileEntry)); + bs - sizeof(struct fileEntry)); } else if (fe->descTag.tagIdent == cpu_to_le16(TAG_IDENT_USE)) { iinfo->i_efe = 0; iinfo->i_use = 1; iinfo->i_lenAlloc = le32_to_cpu( ((struct unallocSpaceEntry *)bh->b_data)-> lengthAllocDescs); - if (udf_alloc_i_data(inode, inode->i_sb->s_blocksize - - sizeof(struct unallocSpaceEntry))) { - make_bad_inode(inode); - return; - } - memcpy(iinfo->i_ext.i_data, - bh->b_data + sizeof(struct unallocSpaceEntry), - inode->i_sb->s_blocksize - + ret = udf_alloc_i_data(inode, bs - sizeof(struct unallocSpaceEntry)); - return; + if (ret) + goto out; + memcpy(iinfo->i_data, + bh->b_data + sizeof(struct unallocSpaceEntry), + bs - sizeof(struct unallocSpaceEntry)); + return 0; } + ret = -EIO; read_lock(&sbi->s_cred_lock); - i_uid_write(inode, le32_to_cpu(fe->uid)); - if (!uid_valid(inode->i_uid) || - UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_UID_IGNORE) || + uid = le32_to_cpu(fe->uid); + if (uid == UDF_INVALID_ID || UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_UID_SET)) - inode->i_uid = UDF_SB(inode->i_sb)->s_uid; + inode->i_uid = sbi->s_uid; + else + i_uid_write(inode, uid); - i_gid_write(inode, le32_to_cpu(fe->gid)); - if (!gid_valid(inode->i_gid) || - UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_GID_IGNORE) || + gid = le32_to_cpu(fe->gid); + if (gid == UDF_INVALID_ID || UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_GID_SET)) - inode->i_gid = UDF_SB(inode->i_sb)->s_gid; + inode->i_gid = sbi->s_gid; + else + i_gid_write(inode, gid); if (fe->icbTag.fileType != ICBTAG_FILE_TYPE_DIRECTORY && sbi->s_fmode != UDF_INVALID_MODE) @@ -1437,11 +1512,18 @@ static void udf_fill_inode(struct inode *inode, struct buffer_head *bh) else inode->i_mode = udf_convert_permissions(fe); inode->i_mode &= ~sbi->s_umask; + iinfo->i_extraPerms = le32_to_cpu(fe->permissions) & ~FE_MAPPED_PERMS; + read_unlock(&sbi->s_cred_lock); link_count = le16_to_cpu(fe->fileLinkCount); - if (!link_count) + if (!link_count) { + if (!hidden_inode) { + ret = -ESTALE; + goto out; + } link_count = 1; + } set_nlink(inode, link_count); inode->i_size = le64_to_cpu(fe->informationLength); @@ -1451,41 +1533,68 @@ static void udf_fill_inode(struct inode *inode, struct buffer_head *bh) inode->i_blocks = le64_to_cpu(fe->logicalBlocksRecorded) << (inode->i_sb->s_blocksize_bits - 9); - if (!udf_disk_stamp_to_time(&inode->i_atime, fe->accessTime)) - inode->i_atime = sbi->s_record_time; - - if (!udf_disk_stamp_to_time(&inode->i_mtime, - fe->modificationTime)) - inode->i_mtime = sbi->s_record_time; - - if (!udf_disk_stamp_to_time(&inode->i_ctime, fe->attrTime)) - inode->i_ctime = sbi->s_record_time; + udf_disk_stamp_to_time(&ts, fe->accessTime); + inode_set_atime_to_ts(inode, ts); + udf_disk_stamp_to_time(&ts, fe->modificationTime); + inode_set_mtime_to_ts(inode, ts); + udf_disk_stamp_to_time(&ts, fe->attrTime); + inode_set_ctime_to_ts(inode, ts); iinfo->i_unique = le64_to_cpu(fe->uniqueID); iinfo->i_lenEAttr = le32_to_cpu(fe->lengthExtendedAttr); iinfo->i_lenAlloc = le32_to_cpu(fe->lengthAllocDescs); iinfo->i_checkpoint = le32_to_cpu(fe->checkpoint); + iinfo->i_streamdir = 0; + iinfo->i_lenStreams = 0; } else { inode->i_blocks = le64_to_cpu(efe->logicalBlocksRecorded) << (inode->i_sb->s_blocksize_bits - 9); - if (!udf_disk_stamp_to_time(&inode->i_atime, efe->accessTime)) - inode->i_atime = sbi->s_record_time; - - if (!udf_disk_stamp_to_time(&inode->i_mtime, - efe->modificationTime)) - inode->i_mtime = sbi->s_record_time; - - if (!udf_disk_stamp_to_time(&iinfo->i_crtime, efe->createTime)) - iinfo->i_crtime = sbi->s_record_time; - - if (!udf_disk_stamp_to_time(&inode->i_ctime, efe->attrTime)) - inode->i_ctime = sbi->s_record_time; + udf_disk_stamp_to_time(&ts, efe->accessTime); + inode_set_atime_to_ts(inode, ts); + udf_disk_stamp_to_time(&ts, efe->modificationTime); + inode_set_mtime_to_ts(inode, ts); + udf_disk_stamp_to_time(&ts, efe->attrTime); + inode_set_ctime_to_ts(inode, ts); + udf_disk_stamp_to_time(&iinfo->i_crtime, efe->createTime); iinfo->i_unique = le64_to_cpu(efe->uniqueID); iinfo->i_lenEAttr = le32_to_cpu(efe->lengthExtendedAttr); iinfo->i_lenAlloc = le32_to_cpu(efe->lengthAllocDescs); iinfo->i_checkpoint = le32_to_cpu(efe->checkpoint); + + /* Named streams */ + iinfo->i_streamdir = (efe->streamDirectoryICB.extLength != 0); + iinfo->i_locStreamdir = + lelb_to_cpu(efe->streamDirectoryICB.extLocation); + iinfo->i_lenStreams = le64_to_cpu(efe->objectSize); + if (iinfo->i_lenStreams >= inode->i_size) + iinfo->i_lenStreams -= inode->i_size; + else + iinfo->i_lenStreams = 0; + } + inode->i_generation = iinfo->i_unique; + + /* + * Sanity check length of allocation descriptors and extended attrs to + * avoid integer overflows + */ + if (iinfo->i_lenEAttr > bs || iinfo->i_lenAlloc > bs) + goto out; + /* Now do exact checks */ + if (udf_file_entry_alloc_offset(inode) + iinfo->i_lenAlloc > bs) + goto out; + /* Sanity checks for files in ICB so that we don't get confused later */ + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { + /* + * For file in ICB data is stored in allocation descriptor + * so sizes should match + */ + if (iinfo->i_lenAlloc != inode->i_size) + goto out; + /* File in ICB has to fit in there... */ + if (inode->i_size > bs - udf_file_entry_alloc_offset(inode)) + goto out; } switch (fe->icbTag.fileType) { @@ -1499,10 +1608,7 @@ static void udf_fill_inode(struct inode *inode, struct buffer_head *bh) case ICBTAG_FILE_TYPE_REGULAR: case ICBTAG_FILE_TYPE_UNDEF: case ICBTAG_FILE_TYPE_VAT20: - if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) - inode->i_data.a_ops = &udf_adinicb_aops; - else - inode->i_data.a_ops = &udf_aops; + inode->i_data.a_ops = &udf_aops; inode->i_op = &udf_file_inode_operations; inode->i_fop = &udf_file_operations; inode->i_mode |= S_IFREG; @@ -1522,7 +1628,8 @@ static void udf_fill_inode(struct inode *inode, struct buffer_head *bh) case ICBTAG_FILE_TYPE_SYMLINK: inode->i_data.a_ops = &udf_symlink_aops; inode->i_op = &udf_symlink_inode_operations; - inode->i_mode = S_IFLNK | S_IRWXUGO; + inode_nohighmem(inode); + inode->i_mode = S_IFLNK | 0777; break; case ICBTAG_FILE_TYPE_MAIN: udf_debug("METADATA FILE-----\n"); @@ -1534,10 +1641,9 @@ static void udf_fill_inode(struct inode *inode, struct buffer_head *bh) udf_debug("METADATA BITMAP FILE-----\n"); break; default: - udf_err(inode->i_sb, "(ino %ld) failed unknown file type=%d\n", + udf_err(inode->i_sb, "(ino %lu) failed unknown file type=%u\n", inode->i_ino, fe->icbTag.fileType); - make_bad_inode(inode); - return; + goto out; } if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) { struct deviceSpec *dsea = @@ -1548,21 +1654,20 @@ static void udf_fill_inode(struct inode *inode, struct buffer_head *bh) le32_to_cpu(dsea->minorDeviceIdent))); /* Developer ID ??? */ } else - make_bad_inode(inode); + goto out; } + ret = 0; +out: + brelse(bh); + return ret; } static int udf_alloc_i_data(struct inode *inode, size_t size) { struct udf_inode_info *iinfo = UDF_I(inode); - iinfo->i_ext.i_data = kmalloc(size, GFP_KERNEL); - - if (!iinfo->i_ext.i_data) { - udf_err(inode->i_sb, "(ino %ld) no free memory\n", - inode->i_ino); + iinfo->i_data = kmalloc(size, GFP_KERNEL); + if (!iinfo->i_data) return -ENOMEM; - } - return 0; } @@ -1575,9 +1680,9 @@ static umode_t udf_convert_permissions(struct fileEntry *fe) permissions = le32_to_cpu(fe->permissions); flags = le16_to_cpu(fe->icbTag.flags); - mode = ((permissions) & S_IRWXO) | - ((permissions >> 2) & S_IRWXG) | - ((permissions >> 4) & S_IRWXU) | + mode = ((permissions) & 0007) | + ((permissions >> 2) & 0070) | + ((permissions >> 4) & 0700) | ((flags & ICBTAG_FLAG_SETUID) ? S_ISUID : 0) | ((flags & ICBTAG_FLAG_SETGID) ? S_ISGID : 0) | ((flags & ICBTAG_FLAG_STICKY) ? S_ISVTX : 0); @@ -1585,6 +1690,23 @@ static umode_t udf_convert_permissions(struct fileEntry *fe) return mode; } +void udf_update_extra_perms(struct inode *inode, umode_t mode) +{ + struct udf_inode_info *iinfo = UDF_I(inode); + + /* + * UDF 2.01 sec. 3.3.3.3 Note 2: + * In Unix, delete permission tracks write + */ + iinfo->i_extraPerms &= ~FE_DELETE_PERMS; + if (mode & 0200) + iinfo->i_extraPerms |= FE_PERM_U_DELETE; + if (mode & 0020) + iinfo->i_extraPerms |= FE_PERM_G_DELETE; + if (mode & 0002) + iinfo->i_extraPerms |= FE_PERM_O_DELETE; +} + int udf_write_inode(struct inode *inode, struct writeback_control *wbc) { return udf_update_inode(inode, wbc->sync_mode == WB_SYNC_ALL); @@ -1595,6 +1717,14 @@ static int udf_sync_inode(struct inode *inode) return udf_update_inode(inode, 1); } +static void udf_adjust_time(struct udf_inode_info *iinfo, struct timespec64 time) +{ + if (iinfo->i_crtime.tv_sec > time.tv_sec || + (iinfo->i_crtime.tv_sec == time.tv_sec && + iinfo->i_crtime.tv_nsec > time.tv_nsec)) + iinfo->i_crtime = time; +} + static int udf_update_inode(struct inode *inode, int do_sync) { struct buffer_head *bh = NULL; @@ -1609,11 +1739,11 @@ static int udf_update_inode(struct inode *inode, int do_sync) unsigned char blocksize_bits = inode->i_sb->s_blocksize_bits; struct udf_inode_info *iinfo = UDF_I(inode); - bh = udf_tgetblk(inode->i_sb, + bh = sb_getblk(inode->i_sb, udf_get_lb_pblock(inode->i_sb, &iinfo->i_location, 0)); if (!bh) { udf_debug("getblk failure\n"); - return -ENOMEM; + return -EIO; } lock_buffer(bh); @@ -1627,46 +1757,39 @@ static int udf_update_inode(struct inode *inode, int do_sync) use->lengthAllocDescs = cpu_to_le32(iinfo->i_lenAlloc); memcpy(bh->b_data + sizeof(struct unallocSpaceEntry), - iinfo->i_ext.i_data, inode->i_sb->s_blocksize - + iinfo->i_data, inode->i_sb->s_blocksize - sizeof(struct unallocSpaceEntry)); use->descTag.tagIdent = cpu_to_le16(TAG_IDENT_USE); - use->descTag.tagLocation = - cpu_to_le32(iinfo->i_location.logicalBlockNum); - crclen = sizeof(struct unallocSpaceEntry) + - iinfo->i_lenAlloc - sizeof(struct tag); - use->descTag.descCRCLength = cpu_to_le16(crclen); - use->descTag.descCRC = cpu_to_le16(crc_itu_t(0, (char *)use + - sizeof(struct tag), - crclen)); - use->descTag.tagChecksum = udf_tag_checksum(&use->descTag); + crclen = sizeof(struct unallocSpaceEntry); - goto out; + goto finish; } if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_UID_FORGET)) - fe->uid = cpu_to_le32(-1); + fe->uid = cpu_to_le32(UDF_INVALID_ID); else fe->uid = cpu_to_le32(i_uid_read(inode)); if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_GID_FORGET)) - fe->gid = cpu_to_le32(-1); + fe->gid = cpu_to_le32(UDF_INVALID_ID); else fe->gid = cpu_to_le32(i_gid_read(inode)); - udfperms = ((inode->i_mode & S_IRWXO)) | - ((inode->i_mode & S_IRWXG) << 2) | - ((inode->i_mode & S_IRWXU) << 4); + udfperms = ((inode->i_mode & 0007)) | + ((inode->i_mode & 0070) << 2) | + ((inode->i_mode & 0700) << 4); - udfperms |= (le32_to_cpu(fe->permissions) & - (FE_PERM_O_DELETE | FE_PERM_O_CHATTR | - FE_PERM_G_DELETE | FE_PERM_G_CHATTR | - FE_PERM_U_DELETE | FE_PERM_U_CHATTR)); + udfperms |= iinfo->i_extraPerms; fe->permissions = cpu_to_le32(udfperms); - if (S_ISDIR(inode->i_mode)) + if (S_ISDIR(inode->i_mode) && inode->i_nlink > 0) fe->fileLinkCount = cpu_to_le16(inode->i_nlink - 1); - else - fe->fileLinkCount = cpu_to_le16(inode->i_nlink); + else { + if (iinfo->i_hidden) + fe->fileLinkCount = cpu_to_le16(0); + else + fe->fileLinkCount = cpu_to_le16(inode->i_nlink); + } fe->informationLength = cpu_to_le64(inode->i_size); @@ -1687,7 +1810,7 @@ static int udf_update_inode(struct inode *inode, int do_sync) dsea->impUseLength = cpu_to_le32(sizeof(struct regid)); } eid = (struct regid *)dsea->impUse; - memset(eid, 0, sizeof(struct regid)); + memset(eid, 0, sizeof(*eid)); strcpy(eid->ident, UDF_ID_DEVELOPER); eid->identSuffix[0] = UDF_OS_CLASS_UNIX; eid->identSuffix[1] = UDF_OS_ID_LINUX; @@ -1704,13 +1827,13 @@ static int udf_update_inode(struct inode *inode, int do_sync) if (iinfo->i_efe == 0) { memcpy(bh->b_data + sizeof(struct fileEntry), - iinfo->i_ext.i_data, + iinfo->i_data, inode->i_sb->s_blocksize - sizeof(struct fileEntry)); fe->logicalBlocksRecorded = cpu_to_le64(lb_recorded); - udf_time_to_disk_stamp(&fe->accessTime, inode->i_atime); - udf_time_to_disk_stamp(&fe->modificationTime, inode->i_mtime); - udf_time_to_disk_stamp(&fe->attrTime, inode->i_ctime); + udf_time_to_disk_stamp(&fe->accessTime, inode_get_atime(inode)); + udf_time_to_disk_stamp(&fe->modificationTime, inode_get_mtime(inode)); + udf_time_to_disk_stamp(&fe->attrTime, inode_get_ctime(inode)); memset(&(fe->impIdent), 0, sizeof(struct regid)); strcpy(fe->impIdent.ident, UDF_ID_DEVELOPER); fe->impIdent.identSuffix[0] = UDF_OS_CLASS_UNIX; @@ -1723,33 +1846,34 @@ static int udf_update_inode(struct inode *inode, int do_sync) crclen = sizeof(struct fileEntry); } else { memcpy(bh->b_data + sizeof(struct extendedFileEntry), - iinfo->i_ext.i_data, + iinfo->i_data, inode->i_sb->s_blocksize - sizeof(struct extendedFileEntry)); - efe->objectSize = cpu_to_le64(inode->i_size); + efe->objectSize = + cpu_to_le64(inode->i_size + iinfo->i_lenStreams); efe->logicalBlocksRecorded = cpu_to_le64(lb_recorded); - if (iinfo->i_crtime.tv_sec > inode->i_atime.tv_sec || - (iinfo->i_crtime.tv_sec == inode->i_atime.tv_sec && - iinfo->i_crtime.tv_nsec > inode->i_atime.tv_nsec)) - iinfo->i_crtime = inode->i_atime; + if (iinfo->i_streamdir) { + struct long_ad *icb_lad = &efe->streamDirectoryICB; - if (iinfo->i_crtime.tv_sec > inode->i_mtime.tv_sec || - (iinfo->i_crtime.tv_sec == inode->i_mtime.tv_sec && - iinfo->i_crtime.tv_nsec > inode->i_mtime.tv_nsec)) - iinfo->i_crtime = inode->i_mtime; + icb_lad->extLocation = + cpu_to_lelb(iinfo->i_locStreamdir); + icb_lad->extLength = + cpu_to_le32(inode->i_sb->s_blocksize); + } - if (iinfo->i_crtime.tv_sec > inode->i_ctime.tv_sec || - (iinfo->i_crtime.tv_sec == inode->i_ctime.tv_sec && - iinfo->i_crtime.tv_nsec > inode->i_ctime.tv_nsec)) - iinfo->i_crtime = inode->i_ctime; + udf_adjust_time(iinfo, inode_get_atime(inode)); + udf_adjust_time(iinfo, inode_get_mtime(inode)); + udf_adjust_time(iinfo, inode_get_ctime(inode)); - udf_time_to_disk_stamp(&efe->accessTime, inode->i_atime); - udf_time_to_disk_stamp(&efe->modificationTime, inode->i_mtime); + udf_time_to_disk_stamp(&efe->accessTime, + inode_get_atime(inode)); + udf_time_to_disk_stamp(&efe->modificationTime, + inode_get_mtime(inode)); udf_time_to_disk_stamp(&efe->createTime, iinfo->i_crtime); - udf_time_to_disk_stamp(&efe->attrTime, inode->i_ctime); + udf_time_to_disk_stamp(&efe->attrTime, inode_get_ctime(inode)); - memset(&(efe->impIdent), 0, sizeof(struct regid)); + memset(&(efe->impIdent), 0, sizeof(efe->impIdent)); strcpy(efe->impIdent.ident, UDF_ID_DEVELOPER); efe->impIdent.identSuffix[0] = UDF_OS_CLASS_UNIX; efe->impIdent.identSuffix[1] = UDF_OS_ID_LINUX; @@ -1760,6 +1884,8 @@ static int udf_update_inode(struct inode *inode, int do_sync) efe->descTag.tagIdent = cpu_to_le16(TAG_IDENT_EFE); crclen = sizeof(struct extendedFileEntry); } + +finish: if (iinfo->i_strat4096) { fe->icbTag.strategyType = cpu_to_le16(4096); fe->icbTag.strategyParameter = cpu_to_le16(1); @@ -1769,7 +1895,9 @@ static int udf_update_inode(struct inode *inode, int do_sync) fe->icbTag.numEntries = cpu_to_le16(1); } - if (S_ISDIR(inode->i_mode)) + if (iinfo->i_use) + fe->icbTag.fileType = ICBTAG_FILE_TYPE_USE; + else if (S_ISDIR(inode->i_mode)) fe->icbTag.fileType = ICBTAG_FILE_TYPE_DIRECTORY; else if (S_ISREG(inode->i_mode)) fe->icbTag.fileType = ICBTAG_FILE_TYPE_REGULAR; @@ -1806,7 +1934,6 @@ static int udf_update_inode(struct inode *inode, int do_sync) crclen)); fe->descTag.tagChecksum = udf_tag_checksum(&fe->descTag); -out: set_buffer_uptodate(bh); unlock_buffer(bh); @@ -1825,54 +1952,125 @@ out: return err; } -struct inode *udf_iget(struct super_block *sb, struct kernel_lb_addr *ino) +struct inode *__udf_iget(struct super_block *sb, struct kernel_lb_addr *ino, + bool hidden_inode) { unsigned long block = udf_get_lb_pblock(sb, ino, 0); struct inode *inode = iget_locked(sb, block); + int err; if (!inode) - return NULL; + return ERR_PTR(-ENOMEM); - if (inode->i_state & I_NEW) { - memcpy(&UDF_I(inode)->i_location, ino, sizeof(struct kernel_lb_addr)); - __udf_read_inode(inode); - unlock_new_inode(inode); + if (!(inode_state_read_once(inode) & I_NEW)) { + if (UDF_I(inode)->i_hidden != hidden_inode) { + iput(inode); + return ERR_PTR(-EFSCORRUPTED); + } + return inode; } - if (is_bad_inode(inode)) - goto out_iput; - - if (ino->logicalBlockNum >= UDF_SB(sb)-> - s_partmaps[ino->partitionReferenceNum].s_partition_len) { - udf_debug("block=%d, partition=%d out of range\n", - ino->logicalBlockNum, ino->partitionReferenceNum); - make_bad_inode(inode); - goto out_iput; + memcpy(&UDF_I(inode)->i_location, ino, sizeof(struct kernel_lb_addr)); + err = udf_read_inode(inode, hidden_inode); + if (err < 0) { + iget_failed(inode); + return ERR_PTR(err); } + unlock_new_inode(inode); return inode; - - out_iput: - iput(inode); - return NULL; } -int udf_add_aext(struct inode *inode, struct extent_position *epos, - struct kernel_lb_addr *eloc, uint32_t elen, int inc) +int udf_setup_indirect_aext(struct inode *inode, udf_pblk_t block, + struct extent_position *epos) { - int adsize; - struct short_ad *sad = NULL; - struct long_ad *lad = NULL; + struct super_block *sb = inode->i_sb; + struct buffer_head *bh; struct allocExtDesc *aed; - uint8_t *ptr; - struct udf_inode_info *iinfo = UDF_I(inode); + struct extent_position nepos; + struct kernel_lb_addr neloc; + int ver, adsize; + int err = 0; - if (!epos->bh) - ptr = iinfo->i_ext.i_data + epos->offset - - udf_file_entry_alloc_offset(inode) + - iinfo->i_lenEAttr; + if (UDF_I(inode)->i_alloc_type == ICBTAG_FLAG_AD_SHORT) + adsize = sizeof(struct short_ad); + else if (UDF_I(inode)->i_alloc_type == ICBTAG_FLAG_AD_LONG) + adsize = sizeof(struct long_ad); else - ptr = epos->bh->b_data + epos->offset; + return -EIO; + + neloc.logicalBlockNum = block; + neloc.partitionReferenceNum = epos->block.partitionReferenceNum; + + bh = sb_getblk(sb, udf_get_lb_pblock(sb, &neloc, 0)); + if (!bh) + return -EIO; + lock_buffer(bh); + memset(bh->b_data, 0x00, sb->s_blocksize); + set_buffer_uptodate(bh); + unlock_buffer(bh); + mark_buffer_dirty_inode(bh, inode); + + aed = (struct allocExtDesc *)(bh->b_data); + if (!UDF_QUERY_FLAG(sb, UDF_FLAG_STRICT)) { + aed->previousAllocExtLocation = + cpu_to_le32(epos->block.logicalBlockNum); + } + aed->lengthAllocDescs = cpu_to_le32(0); + if (UDF_SB(sb)->s_udfrev >= 0x0200) + ver = 3; + else + ver = 2; + udf_new_tag(bh->b_data, TAG_IDENT_AED, ver, 1, block, + sizeof(struct tag)); + + nepos.block = neloc; + nepos.offset = sizeof(struct allocExtDesc); + nepos.bh = bh; + + /* + * Do we have to copy current last extent to make space for indirect + * one? + */ + if (epos->offset + adsize > sb->s_blocksize) { + struct kernel_lb_addr cp_loc; + uint32_t cp_len; + int8_t cp_type; + + epos->offset -= adsize; + err = udf_current_aext(inode, epos, &cp_loc, &cp_len, &cp_type, 0); + if (err <= 0) + goto err_out; + cp_len |= ((uint32_t)cp_type) << 30; + + __udf_add_aext(inode, &nepos, &cp_loc, cp_len, 1); + udf_write_aext(inode, epos, &nepos.block, + sb->s_blocksize | EXT_NEXT_EXTENT_ALLOCDESCS, 0); + } else { + __udf_add_aext(inode, epos, &nepos.block, + sb->s_blocksize | EXT_NEXT_EXTENT_ALLOCDESCS, 0); + } + + brelse(epos->bh); + *epos = nepos; + + return 0; +err_out: + brelse(bh); + return err; +} + +/* + * Append extent at the given position - should be the first free one in inode + * / indirect extent. This function assumes there is enough space in the inode + * or indirect extent. Use udf_add_aext() if you didn't check for this before. + */ +int __udf_add_aext(struct inode *inode, struct extent_position *epos, + struct kernel_lb_addr *eloc, uint32_t elen, int inc) +{ + struct udf_inode_info *iinfo = UDF_I(inode); + struct allocExtDesc *aed; + int adsize; if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT) adsize = sizeof(struct short_ad); @@ -1881,88 +2079,14 @@ int udf_add_aext(struct inode *inode, struct extent_position *epos, else return -EIO; - if (epos->offset + (2 * adsize) > inode->i_sb->s_blocksize) { - unsigned char *sptr, *dptr; - struct buffer_head *nbh; - int err, loffset; - struct kernel_lb_addr obloc = epos->block; - - epos->block.logicalBlockNum = udf_new_block(inode->i_sb, NULL, - obloc.partitionReferenceNum, - obloc.logicalBlockNum, &err); - if (!epos->block.logicalBlockNum) - return -ENOSPC; - nbh = udf_tgetblk(inode->i_sb, udf_get_lb_pblock(inode->i_sb, - &epos->block, - 0)); - if (!nbh) - return -EIO; - lock_buffer(nbh); - memset(nbh->b_data, 0x00, inode->i_sb->s_blocksize); - set_buffer_uptodate(nbh); - unlock_buffer(nbh); - mark_buffer_dirty_inode(nbh, inode); - - aed = (struct allocExtDesc *)(nbh->b_data); - if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT)) - aed->previousAllocExtLocation = - cpu_to_le32(obloc.logicalBlockNum); - if (epos->offset + adsize > inode->i_sb->s_blocksize) { - loffset = epos->offset; - aed->lengthAllocDescs = cpu_to_le32(adsize); - sptr = ptr - adsize; - dptr = nbh->b_data + sizeof(struct allocExtDesc); - memcpy(dptr, sptr, adsize); - epos->offset = sizeof(struct allocExtDesc) + adsize; - } else { - loffset = epos->offset + adsize; - aed->lengthAllocDescs = cpu_to_le32(0); - sptr = ptr; - epos->offset = sizeof(struct allocExtDesc); - - if (epos->bh) { - aed = (struct allocExtDesc *)epos->bh->b_data; - le32_add_cpu(&aed->lengthAllocDescs, adsize); - } else { - iinfo->i_lenAlloc += adsize; - mark_inode_dirty(inode); - } - } - if (UDF_SB(inode->i_sb)->s_udfrev >= 0x0200) - udf_new_tag(nbh->b_data, TAG_IDENT_AED, 3, 1, - epos->block.logicalBlockNum, sizeof(struct tag)); - else - udf_new_tag(nbh->b_data, TAG_IDENT_AED, 2, 1, - epos->block.logicalBlockNum, sizeof(struct tag)); - switch (iinfo->i_alloc_type) { - case ICBTAG_FLAG_AD_SHORT: - sad = (struct short_ad *)sptr; - sad->extLength = cpu_to_le32(EXT_NEXT_EXTENT_ALLOCDECS | - inode->i_sb->s_blocksize); - sad->extPosition = - cpu_to_le32(epos->block.logicalBlockNum); - break; - case ICBTAG_FLAG_AD_LONG: - lad = (struct long_ad *)sptr; - lad->extLength = cpu_to_le32(EXT_NEXT_EXTENT_ALLOCDECS | - inode->i_sb->s_blocksize); - lad->extLocation = cpu_to_lelb(epos->block); - memset(lad->impUse, 0x00, sizeof(lad->impUse)); - break; - } - if (epos->bh) { - if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT) || - UDF_SB(inode->i_sb)->s_udfrev >= 0x0201) - udf_update_tag(epos->bh->b_data, loffset); - else - udf_update_tag(epos->bh->b_data, - sizeof(struct allocExtDesc)); - mark_buffer_dirty_inode(epos->bh, inode); - brelse(epos->bh); - } else { - mark_inode_dirty(inode); - } - epos->bh = nbh; + if (!epos->bh) { + WARN_ON(iinfo->i_lenAlloc != + epos->offset - udf_file_entry_alloc_offset(inode)); + } else { + aed = (struct allocExtDesc *)epos->bh->b_data; + WARN_ON(le32_to_cpu(aed->lengthAllocDescs) != + epos->offset - sizeof(struct allocExtDesc)); + WARN_ON(epos->offset + adsize > inode->i_sb->s_blocksize); } udf_write_aext(inode, epos, eloc, elen, inc); @@ -1986,6 +2110,41 @@ int udf_add_aext(struct inode *inode, struct extent_position *epos, return 0; } +/* + * Append extent at given position - should be the first free one in inode + * / indirect extent. Takes care of allocating and linking indirect blocks. + */ +int udf_add_aext(struct inode *inode, struct extent_position *epos, + struct kernel_lb_addr *eloc, uint32_t elen, int inc) +{ + int adsize; + struct super_block *sb = inode->i_sb; + + if (UDF_I(inode)->i_alloc_type == ICBTAG_FLAG_AD_SHORT) + adsize = sizeof(struct short_ad); + else if (UDF_I(inode)->i_alloc_type == ICBTAG_FLAG_AD_LONG) + adsize = sizeof(struct long_ad); + else + return -EIO; + + if (epos->offset + (2 * adsize) > sb->s_blocksize) { + int err; + udf_pblk_t new_block; + + new_block = udf_new_block(sb, NULL, + epos->block.partitionReferenceNum, + epos->block.logicalBlockNum, &err); + if (!new_block) + return -ENOSPC; + + err = udf_setup_indirect_aext(inode, new_block, epos); + if (err) + return err; + } + + return __udf_add_aext(inode, epos, eloc, elen, inc); +} + void udf_write_aext(struct inode *inode, struct extent_position *epos, struct kernel_lb_addr *eloc, uint32_t elen, int inc) { @@ -1996,7 +2155,7 @@ void udf_write_aext(struct inode *inode, struct extent_position *epos, struct udf_inode_info *iinfo = UDF_I(inode); if (!epos->bh) - ptr = iinfo->i_ext.i_data + epos->offset - + ptr = iinfo->i_data + epos->offset - udf_file_entry_alloc_offset(inode) + iinfo->i_lenEAttr; else @@ -2038,33 +2197,58 @@ void udf_write_aext(struct inode *inode, struct extent_position *epos, epos->offset += adsize; } -int8_t udf_next_aext(struct inode *inode, struct extent_position *epos, - struct kernel_lb_addr *eloc, uint32_t *elen, int inc) +/* + * Only 1 indirect extent in a row really makes sense but allow upto 16 in case + * someone does some weird stuff. + */ +#define UDF_MAX_INDIR_EXTS 16 + +/* + * Returns 1 on success, -errno on error, 0 on hit EOF. + */ +int udf_next_aext(struct inode *inode, struct extent_position *epos, + struct kernel_lb_addr *eloc, uint32_t *elen, int8_t *etype, + int inc) { - int8_t etype; + unsigned int indirections = 0; + int ret = 0; + udf_pblk_t block; + + while (1) { + ret = udf_current_aext(inode, epos, eloc, elen, + etype, inc); + if (ret <= 0) + return ret; + if (*etype != (EXT_NEXT_EXTENT_ALLOCDESCS >> 30)) + return ret; + + if (++indirections > UDF_MAX_INDIR_EXTS) { + udf_err(inode->i_sb, + "too many indirect extents in inode %lu\n", + inode->i_ino); + return -EFSCORRUPTED; + } - while ((etype = udf_current_aext(inode, epos, eloc, elen, inc)) == - (EXT_NEXT_EXTENT_ALLOCDECS >> 30)) { - int block; epos->block = *eloc; epos->offset = sizeof(struct allocExtDesc); brelse(epos->bh); block = udf_get_lb_pblock(inode->i_sb, &epos->block, 0); - epos->bh = udf_tread(inode->i_sb, block); + epos->bh = sb_bread(inode->i_sb, block); if (!epos->bh) { - udf_debug("reading block %d failed!\n", block); - return -1; + udf_debug("reading block %u failed!\n", block); + return -EIO; } } - - return etype; } -int8_t udf_current_aext(struct inode *inode, struct extent_position *epos, - struct kernel_lb_addr *eloc, uint32_t *elen, int inc) +/* + * Returns 1 on success, -errno on error, 0 on hit EOF. + */ +int udf_current_aext(struct inode *inode, struct extent_position *epos, + struct kernel_lb_addr *eloc, uint32_t *elen, int8_t *etype, + int inc) { int alen; - int8_t etype; uint8_t *ptr; struct short_ad *sad; struct long_ad *lad; @@ -2073,26 +2257,32 @@ int8_t udf_current_aext(struct inode *inode, struct extent_position *epos, if (!epos->bh) { if (!epos->offset) epos->offset = udf_file_entry_alloc_offset(inode); - ptr = iinfo->i_ext.i_data + epos->offset - + ptr = iinfo->i_data + epos->offset - udf_file_entry_alloc_offset(inode) + iinfo->i_lenEAttr; alen = udf_file_entry_alloc_offset(inode) + iinfo->i_lenAlloc; } else { + struct allocExtDesc *header = + (struct allocExtDesc *)epos->bh->b_data; + if (!epos->offset) epos->offset = sizeof(struct allocExtDesc); ptr = epos->bh->b_data + epos->offset; - alen = sizeof(struct allocExtDesc) + - le32_to_cpu(((struct allocExtDesc *)epos->bh->b_data)-> - lengthAllocDescs); + if (check_add_overflow(sizeof(struct allocExtDesc), + le32_to_cpu(header->lengthAllocDescs), &alen)) + return -1; + + if (alen > epos->bh->b_size) + return -1; } switch (iinfo->i_alloc_type) { case ICBTAG_FLAG_AD_SHORT: sad = udf_get_fileshortad(ptr, alen, &epos->offset, inc); if (!sad) - return -1; - etype = le32_to_cpu(sad->extLength) >> 30; + return 0; + *etype = le32_to_cpu(sad->extLength) >> 30; eloc->logicalBlockNum = le32_to_cpu(sad->extPosition); eloc->partitionReferenceNum = iinfo->i_location.partitionReferenceNum; @@ -2101,48 +2291,55 @@ int8_t udf_current_aext(struct inode *inode, struct extent_position *epos, case ICBTAG_FLAG_AD_LONG: lad = udf_get_filelongad(ptr, alen, &epos->offset, inc); if (!lad) - return -1; - etype = le32_to_cpu(lad->extLength) >> 30; + return 0; + *etype = le32_to_cpu(lad->extLength) >> 30; *eloc = lelb_to_cpu(lad->extLocation); *elen = le32_to_cpu(lad->extLength) & UDF_EXTENT_LENGTH_MASK; break; default: - udf_debug("alloc_type = %d unsupported\n", iinfo->i_alloc_type); - return -1; + udf_debug("alloc_type = %u unsupported\n", iinfo->i_alloc_type); + return -EINVAL; } - return etype; + return 1; } -static int8_t udf_insert_aext(struct inode *inode, struct extent_position epos, - struct kernel_lb_addr neloc, uint32_t nelen) +static int udf_insert_aext(struct inode *inode, struct extent_position epos, + struct kernel_lb_addr neloc, uint32_t nelen) { struct kernel_lb_addr oeloc; uint32_t oelen; int8_t etype; + int ret; if (epos.bh) get_bh(epos.bh); - while ((etype = udf_next_aext(inode, &epos, &oeloc, &oelen, 0)) != -1) { + while (1) { + ret = udf_next_aext(inode, &epos, &oeloc, &oelen, &etype, 0); + if (ret <= 0) + break; udf_write_aext(inode, &epos, &neloc, nelen, 1); neloc = oeloc; nelen = (etype << 30) | oelen; } - udf_add_aext(inode, &epos, &neloc, nelen, 1); + if (ret == 0) + ret = udf_add_aext(inode, &epos, &neloc, nelen, 1); brelse(epos.bh); - return (nelen >> 30); + return ret; } -int8_t udf_delete_aext(struct inode *inode, struct extent_position epos, - struct kernel_lb_addr eloc, uint32_t elen) +int8_t udf_delete_aext(struct inode *inode, struct extent_position epos) { struct extent_position oepos; int adsize; int8_t etype; struct allocExtDesc *aed; struct udf_inode_info *iinfo; + struct kernel_lb_addr eloc; + uint32_t elen; + int ret; if (epos.bh) { get_bh(epos.bh); @@ -2158,10 +2355,18 @@ int8_t udf_delete_aext(struct inode *inode, struct extent_position epos, adsize = 0; oepos = epos; - if (udf_next_aext(inode, &epos, &eloc, &elen, 1) == -1) + if (udf_next_aext(inode, &epos, &eloc, &elen, &etype, 1) <= 0) return -1; - while ((etype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1) { + while (1) { + ret = udf_next_aext(inode, &epos, &eloc, &elen, &etype, 1); + if (ret < 0) { + brelse(epos.bh); + brelse(oepos.bh); + return -1; + } + if (ret == 0) + break; udf_write_aext(inode, &oepos, &eloc, (etype << 30) | elen, 1); if (oepos.bh != epos.bh) { oepos.block = epos.block; @@ -2218,15 +2423,17 @@ int8_t udf_delete_aext(struct inode *inode, struct extent_position epos, return (elen >> 30); } -int8_t inode_bmap(struct inode *inode, sector_t block, - struct extent_position *pos, struct kernel_lb_addr *eloc, - uint32_t *elen, sector_t *offset) +/* + * Returns 1 on success, -errno on error, 0 on hit EOF. + */ +int inode_bmap(struct inode *inode, sector_t block, struct extent_position *pos, + struct kernel_lb_addr *eloc, uint32_t *elen, sector_t *offset, + int8_t *etype) { unsigned char blocksize_bits = inode->i_sb->s_blocksize_bits; - loff_t lbcount = 0, bcount = - (loff_t) block << blocksize_bits; - int8_t etype; + loff_t lbcount = 0, bcount = (loff_t) block << blocksize_bits; struct udf_inode_info *iinfo; + int err = 0; iinfo = UDF_I(inode); if (!udf_read_extent_cache(inode, bcount, &lbcount, pos)) { @@ -2236,42 +2443,19 @@ int8_t inode_bmap(struct inode *inode, sector_t block, } *elen = 0; do { - etype = udf_next_aext(inode, pos, eloc, elen, 1); - if (etype == -1) { - *offset = (bcount - lbcount) >> blocksize_bits; - iinfo->i_lenExtents = lbcount; - return -1; + err = udf_next_aext(inode, pos, eloc, elen, etype, 1); + if (err <= 0) { + if (err == 0) { + *offset = (bcount - lbcount) >> blocksize_bits; + iinfo->i_lenExtents = lbcount; + } + return err; } lbcount += *elen; } while (lbcount <= bcount); /* update extent cache */ - udf_update_extent_cache(inode, lbcount - *elen, pos, 1); + udf_update_extent_cache(inode, lbcount - *elen, pos); *offset = (bcount + *elen - lbcount) >> blocksize_bits; - return etype; -} - -long udf_block_map(struct inode *inode, sector_t block) -{ - struct kernel_lb_addr eloc; - uint32_t elen; - sector_t offset; - struct extent_position epos = {}; - int ret; - - down_read(&UDF_I(inode)->i_data_sem); - - if (inode_bmap(inode, block, &epos, &eloc, &elen, &offset) == - (EXT_RECORDED_ALLOCATED >> 30)) - ret = udf_get_lb_pblock(inode->i_sb, &eloc, offset); - else - ret = 0; - - up_read(&UDF_I(inode)->i_data_sem); - brelse(epos.bh); - - if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_VARCONV)) - return udf_fixed_to_variable(ret); - else - return ret; + return 1; } diff --git a/fs/udf/lowlevel.c b/fs/udf/lowlevel.c index 6583fe9b0645..9d847a7a0905 100644 --- a/fs/udf/lowlevel.c +++ b/fs/udf/lowlevel.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * lowlevel.c * @@ -5,11 +6,6 @@ * Low Level Device Routines for the UDF filesystem * * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. - * * (C) 1999-2001 Ben Fennema * * HISTORY @@ -21,47 +17,46 @@ #include <linux/blkdev.h> #include <linux/cdrom.h> -#include <asm/uaccess.h> +#include <linux/uaccess.h> #include "udf_sb.h" unsigned int udf_get_last_session(struct super_block *sb) { + struct cdrom_device_info *cdi = disk_to_cdi(sb->s_bdev->bd_disk); struct cdrom_multisession ms_info; - unsigned int vol_desc_start; - struct block_device *bdev = sb->s_bdev; - int i; - vol_desc_start = 0; - ms_info.addr_format = CDROM_LBA; - i = ioctl_by_bdev(bdev, CDROMMULTISESSION, (unsigned long)&ms_info); + if (!cdi) { + udf_debug("CDROMMULTISESSION not supported.\n"); + return 0; + } - if (i == 0) { + ms_info.addr_format = CDROM_LBA; + if (cdrom_multisession(cdi, &ms_info) == 0) { udf_debug("XA disk: %s, vol_desc_start=%d\n", ms_info.xa_flag ? "yes" : "no", ms_info.addr.lba); if (ms_info.xa_flag) /* necessary for a valid ms_info.addr */ - vol_desc_start = ms_info.addr.lba; - } else { - udf_debug("CDROMMULTISESSION not supported: rc=%d\n", i); + return ms_info.addr.lba; } - return vol_desc_start; + return 0; } -unsigned long udf_get_last_block(struct super_block *sb) +udf_pblk_t udf_get_last_block(struct super_block *sb) { - struct block_device *bdev = sb->s_bdev; + struct cdrom_device_info *cdi = disk_to_cdi(sb->s_bdev->bd_disk); unsigned long lblock = 0; /* - * ioctl failed or returned obviously bogus value? + * The cdrom layer call failed or returned obviously bogus value? * Try using the device size... */ - if (ioctl_by_bdev(bdev, CDROM_LAST_WRITTEN, (unsigned long) &lblock) || - lblock == 0) - lblock = bdev->bd_inode->i_size >> sb->s_blocksize_bits; + if (!cdi || cdrom_get_last_written(cdi, &lblock) || lblock == 0) { + if (sb_bdev_nr_blocks(sb) > ~(udf_pblk_t)0) + return 0; + lblock = sb_bdev_nr_blocks(sb); + } if (lblock) return lblock - 1; - else - return 0; + return 0; } diff --git a/fs/udf/misc.c b/fs/udf/misc.c index c175b4dabc14..0788593b6a1d 100644 --- a/fs/udf/misc.c +++ b/fs/udf/misc.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * misc.c * @@ -5,11 +6,6 @@ * Miscellaneous routines for the OSTA-UDF(tm) filesystem. * * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. - * * (C) 1998 Dave Boynton * (C) 1998-2004 Ben Fennema * (C) 1999-2000 Stelias Computing Inc @@ -23,28 +19,11 @@ #include <linux/fs.h> #include <linux/string.h> -#include <linux/buffer_head.h> #include <linux/crc-itu-t.h> #include "udf_i.h" #include "udf_sb.h" -struct buffer_head *udf_tgetblk(struct super_block *sb, int block) -{ - if (UDF_QUERY_FLAG(sb, UDF_FLAG_VARCONV)) - return sb_getblk(sb, udf_fixed_to_variable(block)); - else - return sb_getblk(sb, block); -} - -struct buffer_head *udf_tread(struct super_block *sb, int block) -{ - if (UDF_QUERY_FLAG(sb, UDF_FLAG_VARCONV)) - return sb_bread(sb, udf_fixed_to_variable(block)); - else - return sb_bread(sb, block); -} - struct genericFormat *udf_add_extendedattr(struct inode *inode, uint32_t size, uint32_t type, uint8_t loc) { @@ -53,9 +32,9 @@ struct genericFormat *udf_add_extendedattr(struct inode *inode, uint32_t size, uint16_t crclen; struct udf_inode_info *iinfo = UDF_I(inode); - ea = iinfo->i_ext.i_data; + ea = iinfo->i_data; if (iinfo->i_lenEAttr) { - ad = iinfo->i_ext.i_data + iinfo->i_lenEAttr; + ad = iinfo->i_data + iinfo->i_lenEAttr; } else { ad = ea; size += sizeof(struct extendedAttrHeaderDesc); @@ -142,8 +121,6 @@ struct genericFormat *udf_add_extendedattr(struct inode *inode, uint32_t size, iinfo->i_lenEAttr += size; return (struct genericFormat *)&ea[offset]; } - if (loc & 0x02) - ; return NULL; } @@ -156,7 +133,7 @@ struct genericFormat *udf_get_extendedattr(struct inode *inode, uint32_t type, uint32_t offset; struct udf_inode_info *iinfo = UDF_I(inode); - ea = iinfo->i_ext.i_data; + ea = iinfo->i_data; if (iinfo->i_lenEAttr) { struct extendedAttrHeaderDesc *eahd; @@ -176,13 +153,22 @@ struct genericFormat *udf_get_extendedattr(struct inode *inode, uint32_t type, else offset = le32_to_cpu(eahd->appAttrLocation); - while (offset < iinfo->i_lenEAttr) { + while (offset + sizeof(*gaf) < iinfo->i_lenEAttr) { + uint32_t attrLength; + gaf = (struct genericFormat *)&ea[offset]; + attrLength = le32_to_cpu(gaf->attrLength); + + /* Detect undersized elements and buffer overflows */ + if ((attrLength < sizeof(*gaf)) || + (attrLength > (iinfo->i_lenEAttr - offset))) + break; + if (le32_to_cpu(gaf->attrType) == type && gaf->attrSubtype == subtype) return gaf; else - offset += le32_to_cpu(gaf->attrLength); + offset += attrLength; } } @@ -210,9 +196,9 @@ struct buffer_head *udf_read_tagged(struct super_block *sb, uint32_t block, if (block == 0xFFFFFFFF) return NULL; - bh = udf_tread(sb, block); + bh = sb_bread(sb, block); if (!bh) { - udf_err(sb, "read failed, block=%u, location=%d\n", + udf_err(sb, "read failed, block=%u, location=%u\n", block, location); return NULL; } @@ -250,7 +236,7 @@ struct buffer_head *udf_read_tagged(struct super_block *sb, uint32_t block, le16_to_cpu(tag_p->descCRCLength))) return bh; - udf_debug("Crc failure block %d: crc = %d, crclen = %d\n", block, + udf_debug("Crc failure block %u: crc = %u, crclen = %u\n", block, le16_to_cpu(tag_p->descCRC), le16_to_cpu(tag_p->descCRCLength)); error_out: diff --git a/fs/udf/namei.c b/fs/udf/namei.c index 5f6fc17d6bc5..5f2e9a892bff 100644 --- a/fs/udf/namei.c +++ b/fs/udf/namei.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * namei.c * @@ -5,11 +6,6 @@ * Inode name handling routines for the OSTA-UDF(tm) filesystem. * * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. - * * (C) 1998-2004 Ben Fennema * (C) 1999-2000 Stelias Computing Inc * @@ -27,10 +23,10 @@ #include <linux/errno.h> #include <linux/mm.h> #include <linux/slab.h> -#include <linux/buffer_head.h> #include <linux/sched.h> #include <linux/crc-itu-t.h> #include <linux/exportfs.h> +#include <linux/iversion.h> static inline int udf_match(int len1, const unsigned char *name1, int len2, const unsigned char *name2) @@ -41,871 +37,569 @@ static inline int udf_match(int len1, const unsigned char *name1, int len2, return !memcmp(name1, name2, len1); } -int udf_write_fi(struct inode *inode, struct fileIdentDesc *cfi, - struct fileIdentDesc *sfi, struct udf_fileident_bh *fibh, - uint8_t *impuse, uint8_t *fileident) -{ - uint16_t crclen = fibh->eoffset - fibh->soffset - sizeof(struct tag); - uint16_t crc; - int offset; - uint16_t liu = le16_to_cpu(cfi->lengthOfImpUse); - uint8_t lfi = cfi->lengthFileIdent; - int padlen = fibh->eoffset - fibh->soffset - liu - lfi - - sizeof(struct fileIdentDesc); - int adinicb = 0; - - if (UDF_I(inode)->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) - adinicb = 1; - - offset = fibh->soffset + sizeof(struct fileIdentDesc); - - if (impuse) { - if (adinicb || (offset + liu < 0)) { - memcpy((uint8_t *)sfi->impUse, impuse, liu); - } else if (offset >= 0) { - memcpy(fibh->ebh->b_data + offset, impuse, liu); - } else { - memcpy((uint8_t *)sfi->impUse, impuse, -offset); - memcpy(fibh->ebh->b_data, impuse - offset, - liu + offset); - } - } - - offset += liu; - - if (fileident) { - if (adinicb || (offset + lfi < 0)) { - memcpy((uint8_t *)sfi->fileIdent + liu, fileident, lfi); - } else if (offset >= 0) { - memcpy(fibh->ebh->b_data + offset, fileident, lfi); - } else { - memcpy((uint8_t *)sfi->fileIdent + liu, fileident, - -offset); - memcpy(fibh->ebh->b_data, fileident - offset, - lfi + offset); - } - } - - offset += lfi; - - if (adinicb || (offset + padlen < 0)) { - memset((uint8_t *)sfi->padding + liu + lfi, 0x00, padlen); - } else if (offset >= 0) { - memset(fibh->ebh->b_data + offset, 0x00, padlen); - } else { - memset((uint8_t *)sfi->padding + liu + lfi, 0x00, -offset); - memset(fibh->ebh->b_data, 0x00, padlen + offset); - } - - crc = crc_itu_t(0, (uint8_t *)cfi + sizeof(struct tag), - sizeof(struct fileIdentDesc) - sizeof(struct tag)); - - if (fibh->sbh == fibh->ebh) { - crc = crc_itu_t(crc, (uint8_t *)sfi->impUse, - crclen + sizeof(struct tag) - - sizeof(struct fileIdentDesc)); - } else if (sizeof(struct fileIdentDesc) >= -fibh->soffset) { - crc = crc_itu_t(crc, fibh->ebh->b_data + - sizeof(struct fileIdentDesc) + - fibh->soffset, - crclen + sizeof(struct tag) - - sizeof(struct fileIdentDesc)); - } else { - crc = crc_itu_t(crc, (uint8_t *)sfi->impUse, - -fibh->soffset - sizeof(struct fileIdentDesc)); - crc = crc_itu_t(crc, fibh->ebh->b_data, fibh->eoffset); - } - - cfi->descTag.descCRC = cpu_to_le16(crc); - cfi->descTag.descCRCLength = cpu_to_le16(crclen); - cfi->descTag.tagChecksum = udf_tag_checksum(&cfi->descTag); - - if (adinicb || (sizeof(struct fileIdentDesc) <= -fibh->soffset)) { - memcpy((uint8_t *)sfi, (uint8_t *)cfi, - sizeof(struct fileIdentDesc)); - } else { - memcpy((uint8_t *)sfi, (uint8_t *)cfi, -fibh->soffset); - memcpy(fibh->ebh->b_data, (uint8_t *)cfi - fibh->soffset, - sizeof(struct fileIdentDesc) + fibh->soffset); - } - - if (adinicb) { - mark_inode_dirty(inode); - } else { - if (fibh->sbh != fibh->ebh) - mark_buffer_dirty_inode(fibh->ebh, inode); - mark_buffer_dirty_inode(fibh->sbh, inode); - } - return 0; -} - -static struct fileIdentDesc *udf_find_entry(struct inode *dir, - const struct qstr *child, - struct udf_fileident_bh *fibh, - struct fileIdentDesc *cfi) +/** + * udf_fiiter_find_entry - find entry in given directory. + * + * @dir: directory inode to search in + * @child: qstr of the name + * @iter: iter to use for searching + * + * This function searches in the directory @dir for a file name @child. When + * found, @iter points to the position in the directory with given entry. + * + * Returns 0 on success, < 0 on error (including -ENOENT). + */ +static int udf_fiiter_find_entry(struct inode *dir, const struct qstr *child, + struct udf_fileident_iter *iter) { - struct fileIdentDesc *fi = NULL; - loff_t f_pos; - int block, flen; + int flen; unsigned char *fname = NULL; - unsigned char *nameptr; - uint8_t lfi; - uint16_t liu; - loff_t size; - struct kernel_lb_addr eloc; - uint32_t elen; - sector_t offset; - struct extent_position epos = {}; - struct udf_inode_info *dinfo = UDF_I(dir); + struct super_block *sb = dir->i_sb; int isdotdot = child->len == 2 && child->name[0] == '.' && child->name[1] == '.'; + int ret; - size = udf_ext0_offset(dir) + dir->i_size; - f_pos = udf_ext0_offset(dir); - - fibh->sbh = fibh->ebh = NULL; - fibh->soffset = fibh->eoffset = f_pos & (dir->i_sb->s_blocksize - 1); - if (dinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) { - if (inode_bmap(dir, f_pos >> dir->i_sb->s_blocksize_bits, &epos, - &eloc, &elen, &offset) != (EXT_RECORDED_ALLOCATED >> 30)) - goto out_err; - block = udf_get_lb_pblock(dir->i_sb, &eloc, offset); - if ((++offset << dir->i_sb->s_blocksize_bits) < elen) { - 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); - } else - offset = 0; - - fibh->sbh = fibh->ebh = udf_tread(dir->i_sb, block); - if (!fibh->sbh) - goto out_err; - } - - fname = kmalloc(UDF_NAME_LEN, GFP_NOFS); + fname = kmalloc(UDF_NAME_LEN, GFP_KERNEL); if (!fname) - goto out_err; - - while (f_pos < size) { - fi = udf_fileident_read(dir, &f_pos, fibh, cfi, &epos, &eloc, - &elen, &offset); - if (!fi) - goto out_err; + return -ENOMEM; - liu = le16_to_cpu(cfi->lengthOfImpUse); - lfi = cfi->lengthFileIdent; - - if (fibh->sbh == fibh->ebh) { - nameptr = fi->fileIdent + liu; - } else { - int poffset; /* Unpaded ending offset */ - - poffset = fibh->soffset + sizeof(struct fileIdentDesc) + - liu + lfi; - - if (poffset >= lfi) - nameptr = (uint8_t *)(fibh->ebh->b_data + - poffset - lfi); - else { - nameptr = fname; - memcpy(nameptr, fi->fileIdent + liu, - lfi - poffset); - memcpy(nameptr + lfi - poffset, - fibh->ebh->b_data, poffset); - } - } - - if ((cfi->fileCharacteristics & FID_FILE_CHAR_DELETED) != 0) { - if (!UDF_QUERY_FLAG(dir->i_sb, UDF_FLAG_UNDELETE)) + for (ret = udf_fiiter_init(iter, dir, 0); + !ret && iter->pos < dir->i_size; + ret = udf_fiiter_advance(iter)) { + if (iter->fi.fileCharacteristics & FID_FILE_CHAR_DELETED) { + if (!UDF_QUERY_FLAG(sb, UDF_FLAG_UNDELETE)) continue; } - if ((cfi->fileCharacteristics & FID_FILE_CHAR_HIDDEN) != 0) { - if (!UDF_QUERY_FLAG(dir->i_sb, UDF_FLAG_UNHIDE)) + if (iter->fi.fileCharacteristics & FID_FILE_CHAR_HIDDEN) { + if (!UDF_QUERY_FLAG(sb, UDF_FLAG_UNHIDE)) continue; } - if ((cfi->fileCharacteristics & FID_FILE_CHAR_PARENT) && + if ((iter->fi.fileCharacteristics & FID_FILE_CHAR_PARENT) && isdotdot) goto out_ok; - if (!lfi) + if (!iter->fi.lengthFileIdent) continue; - flen = udf_get_filename(dir->i_sb, nameptr, fname, lfi); - if (flen && udf_match(flen, fname, child->len, child->name)) + flen = udf_get_filename(sb, iter->name, + iter->fi.lengthFileIdent, fname, UDF_NAME_LEN); + if (flen < 0) { + ret = flen; + goto out_err; + } + + if (udf_match(flen, fname, child->len, child->name)) goto out_ok; } + if (!ret) + ret = -ENOENT; out_err: - fi = NULL; - if (fibh->sbh != fibh->ebh) - brelse(fibh->ebh); - brelse(fibh->sbh); + udf_fiiter_release(iter); out_ok: - brelse(epos.bh); kfree(fname); - return fi; + return ret; } static struct dentry *udf_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) { struct inode *inode = NULL; - struct fileIdentDesc cfi; - struct udf_fileident_bh fibh; + struct udf_fileident_iter iter; + int err; - if (dentry->d_name.len > UDF_NAME_LEN - 2) + if (dentry->d_name.len > UDF_NAME_LEN) return ERR_PTR(-ENAMETOOLONG); -#ifdef UDF_RECOVERY - /* temporary shorthand for specifying files by inode number */ - if (!strncmp(dentry->d_name.name, ".B=", 3)) { - struct kernel_lb_addr lb = { - .logicalBlockNum = 0, - .partitionReferenceNum = - simple_strtoul(dentry->d_name.name + 3, - NULL, 0), - }; - inode = udf_iget(dir->i_sb, lb); - if (!inode) { - return ERR_PTR(-EACCES); - } - } else -#endif /* UDF_RECOVERY */ + err = udf_fiiter_find_entry(dir, &dentry->d_name, &iter); + if (err < 0 && err != -ENOENT) + return ERR_PTR(err); - if (udf_find_entry(dir, &dentry->d_name, &fibh, &cfi)) { + if (err == 0) { struct kernel_lb_addr loc; - if (fibh.sbh != fibh.ebh) - brelse(fibh.ebh); - brelse(fibh.sbh); + loc = lelb_to_cpu(iter.fi.icb.extLocation); + udf_fiiter_release(&iter); - loc = lelb_to_cpu(cfi.icb.extLocation); inode = udf_iget(dir->i_sb, &loc); - if (!inode) { - return ERR_PTR(-EACCES); - } } return d_splice_alias(inode, dentry); } -static struct fileIdentDesc *udf_add_entry(struct inode *dir, - struct dentry *dentry, - struct udf_fileident_bh *fibh, - struct fileIdentDesc *cfi, int *err) +static int udf_expand_dir_adinicb(struct inode *inode, udf_pblk_t *block) { - struct super_block *sb = dir->i_sb; - struct fileIdentDesc *fi = NULL; - unsigned char *name = NULL; - int namelen; - loff_t f_pos; - loff_t size = udf_ext0_offset(dir) + dir->i_size; - int nfidlen; - uint8_t lfi; - uint16_t liu; - int block; + udf_pblk_t newblock; + struct buffer_head *dbh = NULL; struct kernel_lb_addr eloc; - uint32_t elen = 0; - sector_t offset; - struct extent_position epos = {}; - struct udf_inode_info *dinfo; + struct extent_position epos; + uint8_t alloctype; + struct udf_inode_info *iinfo = UDF_I(inode); + struct udf_fileident_iter iter; + uint8_t *impuse; + int ret; + + if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_USE_SHORT_AD)) + alloctype = ICBTAG_FLAG_AD_SHORT; + else + alloctype = ICBTAG_FLAG_AD_LONG; - fibh->sbh = fibh->ebh = NULL; - name = kmalloc(UDF_NAME_LEN, GFP_NOFS); - if (!name) { - *err = -ENOMEM; - goto out_err; + if (!inode->i_size) { + iinfo->i_alloc_type = alloctype; + mark_inode_dirty(inode); + return 0; } - if (dentry) { - if (!dentry->d_name.len) { - *err = -EINVAL; - goto out_err; - } - namelen = udf_put_filename(sb, dentry->d_name.name, name, - dentry->d_name.len); - if (!namelen) { - *err = -ENAMETOOLONG; - goto out_err; - } - } else { - namelen = 0; + /* alloc block, and copy data to it */ + *block = udf_new_block(inode->i_sb, inode, + iinfo->i_location.partitionReferenceNum, + iinfo->i_location.logicalBlockNum, &ret); + if (!(*block)) + return ret; + newblock = udf_get_pblock(inode->i_sb, *block, + iinfo->i_location.partitionReferenceNum, + 0); + if (newblock == 0xffffffff) + return -EFSCORRUPTED; + dbh = sb_getblk(inode->i_sb, newblock); + if (!dbh) + return -ENOMEM; + lock_buffer(dbh); + memcpy(dbh->b_data, iinfo->i_data, inode->i_size); + memset(dbh->b_data + inode->i_size, 0, + inode->i_sb->s_blocksize - inode->i_size); + set_buffer_uptodate(dbh); + unlock_buffer(dbh); + + /* Drop inline data, add block instead */ + iinfo->i_alloc_type = alloctype; + memset(iinfo->i_data + iinfo->i_lenEAttr, 0, iinfo->i_lenAlloc); + iinfo->i_lenAlloc = 0; + eloc.logicalBlockNum = *block; + eloc.partitionReferenceNum = + iinfo->i_location.partitionReferenceNum; + iinfo->i_lenExtents = inode->i_size; + epos.bh = NULL; + epos.block = iinfo->i_location; + epos.offset = udf_file_entry_alloc_offset(inode); + ret = udf_add_aext(inode, &epos, &eloc, inode->i_size, 0); + brelse(epos.bh); + if (ret < 0) { + brelse(dbh); + udf_free_blocks(inode->i_sb, inode, &eloc, 0, 1); + return ret; } + mark_inode_dirty(inode); - nfidlen = (sizeof(struct fileIdentDesc) + namelen + 3) & ~3; - - f_pos = udf_ext0_offset(dir); - - fibh->soffset = fibh->eoffset = f_pos & (dir->i_sb->s_blocksize - 1); - dinfo = UDF_I(dir); - if (dinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) { - if (inode_bmap(dir, f_pos >> dir->i_sb->s_blocksize_bits, &epos, - &eloc, &elen, &offset) != (EXT_RECORDED_ALLOCATED >> 30)) { - block = udf_get_lb_pblock(dir->i_sb, - &dinfo->i_location, 0); - fibh->soffset = fibh->eoffset = sb->s_blocksize; - goto add; - } - block = udf_get_lb_pblock(dir->i_sb, &eloc, offset); - if ((++offset << dir->i_sb->s_blocksize_bits) < elen) { - 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); - } else - offset = 0; - - fibh->sbh = fibh->ebh = udf_tread(dir->i_sb, block); - if (!fibh->sbh) { - *err = -EIO; - goto out_err; - } - - block = dinfo->i_location.logicalBlockNum; + /* Now fixup tags in moved directory entries */ + for (ret = udf_fiiter_init(&iter, inode, 0); + !ret && iter.pos < inode->i_size; + ret = udf_fiiter_advance(&iter)) { + iter.fi.descTag.tagLocation = cpu_to_le32(*block); + if (iter.fi.lengthOfImpUse != cpu_to_le16(0)) + impuse = dbh->b_data + iter.pos + + sizeof(struct fileIdentDesc); + else + impuse = NULL; + udf_fiiter_write_fi(&iter, impuse); } + brelse(dbh); + /* + * We don't expect the iteration to fail as the directory has been + * already verified to be correct + */ + WARN_ON_ONCE(ret); + udf_fiiter_release(&iter); - while (f_pos < size) { - fi = udf_fileident_read(dir, &f_pos, fibh, cfi, &epos, &eloc, - &elen, &offset); + return 0; +} - if (!fi) { - *err = -EIO; - goto out_err; - } +static int udf_fiiter_add_entry(struct inode *dir, struct dentry *dentry, + struct udf_fileident_iter *iter) +{ + struct udf_inode_info *dinfo = UDF_I(dir); + int nfidlen, namelen = 0; + int ret; + int off, blksize = 1 << dir->i_blkbits; + udf_pblk_t block; + char name[UDF_NAME_LEN_CS0]; - liu = le16_to_cpu(cfi->lengthOfImpUse); - lfi = cfi->lengthFileIdent; - - if ((cfi->fileCharacteristics & FID_FILE_CHAR_DELETED) != 0) { - if (((sizeof(struct fileIdentDesc) + - liu + lfi + 3) & ~3) == nfidlen) { - cfi->descTag.tagSerialNum = cpu_to_le16(1); - cfi->fileVersionNum = cpu_to_le16(1); - cfi->fileCharacteristics = 0; - cfi->lengthFileIdent = namelen; - cfi->lengthOfImpUse = cpu_to_le16(0); - if (!udf_write_fi(dir, cfi, fi, fibh, NULL, - name)) - goto out_ok; - else { - *err = -EIO; - goto out_err; - } + if (dentry) { + namelen = udf_put_filename(dir->i_sb, dentry->d_name.name, + dentry->d_name.len, + name, UDF_NAME_LEN_CS0); + if (!namelen) + return -ENAMETOOLONG; + } + nfidlen = ALIGN(sizeof(struct fileIdentDesc) + namelen, UDF_NAME_PAD); + + for (ret = udf_fiiter_init(iter, dir, 0); + !ret && iter->pos < dir->i_size; + ret = udf_fiiter_advance(iter)) { + if (iter->fi.fileCharacteristics & FID_FILE_CHAR_DELETED) { + if (udf_dir_entry_len(&iter->fi) == nfidlen) { + iter->fi.descTag.tagSerialNum = cpu_to_le16(1); + iter->fi.fileVersionNum = cpu_to_le16(1); + iter->fi.fileCharacteristics = 0; + iter->fi.lengthFileIdent = namelen; + iter->fi.lengthOfImpUse = cpu_to_le16(0); + memcpy(iter->namebuf, name, namelen); + iter->name = iter->namebuf; + return 0; } } } - -add: - f_pos += nfidlen; - + if (ret) { + udf_fiiter_release(iter); + return ret; + } if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB && - sb->s_blocksize - fibh->eoffset < nfidlen) { - brelse(epos.bh); - epos.bh = NULL; - fibh->soffset -= udf_ext0_offset(dir); - fibh->eoffset -= udf_ext0_offset(dir); - f_pos -= udf_ext0_offset(dir); - if (fibh->sbh != fibh->ebh) - brelse(fibh->ebh); - brelse(fibh->sbh); - fibh->sbh = fibh->ebh = - udf_expand_dir_adinicb(dir, &block, err); - if (!fibh->sbh) - goto out_err; - epos.block = dinfo->i_location; - epos.offset = udf_file_entry_alloc_offset(dir); - /* Load extent udf_expand_dir_adinicb() has created */ - udf_current_aext(dir, &epos, &eloc, &elen, 1); + blksize - udf_ext0_offset(dir) - iter->pos < nfidlen) { + udf_fiiter_release(iter); + ret = udf_expand_dir_adinicb(dir, &block); + if (ret) + return ret; + ret = udf_fiiter_init(iter, dir, dir->i_size); + if (ret < 0) + return ret; } - /* Entry fits into current block? */ - if (sb->s_blocksize - fibh->eoffset >= nfidlen) { - fibh->soffset = fibh->eoffset; - fibh->eoffset += nfidlen; - if (fibh->sbh != fibh->ebh) { - brelse(fibh->sbh); - fibh->sbh = fibh->ebh; - } - - if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { - block = dinfo->i_location.logicalBlockNum; - fi = (struct fileIdentDesc *) - (dinfo->i_ext.i_data + - fibh->soffset - - udf_ext0_offset(dir) + - dinfo->i_lenEAttr); - } else { - block = eloc.logicalBlockNum + - ((elen - 1) >> - dir->i_sb->s_blocksize_bits); - fi = (struct fileIdentDesc *) - (fibh->sbh->b_data + fibh->soffset); - } + /* Get blocknumber to use for entry tag */ + if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { + block = dinfo->i_location.logicalBlockNum; } 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) { - brelse(fibh->sbh); - fibh->sbh = fibh->ebh; - } + block = iter->eloc.logicalBlockNum + + ((iter->elen - 1) >> dir->i_blkbits); + } + off = iter->pos & (blksize - 1); + if (!off) + off = blksize; + /* Entry fits into current block? */ + if (blksize - udf_ext0_offset(dir) - off >= nfidlen) + goto store_fi; - block = eloc.logicalBlockNum + ((elen - 1) >> - dir->i_sb->s_blocksize_bits); - fibh->ebh = udf_bread(dir, - f_pos >> dir->i_sb->s_blocksize_bits, 1, err); - if (!fibh->ebh) - goto out_err; - /* Extents could have been merged, invalidate our position */ - brelse(epos.bh); - epos.bh = NULL; - epos.block = dinfo->i_location; - epos.offset = udf_file_entry_alloc_offset(dir); - - if (!fibh->soffset) { - /* Find the freshly allocated block */ - while (udf_next_aext(dir, &epos, &eloc, &elen, 1) == - (EXT_RECORDED_ALLOCATED >> 30)) - ; - block = eloc.logicalBlockNum + ((elen - 1) >> - dir->i_sb->s_blocksize_bits); - brelse(fibh->sbh); - fibh->sbh = fibh->ebh; - fi = (struct fileIdentDesc *)(fibh->sbh->b_data); - } else { - fi = (struct fileIdentDesc *) - (fibh->sbh->b_data + sb->s_blocksize + - fibh->soffset); - } + ret = udf_fiiter_append_blk(iter); + if (ret) { + udf_fiiter_release(iter); + return ret; } - memset(cfi, 0, sizeof(struct fileIdentDesc)); - if (UDF_SB(sb)->s_udfrev >= 0x0200) - udf_new_tag((char *)cfi, TAG_IDENT_FID, 3, 1, block, + /* Entry will be completely in the new block? Update tag location... */ + if (!(iter->pos & (blksize - 1))) + block = iter->eloc.logicalBlockNum + + ((iter->elen - 1) >> dir->i_blkbits); +store_fi: + memset(&iter->fi, 0, sizeof(struct fileIdentDesc)); + if (UDF_SB(dir->i_sb)->s_udfrev >= 0x0200) + udf_new_tag((char *)(&iter->fi), TAG_IDENT_FID, 3, 1, block, sizeof(struct tag)); else - udf_new_tag((char *)cfi, TAG_IDENT_FID, 2, 1, block, + udf_new_tag((char *)(&iter->fi), TAG_IDENT_FID, 2, 1, block, sizeof(struct tag)); - cfi->fileVersionNum = cpu_to_le16(1); - cfi->lengthFileIdent = namelen; - cfi->lengthOfImpUse = cpu_to_le16(0); - if (!udf_write_fi(dir, cfi, fi, fibh, NULL, name)) { - 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; + iter->fi.fileVersionNum = cpu_to_le16(1); + iter->fi.lengthFileIdent = namelen; + iter->fi.lengthOfImpUse = cpu_to_le16(0); + memcpy(iter->namebuf, name, namelen); + iter->name = iter->namebuf; + + dir->i_size += nfidlen; + if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { + dinfo->i_lenAlloc += nfidlen; } else { - *err = -EIO; - goto out_err; + /* Truncate last extent to proper size */ + udf_fiiter_update_elen(iter, iter->elen - + (dinfo->i_lenExtents - dir->i_size)); } + mark_inode_dirty(dir); -out_err: - fi = NULL; - if (fibh->sbh != fibh->ebh) - brelse(fibh->ebh); - brelse(fibh->sbh); -out_ok: - brelse(epos.bh); - kfree(name); - return fi; + return 0; } -static int udf_delete_entry(struct inode *inode, struct fileIdentDesc *fi, - struct udf_fileident_bh *fibh, - struct fileIdentDesc *cfi) +static void udf_fiiter_delete_entry(struct udf_fileident_iter *iter) { - cfi->fileCharacteristics |= FID_FILE_CHAR_DELETED; + iter->fi.fileCharacteristics |= FID_FILE_CHAR_DELETED; - if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT)) - memset(&(cfi->icb), 0x00, sizeof(struct long_ad)); + if (UDF_QUERY_FLAG(iter->dir->i_sb, UDF_FLAG_STRICT)) + memset(&iter->fi.icb, 0x00, sizeof(struct long_ad)); - return udf_write_fi(inode, cfi, fi, fibh, NULL, NULL); + udf_fiiter_write_fi(iter, NULL); } -static int udf_create(struct inode *dir, struct dentry *dentry, umode_t mode, - bool excl) +static void udf_add_fid_counter(struct super_block *sb, bool dir, int val) { - struct udf_fileident_bh fibh; - struct inode *inode; - struct fileIdentDesc cfi, *fi; - int err; - struct udf_inode_info *iinfo; - - inode = udf_new_inode(dir, mode, &err); - if (!inode) { - return err; - } + struct logicalVolIntegrityDescImpUse *lvidiu = udf_sb_lvidiu(sb); - iinfo = UDF_I(inode); - if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) - inode->i_data.a_ops = &udf_adinicb_aops; + if (!lvidiu) + return; + mutex_lock(&UDF_SB(sb)->s_alloc_mutex); + if (dir) + le32_add_cpu(&lvidiu->numDirs, val); else - inode->i_data.a_ops = &udf_aops; - inode->i_op = &udf_file_inode_operations; - inode->i_fop = &udf_file_operations; - mark_inode_dirty(inode); + le32_add_cpu(&lvidiu->numFiles, val); + udf_updated_lvid(sb); + mutex_unlock(&UDF_SB(sb)->s_alloc_mutex); +} + +static int udf_add_nondir(struct dentry *dentry, struct inode *inode) +{ + struct udf_inode_info *iinfo = UDF_I(inode); + struct inode *dir = d_inode(dentry->d_parent); + struct udf_fileident_iter iter; + int err; - fi = udf_add_entry(dir, dentry, &fibh, &cfi, &err); - if (!fi) { + err = udf_fiiter_add_entry(dir, dentry, &iter); + if (err) { inode_dec_link_count(inode); - iput(inode); + discard_new_inode(inode); return err; } - cfi.icb.extLength = cpu_to_le32(inode->i_sb->s_blocksize); - cfi.icb.extLocation = cpu_to_lelb(iinfo->i_location); - *(__le32 *)((struct allocDescImpUse *)cfi.icb.impUse)->impUse = + iter.fi.icb.extLength = cpu_to_le32(inode->i_sb->s_blocksize); + iter.fi.icb.extLocation = cpu_to_lelb(iinfo->i_location); + *(__le32 *)((struct allocDescImpUse *)iter.fi.icb.impUse)->impUse = cpu_to_le32(iinfo->i_unique & 0x00000000FFFFFFFFUL); - udf_write_fi(dir, &cfi, fi, &fibh, NULL, NULL); - if (UDF_I(dir)->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) - mark_inode_dirty(dir); - if (fibh.sbh != fibh.ebh) - brelse(fibh.ebh); - brelse(fibh.sbh); - d_instantiate(dentry, inode); + udf_fiiter_write_fi(&iter, NULL); + inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); + mark_inode_dirty(dir); + udf_fiiter_release(&iter); + udf_add_fid_counter(dir->i_sb, false, 1); + d_instantiate_new(dentry, inode); return 0; } -static int udf_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode) +static int udf_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, bool excl) { - struct inode *inode; - struct udf_inode_info *iinfo; - int err; + struct inode *inode = udf_new_inode(dir, mode); - inode = udf_new_inode(dir, mode, &err); - if (!inode) - return err; + if (IS_ERR(inode)) + return PTR_ERR(inode); - iinfo = UDF_I(inode); - if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) - inode->i_data.a_ops = &udf_adinicb_aops; - else - inode->i_data.a_ops = &udf_aops; + inode->i_data.a_ops = &udf_aops; inode->i_op = &udf_file_inode_operations; inode->i_fop = &udf_file_operations; mark_inode_dirty(inode); - d_tmpfile(dentry, inode); - return 0; + return udf_add_nondir(dentry, inode); +} + +static int udf_tmpfile(struct mnt_idmap *idmap, struct inode *dir, + struct file *file, umode_t mode) +{ + struct inode *inode = udf_new_inode(dir, mode); + + if (IS_ERR(inode)) + return PTR_ERR(inode); + + inode->i_data.a_ops = &udf_aops; + inode->i_op = &udf_file_inode_operations; + inode->i_fop = &udf_file_operations; + mark_inode_dirty(inode); + d_tmpfile(file, inode); + unlock_new_inode(inode); + return finish_open_simple(file, 0); } -static int udf_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, - dev_t rdev) +static int udf_mknod(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t rdev) { struct inode *inode; - struct udf_fileident_bh fibh; - struct fileIdentDesc cfi, *fi; - int err; - struct udf_inode_info *iinfo; if (!old_valid_dev(rdev)) return -EINVAL; - err = -EIO; - inode = udf_new_inode(dir, mode, &err); - if (!inode) - goto out; + inode = udf_new_inode(dir, mode); + if (IS_ERR(inode)) + return PTR_ERR(inode); - iinfo = UDF_I(inode); init_special_inode(inode, mode, rdev); - fi = udf_add_entry(dir, dentry, &fibh, &cfi, &err); - if (!fi) { - inode_dec_link_count(inode); - iput(inode); - return err; - } - cfi.icb.extLength = cpu_to_le32(inode->i_sb->s_blocksize); - cfi.icb.extLocation = cpu_to_lelb(iinfo->i_location); - *(__le32 *)((struct allocDescImpUse *)cfi.icb.impUse)->impUse = - cpu_to_le32(iinfo->i_unique & 0x00000000FFFFFFFFUL); - udf_write_fi(dir, &cfi, fi, &fibh, NULL, NULL); - if (UDF_I(dir)->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) - mark_inode_dirty(dir); - mark_inode_dirty(inode); - - if (fibh.sbh != fibh.ebh) - brelse(fibh.ebh); - brelse(fibh.sbh); - d_instantiate(dentry, inode); - err = 0; - -out: - return err; + return udf_add_nondir(dentry, inode); } -static int udf_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) +static struct dentry *udf_mkdir(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) { struct inode *inode; - struct udf_fileident_bh fibh; - struct fileIdentDesc cfi, *fi; + struct udf_fileident_iter iter; int err; struct udf_inode_info *dinfo = UDF_I(dir); struct udf_inode_info *iinfo; - err = -EIO; - inode = udf_new_inode(dir, S_IFDIR | mode, &err); - if (!inode) - goto out; + inode = udf_new_inode(dir, S_IFDIR | mode); + if (IS_ERR(inode)) + return ERR_CAST(inode); iinfo = UDF_I(inode); inode->i_op = &udf_dir_inode_operations; inode->i_fop = &udf_dir_operations; - fi = udf_add_entry(inode, NULL, &fibh, &cfi, &err); - if (!fi) { - inode_dec_link_count(inode); - iput(inode); - goto out; + err = udf_fiiter_add_entry(inode, NULL, &iter); + if (err) { + clear_nlink(inode); + discard_new_inode(inode); + return ERR_PTR(err); } set_nlink(inode, 2); - cfi.icb.extLength = cpu_to_le32(inode->i_sb->s_blocksize); - cfi.icb.extLocation = cpu_to_lelb(dinfo->i_location); - *(__le32 *)((struct allocDescImpUse *)cfi.icb.impUse)->impUse = + iter.fi.icb.extLength = cpu_to_le32(inode->i_sb->s_blocksize); + iter.fi.icb.extLocation = cpu_to_lelb(dinfo->i_location); + *(__le32 *)((struct allocDescImpUse *)iter.fi.icb.impUse)->impUse = cpu_to_le32(dinfo->i_unique & 0x00000000FFFFFFFFUL); - cfi.fileCharacteristics = + iter.fi.fileCharacteristics = FID_FILE_CHAR_DIRECTORY | FID_FILE_CHAR_PARENT; - udf_write_fi(inode, &cfi, fi, &fibh, NULL, NULL); - brelse(fibh.sbh); + udf_fiiter_write_fi(&iter, NULL); + udf_fiiter_release(&iter); mark_inode_dirty(inode); - fi = udf_add_entry(dir, dentry, &fibh, &cfi, &err); - if (!fi) { + err = udf_fiiter_add_entry(dir, dentry, &iter); + if (err) { clear_nlink(inode); - mark_inode_dirty(inode); - iput(inode); - goto out; + discard_new_inode(inode); + return ERR_PTR(err); } - cfi.icb.extLength = cpu_to_le32(inode->i_sb->s_blocksize); - cfi.icb.extLocation = cpu_to_lelb(iinfo->i_location); - *(__le32 *)((struct allocDescImpUse *)cfi.icb.impUse)->impUse = + iter.fi.icb.extLength = cpu_to_le32(inode->i_sb->s_blocksize); + iter.fi.icb.extLocation = cpu_to_lelb(iinfo->i_location); + *(__le32 *)((struct allocDescImpUse *)iter.fi.icb.impUse)->impUse = cpu_to_le32(iinfo->i_unique & 0x00000000FFFFFFFFUL); - cfi.fileCharacteristics |= FID_FILE_CHAR_DIRECTORY; - udf_write_fi(dir, &cfi, fi, &fibh, NULL, NULL); + iter.fi.fileCharacteristics |= FID_FILE_CHAR_DIRECTORY; + udf_fiiter_write_fi(&iter, NULL); + udf_fiiter_release(&iter); + udf_add_fid_counter(dir->i_sb, true, 1); inc_nlink(dir); + inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); mark_inode_dirty(dir); - d_instantiate(dentry, inode); - if (fibh.sbh != fibh.ebh) - brelse(fibh.ebh); - brelse(fibh.sbh); - err = 0; + d_instantiate_new(dentry, inode); -out: - return err; + return NULL; } static int empty_dir(struct inode *dir) { - struct fileIdentDesc *fi, cfi; - struct udf_fileident_bh fibh; - loff_t f_pos; - loff_t size = udf_ext0_offset(dir) + dir->i_size; - int block; - struct kernel_lb_addr eloc; - uint32_t elen; - sector_t offset; - struct extent_position epos = {}; - struct udf_inode_info *dinfo = UDF_I(dir); - - f_pos = udf_ext0_offset(dir); - fibh.soffset = fibh.eoffset = f_pos & (dir->i_sb->s_blocksize - 1); - - if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) - fibh.sbh = fibh.ebh = NULL; - else if (inode_bmap(dir, f_pos >> dir->i_sb->s_blocksize_bits, - &epos, &eloc, &elen, &offset) == - (EXT_RECORDED_ALLOCATED >> 30)) { - block = udf_get_lb_pblock(dir->i_sb, &eloc, offset); - if ((++offset << dir->i_sb->s_blocksize_bits) < elen) { - 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); - } else - offset = 0; - - fibh.sbh = fibh.ebh = udf_tread(dir->i_sb, block); - if (!fibh.sbh) { - brelse(epos.bh); - return 0; - } - } else { - brelse(epos.bh); - return 0; - } - - while (f_pos < size) { - fi = udf_fileident_read(dir, &f_pos, &fibh, &cfi, &epos, &eloc, - &elen, &offset); - if (!fi) { - if (fibh.sbh != fibh.ebh) - brelse(fibh.ebh); - brelse(fibh.sbh); - brelse(epos.bh); - return 0; - } - - if (cfi.lengthFileIdent && - (cfi.fileCharacteristics & FID_FILE_CHAR_DELETED) == 0) { - if (fibh.sbh != fibh.ebh) - brelse(fibh.ebh); - brelse(fibh.sbh); - brelse(epos.bh); + struct udf_fileident_iter iter; + int ret; + + for (ret = udf_fiiter_init(&iter, dir, 0); + !ret && iter.pos < dir->i_size; + ret = udf_fiiter_advance(&iter)) { + if (iter.fi.lengthFileIdent && + !(iter.fi.fileCharacteristics & FID_FILE_CHAR_DELETED)) { + udf_fiiter_release(&iter); return 0; } } - - if (fibh.sbh != fibh.ebh) - brelse(fibh.ebh); - brelse(fibh.sbh); - brelse(epos.bh); + udf_fiiter_release(&iter); return 1; } static int udf_rmdir(struct inode *dir, struct dentry *dentry) { - int retval; - struct inode *inode = dentry->d_inode; - struct udf_fileident_bh fibh; - struct fileIdentDesc *fi, cfi; + int ret; + struct inode *inode = d_inode(dentry); + struct udf_fileident_iter iter; struct kernel_lb_addr tloc; - retval = -ENOENT; - fi = udf_find_entry(dir, &dentry->d_name, &fibh, &cfi); - if (!fi) + ret = udf_fiiter_find_entry(dir, &dentry->d_name, &iter); + if (ret) goto out; - retval = -EIO; - tloc = lelb_to_cpu(cfi.icb.extLocation); + ret = -EFSCORRUPTED; + tloc = lelb_to_cpu(iter.fi.icb.extLocation); if (udf_get_lb_pblock(dir->i_sb, &tloc, 0) != inode->i_ino) goto end_rmdir; - retval = -ENOTEMPTY; + ret = -ENOTEMPTY; if (!empty_dir(inode)) goto end_rmdir; - retval = udf_delete_entry(dir, fi, &fibh, &cfi); - if (retval) - goto end_rmdir; + udf_fiiter_delete_entry(&iter); if (inode->i_nlink != 2) - udf_warn(inode->i_sb, "empty directory has nlink != 2 (%d)\n", + udf_warn(inode->i_sb, "empty directory has nlink != 2 (%u)\n", inode->i_nlink); clear_nlink(inode); inode->i_size = 0; - inode_dec_link_count(dir); - inode->i_ctime = dir->i_ctime = dir->i_mtime = - current_fs_time(dir->i_sb); + if (dir->i_nlink >= 3) + inode_dec_link_count(dir); + else + udf_warn(inode->i_sb, "parent dir link count too low (%u)\n", + dir->i_nlink); + udf_add_fid_counter(dir->i_sb, true, -1); + inode_set_mtime_to_ts(dir, + inode_set_ctime_to_ts(dir, inode_set_ctime_current(inode))); mark_inode_dirty(dir); - + ret = 0; end_rmdir: - if (fibh.sbh != fibh.ebh) - brelse(fibh.ebh); - brelse(fibh.sbh); - + udf_fiiter_release(&iter); out: - return retval; + return ret; } static int udf_unlink(struct inode *dir, struct dentry *dentry) { - int retval; - struct inode *inode = dentry->d_inode; - struct udf_fileident_bh fibh; - struct fileIdentDesc *fi; - struct fileIdentDesc cfi; + int ret; + struct inode *inode = d_inode(dentry); + struct udf_fileident_iter iter; struct kernel_lb_addr tloc; - retval = -ENOENT; - fi = udf_find_entry(dir, &dentry->d_name, &fibh, &cfi); - if (!fi) + ret = udf_fiiter_find_entry(dir, &dentry->d_name, &iter); + if (ret) goto out; - retval = -EIO; - tloc = lelb_to_cpu(cfi.icb.extLocation); + ret = -EFSCORRUPTED; + tloc = lelb_to_cpu(iter.fi.icb.extLocation); if (udf_get_lb_pblock(dir->i_sb, &tloc, 0) != inode->i_ino) goto end_unlink; if (!inode->i_nlink) { - udf_debug("Deleting nonexistent file (%lu), %d\n", + udf_debug("Deleting nonexistent file (%lu), %u\n", inode->i_ino, inode->i_nlink); set_nlink(inode, 1); } - retval = udf_delete_entry(dir, fi, &fibh, &cfi); - if (retval) - goto end_unlink; - dir->i_ctime = dir->i_mtime = current_fs_time(dir->i_sb); + udf_fiiter_delete_entry(&iter); + inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); mark_inode_dirty(dir); inode_dec_link_count(inode); - inode->i_ctime = dir->i_ctime; - retval = 0; - + udf_add_fid_counter(dir->i_sb, false, -1); + inode_set_ctime_to_ts(inode, inode_get_ctime(dir)); + ret = 0; end_unlink: - if (fibh.sbh != fibh.ebh) - brelse(fibh.ebh); - brelse(fibh.sbh); - + udf_fiiter_release(&iter); out: - return retval; + return ret; } -static int udf_symlink(struct inode *dir, struct dentry *dentry, - const char *symname) +static int udf_symlink(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *symname) { struct inode *inode; struct pathComponent *pc; const char *compstart; - struct udf_fileident_bh fibh; struct extent_position epos = {}; int eoffset, elen = 0; - struct fileIdentDesc *fi; - struct fileIdentDesc cfi; uint8_t *ea; int err; - int block; + udf_pblk_t block; unsigned char *name = NULL; int namelen; struct udf_inode_info *iinfo; struct super_block *sb = dir->i_sb; - inode = udf_new_inode(dir, S_IFLNK | S_IRWXUGO, &err); - if (!inode) - goto out; - - iinfo = UDF_I(inode); - down_write(&iinfo->i_data_sem); - name = kmalloc(UDF_NAME_LEN, GFP_NOFS); + name = kmalloc(UDF_NAME_LEN_CS0, GFP_KERNEL); if (!name) { err = -ENOMEM; - goto out_no_entry; + goto out; + } + + inode = udf_new_inode(dir, S_IFLNK | 0777); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto out; } + iinfo = UDF_I(inode); + down_write(&iinfo->i_data_sem); inode->i_data.a_ops = &udf_symlink_aops; inode->i_op = &udf_symlink_inode_operations; + inode_nohighmem(inode); if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) { struct kernel_lb_addr eloc; @@ -924,13 +618,22 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry, iinfo->i_location.partitionReferenceNum; bsize = sb->s_blocksize; iinfo->i_lenExtents = bsize; - udf_add_aext(inode, &epos, &eloc, bsize, 0); + err = udf_add_aext(inode, &epos, &eloc, bsize, 0); brelse(epos.bh); + if (err < 0) { + udf_free_blocks(sb, inode, &eloc, 0, 1); + goto out_no_entry; + } block = udf_get_pblock(sb, block, iinfo->i_location.partitionReferenceNum, 0); - epos.bh = udf_tgetblk(sb, block); + epos.bh = sb_getblk(sb, block); + if (unlikely(!epos.bh)) { + err = -ENOMEM; + udf_free_blocks(sb, inode, &eloc, 0, 1); + goto out_no_entry; + } lock_buffer(epos.bh); memset(epos.bh->b_data, 0x00, bsize); set_buffer_uptodate(epos.bh); @@ -938,7 +641,7 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry, mark_buffer_dirty_inode(epos.bh, inode); ea = epos.bh->b_data + udf_ext0_offset(inode); } else - ea = iinfo->i_ext.i_data + iinfo->i_lenEAttr; + ea = iinfo->i_data + iinfo->i_lenEAttr; eoffset = sb->s_blocksize - udf_ext0_offset(inode); pc = (struct pathComponent *)ea; @@ -980,8 +683,9 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry, } if (pc->componentType == 5) { - namelen = udf_put_filename(sb, compstart, name, - symname - compstart); + namelen = udf_put_filename(sb, compstart, + symname - compstart, + name, UDF_NAME_LEN_CS0); if (!namelen) goto out_no_entry; @@ -1010,26 +714,9 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry, else udf_truncate_tail_extent(inode); mark_inode_dirty(inode); - - fi = udf_add_entry(dir, dentry, &fibh, &cfi, &err); - if (!fi) - goto out_no_entry; - cfi.icb.extLength = cpu_to_le32(sb->s_blocksize); - cfi.icb.extLocation = cpu_to_lelb(iinfo->i_location); - if (UDF_SB(inode->i_sb)->s_lvid_bh) { - *(__le32 *)((struct allocDescImpUse *)cfi.icb.impUse)->impUse = - cpu_to_le32(lvid_get_unique_id(sb)); - } - udf_write_fi(dir, &cfi, fi, &fibh, NULL, NULL); - if (UDF_I(dir)->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) - mark_inode_dirty(dir); up_write(&iinfo->i_data_sem); - if (fibh.sbh != fibh.ebh) - brelse(fibh.ebh); - brelse(fibh.sbh); - d_instantiate(dentry, inode); - err = 0; + err = udf_add_nondir(dentry, inode); out: kfree(name); return err; @@ -1037,38 +724,35 @@ out: out_no_entry: up_write(&iinfo->i_data_sem); inode_dec_link_count(inode); - iput(inode); + discard_new_inode(inode); goto out; } static int udf_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry) { - struct inode *inode = old_dentry->d_inode; - struct udf_fileident_bh fibh; - struct fileIdentDesc cfi, *fi; + struct inode *inode = d_inode(old_dentry); + struct udf_fileident_iter iter; int err; - fi = udf_add_entry(dir, dentry, &fibh, &cfi, &err); - if (!fi) { + err = udf_fiiter_add_entry(dir, dentry, &iter); + if (err) return err; - } - cfi.icb.extLength = cpu_to_le32(inode->i_sb->s_blocksize); - cfi.icb.extLocation = cpu_to_lelb(UDF_I(inode)->i_location); + iter.fi.icb.extLength = cpu_to_le32(inode->i_sb->s_blocksize); + iter.fi.icb.extLocation = cpu_to_lelb(UDF_I(inode)->i_location); if (UDF_SB(inode->i_sb)->s_lvid_bh) { - *(__le32 *)((struct allocDescImpUse *)cfi.icb.impUse)->impUse = + *(__le32 *)((struct allocDescImpUse *)iter.fi.icb.impUse)->impUse = cpu_to_le32(lvid_get_unique_id(inode->i_sb)); } - udf_write_fi(dir, &cfi, fi, &fibh, NULL, NULL); - if (UDF_I(dir)->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) - mark_inode_dirty(dir); + udf_fiiter_write_fi(&iter, NULL); + udf_fiiter_release(&iter); - if (fibh.sbh != fibh.ebh) - brelse(fibh.ebh); - brelse(fibh.sbh); inc_nlink(inode); - inode->i_ctime = current_fs_time(inode->i_sb); + udf_add_fid_counter(dir->i_sb, false, 1); + inode_set_ctime_current(inode); mark_inode_dirty(inode); + inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); + mark_inode_dirty(dir); ihold(inode); d_instantiate(dentry, inode); @@ -1078,112 +762,139 @@ static int udf_link(struct dentry *old_dentry, struct inode *dir, /* Anybody can rename anything with this: the permission checks are left to the * higher-level routines. */ -static int udf_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) +static int udf_rename(struct mnt_idmap *idmap, struct inode *old_dir, + struct dentry *old_dentry, struct inode *new_dir, + struct dentry *new_dentry, unsigned int flags) { - struct inode *old_inode = old_dentry->d_inode; - struct inode *new_inode = new_dentry->d_inode; - struct udf_fileident_bh ofibh, nfibh; - struct fileIdentDesc *ofi = NULL, *nfi = NULL, *dir_fi = NULL; - struct fileIdentDesc ocfi, ncfi; - struct buffer_head *dir_bh = NULL; - int retval = -ENOENT; + struct inode *old_inode = d_inode(old_dentry); + struct inode *new_inode = d_inode(new_dentry); + struct udf_fileident_iter oiter, niter, diriter; + bool has_diriter = false, is_dir = false; + int retval; struct kernel_lb_addr tloc; - struct udf_inode_info *old_iinfo = UDF_I(old_inode); - ofi = udf_find_entry(old_dir, &old_dentry->d_name, &ofibh, &ocfi); - if (ofi) { - if (ofibh.sbh != ofibh.ebh) - brelse(ofibh.ebh); - brelse(ofibh.sbh); - } - tloc = lelb_to_cpu(ocfi.icb.extLocation); - if (!ofi || udf_get_lb_pblock(old_dir->i_sb, &tloc, 0) - != old_inode->i_ino) - goto end_rename; - - nfi = udf_find_entry(new_dir, &new_dentry->d_name, &nfibh, &ncfi); - if (nfi) { - if (!new_inode) { - if (nfibh.sbh != nfibh.ebh) - brelse(nfibh.ebh); - brelse(nfibh.sbh); - nfi = NULL; - } + if (flags & ~RENAME_NOREPLACE) + return -EINVAL; + + retval = udf_fiiter_find_entry(old_dir, &old_dentry->d_name, &oiter); + if (retval) + return retval; + + tloc = lelb_to_cpu(oiter.fi.icb.extLocation); + if (udf_get_lb_pblock(old_dir->i_sb, &tloc, 0) != old_inode->i_ino) { + retval = -ENOENT; + goto out_oiter; } - if (S_ISDIR(old_inode->i_mode)) { - int offset = udf_ext0_offset(old_inode); + if (S_ISDIR(old_inode->i_mode)) { if (new_inode) { retval = -ENOTEMPTY; if (!empty_dir(new_inode)) - goto end_rename; + goto out_oiter; + retval = -EFSCORRUPTED; + if (new_inode->i_nlink != 2) + goto out_oiter; } - retval = -EIO; - if (old_iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { - dir_fi = udf_get_fileident( - old_iinfo->i_ext.i_data - - (old_iinfo->i_efe ? - sizeof(struct extendedFileEntry) : - sizeof(struct fileEntry)), - old_inode->i_sb->s_blocksize, &offset); - } else { - dir_bh = udf_bread(old_inode, 0, 0, &retval); - if (!dir_bh) - goto end_rename; - dir_fi = udf_get_fileident(dir_bh->b_data, - old_inode->i_sb->s_blocksize, &offset); + retval = -EFSCORRUPTED; + if (old_dir->i_nlink < 3) + goto out_oiter; + is_dir = true; + } else if (new_inode) { + retval = -EFSCORRUPTED; + if (new_inode->i_nlink < 1) + goto out_oiter; + } + if (is_dir && old_dir != new_dir) { + retval = udf_fiiter_find_entry(old_inode, &dotdot_name, + &diriter); + if (retval == -ENOENT) { + udf_err(old_inode->i_sb, + "directory (ino %lu) has no '..' entry\n", + old_inode->i_ino); + retval = -EFSCORRUPTED; } - if (!dir_fi) - goto end_rename; - tloc = lelb_to_cpu(dir_fi->icb.extLocation); + if (retval) + goto out_oiter; + has_diriter = true; + tloc = lelb_to_cpu(diriter.fi.icb.extLocation); if (udf_get_lb_pblock(old_inode->i_sb, &tloc, 0) != - old_dir->i_ino) - goto end_rename; + old_dir->i_ino) { + retval = -EFSCORRUPTED; + udf_err(old_inode->i_sb, + "directory (ino %lu) has parent entry pointing to another inode (%lu != %u)\n", + old_inode->i_ino, old_dir->i_ino, + udf_get_lb_pblock(old_inode->i_sb, &tloc, 0)); + goto out_oiter; + } + } + + retval = udf_fiiter_find_entry(new_dir, &new_dentry->d_name, &niter); + if (retval && retval != -ENOENT) + goto out_oiter; + /* Entry found but not passed by VFS? */ + if (!retval && !new_inode) { + retval = -EFSCORRUPTED; + udf_fiiter_release(&niter); + goto out_oiter; } - if (!nfi) { - nfi = udf_add_entry(new_dir, new_dentry, &nfibh, &ncfi, - &retval); - if (!nfi) - goto end_rename; + /* Entry not found? Need to add one... */ + if (retval) { + udf_fiiter_release(&niter); + retval = udf_fiiter_add_entry(new_dir, new_dentry, &niter); + if (retval) + goto out_oiter; } /* * Like most other Unix systems, set the ctime for inodes on a * rename. */ - old_inode->i_ctime = current_fs_time(old_inode->i_sb); + inode_set_ctime_current(old_inode); mark_inode_dirty(old_inode); /* * ok, that's it */ - ncfi.fileVersionNum = ocfi.fileVersionNum; - ncfi.fileCharacteristics = ocfi.fileCharacteristics; - memcpy(&(ncfi.icb), &(ocfi.icb), sizeof(struct long_ad)); - udf_write_fi(new_dir, &ncfi, nfi, &nfibh, NULL, NULL); + niter.fi.fileVersionNum = oiter.fi.fileVersionNum; + niter.fi.fileCharacteristics = oiter.fi.fileCharacteristics; + memcpy(&(niter.fi.icb), &(oiter.fi.icb), sizeof(oiter.fi.icb)); + udf_fiiter_write_fi(&niter, NULL); + udf_fiiter_release(&niter); - /* The old fid may have moved - find it again */ - ofi = udf_find_entry(old_dir, &old_dentry->d_name, &ofibh, &ocfi); - udf_delete_entry(old_dir, ofi, &ofibh, &ocfi); + /* + * The old entry may have moved due to new entry allocation. Find it + * again. + */ + udf_fiiter_release(&oiter); + retval = udf_fiiter_find_entry(old_dir, &old_dentry->d_name, &oiter); + if (retval) { + udf_err(old_dir->i_sb, + "failed to find renamed entry again in directory (ino %lu)\n", + old_dir->i_ino); + } else { + udf_fiiter_delete_entry(&oiter); + udf_fiiter_release(&oiter); + } if (new_inode) { - new_inode->i_ctime = current_fs_time(new_inode->i_sb); + inode_set_ctime_current(new_inode); inode_dec_link_count(new_inode); + udf_add_fid_counter(old_dir->i_sb, S_ISDIR(new_inode->i_mode), + -1); } - old_dir->i_ctime = old_dir->i_mtime = current_fs_time(old_dir->i_sb); + inode_set_mtime_to_ts(old_dir, inode_set_ctime_current(old_dir)); + inode_set_mtime_to_ts(new_dir, inode_set_ctime_current(new_dir)); mark_inode_dirty(old_dir); + mark_inode_dirty(new_dir); - if (dir_fi) { - dir_fi->icb.extLocation = cpu_to_lelb(UDF_I(new_dir)->i_location); - udf_update_tag((char *)dir_fi, - (sizeof(struct fileIdentDesc) + - le16_to_cpu(dir_fi->lengthOfImpUse) + 3) & ~3); - if (old_iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) - mark_inode_dirty(old_inode); - else - mark_buffer_dirty_inode(dir_bh, old_inode); + if (has_diriter) { + diriter.fi.icb.extLocation = + cpu_to_lelb(UDF_I(new_dir)->i_location); + udf_fiiter_write_fi(&diriter, NULL); + udf_fiiter_release(&diriter); + } + if (is_dir) { inode_dec_link_count(old_dir); if (new_inode) inode_dec_link_count(new_inode); @@ -1192,22 +903,11 @@ static int udf_rename(struct inode *old_dir, struct dentry *old_dentry, mark_inode_dirty(new_dir); } } - - if (ofi) { - if (ofibh.sbh != ofibh.ebh) - brelse(ofibh.ebh); - brelse(ofibh.sbh); - } - - retval = 0; - -end_rename: - brelse(dir_bh); - if (nfi) { - if (nfibh.sbh != nfibh.ebh) - brelse(nfibh.ebh); - brelse(nfibh.sbh); - } + return 0; +out_oiter: + if (has_diriter) + udf_fiiter_release(&diriter); + udf_fiiter_release(&oiter); return retval; } @@ -1215,26 +915,16 @@ end_rename: static struct dentry *udf_get_parent(struct dentry *child) { struct kernel_lb_addr tloc; - struct inode *inode = NULL; - struct qstr dotdot = QSTR_INIT("..", 2); - struct fileIdentDesc cfi; - struct udf_fileident_bh fibh; - - if (!udf_find_entry(child->d_inode, &dotdot, &fibh, &cfi)) - goto out_unlock; - - if (fibh.sbh != fibh.ebh) - brelse(fibh.ebh); - brelse(fibh.sbh); + struct udf_fileident_iter iter; + int err; - tloc = lelb_to_cpu(cfi.icb.extLocation); - inode = udf_iget(child->d_inode->i_sb, &tloc); - if (!inode) - goto out_unlock; + err = udf_fiiter_find_entry(d_inode(child), &dotdot_name, &iter); + if (err) + return ERR_PTR(err); - return d_obtain_alias(inode); -out_unlock: - return ERR_PTR(-EACCES); + tloc = lelb_to_cpu(iter.fi.icb.extLocation); + udf_fiiter_release(&iter); + return d_obtain_alias(udf_iget(child->d_sb, &tloc)); } @@ -1251,8 +941,8 @@ static struct dentry *udf_nfs_get_inode(struct super_block *sb, u32 block, loc.partitionReferenceNum = partref; inode = udf_iget(sb, &loc); - if (inode == NULL) - return ERR_PTR(-ENOMEM); + if (IS_ERR(inode)) + return ERR_CAST(inode); if (generation && inode->i_generation != generation) { iput(inode); @@ -1264,7 +954,7 @@ static struct dentry *udf_nfs_get_inode(struct super_block *sb, u32 block, static struct dentry *udf_fh_to_dentry(struct super_block *sb, struct fid *fid, int fh_len, int fh_type) { - if ((fh_len != 3 && fh_len != 5) || + if (fh_len < 3 || (fh_type != FILEID_UDF_WITH_PARENT && fh_type != FILEID_UDF_WITHOUT_PARENT)) return NULL; @@ -1276,7 +966,7 @@ static struct dentry *udf_fh_to_dentry(struct super_block *sb, static struct dentry *udf_fh_to_parent(struct super_block *sb, struct fid *fid, int fh_len, int fh_type) { - if (fh_len != 5 || fh_type != FILEID_UDF_WITH_PARENT) + if (fh_len < 5 || fh_type != FILEID_UDF_WITH_PARENT) return NULL; return udf_nfs_get_inode(sb, fid->udf.parent_block, @@ -1336,8 +1026,3 @@ const struct inode_operations udf_dir_inode_operations = { .rename = udf_rename, .tmpfile = udf_tmpfile, }; -const struct inode_operations udf_symlink_inode_operations = { - .readlink = generic_readlink, - .follow_link = page_follow_link_light, - .put_link = page_put_link, -}; diff --git a/fs/udf/osta_udf.h b/fs/udf/osta_udf.h index fbff74654df2..157de0ec0cd5 100644 --- a/fs/udf/osta_udf.h +++ b/fs/udf/osta_udf.h @@ -1,10 +1,11 @@ /* * osta_udf.h * - * This file is based on OSTA UDF(tm) 2.50 (April 30, 2003) + * This file is based on OSTA UDF(tm) 2.60 (March 1, 2005) * http://www.osta.org * - * Copyright (c) 2001-2004 Ben Fennema <bfennema@falcon.csc.calpoly.edu> + * Copyright (c) 2001-2004 Ben Fennema + * Copyright (c) 2017-2019 Pali Rohár <pali@kernel.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,58 +33,77 @@ * SUCH DAMAGE. */ +/** + * @file + * OSTA-UDF defines and structure definitions + */ + #include "ecma_167.h" #ifndef _OSTA_UDF_H #define _OSTA_UDF_H 1 -/* OSTA CS0 Charspec (UDF 2.50 2.1.2) */ +/* OSTA CS0 Charspec (UDF 2.60 2.1.2) */ #define UDF_CHAR_SET_TYPE 0 #define UDF_CHAR_SET_INFO "OSTA Compressed Unicode" -/* Entity Identifier (UDF 2.50 2.1.5) */ -/* Identifiers (UDF 2.50 2.1.5.2) */ +/* Entity Identifier (UDF 2.60 2.1.5) */ +/* Identifiers (UDF 2.60 2.1.5.2) */ +/* Implementation Use Extended Attribute (UDF 2.60 3.3.4.5) */ +/* Virtual Allocation Table (UDF 1.50 2.2.10) */ +/* Logical Volume Extended Information (UDF 1.50 Errata, DCN 5003, 3.3.4.5.1.3) */ +/* OS2EA (UDF 1.50 3.3.4.5.3.1) */ +/* MacUniqueIDTable (UDF 1.50 3.3.4.5.4.3) */ +/* MacResourceFork (UDF 1.50 3.3.4.5.4.4) */ #define UDF_ID_DEVELOPER "*Linux UDFFS" #define UDF_ID_COMPLIANT "*OSTA UDF Compliant" #define UDF_ID_LV_INFO "*UDF LV Info" #define UDF_ID_FREE_EA "*UDF FreeEASpace" #define UDF_ID_FREE_APP_EA "*UDF FreeAppEASpace" #define UDF_ID_DVD_CGMS "*UDF DVD CGMS Info" +#define UDF_ID_VAT_LVEXTENSION "*UDF VAT LVExtension" #define UDF_ID_OS2_EA "*UDF OS/2 EA" #define UDF_ID_OS2_EA_LENGTH "*UDF OS/2 EALength" #define UDF_ID_MAC_VOLUME "*UDF Mac VolumeInfo" #define UDF_ID_MAC_FINDER "*UDF Mac FinderInfo" #define UDF_ID_MAC_UNIQUE "*UDF Mac UniqueIDTable" #define UDF_ID_MAC_RESOURCE "*UDF Mac ResourceFork" +#define UDF_ID_OS400_DIRINFO "*UDF OS/400 DirInfo" #define UDF_ID_VIRTUAL "*UDF Virtual Partition" #define UDF_ID_SPARABLE "*UDF Sparable Partition" #define UDF_ID_ALLOC "*UDF Virtual Alloc Tbl" #define UDF_ID_SPARING "*UDF Sparing Table" #define UDF_ID_METADATA "*UDF Metadata Partition" -/* Identifier Suffix (UDF 2.50 2.1.5.3) */ -#define IS_DF_HARD_WRITE_PROTECT 0x01 -#define IS_DF_SOFT_WRITE_PROTECT 0x02 +/* Identifier Suffix (UDF 2.60 2.1.5.3) */ +#define DOMAIN_FLAGS_HARD_WRITE_PROTECT 0x01 +#define DOMAIN_FLAGS_SOFT_WRITE_PROTECT 0x02 + +struct domainIdentSuffix { + __le16 UDFRevision; + uint8_t domainFlags; + uint8_t reserved[5]; +} __packed; struct UDFIdentSuffix { __le16 UDFRevision; uint8_t OSClass; uint8_t OSIdentifier; uint8_t reserved[4]; -} __attribute__ ((packed)); +} __packed; struct impIdentSuffix { uint8_t OSClass; uint8_t OSIdentifier; - uint8_t reserved[6]; -} __attribute__ ((packed)); + uint8_t impUse[6]; +} __packed; struct appIdentSuffix { uint8_t impUse[8]; -} __attribute__ ((packed)); +} __packed; -/* Logical Volume Integrity Descriptor (UDF 2.50 2.2.6) */ -/* Implementation Use (UDF 2.50 2.2.6.4) */ +/* Logical Volume Integrity Descriptor (UDF 2.60 2.2.6) */ +/* Implementation Use (UDF 2.60 2.2.6.4) */ struct logicalVolIntegrityDescImpUse { struct regid impIdent; __le32 numFiles; @@ -91,11 +111,11 @@ struct logicalVolIntegrityDescImpUse { __le16 minUDFReadRev; __le16 minUDFWriteRev; __le16 maxUDFWriteRev; - uint8_t impUse[0]; -} __attribute__ ((packed)); + uint8_t impUse[]; +} __packed; -/* Implementation Use Volume Descriptor (UDF 2.50 2.2.7) */ -/* Implementation Use (UDF 2.50 2.2.7.2) */ +/* Implementation Use Volume Descriptor (UDF 2.60 2.2.7) */ +/* Implementation Use (UDF 2.60 2.2.7.2) */ struct impUseVolDescImpUse { struct charspec LVICharset; dstring logicalVolIdent[128]; @@ -104,7 +124,7 @@ struct impUseVolDescImpUse { dstring LVInfo3[36]; struct regid impIdent; uint8_t impUse[128]; -} __attribute__ ((packed)); +} __packed; struct udfPartitionMap2 { uint8_t partitionMapType; @@ -113,9 +133,9 @@ struct udfPartitionMap2 { struct regid partIdent; __le16 volSeqNum; __le16 partitionNum; -} __attribute__ ((packed)); +} __packed; -/* Virtual Partition Map (UDF 2.50 2.2.8) */ +/* Virtual Partition Map (UDF 2.60 2.2.8) */ struct virtualPartitionMap { uint8_t partitionMapType; uint8_t partitionMapLength; @@ -124,9 +144,9 @@ struct virtualPartitionMap { __le16 volSeqNum; __le16 partitionNum; uint8_t reserved2[24]; -} __attribute__ ((packed)); +} __packed; -/* Sparable Partition Map (UDF 2.50 2.2.9) */ +/* Sparable Partition Map (UDF 2.60 2.2.9) */ struct sparablePartitionMap { uint8_t partitionMapType; uint8_t partitionMapLength; @@ -139,9 +159,9 @@ struct sparablePartitionMap { uint8_t reserved2[1]; __le32 sizeSparingTable; __le32 locSparingTable[4]; -} __attribute__ ((packed)); +} __packed; -/* Metadata Partition Map (UDF 2.4.0 2.2.10) */ +/* Metadata Partition Map (UDF 2.60 2.2.10) */ struct metadataPartitionMap { uint8_t partitionMapType; uint8_t partitionMapLength; @@ -156,18 +176,9 @@ struct metadataPartitionMap { __le16 alignUnitSize; uint8_t flags; uint8_t reserved2[5]; -} __attribute__ ((packed)); +} __packed; -/* Virtual Allocation Table (UDF 1.5 2.2.10) */ -struct virtualAllocationTable15 { - __le32 VirtualSector[0]; - struct regid vatIdent; - __le32 previousVATICBLoc; -} __attribute__ ((packed)); - -#define ICBTAG_FILE_TYPE_VAT15 0x00U - -/* Virtual Allocation Table (UDF 2.50 2.2.11) */ +/* Virtual Allocation Table (UDF 2.60 2.2.11) */ struct virtualAllocationTable20 { __le16 lengthHeader; __le16 lengthImpUse; @@ -175,21 +186,21 @@ struct virtualAllocationTable20 { __le32 previousVATICBLoc; __le32 numFiles; __le32 numDirs; - __le16 minReadRevision; - __le16 minWriteRevision; - __le16 maxWriteRevision; + __le16 minUDFReadRev; + __le16 minUDFWriteRev; + __le16 maxUDFWriteRev; __le16 reserved; - uint8_t impUse[0]; - __le32 vatEntry[0]; -} __attribute__ ((packed)); + uint8_t impUse[]; + /* __le32 vatEntry[]; */ +} __packed; #define ICBTAG_FILE_TYPE_VAT20 0xF8U -/* Sparing Table (UDF 2.50 2.2.12) */ +/* Sparing Table (UDF 2.60 2.2.12) */ struct sparingEntry { __le32 origLocation; __le32 mappedLocation; -} __attribute__ ((packed)); +} __packed; struct sparingTable { struct tag descTag; @@ -197,55 +208,69 @@ struct sparingTable { __le16 reallocationTableLen; __le16 reserved; __le32 sequenceNum; - struct sparingEntry - mapEntry[0]; -} __attribute__ ((packed)); + struct sparingEntry mapEntry[]; +} __packed; -/* Metadata File (and Metadata Mirror File) (UDF 2.50 2.2.13.1) */ +/* Metadata File (and Metadata Mirror File) (UDF 2.60 2.2.13.1) */ #define ICBTAG_FILE_TYPE_MAIN 0xFA #define ICBTAG_FILE_TYPE_MIRROR 0xFB #define ICBTAG_FILE_TYPE_BITMAP 0xFC -/* struct struct long_ad ICB - ADImpUse (UDF 2.50 2.2.4.3) */ +/* struct long_ad ICB - ADImpUse (UDF 2.60 2.2.4.3) */ struct allocDescImpUse { __le16 flags; uint8_t impUse[4]; -} __attribute__ ((packed)); +} __packed; #define AD_IU_EXT_ERASED 0x0001 -/* Real-Time Files (UDF 2.50 6.11) */ +/* Real-Time Files (UDF 2.60 6.11) */ #define ICBTAG_FILE_TYPE_REALTIME 0xF9U -/* Implementation Use Extended Attribute (UDF 2.50 3.3.4.5) */ -/* FreeEASpace (UDF 2.50 3.3.4.5.1.1) */ +/* Implementation Use Extended Attribute (UDF 2.60 3.3.4.5) */ +/* FreeEASpace (UDF 2.60 3.3.4.5.1.1) */ struct freeEaSpace { __le16 headerChecksum; - uint8_t freeEASpace[0]; -} __attribute__ ((packed)); + uint8_t freeEASpace[]; +} __packed; -/* DVD Copyright Management Information (UDF 2.50 3.3.4.5.1.2) */ +/* DVD Copyright Management Information (UDF 2.60 3.3.4.5.1.2) */ struct DVDCopyrightImpUse { __le16 headerChecksum; uint8_t CGMSInfo; uint8_t dataType; uint8_t protectionSystemInfo[4]; -} __attribute__ ((packed)); +} __packed; + +/* Logical Volume Extended Information (UDF 1.50 Errata, DCN 5003, 3.3.4.5.1.3) */ +struct LVExtensionEA { + __le16 headerChecksum; + __le64 verificationID; + __le32 numFiles; + __le32 numDirs; + dstring logicalVolIdent[128]; +} __packed; -/* Application Use Extended Attribute (UDF 2.50 3.3.4.6) */ -/* FreeAppEASpace (UDF 2.50 3.3.4.6.1) */ +/* Application Use Extended Attribute (UDF 2.60 3.3.4.6) */ +/* FreeAppEASpace (UDF 2.60 3.3.4.6.1) */ struct freeAppEASpace { __le16 headerChecksum; - uint8_t freeEASpace[0]; -} __attribute__ ((packed)); + uint8_t freeEASpace[]; +} __packed; -/* UDF Defined System Stream (UDF 2.50 3.3.7) */ +/* UDF Defined System Stream (UDF 2.60 3.3.7) */ #define UDF_ID_UNIQUE_ID "*UDF Unique ID Mapping Data" #define UDF_ID_NON_ALLOC "*UDF Non-Allocatable Space" #define UDF_ID_POWER_CAL "*UDF Power Cal Table" #define UDF_ID_BACKUP "*UDF Backup" -/* Operating System Identifiers (UDF 2.50 6.3) */ +/* UDF Defined Non-System Streams (UDF 2.60 3.3.8) */ +#define UDF_ID_MAC_RESOURCE_FORK_STREAM "*UDF Macintosh Resource Fork" +/* #define UDF_ID_OS2_EA "*UDF OS/2 EA" */ +#define UDF_ID_NT_ACL "*UDF NT ACL" +#define UDF_ID_UNIX_ACL "*UDF UNIX ACL" + +/* Operating System Identifiers (UDF 2.60 6.3) */ #define UDF_OS_CLASS_UNDEF 0x00U #define UDF_OS_CLASS_DOS 0x01U #define UDF_OS_CLASS_OS2 0x02U @@ -270,6 +295,7 @@ struct freeAppEASpace { #define UDF_OS_ID_LINUX 0x05U #define UDF_OS_ID_MKLINUX 0x06U #define UDF_OS_ID_FREEBSD 0x07U +#define UDF_OS_ID_NETBSD 0x08U #define UDF_OS_ID_WIN9X 0x00U #define UDF_OS_ID_WINNT 0x00U #define UDF_OS_ID_OS400 0x00U diff --git a/fs/udf/partition.c b/fs/udf/partition.c index d6caf01a2097..2b85c9501bed 100644 --- a/fs/udf/partition.c +++ b/fs/udf/partition.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * partition.c * @@ -5,11 +6,6 @@ * Partition handling routines for the OSTA-UDF(tm) filesystem. * * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. - * * (C) 1998-2001 Ben Fennema * * HISTORY @@ -24,7 +20,6 @@ #include <linux/fs.h> #include <linux/string.h> -#include <linux/buffer_head.h> #include <linux/mutex.h> uint32_t udf_get_pblock(struct super_block *sb, uint32_t block, @@ -33,7 +28,7 @@ uint32_t udf_get_pblock(struct super_block *sb, uint32_t block, struct udf_sb_info *sbi = UDF_SB(sb); struct udf_part_map *map; if (partition >= sbi->s_partitions) { - udf_debug("block=%d, partition=%d, offset=%d: invalid partition\n", + udf_debug("block=%u, partition=%u, offset=%u: invalid partition\n", block, partition, offset); return 0xFFFFFFFF; } @@ -55,18 +50,19 @@ uint32_t udf_get_pblock_virt15(struct super_block *sb, uint32_t block, struct udf_part_map *map; struct udf_virtual_data *vdata; struct udf_inode_info *iinfo = UDF_I(sbi->s_vat_inode); + int err; map = &sbi->s_partmaps[partition]; vdata = &map->s_type_specific.s_virtual; if (block > vdata->s_num_entries) { - udf_debug("Trying to access block beyond end of VAT (%d max %d)\n", + udf_debug("Trying to access block beyond end of VAT (%u max %u)\n", block, vdata->s_num_entries); return 0xFFFFFFFF; } if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { - loc = le32_to_cpu(((__le32 *)(iinfo->i_ext.i_data + + loc = le32_to_cpu(((__le32 *)(iinfo->i_data + vdata->s_start_offset))[block]); goto translate; } @@ -80,12 +76,10 @@ uint32_t udf_get_pblock_virt15(struct super_block *sb, uint32_t block, index = vdata->s_start_offset / sizeof(uint32_t) + block; } - loc = udf_block_map(sbi->s_vat_inode, newblock); - - bh = sb_bread(sb, loc); + bh = udf_bread(sbi->s_vat_inode, newblock, 0, &err); if (!bh) { - udf_debug("get_pblock(UDF_VIRTUAL_MAP:%p,%d,%d) VAT: %d[%d]\n", - sb, block, partition, loc, index); + udf_debug("get_pblock(UDF_VIRTUAL_MAP:%p,%u,%u)\n", + sb, block, partition); return 0xFFFFFFFF; } @@ -288,15 +282,18 @@ static uint32_t udf_try_read_meta(struct inode *inode, uint32_t block, sector_t ext_offset; struct extent_position epos = {}; uint32_t phyblock; + int8_t etype; + int err = 0; - if (inode_bmap(inode, block, &epos, &eloc, &elen, &ext_offset) != - (EXT_RECORDED_ALLOCATED >> 30)) + err = inode_bmap(inode, block, &epos, &eloc, &elen, &ext_offset, &etype); + if (err <= 0 || etype != (EXT_RECORDED_ALLOCATED >> 30)) phyblock = 0xFFFFFFFF; else { map = &UDF_SB(sb)->s_partmaps[partition]; /* map to sparable/physical partition desc */ phyblock = udf_get_pblock(sb, eloc.logicalBlockNum, - map->s_partition_num, ext_offset + offset); + map->s_type_specific.s_metadata.s_phys_partition_ref, + ext_offset + offset); } brelse(epos.bh); @@ -318,14 +315,18 @@ uint32_t udf_get_pblock_meta25(struct super_block *sb, uint32_t block, mdata = &map->s_type_specific.s_metadata; inode = mdata->s_metadata_fe ? : mdata->s_mirror_fe; - /* We shouldn't mount such media... */ - BUG_ON(!inode); + if (!inode) + return 0xFFFFFFFF; + retblk = udf_try_read_meta(inode, block, partition, offset); if (retblk == 0xFFFFFFFF && mdata->s_metadata_fe) { udf_warn(sb, "error reading from METADATA, trying to read from MIRROR\n"); if (!(mdata->s_flags & MF_MIRROR_FE_LOADED)) { mdata->s_mirror_fe = udf_find_metadata_inode_efe(sb, - mdata->s_mirror_file_loc, map->s_partition_num); + mdata->s_mirror_file_loc, + mdata->s_phys_partition_ref); + if (IS_ERR(mdata->s_mirror_fe)) + mdata->s_mirror_fe = NULL; mdata->s_flags |= MF_MIRROR_FE_LOADED; } diff --git a/fs/udf/super.c b/fs/udf/super.c index 9ac4057a86c9..b2f168b0a0d1 100644 --- a/fs/udf/super.c +++ b/fs/udf/super.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * super.c * @@ -11,15 +12,10 @@ * This code is based on version 2.00 of the UDF specification, * and revision 3 of the ECMA 167 standard [equivalent to ISO 13346]. * http://www.osta.org/ - * http://www.ecma.ch/ - * http://www.iso.org/ + * https://www.ecma.ch/ + * https://www.iso.org/ * * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. - * * (C) 1998 Dave Boynton * (C) 1998-2004 Ben Fennema * (C) 2000 Stelias Computing Inc @@ -44,79 +40,108 @@ #include <linux/slab.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/parser.h> #include <linux/stat.h> #include <linux/cdrom.h> #include <linux/nls.h> -#include <linux/buffer_head.h> #include <linux/vfs.h> #include <linux/vmalloc.h> #include <linux/errno.h> -#include <linux/mount.h> #include <linux/seq_file.h> #include <linux/bitmap.h> #include <linux/crc-itu-t.h> #include <linux/log2.h> #include <asm/byteorder.h> +#include <linux/iversion.h> +#include <linux/fs_context.h> +#include <linux/fs_parser.h> #include "udf_sb.h" #include "udf_i.h" #include <linux/init.h> -#include <asm/uaccess.h> +#include <linux/uaccess.h> -#define VDS_POS_PRIMARY_VOL_DESC 0 -#define VDS_POS_UNALLOC_SPACE_DESC 1 -#define VDS_POS_LOGICAL_VOL_DESC 2 -#define VDS_POS_PARTITION_DESC 3 -#define VDS_POS_IMP_USE_VOL_DESC 4 -#define VDS_POS_VOL_DESC_PTR 5 -#define VDS_POS_TERMINATING_DESC 6 -#define VDS_POS_LENGTH 7 +enum { + VDS_POS_PRIMARY_VOL_DESC, + VDS_POS_UNALLOC_SPACE_DESC, + VDS_POS_LOGICAL_VOL_DESC, + VDS_POS_IMP_USE_VOL_DESC, + VDS_POS_LENGTH +}; -#define UDF_DEFAULT_BLOCKSIZE 2048 +#define VSD_FIRST_SECTOR_OFFSET 32768 +#define VSD_MAX_SECTOR_OFFSET 0x800000 + +/* + * Maximum number of Terminating Descriptor / Logical Volume Integrity + * Descriptor redirections. The chosen numbers are arbitrary - just that we + * hopefully don't limit any real use of rewritten inode on write-once media + * but avoid looping for too long on corrupted media. + */ +#define UDF_MAX_TD_NESTING 64 +#define UDF_MAX_LVID_NESTING 1000 enum { UDF_MAX_LINKS = 0xffff }; +/* + * We limit filesize to 4TB. This is arbitrary as the on-disk format supports + * more but because the file space is described by a linked list of extents, + * each of which can have at most 1GB, the creation and handling of extents + * gets unusably slow beyond certain point... + */ +#define UDF_MAX_FILESIZE (1ULL << 42) /* These are the "meat" - everything else is stuffing */ -static int udf_fill_super(struct super_block *, void *, int); +static int udf_fill_super(struct super_block *sb, struct fs_context *fc); static void udf_put_super(struct super_block *); static int udf_sync_fs(struct super_block *, int); -static int udf_remount_fs(struct super_block *, int *, char *); static void udf_load_logicalvolint(struct super_block *, struct kernel_extent_ad); -static int udf_find_fileset(struct super_block *, struct kernel_lb_addr *, - struct kernel_lb_addr *); -static void udf_load_fileset(struct super_block *, struct buffer_head *, - struct kernel_lb_addr *); static void udf_open_lvid(struct super_block *); static void udf_close_lvid(struct super_block *); static unsigned int udf_count_free(struct super_block *); static int udf_statfs(struct dentry *, struct kstatfs *); static int udf_show_options(struct seq_file *, struct dentry *); +static int udf_init_fs_context(struct fs_context *fc); +static int udf_parse_param(struct fs_context *fc, struct fs_parameter *param); +static int udf_reconfigure(struct fs_context *fc); +static void udf_free_fc(struct fs_context *fc); +static const struct fs_parameter_spec udf_param_spec[]; -struct logicalVolIntegrityDescImpUse *udf_sb_lvidiu(struct udf_sb_info *sbi) +struct logicalVolIntegrityDescImpUse *udf_sb_lvidiu(struct super_block *sb) { - struct logicalVolIntegrityDesc *lvid = - (struct logicalVolIntegrityDesc *)sbi->s_lvid_bh->b_data; - __u32 number_of_partitions = le32_to_cpu(lvid->numOfPartitions); - __u32 offset = number_of_partitions * 2 * - sizeof(uint32_t)/sizeof(uint8_t); - return (struct logicalVolIntegrityDescImpUse *)&(lvid->impUse[offset]); + struct logicalVolIntegrityDesc *lvid; + unsigned int partnum; + unsigned int offset; + + if (!UDF_SB(sb)->s_lvid_bh) + return NULL; + lvid = (struct logicalVolIntegrityDesc *)UDF_SB(sb)->s_lvid_bh->b_data; + partnum = le32_to_cpu(lvid->numOfPartitions); + /* The offset is to skip freeSpaceTable and sizeTable arrays */ + offset = partnum * 2 * sizeof(uint32_t); + return (struct logicalVolIntegrityDescImpUse *) + (((uint8_t *)(lvid + 1)) + offset); } /* UDF filesystem type */ -static struct dentry *udf_mount(struct file_system_type *fs_type, - int flags, const char *dev_name, void *data) +static int udf_get_tree(struct fs_context *fc) { - return mount_bdev(fs_type, flags, dev_name, data, udf_fill_super); + return get_tree_bdev(fc, udf_fill_super); } +static const struct fs_context_operations udf_context_ops = { + .parse_param = udf_parse_param, + .get_tree = udf_get_tree, + .reconfigure = udf_reconfigure, + .free = udf_free_fc, +}; + static struct file_system_type udf_fstype = { .owner = THIS_MODULE, .name = "udf", - .mount = udf_mount, .kill_sb = kill_block_super, .fs_flags = FS_REQUIRES_DEV, + .init_fs_context = udf_init_fs_context, + .parameters = udf_param_spec, }; MODULE_ALIAS_FS("udf"); @@ -125,47 +150,45 @@ static struct kmem_cache *udf_inode_cachep; static struct inode *udf_alloc_inode(struct super_block *sb) { struct udf_inode_info *ei; - ei = kmem_cache_alloc(udf_inode_cachep, GFP_KERNEL); + ei = alloc_inode_sb(sb, udf_inode_cachep, GFP_KERNEL); if (!ei) return NULL; ei->i_unique = 0; ei->i_lenExtents = 0; + ei->i_lenStreams = 0; ei->i_next_alloc_block = 0; ei->i_next_alloc_goal = 0; ei->i_strat4096 = 0; + ei->i_streamdir = 0; + ei->i_hidden = 0; init_rwsem(&ei->i_data_sem); ei->cached_extent.lstart = -1; spin_lock_init(&ei->i_extent_cache_lock); + inode_set_iversion(&ei->vfs_inode, 1); return &ei->vfs_inode; } -static void udf_i_callback(struct rcu_head *head) +static void udf_free_in_core_inode(struct inode *inode) { - struct inode *inode = container_of(head, struct inode, i_rcu); kmem_cache_free(udf_inode_cachep, UDF_I(inode)); } -static void udf_destroy_inode(struct inode *inode) -{ - call_rcu(&inode->i_rcu, udf_i_callback); -} - static void init_once(void *foo) { - struct udf_inode_info *ei = (struct udf_inode_info *)foo; + struct udf_inode_info *ei = foo; - ei->i_ext.i_data = NULL; + ei->i_data = NULL; inode_init_once(&ei->vfs_inode); } -static int init_inodecache(void) +static int __init init_inodecache(void) { udf_inode_cachep = kmem_cache_create("udf_inode_cache", sizeof(struct udf_inode_info), 0, (SLAB_RECLAIM_ACCOUNT | - SLAB_MEM_SPREAD), + SLAB_ACCOUNT), init_once); if (!udf_inode_cachep) return -ENOMEM; @@ -185,26 +208,20 @@ static void destroy_inodecache(void) /* Superblock operations */ static const struct super_operations udf_sb_ops = { .alloc_inode = udf_alloc_inode, - .destroy_inode = udf_destroy_inode, + .free_inode = udf_free_in_core_inode, .write_inode = udf_write_inode, .evict_inode = udf_evict_inode, .put_super = udf_put_super, .sync_fs = udf_sync_fs, .statfs = udf_statfs, - .remount_fs = udf_remount_fs, .show_options = udf_show_options, }; struct udf_options { - unsigned char novrs; unsigned int blocksize; unsigned int session; unsigned int lastblock; unsigned int anchor; - unsigned int volume; - unsigned short partition; - unsigned int fileset; - unsigned int rootdir; unsigned int flags; umode_t umask; kgid_t gid; @@ -214,6 +231,65 @@ struct udf_options { struct nls_table *nls_map; }; +/* + * UDF has historically preserved prior mount options across + * a remount, so copy those here if remounting, otherwise set + * initial mount defaults. + */ +static void udf_init_options(struct fs_context *fc, struct udf_options *uopt) +{ + if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) { + struct super_block *sb = fc->root->d_sb; + struct udf_sb_info *sbi = UDF_SB(sb); + + uopt->flags = sbi->s_flags; + uopt->uid = sbi->s_uid; + uopt->gid = sbi->s_gid; + uopt->umask = sbi->s_umask; + uopt->fmode = sbi->s_fmode; + uopt->dmode = sbi->s_dmode; + uopt->nls_map = NULL; + } else { + uopt->flags = (1 << UDF_FLAG_USE_AD_IN_ICB) | + (1 << UDF_FLAG_STRICT); + /* + * By default we'll use overflow[ug]id when UDF + * inode [ug]id == -1 + */ + uopt->uid = make_kuid(current_user_ns(), overflowuid); + uopt->gid = make_kgid(current_user_ns(), overflowgid); + uopt->umask = 0; + uopt->fmode = UDF_INVALID_MODE; + uopt->dmode = UDF_INVALID_MODE; + uopt->nls_map = NULL; + uopt->session = 0xFFFFFFFF; + } +} + +static int udf_init_fs_context(struct fs_context *fc) +{ + struct udf_options *uopt; + + uopt = kzalloc(sizeof(*uopt), GFP_KERNEL); + if (!uopt) + return -ENOMEM; + + udf_init_options(fc, uopt); + + fc->fs_private = uopt; + fc->ops = &udf_context_ops; + + return 0; +} + +static void udf_free_fc(struct fs_context *fc) +{ + struct udf_options *uopt = fc->fs_private; + + unload_nls(uopt->nls_map); + kfree(fc->fs_private); +} + static int __init init_udf_fs(void) { int err; @@ -240,18 +316,12 @@ static void __exit exit_udf_fs(void) destroy_inodecache(); } -module_init(init_udf_fs) -module_exit(exit_udf_fs) - static int udf_sb_alloc_partition_maps(struct super_block *sb, u32 count) { struct udf_sb_info *sbi = UDF_SB(sb); - sbi->s_partmaps = kcalloc(count, sizeof(struct udf_part_map), - GFP_KERNEL); + sbi->s_partmaps = kcalloc(count, sizeof(*sbi->s_partmaps), GFP_KERNEL); if (!sbi->s_partmaps) { - udf_err(sb, "Unable to allocate space for %d partition maps\n", - count); sbi->s_partitions = 0; return -ENOMEM; } @@ -264,17 +334,12 @@ static void udf_sb_free_bitmap(struct udf_bitmap *bitmap) { int i; int nr_groups = bitmap->s_nr_groups; - int size = sizeof(struct udf_bitmap) + (sizeof(struct buffer_head *) * - nr_groups); for (i = 0; i < nr_groups; i++) - if (bitmap->s_block_bitmap[i]) + if (!IS_ERR_OR_NULL(bitmap->s_block_bitmap[i])) brelse(bitmap->s_block_bitmap[i]); - if (size <= PAGE_SIZE) - kfree(bitmap); - else - vfree(bitmap); + kvfree(bitmap); } static void udf_free_partition(struct udf_part_map *map) @@ -284,12 +349,8 @@ static void udf_free_partition(struct udf_part_map *map) if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_TABLE) iput(map->s_uspace.s_table); - if (map->s_partition_flags & UDF_PART_FLAG_FREED_TABLE) - iput(map->s_fspace.s_table); if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_BITMAP) udf_sb_free_bitmap(map->s_uspace.s_bitmap); - if (map->s_partition_flags & UDF_PART_FLAG_FREED_BITMAP) - udf_sb_free_bitmap(map->s_fspace.s_bitmap); if (map->s_partition_type == UDF_SPARABLE_MAP15) for (i = 0; i < 4; i++) brelse(map->s_type_specific.s_sparing.s_spar_map[i]); @@ -310,7 +371,8 @@ static void udf_sb_free_partitions(struct super_block *sb) { struct udf_sb_info *sbi = UDF_SB(sb); int i; - if (sbi->s_partmaps == NULL) + + if (!sbi->s_partmaps) return; for (i = 0; i < sbi->s_partitions; i++) udf_free_partition(&sbi->s_partmaps[i]); @@ -337,12 +399,8 @@ static int udf_show_options(struct seq_file *seq, struct dentry *root) seq_puts(seq, ",shortad"); if (UDF_QUERY_FLAG(sb, UDF_FLAG_UID_FORGET)) seq_puts(seq, ",uid=forget"); - if (UDF_QUERY_FLAG(sb, UDF_FLAG_UID_IGNORE)) - seq_puts(seq, ",uid=ignore"); if (UDF_QUERY_FLAG(sb, UDF_FLAG_GID_FORGET)) seq_puts(seq, ",gid=forget"); - if (UDF_QUERY_FLAG(sb, UDF_FLAG_GID_IGNORE)) - seq_puts(seq, ",gid=ignore"); if (UDF_QUERY_FLAG(sb, UDF_FLAG_UID_SET)) seq_printf(seq, ",uid=%u", from_kuid(&init_user_ns, sbi->s_uid)); if (UDF_QUERY_FLAG(sb, UDF_FLAG_GID_SET)) @@ -354,25 +412,21 @@ static int udf_show_options(struct seq_file *seq, struct dentry *root) if (sbi->s_dmode != UDF_INVALID_MODE) seq_printf(seq, ",dmode=%ho", sbi->s_dmode); if (UDF_QUERY_FLAG(sb, UDF_FLAG_SESSION_SET)) - seq_printf(seq, ",session=%u", sbi->s_session); + seq_printf(seq, ",session=%d", sbi->s_session); if (UDF_QUERY_FLAG(sb, UDF_FLAG_LASTBLOCK_SET)) seq_printf(seq, ",lastblock=%u", sbi->s_last_block); if (sbi->s_anchor != 0) seq_printf(seq, ",anchor=%u", sbi->s_anchor); - /* - * volume, partition, fileset and rootdir seem to be ignored - * currently - */ - if (UDF_QUERY_FLAG(sb, UDF_FLAG_UTF8)) - seq_puts(seq, ",utf8"); - if (UDF_QUERY_FLAG(sb, UDF_FLAG_NLS_MAP) && sbi->s_nls_map) + if (sbi->s_nls_map) seq_printf(seq, ",iocharset=%s", sbi->s_nls_map->charset); + else + seq_puts(seq, ",iocharset=utf8"); return 0; } /* - * udf_parse_options + * udf_parse_param * * PURPOSE * Parse mount options. @@ -415,12 +469,12 @@ static int udf_show_options(struct seq_file *seq, struct dentry *root) * yield highly unpredictable results. * * PRE-CONDITIONS - * options Pointer to mount options string. - * uopts Pointer to mount options variable. + * fc fs_context with pointer to mount options variable. + * param Pointer to fs_parameter being parsed. * * POST-CONDITIONS - * <return> 1 Mount options parsed okay. - * <return> 0 Error parsing mount options. + * <return> 0 Mount options parsed okay. + * <return> errno Error parsing mount options. * * HISTORY * July 1, 1997 - Andrew E. Mileski @@ -432,233 +486,193 @@ enum { Opt_noadinicb, Opt_adinicb, Opt_shortad, Opt_longad, Opt_gid, Opt_uid, Opt_umask, Opt_session, Opt_lastblock, Opt_anchor, Opt_volume, Opt_partition, Opt_fileset, - Opt_rootdir, Opt_utf8, Opt_iocharset, - Opt_err, Opt_uforget, Opt_uignore, Opt_gforget, Opt_gignore, - Opt_fmode, Opt_dmode -}; - -static const match_table_t tokens = { - {Opt_novrs, "novrs"}, - {Opt_nostrict, "nostrict"}, - {Opt_bs, "bs=%u"}, - {Opt_unhide, "unhide"}, - {Opt_undelete, "undelete"}, - {Opt_noadinicb, "noadinicb"}, - {Opt_adinicb, "adinicb"}, - {Opt_shortad, "shortad"}, - {Opt_longad, "longad"}, - {Opt_uforget, "uid=forget"}, - {Opt_uignore, "uid=ignore"}, - {Opt_gforget, "gid=forget"}, - {Opt_gignore, "gid=ignore"}, - {Opt_gid, "gid=%u"}, - {Opt_uid, "uid=%u"}, - {Opt_umask, "umask=%o"}, - {Opt_session, "session=%u"}, - {Opt_lastblock, "lastblock=%u"}, - {Opt_anchor, "anchor=%u"}, - {Opt_volume, "volume=%u"}, - {Opt_partition, "partition=%u"}, - {Opt_fileset, "fileset=%u"}, - {Opt_rootdir, "rootdir=%u"}, - {Opt_utf8, "utf8"}, - {Opt_iocharset, "iocharset=%s"}, - {Opt_fmode, "mode=%o"}, - {Opt_dmode, "dmode=%o"}, - {Opt_err, NULL} + Opt_rootdir, Opt_utf8, Opt_iocharset, Opt_err, Opt_fmode, Opt_dmode }; -static int udf_parse_options(char *options, struct udf_options *uopt, - bool remount) +static const struct fs_parameter_spec udf_param_spec[] = { + fsparam_flag ("novrs", Opt_novrs), + fsparam_flag ("nostrict", Opt_nostrict), + fsparam_u32 ("bs", Opt_bs), + fsparam_flag ("unhide", Opt_unhide), + fsparam_flag ("undelete", Opt_undelete), + fsparam_flag_no ("adinicb", Opt_adinicb), + fsparam_flag ("shortad", Opt_shortad), + fsparam_flag ("longad", Opt_longad), + fsparam_string ("gid", Opt_gid), + fsparam_string ("uid", Opt_uid), + fsparam_u32 ("umask", Opt_umask), + fsparam_u32 ("session", Opt_session), + fsparam_u32 ("lastblock", Opt_lastblock), + fsparam_u32 ("anchor", Opt_anchor), + fsparam_u32 ("volume", Opt_volume), + fsparam_u32 ("partition", Opt_partition), + fsparam_u32 ("fileset", Opt_fileset), + fsparam_u32 ("rootdir", Opt_rootdir), + fsparam_flag ("utf8", Opt_utf8), + fsparam_string ("iocharset", Opt_iocharset), + fsparam_u32 ("mode", Opt_fmode), + fsparam_u32 ("dmode", Opt_dmode), + {} + }; + +static int udf_parse_param(struct fs_context *fc, struct fs_parameter *param) { - char *p; - int option; - - uopt->novrs = 0; - uopt->partition = 0xFFFF; - uopt->session = 0xFFFFFFFF; - uopt->lastblock = 0; - uopt->anchor = 0; - uopt->volume = 0xFFFFFFFF; - uopt->rootdir = 0xFFFFFFFF; - uopt->fileset = 0xFFFFFFFF; - uopt->nls_map = NULL; - - if (!options) - return 1; - - while ((p = strsep(&options, ",")) != NULL) { - substring_t args[MAX_OPT_ARGS]; - int token; - if (!*p) - continue; - - token = match_token(p, tokens, args); - switch (token) { - case Opt_novrs: - uopt->novrs = 1; - break; - case Opt_bs: - if (match_int(&args[0], &option)) - return 0; - uopt->blocksize = option; - uopt->flags |= (1 << UDF_FLAG_BLOCKSIZE_SET); - break; - case Opt_unhide: - uopt->flags |= (1 << UDF_FLAG_UNHIDE); - break; - case Opt_undelete: - uopt->flags |= (1 << UDF_FLAG_UNDELETE); - break; - case Opt_noadinicb: + unsigned int uv; + unsigned int n; + struct udf_options *uopt = fc->fs_private; + struct fs_parse_result result; + int token; + bool remount = (fc->purpose & FS_CONTEXT_FOR_RECONFIGURE); + + token = fs_parse(fc, udf_param_spec, param, &result); + if (token < 0) + return token; + + switch (token) { + case Opt_novrs: + uopt->flags |= (1 << UDF_FLAG_NOVRS); + break; + case Opt_bs: + n = result.uint_32; + if (n != 512 && n != 1024 && n != 2048 && n != 4096) + return -EINVAL; + uopt->blocksize = n; + uopt->flags |= (1 << UDF_FLAG_BLOCKSIZE_SET); + break; + case Opt_unhide: + uopt->flags |= (1 << UDF_FLAG_UNHIDE); + break; + case Opt_undelete: + uopt->flags |= (1 << UDF_FLAG_UNDELETE); + break; + case Opt_adinicb: + if (result.negated) uopt->flags &= ~(1 << UDF_FLAG_USE_AD_IN_ICB); - break; - case Opt_adinicb: + else uopt->flags |= (1 << UDF_FLAG_USE_AD_IN_ICB); - break; - case Opt_shortad: - uopt->flags |= (1 << UDF_FLAG_USE_SHORT_AD); - break; - case Opt_longad: - uopt->flags &= ~(1 << UDF_FLAG_USE_SHORT_AD); - break; - case Opt_gid: - if (match_int(args, &option)) - return 0; - uopt->gid = make_kgid(current_user_ns(), option); - if (!gid_valid(uopt->gid)) - return 0; + break; + case Opt_shortad: + uopt->flags |= (1 << UDF_FLAG_USE_SHORT_AD); + break; + case Opt_longad: + uopt->flags &= ~(1 << UDF_FLAG_USE_SHORT_AD); + break; + case Opt_gid: + if (kstrtoint(param->string, 10, &uv) == 0) { + kgid_t gid = make_kgid(current_user_ns(), uv); + if (!gid_valid(gid)) + return -EINVAL; + uopt->gid = gid; uopt->flags |= (1 << UDF_FLAG_GID_SET); - break; - case Opt_uid: - if (match_int(args, &option)) - return 0; - uopt->uid = make_kuid(current_user_ns(), option); - if (!uid_valid(uopt->uid)) - return 0; + } else if (!strcmp(param->string, "forget")) { + uopt->flags |= (1 << UDF_FLAG_GID_FORGET); + } else if (!strcmp(param->string, "ignore")) { + /* this option is superseded by gid=<number> */ + ; + } else { + return -EINVAL; + } + break; + case Opt_uid: + if (kstrtoint(param->string, 10, &uv) == 0) { + kuid_t uid = make_kuid(current_user_ns(), uv); + if (!uid_valid(uid)) + return -EINVAL; + uopt->uid = uid; uopt->flags |= (1 << UDF_FLAG_UID_SET); - break; - case Opt_umask: - if (match_octal(args, &option)) - return 0; - uopt->umask = option; - break; - case Opt_nostrict: - uopt->flags &= ~(1 << UDF_FLAG_STRICT); - break; - case Opt_session: - if (match_int(args, &option)) - return 0; - uopt->session = option; - if (!remount) - uopt->flags |= (1 << UDF_FLAG_SESSION_SET); - break; - case Opt_lastblock: - if (match_int(args, &option)) - return 0; - uopt->lastblock = option; - if (!remount) - uopt->flags |= (1 << UDF_FLAG_LASTBLOCK_SET); - break; - case Opt_anchor: - if (match_int(args, &option)) - return 0; - uopt->anchor = option; - break; - case Opt_volume: - if (match_int(args, &option)) - return 0; - uopt->volume = option; - break; - case Opt_partition: - if (match_int(args, &option)) - return 0; - uopt->partition = option; - break; - case Opt_fileset: - if (match_int(args, &option)) - return 0; - uopt->fileset = option; - break; - case Opt_rootdir: - if (match_int(args, &option)) - return 0; - uopt->rootdir = option; - break; - case Opt_utf8: - uopt->flags |= (1 << UDF_FLAG_UTF8); - break; -#ifdef CONFIG_UDF_NLS - case Opt_iocharset: - uopt->nls_map = load_nls(args[0].from); - uopt->flags |= (1 << UDF_FLAG_NLS_MAP); - break; -#endif - case Opt_uignore: - uopt->flags |= (1 << UDF_FLAG_UID_IGNORE); - break; - case Opt_uforget: + } else if (!strcmp(param->string, "forget")) { uopt->flags |= (1 << UDF_FLAG_UID_FORGET); - break; - case Opt_gignore: - uopt->flags |= (1 << UDF_FLAG_GID_IGNORE); - break; - case Opt_gforget: - uopt->flags |= (1 << UDF_FLAG_GID_FORGET); - break; - case Opt_fmode: - if (match_octal(args, &option)) - return 0; - uopt->fmode = option & 0777; - break; - case Opt_dmode: - if (match_octal(args, &option)) - return 0; - uopt->dmode = option & 0777; - break; - default: - pr_err("bad mount option \"%s\" or missing value\n", p); - return 0; + } else if (!strcmp(param->string, "ignore")) { + /* this option is superseded by uid=<number> */ + ; + } else { + return -EINVAL; + } + break; + case Opt_umask: + uopt->umask = result.uint_32; + break; + case Opt_nostrict: + uopt->flags &= ~(1 << UDF_FLAG_STRICT); + break; + case Opt_session: + uopt->session = result.uint_32; + if (!remount) + uopt->flags |= (1 << UDF_FLAG_SESSION_SET); + break; + case Opt_lastblock: + uopt->lastblock = result.uint_32; + if (!remount) + uopt->flags |= (1 << UDF_FLAG_LASTBLOCK_SET); + break; + case Opt_anchor: + uopt->anchor = result.uint_32; + break; + case Opt_volume: + case Opt_partition: + case Opt_fileset: + case Opt_rootdir: + /* Ignored (never implemented properly) */ + break; + case Opt_utf8: + if (!remount) { + unload_nls(uopt->nls_map); + uopt->nls_map = NULL; } + break; + case Opt_iocharset: + if (!remount) { + unload_nls(uopt->nls_map); + uopt->nls_map = NULL; + } + /* When nls_map is not loaded then UTF-8 is used */ + if (!remount && strcmp(param->string, "utf8") != 0) { + uopt->nls_map = load_nls(param->string); + if (!uopt->nls_map) { + errorf(fc, "iocharset %s not found", + param->string); + return -EINVAL; + } + } + break; + case Opt_fmode: + uopt->fmode = result.uint_32 & 0777; + break; + case Opt_dmode: + uopt->dmode = result.uint_32 & 0777; + break; + default: + return -EINVAL; } - return 1; + return 0; } -static int udf_remount_fs(struct super_block *sb, int *flags, char *options) +static int udf_reconfigure(struct fs_context *fc) { - struct udf_options uopt; + struct udf_options *uopt = fc->fs_private; + struct super_block *sb = fc->root->d_sb; struct udf_sb_info *sbi = UDF_SB(sb); + int readonly = fc->sb_flags & SB_RDONLY; int error = 0; - uopt.flags = sbi->s_flags; - uopt.uid = sbi->s_uid; - uopt.gid = sbi->s_gid; - uopt.umask = sbi->s_umask; - uopt.fmode = sbi->s_fmode; - uopt.dmode = sbi->s_dmode; + if (!readonly && UDF_QUERY_FLAG(sb, UDF_FLAG_RW_INCOMPAT)) + return -EACCES; - if (!udf_parse_options(options, &uopt, true)) - return -EINVAL; + sync_filesystem(sb); write_lock(&sbi->s_cred_lock); - sbi->s_flags = uopt.flags; - sbi->s_uid = uopt.uid; - sbi->s_gid = uopt.gid; - sbi->s_umask = uopt.umask; - sbi->s_fmode = uopt.fmode; - sbi->s_dmode = uopt.dmode; + sbi->s_flags = uopt->flags; + sbi->s_uid = uopt->uid; + sbi->s_gid = uopt->gid; + sbi->s_umask = uopt->umask; + sbi->s_fmode = uopt->fmode; + sbi->s_dmode = uopt->dmode; write_unlock(&sbi->s_cred_lock); - if (sbi->s_lvid_bh) { - int write_rev = le16_to_cpu(udf_sb_lvidiu(sbi)->minUDFWriteRev); - if (write_rev > UDF_MAX_WRITE_VERSION) - *flags |= MS_RDONLY; - } - - if ((*flags & MS_RDONLY) == (sb->s_flags & MS_RDONLY)) + if (readonly == sb_rdonly(sb)) goto out_unlock; - if (*flags & MS_RDONLY) + if (readonly) udf_close_lvid(sb); else udf_open_lvid(sb); @@ -667,17 +681,69 @@ out_unlock: return error; } -/* Check Volume Structure Descriptors (ECMA 167 2/9.1) */ -/* We also check any "CD-ROM Volume Descriptor Set" (ECMA 167 2/8.3.1) */ -static loff_t udf_check_vsd(struct super_block *sb) +/* + * Check VSD descriptor. Returns -1 in case we are at the end of volume + * recognition area, 0 if the descriptor is valid but non-interesting, 1 if + * we found one of NSR descriptors we are looking for. + */ +static int identify_vsd(const struct volStructDesc *vsd) +{ + int ret = 0; + + if (!memcmp(vsd->stdIdent, VSD_STD_ID_CD001, VSD_STD_ID_LEN)) { + switch (vsd->structType) { + case 0: + udf_debug("ISO9660 Boot Record found\n"); + break; + case 1: + udf_debug("ISO9660 Primary Volume Descriptor found\n"); + break; + case 2: + udf_debug("ISO9660 Supplementary Volume Descriptor found\n"); + break; + case 3: + udf_debug("ISO9660 Volume Partition Descriptor found\n"); + break; + case 255: + udf_debug("ISO9660 Volume Descriptor Set Terminator found\n"); + break; + default: + udf_debug("ISO9660 VRS (%u) found\n", vsd->structType); + break; + } + } else if (!memcmp(vsd->stdIdent, VSD_STD_ID_BEA01, VSD_STD_ID_LEN)) + ; /* ret = 0 */ + else if (!memcmp(vsd->stdIdent, VSD_STD_ID_NSR02, VSD_STD_ID_LEN)) + ret = 1; + else if (!memcmp(vsd->stdIdent, VSD_STD_ID_NSR03, VSD_STD_ID_LEN)) + ret = 1; + else if (!memcmp(vsd->stdIdent, VSD_STD_ID_BOOT2, VSD_STD_ID_LEN)) + ; /* ret = 0 */ + else if (!memcmp(vsd->stdIdent, VSD_STD_ID_CDW02, VSD_STD_ID_LEN)) + ; /* ret = 0 */ + else { + /* TEA01 or invalid id : end of volume recognition area */ + ret = -1; + } + + return ret; +} + +/* + * Check Volume Structure Descriptors (ECMA 167 2/9.1) + * We also check any "CD-ROM Volume Descriptor Set" (ECMA 167 2/8.3.1) + * @return 1 if NSR02 or NSR03 found, + * -1 if first sector read error, 0 otherwise + */ +static int udf_check_vsd(struct super_block *sb) { struct volStructDesc *vsd = NULL; - loff_t sector = 32768; + loff_t sector = VSD_FIRST_SECTOR_OFFSET; int sectorsize; struct buffer_head *bh = NULL; - int nsr02 = 0; - int nsr03 = 0; + int nsr = 0; struct udf_sb_info *sbi; + loff_t session_offset; sbi = UDF_SB(sb); if (sb->s_blocksize < sizeof(struct volStructDesc)) @@ -685,271 +751,260 @@ static loff_t udf_check_vsd(struct super_block *sb) else sectorsize = sb->s_blocksize; - sector += (sbi->s_session << sb->s_blocksize_bits); + session_offset = (loff_t)sbi->s_session << sb->s_blocksize_bits; + sector += session_offset; - udf_debug("Starting at sector %u (%ld byte sectors)\n", + udf_debug("Starting at sector %u (%lu byte sectors)\n", (unsigned int)(sector >> sb->s_blocksize_bits), sb->s_blocksize); - /* Process the sequence (if applicable) */ - for (; !nsr02 && !nsr03; sector += sectorsize) { + /* Process the sequence (if applicable). The hard limit on the sector + * offset is arbitrary, hopefully large enough so that all valid UDF + * filesystems will be recognised. There is no mention of an upper + * bound to the size of the volume recognition area in the standard. + * The limit will prevent the code to read all the sectors of a + * specially crafted image (like a bluray disc full of CD001 sectors), + * potentially causing minutes or even hours of uninterruptible I/O + * activity. This actually happened with uninitialised SSD partitions + * (all 0xFF) before the check for the limit and all valid IDs were + * added */ + for (; !nsr && sector < VSD_MAX_SECTOR_OFFSET; sector += sectorsize) { /* Read a block */ - bh = udf_tread(sb, sector >> sb->s_blocksize_bits); + bh = sb_bread(sb, sector >> sb->s_blocksize_bits); if (!bh) break; - /* Look for ISO descriptors */ vsd = (struct volStructDesc *)(bh->b_data + (sector & (sb->s_blocksize - 1))); - - if (vsd->stdIdent[0] == 0) { - brelse(bh); - break; - } else if (!strncmp(vsd->stdIdent, VSD_STD_ID_CD001, - VSD_STD_ID_LEN)) { - switch (vsd->structType) { - case 0: - udf_debug("ISO9660 Boot Record found\n"); - break; - case 1: - udf_debug("ISO9660 Primary Volume Descriptor found\n"); - break; - case 2: - udf_debug("ISO9660 Supplementary Volume Descriptor found\n"); - break; - case 3: - udf_debug("ISO9660 Volume Partition Descriptor found\n"); - break; - case 255: - udf_debug("ISO9660 Volume Descriptor Set Terminator found\n"); - break; - default: - udf_debug("ISO9660 VRS (%u) found\n", - vsd->structType); - break; - } - } else if (!strncmp(vsd->stdIdent, VSD_STD_ID_BEA01, - VSD_STD_ID_LEN)) - ; /* nothing */ - else if (!strncmp(vsd->stdIdent, VSD_STD_ID_TEA01, - VSD_STD_ID_LEN)) { + nsr = identify_vsd(vsd); + /* Found NSR or end? */ + if (nsr) { brelse(bh); break; - } else if (!strncmp(vsd->stdIdent, VSD_STD_ID_NSR02, - VSD_STD_ID_LEN)) - nsr02 = sector; - else if (!strncmp(vsd->stdIdent, VSD_STD_ID_NSR03, - VSD_STD_ID_LEN)) - nsr03 = sector; + } + /* + * Special handling for improperly formatted VRS (e.g., Win10) + * where components are separated by 2048 bytes even though + * sectors are 4K + */ + if (sb->s_blocksize == 4096) { + nsr = identify_vsd(vsd + 1); + /* Ignore unknown IDs... */ + if (nsr < 0) + nsr = 0; + } brelse(bh); } - if (nsr03) - return nsr03; - else if (nsr02) - return nsr02; - else if (sector - (sbi->s_session << sb->s_blocksize_bits) == 32768) + if (nsr > 0) + return 1; + else if (!bh && sector - session_offset == VSD_FIRST_SECTOR_OFFSET) return -1; else return 0; } -static int udf_find_fileset(struct super_block *sb, - struct kernel_lb_addr *fileset, - struct kernel_lb_addr *root) +static int udf_verify_domain_identifier(struct super_block *sb, + struct regid *ident, char *dname) { - struct buffer_head *bh = NULL; - long lastblock; - uint16_t ident; - struct udf_sb_info *sbi; + struct domainIdentSuffix *suffix; - if (fileset->logicalBlockNum != 0xFFFFFFFF || - fileset->partitionReferenceNum != 0xFFFF) { - bh = udf_read_ptagged(sb, fileset, 0, &ident); - - if (!bh) { - return 1; - } else if (ident != TAG_IDENT_FSD) { - brelse(bh); - return 1; + if (memcmp(ident->ident, UDF_ID_COMPLIANT, strlen(UDF_ID_COMPLIANT))) { + udf_warn(sb, "Not OSTA UDF compliant %s descriptor.\n", dname); + goto force_ro; + } + if (ident->flags & ENTITYID_FLAGS_DIRTY) { + udf_warn(sb, "Possibly not OSTA UDF compliant %s descriptor.\n", + dname); + goto force_ro; + } + suffix = (struct domainIdentSuffix *)ident->identSuffix; + if ((suffix->domainFlags & DOMAIN_FLAGS_HARD_WRITE_PROTECT) || + (suffix->domainFlags & DOMAIN_FLAGS_SOFT_WRITE_PROTECT)) { + if (!sb_rdonly(sb)) { + udf_warn(sb, "Descriptor for %s marked write protected." + " Forcing read only mount.\n", dname); } - + goto force_ro; } + return 0; - sbi = UDF_SB(sb); - if (!bh) { - /* Search backwards through the partitions */ - struct kernel_lb_addr newfileset; +force_ro: + if (!sb_rdonly(sb)) + return -EACCES; + UDF_SET_FLAG(sb, UDF_FLAG_RW_INCOMPAT); + return 0; +} -/* --> cvg: FIXME - is it reasonable? */ - return 1; +static int udf_load_fileset(struct super_block *sb, struct fileSetDesc *fset, + struct kernel_lb_addr *root) +{ + int ret; - for (newfileset.partitionReferenceNum = sbi->s_partitions - 1; - (newfileset.partitionReferenceNum != 0xFFFF && - fileset->logicalBlockNum == 0xFFFFFFFF && - fileset->partitionReferenceNum == 0xFFFF); - newfileset.partitionReferenceNum--) { - lastblock = sbi->s_partmaps - [newfileset.partitionReferenceNum] - .s_partition_len; - newfileset.logicalBlockNum = 0; - - do { - bh = udf_read_ptagged(sb, &newfileset, 0, - &ident); - if (!bh) { - newfileset.logicalBlockNum++; - continue; - } + ret = udf_verify_domain_identifier(sb, &fset->domainIdent, "file set"); + if (ret < 0) + return ret; - switch (ident) { - case TAG_IDENT_SBD: - { - struct spaceBitmapDesc *sp; - sp = (struct spaceBitmapDesc *) - bh->b_data; - newfileset.logicalBlockNum += 1 + - ((le32_to_cpu(sp->numOfBytes) + - sizeof(struct spaceBitmapDesc) - - 1) >> sb->s_blocksize_bits); - brelse(bh); - break; - } - case TAG_IDENT_FSD: - *fileset = newfileset; - break; - default: - newfileset.logicalBlockNum++; - brelse(bh); - bh = NULL; - break; - } - } while (newfileset.logicalBlockNum < lastblock && - fileset->logicalBlockNum == 0xFFFFFFFF && - fileset->partitionReferenceNum == 0xFFFF); - } - } + *root = lelb_to_cpu(fset->rootDirectoryICB.extLocation); + UDF_SB(sb)->s_serial_number = le16_to_cpu(fset->descTag.tagSerialNum); - if ((fileset->logicalBlockNum != 0xFFFFFFFF || - fileset->partitionReferenceNum != 0xFFFF) && bh) { - udf_debug("Fileset at block=%d, partition=%d\n", - fileset->logicalBlockNum, - fileset->partitionReferenceNum); + udf_debug("Rootdir at block=%u, partition=%u\n", + root->logicalBlockNum, root->partitionReferenceNum); + return 0; +} - sbi->s_partition = fileset->partitionReferenceNum; - udf_load_fileset(sb, bh, root); +static int udf_find_fileset(struct super_block *sb, + struct kernel_lb_addr *fileset, + struct kernel_lb_addr *root) +{ + struct buffer_head *bh; + uint16_t ident; + int ret; + + if (fileset->logicalBlockNum == 0xFFFFFFFF && + fileset->partitionReferenceNum == 0xFFFF) + return -EINVAL; + + bh = udf_read_ptagged(sb, fileset, 0, &ident); + if (!bh) + return -EIO; + if (ident != TAG_IDENT_FSD) { brelse(bh); - return 0; + return -EINVAL; } - return 1; + + udf_debug("Fileset at block=%u, partition=%u\n", + fileset->logicalBlockNum, fileset->partitionReferenceNum); + + UDF_SB(sb)->s_partition = fileset->partitionReferenceNum; + ret = udf_load_fileset(sb, (struct fileSetDesc *)bh->b_data, root); + brelse(bh); + return ret; } +/* + * Load primary Volume Descriptor Sequence + * + * Return <0 on error, 0 on success. -EAGAIN is special meaning next sequence + * should be tried. + */ static int udf_load_pvoldesc(struct super_block *sb, sector_t block) { struct primaryVolDesc *pvoldesc; - struct ustr *instr, *outstr; + uint8_t *outstr; struct buffer_head *bh; uint16_t ident; - int ret = 1; - - instr = kmalloc(sizeof(struct ustr), GFP_NOFS); - if (!instr) - return 1; + int ret; + struct timestamp *ts; - outstr = kmalloc(sizeof(struct ustr), GFP_NOFS); + outstr = kzalloc(128, GFP_KERNEL); if (!outstr) - goto out1; + return -ENOMEM; bh = udf_read_tagged(sb, block, block, &ident); - if (!bh) + if (!bh) { + ret = -EAGAIN; goto out2; + } - BUG_ON(ident != TAG_IDENT_PVD); + if (ident != TAG_IDENT_PVD) { + ret = -EIO; + goto out_bh; + } pvoldesc = (struct primaryVolDesc *)bh->b_data; - if (udf_disk_stamp_to_time(&UDF_SB(sb)->s_record_time, - pvoldesc->recordingDateAndTime)) { -#ifdef UDFFS_DEBUG - struct timestamp *ts = &pvoldesc->recordingDateAndTime; - udf_debug("recording time %04u/%02u/%02u %02u:%02u (%x)\n", - le16_to_cpu(ts->year), ts->month, ts->day, ts->hour, - ts->minute, le16_to_cpu(ts->typeAndTimezone)); -#endif - } - - if (!udf_build_ustr(instr, pvoldesc->volIdent, 32)) - if (udf_CS0toUTF8(outstr, instr)) { - strncpy(UDF_SB(sb)->s_volume_ident, outstr->u_name, - outstr->u_len > 31 ? 31 : outstr->u_len); - udf_debug("volIdent[] = '%s'\n", - UDF_SB(sb)->s_volume_ident); - } + udf_disk_stamp_to_time(&UDF_SB(sb)->s_record_time, + pvoldesc->recordingDateAndTime); + ts = &pvoldesc->recordingDateAndTime; + udf_debug("recording time %04u/%02u/%02u %02u:%02u (%x)\n", + le16_to_cpu(ts->year), ts->month, ts->day, ts->hour, + ts->minute, le16_to_cpu(ts->typeAndTimezone)); + + ret = udf_dstrCS0toChar(sb, outstr, 31, pvoldesc->volIdent, 32); + if (ret < 0) { + strscpy_pad(UDF_SB(sb)->s_volume_ident, "InvalidName"); + pr_warn("incorrect volume identification, setting to " + "'InvalidName'\n"); + } else { + strscpy_pad(UDF_SB(sb)->s_volume_ident, outstr); + } + udf_debug("volIdent[] = '%s'\n", UDF_SB(sb)->s_volume_ident); - if (!udf_build_ustr(instr, pvoldesc->volSetIdent, 128)) - if (udf_CS0toUTF8(outstr, instr)) - udf_debug("volSetIdent[] = '%s'\n", outstr->u_name); + ret = udf_dstrCS0toChar(sb, outstr, 127, pvoldesc->volSetIdent, 128); + if (ret < 0) { + ret = 0; + goto out_bh; + } + outstr[ret] = 0; + udf_debug("volSetIdent[] = '%s'\n", outstr); - brelse(bh); ret = 0; +out_bh: + brelse(bh); out2: kfree(outstr); -out1: - kfree(instr); return ret; } struct inode *udf_find_metadata_inode_efe(struct super_block *sb, - u32 meta_file_loc, u32 partition_num) + u32 meta_file_loc, u32 partition_ref) { struct kernel_lb_addr addr; struct inode *metadata_fe; addr.logicalBlockNum = meta_file_loc; - addr.partitionReferenceNum = partition_num; + addr.partitionReferenceNum = partition_ref; - metadata_fe = udf_iget(sb, &addr); + metadata_fe = udf_iget_special(sb, &addr); - if (metadata_fe == NULL) + if (IS_ERR(metadata_fe)) { udf_warn(sb, "metadata inode efe not found\n"); - else if (UDF_I(metadata_fe)->i_alloc_type != ICBTAG_FLAG_AD_SHORT) { + return metadata_fe; + } + if (UDF_I(metadata_fe)->i_alloc_type != ICBTAG_FLAG_AD_SHORT) { udf_warn(sb, "metadata inode efe does not have short allocation descriptors!\n"); iput(metadata_fe); - metadata_fe = NULL; + return ERR_PTR(-EIO); } return metadata_fe; } -static int udf_load_metadata_files(struct super_block *sb, int partition) +static int udf_load_metadata_files(struct super_block *sb, int partition, + int type1_index) { struct udf_sb_info *sbi = UDF_SB(sb); struct udf_part_map *map; struct udf_meta_data *mdata; struct kernel_lb_addr addr; + struct inode *fe; map = &sbi->s_partmaps[partition]; mdata = &map->s_type_specific.s_metadata; + mdata->s_phys_partition_ref = type1_index; /* metadata address */ - udf_debug("Metadata file location: block = %d part = %d\n", - mdata->s_meta_file_loc, map->s_partition_num); - - mdata->s_metadata_fe = udf_find_metadata_inode_efe(sb, - mdata->s_meta_file_loc, map->s_partition_num); + udf_debug("Metadata file location: block = %u part = %u\n", + mdata->s_meta_file_loc, mdata->s_phys_partition_ref); - if (mdata->s_metadata_fe == NULL) { + fe = udf_find_metadata_inode_efe(sb, mdata->s_meta_file_loc, + mdata->s_phys_partition_ref); + if (IS_ERR(fe)) { /* mirror file entry */ - udf_debug("Mirror metadata file location: block = %d part = %d\n", - mdata->s_mirror_file_loc, map->s_partition_num); + udf_debug("Mirror metadata file location: block = %u part = %u\n", + mdata->s_mirror_file_loc, mdata->s_phys_partition_ref); - mdata->s_mirror_fe = udf_find_metadata_inode_efe(sb, - mdata->s_mirror_file_loc, map->s_partition_num); + fe = udf_find_metadata_inode_efe(sb, mdata->s_mirror_file_loc, + mdata->s_phys_partition_ref); - if (mdata->s_mirror_fe == NULL) { + if (IS_ERR(fe)) { udf_err(sb, "Both metadata and mirror metadata inode efe can not found\n"); - goto error_exit; + return PTR_ERR(fe); } - } + mdata->s_mirror_fe = fe; + } else + mdata->s_metadata_fe = fe; + /* * bitmap file entry @@ -958,44 +1013,25 @@ static int udf_load_metadata_files(struct super_block *sb, int partition) */ if (mdata->s_bitmap_file_loc != 0xFFFFFFFF) { addr.logicalBlockNum = mdata->s_bitmap_file_loc; - addr.partitionReferenceNum = map->s_partition_num; + addr.partitionReferenceNum = mdata->s_phys_partition_ref; - udf_debug("Bitmap file location: block = %d part = %d\n", + udf_debug("Bitmap file location: block = %u part = %u\n", addr.logicalBlockNum, addr.partitionReferenceNum); - mdata->s_bitmap_fe = udf_iget(sb, &addr); - - if (mdata->s_bitmap_fe == NULL) { - if (sb->s_flags & MS_RDONLY) + fe = udf_iget_special(sb, &addr); + if (IS_ERR(fe)) { + if (sb_rdonly(sb)) udf_warn(sb, "bitmap inode efe not found but it's ok since the disc is mounted read-only\n"); else { udf_err(sb, "bitmap inode efe not found and attempted read-write mount\n"); - goto error_exit; + return PTR_ERR(fe); } - } + } else + mdata->s_bitmap_fe = fe; } udf_debug("udf_load_metadata_files Ok\n"); - return 0; - -error_exit: - return 1; -} - -static void udf_load_fileset(struct super_block *sb, struct buffer_head *bh, - struct kernel_lb_addr *root) -{ - struct fileSetDesc *fset; - - fset = (struct fileSetDesc *)bh->b_data; - - *root = lelb_to_cpu(fset->rootDirectoryICB.extLocation); - - UDF_SB(sb)->s_serial_number = le16_to_cpu(fset->descTag.tagSerialNum); - - udf_debug("Rootdir at block=%d, partition=%d\n", - root->logicalBlockNum, root->partitionReferenceNum); } int udf_compute_nr_groups(struct super_block *sb, u32 partition) @@ -1009,36 +1045,85 @@ int udf_compute_nr_groups(struct super_block *sb, u32 partition) static struct udf_bitmap *udf_sb_alloc_bitmap(struct super_block *sb, u32 index) { struct udf_bitmap *bitmap; - int nr_groups; - int size; - - nr_groups = udf_compute_nr_groups(sb, index); - size = sizeof(struct udf_bitmap) + - (sizeof(struct buffer_head *) * nr_groups); - - if (size <= PAGE_SIZE) - bitmap = kzalloc(size, GFP_KERNEL); - else - bitmap = vzalloc(size); /* TODO: get rid of vzalloc */ + int nr_groups = udf_compute_nr_groups(sb, index); - if (bitmap == NULL) + bitmap = kvzalloc(struct_size(bitmap, s_block_bitmap, nr_groups), + GFP_KERNEL); + if (!bitmap) return NULL; bitmap->s_nr_groups = nr_groups; return bitmap; } +static int check_partition_desc(struct super_block *sb, + struct partitionDesc *p, + struct udf_part_map *map) +{ + bool umap, utable, fmap, ftable; + struct partitionHeaderDesc *phd; + + switch (le32_to_cpu(p->accessType)) { + case PD_ACCESS_TYPE_READ_ONLY: + case PD_ACCESS_TYPE_WRITE_ONCE: + case PD_ACCESS_TYPE_NONE: + goto force_ro; + } + + /* No Partition Header Descriptor? */ + if (strcmp(p->partitionContents.ident, PD_PARTITION_CONTENTS_NSR02) && + strcmp(p->partitionContents.ident, PD_PARTITION_CONTENTS_NSR03)) + goto force_ro; + + phd = (struct partitionHeaderDesc *)p->partitionContentsUse; + utable = phd->unallocSpaceTable.extLength; + umap = phd->unallocSpaceBitmap.extLength; + ftable = phd->freedSpaceTable.extLength; + fmap = phd->freedSpaceBitmap.extLength; + + /* No allocation info? */ + if (!utable && !umap && !ftable && !fmap) + goto force_ro; + + /* We don't support blocks that require erasing before overwrite */ + if (ftable || fmap) + goto force_ro; + /* UDF 2.60: 2.3.3 - no mixing of tables & bitmaps, no VAT. */ + if (utable && umap) + goto force_ro; + + if (map->s_partition_type == UDF_VIRTUAL_MAP15 || + map->s_partition_type == UDF_VIRTUAL_MAP20 || + map->s_partition_type == UDF_METADATA_MAP25) + goto force_ro; + + return 0; +force_ro: + if (!sb_rdonly(sb)) + return -EACCES; + UDF_SET_FLAG(sb, UDF_FLAG_RW_INCOMPAT); + return 0; +} + static int udf_fill_partdesc_info(struct super_block *sb, struct partitionDesc *p, int p_index) { struct udf_part_map *map; struct udf_sb_info *sbi = UDF_SB(sb); struct partitionHeaderDesc *phd; + u32 sum; + int err; map = &sbi->s_partmaps[p_index]; map->s_partition_len = le32_to_cpu(p->partitionLength); /* blocks */ map->s_partition_root = le32_to_cpu(p->partitionStartingLocation); + if (check_add_overflow(map->s_partition_root, map->s_partition_len, + &sum)) { + udf_err(sb, "Partition %d has invalid location %u + %u\n", + p_index, map->s_partition_root, map->s_partition_len); + return -EFSCORRUPTED; + } if (p->accessType == cpu_to_le32(PD_ACCESS_TYPE_READ_ONLY)) map->s_partition_flags |= UDF_PART_FLAG_READ_ONLY; @@ -1049,12 +1134,20 @@ static int udf_fill_partdesc_info(struct super_block *sb, if (p->accessType == cpu_to_le32(PD_ACCESS_TYPE_OVERWRITABLE)) map->s_partition_flags |= UDF_PART_FLAG_OVERWRITABLE; - udf_debug("Partition (%d type %x) starts at physical %d, block length %d\n", + udf_debug("Partition (%d type %x) starts at physical %u, block length %u\n", p_index, map->s_partition_type, map->s_partition_root, map->s_partition_len); - if (strcmp(p->partitionContents.ident, PD_PARTITION_CONTENTS_NSR02) && - strcmp(p->partitionContents.ident, PD_PARTITION_CONTENTS_NSR03)) + err = check_partition_desc(sb, p, map); + if (err) + return err; + + /* + * Skip loading allocation info it we cannot ever write to the fs. + * This is a correctness thing as we may have decided to force ro mount + * to avoid allocation info we don't support. + */ + if (UDF_QUERY_FLAG(sb, UDF_FLAG_RW_INCOMPAT)) return 0; phd = (struct partitionHeaderDesc *)p->partitionContentsUse; @@ -1064,63 +1157,40 @@ static int udf_fill_partdesc_info(struct super_block *sb, phd->unallocSpaceTable.extPosition), .partitionReferenceNum = p_index, }; + struct inode *inode; - map->s_uspace.s_table = udf_iget(sb, &loc); - if (!map->s_uspace.s_table) { + inode = udf_iget_special(sb, &loc); + if (IS_ERR(inode)) { udf_debug("cannot load unallocSpaceTable (part %d)\n", p_index); - return 1; + return PTR_ERR(inode); } + map->s_uspace.s_table = inode; map->s_partition_flags |= UDF_PART_FLAG_UNALLOC_TABLE; - udf_debug("unallocSpaceTable (part %d) @ %ld\n", + udf_debug("unallocSpaceTable (part %d) @ %lu\n", p_index, map->s_uspace.s_table->i_ino); } if (phd->unallocSpaceBitmap.extLength) { struct udf_bitmap *bitmap = udf_sb_alloc_bitmap(sb, p_index); if (!bitmap) - return 1; + return -ENOMEM; map->s_uspace.s_bitmap = bitmap; bitmap->s_extPosition = le32_to_cpu( phd->unallocSpaceBitmap.extPosition); map->s_partition_flags |= UDF_PART_FLAG_UNALLOC_BITMAP; - udf_debug("unallocSpaceBitmap (part %d) @ %d\n", - p_index, bitmap->s_extPosition); - } - - if (phd->partitionIntegrityTable.extLength) - udf_debug("partitionIntegrityTable (part %d)\n", p_index); - - if (phd->freedSpaceTable.extLength) { - struct kernel_lb_addr loc = { - .logicalBlockNum = le32_to_cpu( - phd->freedSpaceTable.extPosition), - .partitionReferenceNum = p_index, - }; - - map->s_fspace.s_table = udf_iget(sb, &loc); - if (!map->s_fspace.s_table) { - udf_debug("cannot load freedSpaceTable (part %d)\n", - p_index); - return 1; + /* Check whether math over bitmap won't overflow. */ + if (check_add_overflow(map->s_partition_len, + sizeof(struct spaceBitmapDesc) << 3, + &sum)) { + udf_err(sb, "Partition %d is too long (%u)\n", p_index, + map->s_partition_len); + return -EFSCORRUPTED; } - - map->s_partition_flags |= UDF_PART_FLAG_FREED_TABLE; - udf_debug("freedSpaceTable (part %d) @ %ld\n", - p_index, map->s_fspace.s_table->i_ino); - } - - if (phd->freedSpaceBitmap.extLength) { - struct udf_bitmap *bitmap = udf_sb_alloc_bitmap(sb, p_index); - if (!bitmap) - return 1; - map->s_fspace.s_bitmap = bitmap; - bitmap->s_extPosition = le32_to_cpu( - phd->freedSpaceBitmap.extPosition); - map->s_partition_flags |= UDF_PART_FLAG_FREED_BITMAP; - udf_debug("freedSpaceBitmap (part %d) @ %d\n", + udf_debug("unallocSpaceBitmap (part %d) @ %u\n", p_index, bitmap->s_extPosition); } + return 0; } @@ -1131,6 +1201,7 @@ static void udf_find_vat_block(struct super_block *sb, int p_index, struct udf_part_map *map = &sbi->s_partmaps[p_index]; sector_t vat_block; struct kernel_lb_addr ino; + struct inode *inode; /* * VAT file entry is in the last recorded block. Some broken disks have @@ -1139,10 +1210,13 @@ static void udf_find_vat_block(struct super_block *sb, int p_index, ino.partitionReferenceNum = type1_index; for (vat_block = start_block; vat_block >= map->s_partition_root && - vat_block >= start_block - 3 && - !sbi->s_vat_inode; vat_block--) { + vat_block >= start_block - 3; vat_block--) { ino.logicalBlockNum = vat_block - map->s_partition_root; - sbi->s_vat_inode = udf_iget(sb, &ino); + inode = udf_iget_special(sb, &ino); + if (!IS_ERR(inode)) { + sbi->s_vat_inode = inode; + break; + } } } @@ -1152,9 +1226,8 @@ static int udf_load_vat(struct super_block *sb, int p_index, int type1_index) struct udf_part_map *map = &sbi->s_partmaps[p_index]; struct buffer_head *bh = NULL; struct udf_inode_info *vati; - uint32_t pos; struct virtualAllocationTable20 *vat20; - sector_t blocks = sb->s_bdev->bd_inode->i_size >> sb->s_blocksize_bits; + sector_t blocks = sb_bdev_nr_blocks(sb); udf_find_vat_block(sb, p_index, type1_index, sbi->s_last_block); if (!sbi->s_vat_inode && @@ -1165,7 +1238,7 @@ static int udf_load_vat(struct super_block *sb, int p_index, int type1_index) udf_find_vat_block(sb, p_index, type1_index, blocks - 1); } if (!sbi->s_vat_inode) - return 1; + return -EIO; if (map->s_partition_type == UDF_VIRTUAL_MAP15) { map->s_type_specific.s_virtual.s_start_offset = 0; @@ -1174,14 +1247,18 @@ static int udf_load_vat(struct super_block *sb, int p_index, int type1_index) } else if (map->s_partition_type == UDF_VIRTUAL_MAP20) { vati = UDF_I(sbi->s_vat_inode); if (vati->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) { - pos = udf_block_map(sbi->s_vat_inode, 0); - bh = sb_bread(sb, pos); - if (!bh) - return 1; + int err = 0; + + bh = udf_bread(sbi->s_vat_inode, 0, 0, &err); + if (!bh) { + if (!err) + err = -EFSCORRUPTED; + return err; + } vat20 = (struct virtualAllocationTable20 *)bh->b_data; } else { vat20 = (struct virtualAllocationTable20 *) - vati->i_ext.i_data; + vati->i_data; } map->s_type_specific.s_virtual.s_start_offset = @@ -1195,6 +1272,12 @@ static int udf_load_vat(struct super_block *sb, int p_index, int type1_index) return 0; } +/* + * Load partition descriptor block + * + * Returns <0 on error, 0 on success, -EAGAIN is special - try next descriptor + * sequence. + */ static int udf_load_partdesc(struct super_block *sb, sector_t block) { struct buffer_head *bh; @@ -1204,21 +1287,23 @@ static int udf_load_partdesc(struct super_block *sb, sector_t block) int i, type1_idx; uint16_t partitionNumber; uint16_t ident; - int ret = 0; + int ret; bh = udf_read_tagged(sb, block, block, &ident); if (!bh) - return 1; - if (ident != TAG_IDENT_PD) + return -EAGAIN; + if (ident != TAG_IDENT_PD) { + ret = 0; goto out_bh; + } p = (struct partitionDesc *)bh->b_data; partitionNumber = le16_to_cpu(p->partitionNumber); - /* First scan for TYPE1, SPARABLE and METADATA partitions */ + /* First scan for TYPE1 and SPARABLE partitions */ for (i = 0; i < sbi->s_partitions; i++) { map = &sbi->s_partmaps[i]; - udf_debug("Searching map: (%d == %d)\n", + udf_debug("Searching map: (%u == %u)\n", map->s_partition_num, partitionNumber); if (map->s_partition_num == partitionNumber && (map->s_partition_type == UDF_TYPE1_MAP15 || @@ -1227,18 +1312,22 @@ static int udf_load_partdesc(struct super_block *sb, sector_t block) } if (i >= sbi->s_partitions) { - udf_debug("Partition (%d) not found in partition map\n", + udf_debug("Partition (%u) not found in partition map\n", partitionNumber); + ret = 0; goto out_bh; } ret = udf_fill_partdesc_info(sb, p, i); + if (ret < 0) + goto out_bh; /* * Now rescan for VIRTUAL or METADATA partitions when SPARABLE and * PHYSICAL partitions are already set up */ type1_idx = i; + map = NULL; /* supress 'maybe used uninitialized' warning */ for (i = 0; i < sbi->s_partitions; i++) { map = &sbi->s_partmaps[i]; @@ -1249,32 +1338,38 @@ static int udf_load_partdesc(struct super_block *sb, sector_t block) break; } - if (i >= sbi->s_partitions) + if (i >= sbi->s_partitions) { + ret = 0; goto out_bh; + } ret = udf_fill_partdesc_info(sb, p, i); - if (ret) + if (ret < 0) goto out_bh; if (map->s_partition_type == UDF_METADATA_MAP25) { - ret = udf_load_metadata_files(sb, i); - if (ret) { + ret = udf_load_metadata_files(sb, i, type1_idx); + if (ret < 0) { udf_err(sb, "error loading MetaData partition map %d\n", i); goto out_bh; } } else { - ret = udf_load_vat(sb, i, type1_idx); - if (ret) - goto out_bh; /* - * Mark filesystem read-only if we have a partition with - * virtual map since we don't handle writing to it (we - * overwrite blocks instead of relocating them). + * If we have a partition with virtual map, we don't handle + * writing to it (we overwrite blocks instead of relocating + * them). */ - sb->s_flags |= MS_RDONLY; - pr_notice("Filesystem marked read-only because writing to pseudooverwrite partition is not implemented\n"); + if (!sb_rdonly(sb)) { + ret = -EACCES; + goto out_bh; + } + UDF_SET_FLAG(sb, UDF_FLAG_RW_INCOMPAT); + ret = udf_load_vat(sb, i, type1_idx); + if (ret < 0) + goto out_bh; } + ret = 0; out_bh: /* In case loading failed, we handle cleanup in udf_fill_super */ brelse(bh); @@ -1306,6 +1401,12 @@ static int udf_load_sparable_map(struct super_block *sb, (int)spm->numSparingTables); return -EIO; } + if (le32_to_cpu(spm->sizeSparingTable) > sb->s_blocksize) { + udf_err(sb, "error loading logical volume descriptor: " + "Too big sparing table size (%u)\n", + le32_to_cpu(spm->sizeSparingTable)); + return -EIO; + } for (i = 0; i < spm->numSparingTables; i++) { loc = le32_to_cpu(spm->locSparingTable[i]); @@ -1339,12 +1440,12 @@ static int udf_load_logicalvol(struct super_block *sb, sector_t block, struct genericPartitionMap *gpm; uint16_t ident; struct buffer_head *bh; - unsigned int table_len; - int ret = 0; + unsigned int table_len, part_map_count; + int ret; bh = udf_read_tagged(sb, block, block, &ident); if (!bh) - return 1; + return -EAGAIN; BUG_ON(ident != TAG_IDENT_LVD); lvd = (struct logicalVolDesc *)bh->b_data; table_len = le32_to_cpu(lvd->mapTableLength); @@ -1352,11 +1453,24 @@ static int udf_load_logicalvol(struct super_block *sb, sector_t block, udf_err(sb, "error loading logical volume descriptor: " "Partition table too long (%u > %lu)\n", table_len, sb->s_blocksize - sizeof(*lvd)); - ret = 1; + ret = -EIO; goto out_bh; } - ret = udf_sb_alloc_partition_maps(sb, le32_to_cpu(lvd->numPartitionMaps)); + ret = udf_verify_domain_identifier(sb, &lvd->domainIdent, + "logical volume"); + if (ret) + goto out_bh; + + part_map_count = le32_to_cpu(lvd->numPartitionMaps); + if (part_map_count > table_len / sizeof(struct genericPartitionMap1)) { + udf_err(sb, "error loading logical volume descriptor: " + "Too many partition maps (%u > %u)\n", part_map_count, + table_len / (unsigned)sizeof(struct genericPartitionMap1)); + ret = -EIO; + goto out_bh; + } + ret = udf_sb_alloc_partition_maps(sb, part_map_count); if (ret) goto out_bh; @@ -1396,11 +1510,10 @@ static int udf_load_logicalvol(struct super_block *sb, sector_t block, } else if (!strncmp(upm2->partIdent.ident, UDF_ID_SPARABLE, strlen(UDF_ID_SPARABLE))) { - if (udf_load_sparable_map(sb, map, - (struct sparablePartitionMap *)gpm) < 0) { - ret = 1; + ret = udf_load_sparable_map(sb, map, + (struct sparablePartitionMap *)gpm); + if (ret < 0) goto out_bh; - } } else if (!strncmp(upm2->partIdent.ident, UDF_ID_METADATA, strlen(UDF_ID_METADATA))) { @@ -1409,7 +1522,7 @@ static int udf_load_logicalvol(struct super_block *sb, sector_t block, struct metadataPartitionMap *mdm = (struct metadataPartitionMap *) &(lvd->partitionMaps[offset]); - udf_debug("Parsing Logical vol part %d type %d id=%s\n", + udf_debug("Parsing Logical vol part %d type %u id=%s\n", i, type, UDF_ID_METADATA); map->s_partition_type = UDF_METADATA_MAP25; @@ -1431,17 +1544,17 @@ static int udf_load_logicalvol(struct super_block *sb, sector_t block, udf_debug("Metadata Ident suffix=0x%x\n", le16_to_cpu(*(__le16 *) mdm->partIdent.identSuffix)); - udf_debug("Metadata part num=%d\n", + udf_debug("Metadata part num=%u\n", le16_to_cpu(mdm->partitionNum)); - udf_debug("Metadata part alloc unit size=%d\n", + udf_debug("Metadata part alloc unit size=%u\n", le32_to_cpu(mdm->allocUnitSize)); - udf_debug("Metadata file loc=%d\n", + udf_debug("Metadata file loc=%u\n", le32_to_cpu(mdm->metadataFileLoc)); - udf_debug("Mirror file loc=%d\n", + udf_debug("Mirror file loc=%u\n", le32_to_cpu(mdm->metadataMirrorFileLoc)); - udf_debug("Bitmap file loc=%d\n", + udf_debug("Bitmap file loc=%u\n", le32_to_cpu(mdm->metadataBitmapFileLoc)); - udf_debug("Flags: %d %d\n", + udf_debug("Flags: %d %u\n", mdata->s_flags, mdm->flags); } else { udf_debug("Unknown ident: %s\n", @@ -1451,7 +1564,7 @@ static int udf_load_logicalvol(struct super_block *sb, sector_t block, map->s_volumeseqnum = le16_to_cpu(upm2->volSeqNum); map->s_partition_num = le16_to_cpu(upm2->partitionNum); } - udf_debug("Partition (%d:%d) type %d on volume %d\n", + udf_debug("Partition (%d:%u) type %u on volume %u\n", i, map->s_partition_num, type, map->s_volumeseqnum); } @@ -1459,153 +1572,250 @@ static int udf_load_logicalvol(struct super_block *sb, sector_t block, struct long_ad *la = (struct long_ad *)&(lvd->logicalVolContentsUse[0]); *fileset = lelb_to_cpu(la->extLocation); - udf_debug("FileSet found in LogicalVolDesc at block=%d, partition=%d\n", + udf_debug("FileSet found in LogicalVolDesc at block=%u, partition=%u\n", fileset->logicalBlockNum, fileset->partitionReferenceNum); } if (lvd->integritySeqExt.extLength) udf_load_logicalvolint(sb, leea_to_cpu(lvd->integritySeqExt)); + ret = 0; + if (!sbi->s_lvid_bh) { + /* We can't generate unique IDs without a valid LVID */ + if (sb_rdonly(sb)) { + UDF_SET_FLAG(sb, UDF_FLAG_RW_INCOMPAT); + } else { + udf_warn(sb, "Damaged or missing LVID, forcing " + "readonly mount\n"); + ret = -EACCES; + } + } out_bh: brelse(bh); return ret; } +static bool udf_lvid_valid(struct super_block *sb, + struct logicalVolIntegrityDesc *lvid) +{ + u32 parts, impuselen; + + parts = le32_to_cpu(lvid->numOfPartitions); + impuselen = le32_to_cpu(lvid->lengthOfImpUse); + if (parts >= sb->s_blocksize || impuselen >= sb->s_blocksize || + sizeof(struct logicalVolIntegrityDesc) + impuselen + + 2 * parts * sizeof(u32) > sb->s_blocksize) + return false; + return true; +} + /* - * udf_load_logicalvolint - * + * Find the prevailing Logical Volume Integrity Descriptor. */ static void udf_load_logicalvolint(struct super_block *sb, struct kernel_extent_ad loc) { - struct buffer_head *bh = NULL; + struct buffer_head *bh, *final_bh; uint16_t ident; struct udf_sb_info *sbi = UDF_SB(sb); struct logicalVolIntegrityDesc *lvid; + int indirections = 0; + + while (++indirections <= UDF_MAX_LVID_NESTING) { + final_bh = NULL; + while (loc.extLength > 0 && + (bh = udf_read_tagged(sb, loc.extLocation, + loc.extLocation, &ident))) { + if (ident != TAG_IDENT_LVID) { + brelse(bh); + break; + } - while (loc.extLength > 0 && - (bh = udf_read_tagged(sb, loc.extLocation, - loc.extLocation, &ident)) && - ident == TAG_IDENT_LVID) { - sbi->s_lvid_bh = bh; - lvid = (struct logicalVolIntegrityDesc *)bh->b_data; + brelse(final_bh); + final_bh = bh; + + loc.extLength -= sb->s_blocksize; + loc.extLocation++; + } - if (lvid->nextIntegrityExt.extLength) - udf_load_logicalvolint(sb, - leea_to_cpu(lvid->nextIntegrityExt)); + if (!final_bh) + return; - if (sbi->s_lvid_bh != bh) - brelse(bh); - loc.extLength -= sb->s_blocksize; - loc.extLocation++; + lvid = (struct logicalVolIntegrityDesc *)final_bh->b_data; + if (udf_lvid_valid(sb, lvid)) { + brelse(sbi->s_lvid_bh); + sbi->s_lvid_bh = final_bh; + } else { + udf_warn(sb, "Corrupted LVID (parts=%u, impuselen=%u), " + "ignoring.\n", + le32_to_cpu(lvid->numOfPartitions), + le32_to_cpu(lvid->lengthOfImpUse)); + } + + if (lvid->nextIntegrityExt.extLength == 0) + return; + + loc = leea_to_cpu(lvid->nextIntegrityExt); } - if (sbi->s_lvid_bh != bh) - brelse(bh); + + udf_warn(sb, "Too many LVID indirections (max %u), ignoring.\n", + UDF_MAX_LVID_NESTING); + brelse(sbi->s_lvid_bh); + sbi->s_lvid_bh = NULL; } /* - * udf_process_sequence - * - * PURPOSE - * Process a main/reserve volume descriptor sequence. - * - * PRE-CONDITIONS - * sb Pointer to _locked_ superblock. - * block First block of first extent of the sequence. - * lastblock Lastblock of first extent of the sequence. + * Step for reallocation of table of partition descriptor sequence numbers. + * Must be power of 2. + */ +#define PART_DESC_ALLOC_STEP 32 + +struct part_desc_seq_scan_data { + struct udf_vds_record rec; + u32 partnum; +}; + +struct desc_seq_scan_data { + struct udf_vds_record vds[VDS_POS_LENGTH]; + unsigned int size_part_descs; + unsigned int num_part_descs; + struct part_desc_seq_scan_data *part_descs_loc; +}; + +static struct udf_vds_record *handle_partition_descriptor( + struct buffer_head *bh, + struct desc_seq_scan_data *data) +{ + struct partitionDesc *desc = (struct partitionDesc *)bh->b_data; + int partnum; + int i; + + partnum = le16_to_cpu(desc->partitionNumber); + for (i = 0; i < data->num_part_descs; i++) + if (partnum == data->part_descs_loc[i].partnum) + return &(data->part_descs_loc[i].rec); + if (data->num_part_descs >= data->size_part_descs) { + struct part_desc_seq_scan_data *new_loc; + unsigned int new_size = ALIGN(partnum, PART_DESC_ALLOC_STEP); + + new_loc = kcalloc(new_size, sizeof(*new_loc), GFP_KERNEL); + if (!new_loc) + return ERR_PTR(-ENOMEM); + memcpy(new_loc, data->part_descs_loc, + data->size_part_descs * sizeof(*new_loc)); + kfree(data->part_descs_loc); + data->part_descs_loc = new_loc; + data->size_part_descs = new_size; + } + return &(data->part_descs_loc[data->num_part_descs++].rec); +} + + +static struct udf_vds_record *get_volume_descriptor_record(uint16_t ident, + struct buffer_head *bh, struct desc_seq_scan_data *data) +{ + switch (ident) { + case TAG_IDENT_PVD: /* ISO 13346 3/10.1 */ + return &(data->vds[VDS_POS_PRIMARY_VOL_DESC]); + case TAG_IDENT_IUVD: /* ISO 13346 3/10.4 */ + return &(data->vds[VDS_POS_IMP_USE_VOL_DESC]); + case TAG_IDENT_LVD: /* ISO 13346 3/10.6 */ + return &(data->vds[VDS_POS_LOGICAL_VOL_DESC]); + case TAG_IDENT_USD: /* ISO 13346 3/10.8 */ + return &(data->vds[VDS_POS_UNALLOC_SPACE_DESC]); + case TAG_IDENT_PD: /* ISO 13346 3/10.5 */ + return handle_partition_descriptor(bh, data); + } + return NULL; +} + +/* + * Process a main/reserve volume descriptor sequence. + * @block First block of first extent of the sequence. + * @lastblock Lastblock of first extent of the sequence. + * @fileset There we store extent containing root fileset * - * HISTORY - * July 1, 1997 - Andrew E. Mileski - * Written, tested, and released. + * Returns <0 on error, 0 on success. -EAGAIN is special - try next descriptor + * sequence */ -static noinline int udf_process_sequence(struct super_block *sb, long block, - long lastblock, struct kernel_lb_addr *fileset) +static noinline int udf_process_sequence( + struct super_block *sb, + sector_t block, sector_t lastblock, + struct kernel_lb_addr *fileset) { struct buffer_head *bh = NULL; - struct udf_vds_record vds[VDS_POS_LENGTH]; struct udf_vds_record *curr; struct generic_desc *gd; struct volDescPtr *vdp; - int done = 0; + bool done = false; uint32_t vdsn; uint16_t ident; - long next_s = 0, next_e = 0; - - memset(vds, 0, sizeof(struct udf_vds_record) * VDS_POS_LENGTH); + int ret; + unsigned int indirections = 0; + struct desc_seq_scan_data data; + unsigned int i; + + memset(data.vds, 0, sizeof(struct udf_vds_record) * VDS_POS_LENGTH); + data.size_part_descs = PART_DESC_ALLOC_STEP; + data.num_part_descs = 0; + data.part_descs_loc = kcalloc(data.size_part_descs, + sizeof(*data.part_descs_loc), + GFP_KERNEL); + if (!data.part_descs_loc) + return -ENOMEM; /* * Read the main descriptor sequence and find which descriptors * are in it. */ for (; (!done && block <= lastblock); block++) { - bh = udf_read_tagged(sb, block, block, &ident); - if (!bh) { - udf_err(sb, - "Block %llu of volume descriptor sequence is corrupted or we could not read it\n", - (unsigned long long)block); - return 1; - } + if (!bh) + break; /* Process each descriptor (ISO 13346 3/8.3-8.4) */ gd = (struct generic_desc *)bh->b_data; vdsn = le32_to_cpu(gd->volDescSeqNum); switch (ident) { - case TAG_IDENT_PVD: /* ISO 13346 3/10.1 */ - curr = &vds[VDS_POS_PRIMARY_VOL_DESC]; - if (vdsn >= curr->volDescSeqNum) { - curr->volDescSeqNum = vdsn; - curr->block = block; - } - break; case TAG_IDENT_VDP: /* ISO 13346 3/10.3 */ - curr = &vds[VDS_POS_VOL_DESC_PTR]; - if (vdsn >= curr->volDescSeqNum) { - curr->volDescSeqNum = vdsn; - curr->block = block; - - vdp = (struct volDescPtr *)bh->b_data; - next_s = le32_to_cpu( - vdp->nextVolDescSeqExt.extLocation); - next_e = le32_to_cpu( - vdp->nextVolDescSeqExt.extLength); - next_e = next_e >> sb->s_blocksize_bits; - next_e += next_s; + if (++indirections > UDF_MAX_TD_NESTING) { + udf_err(sb, "too many Volume Descriptor " + "Pointers (max %u supported)\n", + UDF_MAX_TD_NESTING); + brelse(bh); + ret = -EIO; + goto out; } + + vdp = (struct volDescPtr *)bh->b_data; + block = le32_to_cpu(vdp->nextVolDescSeqExt.extLocation); + lastblock = le32_to_cpu( + vdp->nextVolDescSeqExt.extLength) >> + sb->s_blocksize_bits; + lastblock += block - 1; + /* For loop is going to increment 'block' again */ + block--; break; + case TAG_IDENT_PVD: /* ISO 13346 3/10.1 */ case TAG_IDENT_IUVD: /* ISO 13346 3/10.4 */ - curr = &vds[VDS_POS_IMP_USE_VOL_DESC]; - if (vdsn >= curr->volDescSeqNum) { - curr->volDescSeqNum = vdsn; - curr->block = block; - } - break; - case TAG_IDENT_PD: /* ISO 13346 3/10.5 */ - curr = &vds[VDS_POS_PARTITION_DESC]; - if (!curr->block) - curr->block = block; - break; case TAG_IDENT_LVD: /* ISO 13346 3/10.6 */ - curr = &vds[VDS_POS_LOGICAL_VOL_DESC]; - if (vdsn >= curr->volDescSeqNum) { - curr->volDescSeqNum = vdsn; - curr->block = block; - } - break; case TAG_IDENT_USD: /* ISO 13346 3/10.8 */ - curr = &vds[VDS_POS_UNALLOC_SPACE_DESC]; + case TAG_IDENT_PD: /* ISO 13346 3/10.5 */ + curr = get_volume_descriptor_record(ident, bh, &data); + if (IS_ERR(curr)) { + brelse(bh); + ret = PTR_ERR(curr); + goto out; + } + /* Descriptor we don't care about? */ + if (!curr) + break; if (vdsn >= curr->volDescSeqNum) { curr->volDescSeqNum = vdsn; curr->block = block; } break; case TAG_IDENT_TD: /* ISO 13346 3/10.9 */ - vds[VDS_POS_TERMINATING_DESC].block = block; - if (next_e) { - block = next_s; - lastblock = next_e; - next_s = next_e = 0; - } else - done = 1; + done = true; break; } brelse(bh); @@ -1614,37 +1824,46 @@ static noinline int udf_process_sequence(struct super_block *sb, long block, * Now read interesting descriptors again and process them * in a suitable order */ - if (!vds[VDS_POS_PRIMARY_VOL_DESC].block) { + if (!data.vds[VDS_POS_PRIMARY_VOL_DESC].block) { udf_err(sb, "Primary Volume Descriptor not found!\n"); - return 1; + ret = -EAGAIN; + goto out; } - if (udf_load_pvoldesc(sb, vds[VDS_POS_PRIMARY_VOL_DESC].block)) - return 1; - - if (vds[VDS_POS_LOGICAL_VOL_DESC].block && udf_load_logicalvol(sb, - vds[VDS_POS_LOGICAL_VOL_DESC].block, fileset)) - return 1; + ret = udf_load_pvoldesc(sb, data.vds[VDS_POS_PRIMARY_VOL_DESC].block); + if (ret < 0) + goto out; - if (vds[VDS_POS_PARTITION_DESC].block) { - /* - * We rescan the whole descriptor sequence to find - * partition descriptor blocks and process them. - */ - for (block = vds[VDS_POS_PARTITION_DESC].block; - block < vds[VDS_POS_TERMINATING_DESC].block; - block++) - if (udf_load_partdesc(sb, block)) - return 1; + if (data.vds[VDS_POS_LOGICAL_VOL_DESC].block) { + ret = udf_load_logicalvol(sb, + data.vds[VDS_POS_LOGICAL_VOL_DESC].block, + fileset); + if (ret < 0) + goto out; } - return 0; + /* Now handle prevailing Partition Descriptors */ + for (i = 0; i < data.num_part_descs; i++) { + ret = udf_load_partdesc(sb, data.part_descs_loc[i].rec.block); + if (ret < 0) + goto out; + } + ret = 0; +out: + kfree(data.part_descs_loc); + return ret; } +/* + * Load Volume Descriptor Sequence described by anchor in bh + * + * Returns <0 on error, 0 on success + */ static int udf_load_sequence(struct super_block *sb, struct buffer_head *bh, struct kernel_lb_addr *fileset) { struct anchorVolDescPtr *anchor; - long main_s, main_e, reserve_s, reserve_e; + sector_t main_s, main_e, reserve_s, reserve_e; + int ret; anchor = (struct anchorVolDescPtr *)bh->b_data; @@ -1652,28 +1871,36 @@ static int udf_load_sequence(struct super_block *sb, struct buffer_head *bh, main_s = le32_to_cpu(anchor->mainVolDescSeqExt.extLocation); main_e = le32_to_cpu(anchor->mainVolDescSeqExt.extLength); main_e = main_e >> sb->s_blocksize_bits; - main_e += main_s; + main_e += main_s - 1; /* Locate the reserve sequence */ reserve_s = le32_to_cpu(anchor->reserveVolDescSeqExt.extLocation); reserve_e = le32_to_cpu(anchor->reserveVolDescSeqExt.extLength); reserve_e = reserve_e >> sb->s_blocksize_bits; - reserve_e += reserve_s; + reserve_e += reserve_s - 1; /* Process the main & reserve sequences */ /* responsible for finding the PartitionDesc(s) */ - if (!udf_process_sequence(sb, main_s, main_e, fileset)) - return 1; - udf_sb_free_partitions(sb); - if (!udf_process_sequence(sb, reserve_s, reserve_e, fileset)) - return 1; + ret = udf_process_sequence(sb, main_s, main_e, fileset); + if (ret != -EAGAIN) + return ret; udf_sb_free_partitions(sb); - return 0; + ret = udf_process_sequence(sb, reserve_s, reserve_e, fileset); + if (ret < 0) { + udf_sb_free_partitions(sb); + /* No sequence was OK, return -EIO */ + if (ret == -EAGAIN) + ret = -EIO; + } + return ret; } /* * Check whether there is an anchor block in the given block and * load Volume Descriptor Sequence if so. + * + * Returns <0 on error, 0 on success, -EAGAIN is special - try next anchor + * block */ static int udf_check_anchor_block(struct super_block *sb, sector_t block, struct kernel_lb_addr *fileset) @@ -1682,36 +1909,38 @@ static int udf_check_anchor_block(struct super_block *sb, sector_t block, uint16_t ident; int ret; - if (UDF_QUERY_FLAG(sb, UDF_FLAG_VARCONV) && - udf_fixed_to_variable(block) >= - sb->s_bdev->bd_inode->i_size >> sb->s_blocksize_bits) - return 0; - bh = udf_read_tagged(sb, block, block, &ident); if (!bh) - return 0; + return -EAGAIN; if (ident != TAG_IDENT_AVDP) { brelse(bh); - return 0; + return -EAGAIN; } ret = udf_load_sequence(sb, bh, fileset); brelse(bh); return ret; } -/* Search for an anchor volume descriptor pointer */ -static sector_t udf_scan_anchors(struct super_block *sb, sector_t lastblock, - struct kernel_lb_addr *fileset) +/* + * Search for an anchor volume descriptor pointer. + * + * Returns < 0 on error, 0 on success. -EAGAIN is special - try next set + * of anchors. + */ +static int udf_scan_anchors(struct super_block *sb, udf_pblk_t *lastblock, + struct kernel_lb_addr *fileset) { - sector_t last[6]; + udf_pblk_t last[6]; int i; struct udf_sb_info *sbi = UDF_SB(sb); int last_count = 0; + int ret; /* First try user provided anchor */ if (sbi->s_anchor) { - if (udf_check_anchor_block(sb, sbi->s_anchor, fileset)) - return lastblock; + ret = udf_check_anchor_block(sb, sbi->s_anchor, fileset); + if (ret != -EAGAIN) + return ret; } /* * according to spec, anchor is in either: @@ -1720,106 +1949,79 @@ static sector_t udf_scan_anchors(struct super_block *sb, sector_t lastblock, * lastblock * however, if the disc isn't closed, it could be 512. */ - if (udf_check_anchor_block(sb, sbi->s_session + 256, fileset)) - return lastblock; + ret = udf_check_anchor_block(sb, sbi->s_session + 256, fileset); + if (ret != -EAGAIN) + return ret; /* * The trouble is which block is the last one. Drives often misreport * this so we try various possibilities. */ - last[last_count++] = lastblock; - if (lastblock >= 1) - last[last_count++] = lastblock - 1; - last[last_count++] = lastblock + 1; - if (lastblock >= 2) - last[last_count++] = lastblock - 2; - if (lastblock >= 150) - last[last_count++] = lastblock - 150; - if (lastblock >= 152) - last[last_count++] = lastblock - 152; + last[last_count++] = *lastblock; + if (*lastblock >= 1) + last[last_count++] = *lastblock - 1; + last[last_count++] = *lastblock + 1; + if (*lastblock >= 2) + last[last_count++] = *lastblock - 2; + if (*lastblock >= 150) + last[last_count++] = *lastblock - 150; + if (*lastblock >= 152) + last[last_count++] = *lastblock - 152; for (i = 0; i < last_count; i++) { - if (last[i] >= sb->s_bdev->bd_inode->i_size >> - sb->s_blocksize_bits) + if (last[i] >= sb_bdev_nr_blocks(sb)) continue; - if (udf_check_anchor_block(sb, last[i], fileset)) - return last[i]; + ret = udf_check_anchor_block(sb, last[i], fileset); + if (ret != -EAGAIN) { + if (!ret) + *lastblock = last[i]; + return ret; + } if (last[i] < 256) continue; - if (udf_check_anchor_block(sb, last[i] - 256, fileset)) - return last[i]; + ret = udf_check_anchor_block(sb, last[i] - 256, fileset); + if (ret != -EAGAIN) { + if (!ret) + *lastblock = last[i]; + return ret; + } } /* Finally try block 512 in case media is open */ - if (udf_check_anchor_block(sb, sbi->s_session + 512, fileset)) - return last[0]; - return 0; -} - -/* - * Find an anchor volume descriptor and load Volume Descriptor Sequence from - * area specified by it. The function expects sbi->s_lastblock to be the last - * block on the media. - * - * Return 1 if ok, 0 if not found. - * - */ -static int udf_find_anchor(struct super_block *sb, - struct kernel_lb_addr *fileset) -{ - sector_t lastblock; - struct udf_sb_info *sbi = UDF_SB(sb); - - lastblock = udf_scan_anchors(sb, sbi->s_last_block, fileset); - if (lastblock) - goto out; - - /* No anchor found? Try VARCONV conversion of block numbers */ - UDF_SET_FLAG(sb, UDF_FLAG_VARCONV); - /* Firstly, we try to not convert number of the last block */ - lastblock = udf_scan_anchors(sb, - udf_variable_to_fixed(sbi->s_last_block), - fileset); - if (lastblock) - goto out; - - /* Secondly, we try with converted number of the last block */ - lastblock = udf_scan_anchors(sb, sbi->s_last_block, fileset); - if (!lastblock) { - /* VARCONV didn't help. Clear it. */ - UDF_CLEAR_FLAG(sb, UDF_FLAG_VARCONV); - return 0; - } -out: - sbi->s_last_block = lastblock; - return 1; + return udf_check_anchor_block(sb, sbi->s_session + 512, fileset); } /* * Check Volume Structure Descriptor, find Anchor block and load Volume - * Descriptor Sequence + * Descriptor Sequence. + * + * Returns < 0 on error, 0 on success. -EAGAIN is special meaning anchor + * block was not found. */ static int udf_load_vrs(struct super_block *sb, struct udf_options *uopt, int silent, struct kernel_lb_addr *fileset) { struct udf_sb_info *sbi = UDF_SB(sb); - loff_t nsr_off; + int nsr = 0; + int ret; if (!sb_set_blocksize(sb, uopt->blocksize)) { if (!silent) udf_warn(sb, "Bad block size\n"); - return 0; + return -EINVAL; } sbi->s_last_block = uopt->lastblock; - if (!uopt->novrs) { + if (!UDF_QUERY_FLAG(sb, UDF_FLAG_NOVRS)) { /* Check that it is NSR02 compliant */ - nsr_off = udf_check_vsd(sb); - if (!nsr_off) { + nsr = udf_check_vsd(sb); + if (!nsr) { if (!silent) udf_warn(sb, "No VRS found\n"); - return 0; + return -EINVAL; } - if (nsr_off == -1) - udf_debug("Failed to read byte 32768. Assuming open disc. Skipping validity check\n"); + if (nsr == -1) + udf_debug("Failed to read sector at offset %d. " + "Assuming open disc. Skipping validity " + "check\n", VSD_FIRST_SECTOR_OFFSET); if (!sbi->s_last_block) sbi->s_last_block = udf_get_last_block(sb); } else { @@ -1828,12 +2030,25 @@ static int udf_load_vrs(struct super_block *sb, struct udf_options *uopt, /* Look for anchor block and load Volume Descriptor Sequence */ sbi->s_anchor = uopt->anchor; - if (!udf_find_anchor(sb, fileset)) { - if (!silent) + ret = udf_scan_anchors(sb, &sbi->s_last_block, fileset); + if (ret < 0) { + if (!silent && ret == -EAGAIN) udf_warn(sb, "No anchor found\n"); - return 0; + return ret; } - return 1; + return 0; +} + +static void udf_finalize_lvid(struct logicalVolIntegrityDesc *lvid) +{ + struct timespec64 ts; + + ktime_get_real_ts64(&ts); + udf_time_to_disk_stamp(&lvid->recordingDateAndTime, ts); + lvid->descTag.descCRC = cpu_to_le16( + crc_itu_t(0, (char *)lvid + sizeof(struct tag), + le16_to_cpu(lvid->descTag.descCRCLength))); + lvid->descTag.tagChecksum = udf_tag_checksum(&lvid->descTag); } static void udf_open_lvid(struct super_block *sb) @@ -1845,22 +2060,20 @@ static void udf_open_lvid(struct super_block *sb) if (!bh) return; - - mutex_lock(&sbi->s_alloc_mutex); lvid = (struct logicalVolIntegrityDesc *)bh->b_data; - lvidiu = udf_sb_lvidiu(sbi); + lvidiu = udf_sb_lvidiu(sb); + if (!lvidiu) + return; + mutex_lock(&sbi->s_alloc_mutex); lvidiu->impIdent.identSuffix[0] = UDF_OS_CLASS_UNIX; lvidiu->impIdent.identSuffix[1] = UDF_OS_ID_LINUX; - udf_time_to_disk_stamp(&lvid->recordingDateAndTime, - CURRENT_TIME); - lvid->integrityType = cpu_to_le32(LVID_INTEGRITY_TYPE_OPEN); - - lvid->descTag.descCRC = cpu_to_le16( - crc_itu_t(0, (char *)lvid + sizeof(struct tag), - le16_to_cpu(lvid->descTag.descCRCLength))); + if (le32_to_cpu(lvid->integrityType) == LVID_INTEGRITY_TYPE_CLOSE) + lvid->integrityType = cpu_to_le32(LVID_INTEGRITY_TYPE_OPEN); + else + UDF_SET_FLAG(sb, UDF_FLAG_INCONSISTENT); - lvid->descTag.tagChecksum = udf_tag_checksum(&lvid->descTag); + udf_finalize_lvid(lvid); mark_buffer_dirty(bh); sbi->s_lvid_dirty = 0; mutex_unlock(&sbi->s_alloc_mutex); @@ -1877,32 +2090,30 @@ static void udf_close_lvid(struct super_block *sb) if (!bh) return; + lvid = (struct logicalVolIntegrityDesc *)bh->b_data; + lvidiu = udf_sb_lvidiu(sb); + if (!lvidiu) + return; mutex_lock(&sbi->s_alloc_mutex); - lvid = (struct logicalVolIntegrityDesc *)bh->b_data; - lvidiu = udf_sb_lvidiu(sbi); lvidiu->impIdent.identSuffix[0] = UDF_OS_CLASS_UNIX; lvidiu->impIdent.identSuffix[1] = UDF_OS_ID_LINUX; - udf_time_to_disk_stamp(&lvid->recordingDateAndTime, CURRENT_TIME); if (UDF_MAX_WRITE_VERSION > le16_to_cpu(lvidiu->maxUDFWriteRev)) lvidiu->maxUDFWriteRev = cpu_to_le16(UDF_MAX_WRITE_VERSION); if (sbi->s_udfrev > le16_to_cpu(lvidiu->minUDFReadRev)) lvidiu->minUDFReadRev = cpu_to_le16(sbi->s_udfrev); if (sbi->s_udfrev > le16_to_cpu(lvidiu->minUDFWriteRev)) lvidiu->minUDFWriteRev = cpu_to_le16(sbi->s_udfrev); - lvid->integrityType = cpu_to_le32(LVID_INTEGRITY_TYPE_CLOSE); - - lvid->descTag.descCRC = cpu_to_le16( - crc_itu_t(0, (char *)lvid + sizeof(struct tag), - le16_to_cpu(lvid->descTag.descCRCLength))); + if (!UDF_QUERY_FLAG(sb, UDF_FLAG_INCONSISTENT)) + lvid->integrityType = cpu_to_le32(LVID_INTEGRITY_TYPE_CLOSE); - lvid->descTag.tagChecksum = udf_tag_checksum(&lvid->descTag); /* * We set buffer uptodate unconditionally here to avoid spurious * warnings from mark_buffer_dirty() when previous EIO has marked * the buffer as !uptodate */ set_buffer_uptodate(bh); + udf_finalize_lvid(lvid); mark_buffer_dirty(bh); sbi->s_lvid_dirty = 0; mutex_unlock(&sbi->s_alloc_mutex); @@ -1931,28 +2142,23 @@ u64 lvid_get_unique_id(struct super_block *sb) if (!(++uniqueID & 0xFFFFFFFF)) uniqueID += 16; lvhd->uniqueID = cpu_to_le64(uniqueID); + udf_updated_lvid(sb); mutex_unlock(&sbi->s_alloc_mutex); - mark_buffer_dirty(bh); return ret; } -static int udf_fill_super(struct super_block *sb, void *options, int silent) +static int udf_fill_super(struct super_block *sb, struct fs_context *fc) { - int ret; + int ret = -EINVAL; struct inode *inode = NULL; - struct udf_options uopt; + struct udf_options *uopt = fc->fs_private; struct kernel_lb_addr rootdir, fileset; struct udf_sb_info *sbi; + bool lvid_open = false; + int silent = fc->sb_flags & SB_SILENT; - uopt.flags = (1 << UDF_FLAG_USE_AD_IN_ICB) | (1 << UDF_FLAG_STRICT); - uopt.uid = INVALID_UID; - uopt.gid = INVALID_GID; - uopt.umask = 0; - uopt.fmode = UDF_INVALID_MODE; - uopt.dmode = UDF_INVALID_MODE; - - sbi = kzalloc(sizeof(struct udf_sb_info), GFP_KERNEL); + sbi = kzalloc(sizeof(*sbi), GFP_KERNEL); if (!sbi) return -ENOMEM; @@ -1960,42 +2166,23 @@ static int udf_fill_super(struct super_block *sb, void *options, int silent) mutex_init(&sbi->s_alloc_mutex); - if (!udf_parse_options((char *)options, &uopt, false)) - goto error_out; - - if (uopt.flags & (1 << UDF_FLAG_UTF8) && - uopt.flags & (1 << UDF_FLAG_NLS_MAP)) { - udf_err(sb, "utf8 cannot be combined with iocharset\n"); - goto error_out; - } -#ifdef CONFIG_UDF_NLS - if ((uopt.flags & (1 << UDF_FLAG_NLS_MAP)) && !uopt.nls_map) { - uopt.nls_map = load_nls_default(); - if (!uopt.nls_map) - uopt.flags &= ~(1 << UDF_FLAG_NLS_MAP); - else - udf_debug("Using default NLS map\n"); - } -#endif - if (!(uopt.flags & (1 << UDF_FLAG_NLS_MAP))) - uopt.flags |= (1 << UDF_FLAG_UTF8); - fileset.logicalBlockNum = 0xFFFFFFFF; fileset.partitionReferenceNum = 0xFFFF; - sbi->s_flags = uopt.flags; - sbi->s_uid = uopt.uid; - sbi->s_gid = uopt.gid; - sbi->s_umask = uopt.umask; - sbi->s_fmode = uopt.fmode; - sbi->s_dmode = uopt.dmode; - sbi->s_nls_map = uopt.nls_map; + sbi->s_flags = uopt->flags; + sbi->s_uid = uopt->uid; + sbi->s_gid = uopt->gid; + sbi->s_umask = uopt->umask; + sbi->s_fmode = uopt->fmode; + sbi->s_dmode = uopt->dmode; + sbi->s_nls_map = uopt->nls_map; + uopt->nls_map = NULL; rwlock_init(&sbi->s_cred_lock); - if (uopt.session == 0xFFFFFFFF) + if (uopt->session == 0xFFFFFFFF) sbi->s_session = udf_get_last_session(sb); else - sbi->s_session = uopt.session; + sbi->s_session = uopt->session; udf_debug("Multi-session=%d\n", sbi->s_session); @@ -2006,43 +2193,66 @@ static int udf_fill_super(struct super_block *sb, void *options, int silent) sb->s_magic = UDF_SUPER_MAGIC; sb->s_time_gran = 1000; - if (uopt.flags & (1 << UDF_FLAG_BLOCKSIZE_SET)) { - ret = udf_load_vrs(sb, &uopt, silent, &fileset); + if (uopt->flags & (1 << UDF_FLAG_BLOCKSIZE_SET)) { + ret = udf_load_vrs(sb, uopt, silent, &fileset); } else { - uopt.blocksize = bdev_logical_block_size(sb->s_bdev); - ret = udf_load_vrs(sb, &uopt, silent, &fileset); - if (!ret && uopt.blocksize != UDF_DEFAULT_BLOCKSIZE) { - if (!silent) - pr_notice("Rescanning with blocksize %d\n", - UDF_DEFAULT_BLOCKSIZE); - brelse(sbi->s_lvid_bh); - sbi->s_lvid_bh = NULL; - uopt.blocksize = UDF_DEFAULT_BLOCKSIZE; - ret = udf_load_vrs(sb, &uopt, silent, &fileset); + uopt->blocksize = bdev_logical_block_size(sb->s_bdev); + while (uopt->blocksize <= 4096) { + ret = udf_load_vrs(sb, uopt, silent, &fileset); + if (ret < 0) { + if (!silent && ret != -EACCES) { + pr_notice("Scanning with blocksize %u failed\n", + uopt->blocksize); + } + brelse(sbi->s_lvid_bh); + sbi->s_lvid_bh = NULL; + /* + * EACCES is special - we want to propagate to + * upper layers that we cannot handle RW mount. + */ + if (ret == -EACCES) + break; + } else + break; + + uopt->blocksize <<= 1; } } - if (!ret) { - udf_warn(sb, "No partition found (1)\n"); + if (ret < 0) { + if (ret == -EAGAIN) { + udf_warn(sb, "No partition found (1)\n"); + ret = -EINVAL; + } goto error_out; } - udf_debug("Lastblock=%d\n", sbi->s_last_block); + udf_debug("Lastblock=%u\n", sbi->s_last_block); if (sbi->s_lvid_bh) { struct logicalVolIntegrityDescImpUse *lvidiu = - udf_sb_lvidiu(sbi); - uint16_t minUDFReadRev = le16_to_cpu(lvidiu->minUDFReadRev); - uint16_t minUDFWriteRev = le16_to_cpu(lvidiu->minUDFWriteRev); - /* uint16_t maxUDFWriteRev = - le16_to_cpu(lvidiu->maxUDFWriteRev); */ + udf_sb_lvidiu(sb); + uint16_t minUDFReadRev; + uint16_t minUDFWriteRev; + if (!lvidiu) { + ret = -EINVAL; + goto error_out; + } + minUDFReadRev = le16_to_cpu(lvidiu->minUDFReadRev); + minUDFWriteRev = le16_to_cpu(lvidiu->minUDFWriteRev); if (minUDFReadRev > UDF_MAX_READ_VERSION) { udf_err(sb, "minUDFReadRev=%x (max is %x)\n", - le16_to_cpu(lvidiu->minUDFReadRev), + minUDFReadRev, UDF_MAX_READ_VERSION); + ret = -EINVAL; goto error_out; - } else if (minUDFWriteRev > UDF_MAX_WRITE_VERSION) - sb->s_flags |= MS_RDONLY; + } else if (minUDFWriteRev > UDF_MAX_WRITE_VERSION) { + if (!sb_rdonly(sb)) { + ret = -EACCES; + goto error_out; + } + UDF_SET_FLAG(sb, UDF_FLAG_RW_INCOMPAT); + } sbi->s_udfrev = minUDFWriteRev; @@ -2054,16 +2264,21 @@ static int udf_fill_super(struct super_block *sb, void *options, int silent) if (!sbi->s_partitions) { udf_warn(sb, "No partition found (2)\n"); + ret = -EINVAL; goto error_out; } if (sbi->s_partmaps[sbi->s_partition].s_partition_flags & UDF_PART_FLAG_READ_ONLY) { - pr_notice("Partition marked readonly; forcing readonly mount\n"); - sb->s_flags |= MS_RDONLY; + if (!sb_rdonly(sb)) { + ret = -EACCES; + goto error_out; + } + UDF_SET_FLAG(sb, UDF_FLAG_RW_INCOMPAT); } - if (udf_find_fileset(sb, &fileset, &rootdir)) { + ret = udf_find_fileset(sb, &fileset, &rootdir); + if (ret < 0) { udf_warn(sb, "No fileset found\n"); goto error_out; } @@ -2076,16 +2291,19 @@ static int udf_fill_super(struct super_block *sb, void *options, int silent) le16_to_cpu(ts.year), ts.month, ts.day, ts.hour, ts.minute, le16_to_cpu(ts.typeAndTimezone)); } - if (!(sb->s_flags & MS_RDONLY)) + if (!sb_rdonly(sb)) { udf_open_lvid(sb); + lvid_open = true; + } /* Assign the root inode */ /* assign inodes by physical block number */ /* perhaps it's not extensible enough, but for now ... */ inode = udf_iget(sb, &rootdir); - if (!inode) { - udf_err(sb, "Error in udf_iget, block=%d, partition=%d\n", + if (IS_ERR(inode)) { + udf_err(sb, "Error in udf_iget, block=%u, partition=%u\n", rootdir.logicalBlockNum, rootdir.partitionReferenceNum); + ret = PTR_ERR(inode); goto error_out; } @@ -2093,27 +2311,24 @@ static int udf_fill_super(struct super_block *sb, void *options, int silent) sb->s_root = d_make_root(inode); if (!sb->s_root) { udf_err(sb, "Couldn't allocate root dentry\n"); + ret = -ENOMEM; goto error_out; } - sb->s_maxbytes = MAX_LFS_FILESIZE; + sb->s_maxbytes = UDF_MAX_FILESIZE; sb->s_max_links = UDF_MAX_LINKS; return 0; error_out: - if (sbi->s_vat_inode) - iput(sbi->s_vat_inode); -#ifdef CONFIG_UDF_NLS - if (UDF_QUERY_FLAG(sb, UDF_FLAG_NLS_MAP)) - unload_nls(sbi->s_nls_map); -#endif - if (!(sb->s_flags & MS_RDONLY)) + iput(sbi->s_vat_inode); + unload_nls(uopt->nls_map); + if (lvid_open) udf_close_lvid(sb); brelse(sbi->s_lvid_bh); udf_sb_free_partitions(sb); kfree(sbi); sb->s_fs_info = NULL; - return -EINVAL; + return ret; } void _udf_err(struct super_block *sb, const char *function, @@ -2154,16 +2369,13 @@ static void udf_put_super(struct super_block *sb) sbi = UDF_SB(sb); - if (sbi->s_vat_inode) - iput(sbi->s_vat_inode); -#ifdef CONFIG_UDF_NLS - if (UDF_QUERY_FLAG(sb, UDF_FLAG_NLS_MAP)) - unload_nls(sbi->s_nls_map); -#endif - if (!(sb->s_flags & MS_RDONLY)) + iput(sbi->s_vat_inode); + unload_nls(sbi->s_nls_map); + if (!sb_rdonly(sb)) udf_close_lvid(sb); brelse(sbi->s_lvid_bh); udf_sb_free_partitions(sb); + mutex_destroy(&sbi->s_alloc_mutex); kfree(sb->s_fs_info); sb->s_fs_info = NULL; } @@ -2174,11 +2386,17 @@ static int udf_sync_fs(struct super_block *sb, int wait) mutex_lock(&sbi->s_alloc_mutex); if (sbi->s_lvid_dirty) { + struct buffer_head *bh = sbi->s_lvid_bh; + struct logicalVolIntegrityDesc *lvid; + + lvid = (struct logicalVolIntegrityDesc *)bh->b_data; + udf_finalize_lvid(lvid); + /* * Blockdevice will be synced later so we don't have to submit * the buffer for IO */ - mark_buffer_dirty(sbi->s_lvid_bh); + mark_buffer_dirty(bh); sbi->s_lvid_dirty = 0; } mutex_unlock(&sbi->s_alloc_mutex); @@ -2193,23 +2411,22 @@ static int udf_statfs(struct dentry *dentry, struct kstatfs *buf) struct logicalVolIntegrityDescImpUse *lvidiu; u64 id = huge_encode_dev(sb->s_bdev->bd_dev); - if (sbi->s_lvid_bh != NULL) - lvidiu = udf_sb_lvidiu(sbi); - else - lvidiu = NULL; - + lvidiu = udf_sb_lvidiu(sb); buf->f_type = UDF_SUPER_MAGIC; buf->f_bsize = sb->s_blocksize; buf->f_blocks = sbi->s_partmaps[sbi->s_partition].s_partition_len; buf->f_bfree = udf_count_free(sb); buf->f_bavail = buf->f_bfree; + /* + * Let's pretend each free block is also a free 'inode' since UDF does + * not have separate preallocated table of inodes. + */ buf->f_files = (lvidiu != NULL ? (le32_to_cpu(lvidiu->numFiles) + le32_to_cpu(lvidiu->numDirs)) : 0) + buf->f_bfree; buf->f_ffree = buf->f_bfree; - buf->f_namelen = UDF_NAME_LEN - 2; - buf->f_fsid.val[0] = (u32)id; - buf->f_fsid.val[1] = (u32)(id >> 32); + buf->f_namelen = UDF_NAME_LEN; + buf->f_fsid = u64_to_fsid(id); return 0; } @@ -2220,7 +2437,7 @@ static unsigned int udf_count_free_bitmap(struct super_block *sb, struct buffer_head *bh = NULL; unsigned int accum = 0; int index; - int block = 0, newblock; + udf_pblk_t block = 0, newblock; struct kernel_lb_addr loc; uint32_t bytes; uint8_t *ptr; @@ -2253,7 +2470,7 @@ static unsigned int udf_count_free_bitmap(struct super_block *sb, if (bytes) { brelse(bh); newblock = udf_get_lb_pblock(sb, &loc, ++block); - bh = udf_tread(sb, newblock); + bh = sb_bread(sb, newblock); if (!bh) { udf_debug("read failed\n"); goto out; @@ -2273,15 +2490,15 @@ static unsigned int udf_count_free_table(struct super_block *sb, unsigned int accum = 0; uint32_t elen; struct kernel_lb_addr eloc; - int8_t etype; struct extent_position epos; + int8_t etype; mutex_lock(&UDF_SB(sb)->s_alloc_mutex); epos.block = UDF_I(table)->i_location; epos.offset = sizeof(struct unallocSpaceEntry); epos.bh = NULL; - while ((etype = udf_next_aext(table, &epos, &eloc, &elen, 1)) != -1) + while (udf_next_aext(table, &epos, &eloc, &elen, &etype, 1) > 0) accum += (elen >> table->i_sb->s_blocksize_bits); brelse(epos.bh); @@ -2293,17 +2510,29 @@ static unsigned int udf_count_free_table(struct super_block *sb, static unsigned int udf_count_free(struct super_block *sb) { unsigned int accum = 0; - struct udf_sb_info *sbi; + struct udf_sb_info *sbi = UDF_SB(sb); struct udf_part_map *map; + unsigned int part = sbi->s_partition; + int ptype = sbi->s_partmaps[part].s_partition_type; + + if (ptype == UDF_METADATA_MAP25) { + part = sbi->s_partmaps[part].s_type_specific.s_metadata. + s_phys_partition_ref; + } else if (ptype == UDF_VIRTUAL_MAP15 || ptype == UDF_VIRTUAL_MAP20) { + /* + * Filesystems with VAT are append-only and we cannot write to + * them. Let's just report 0 here. + */ + return 0; + } - sbi = UDF_SB(sb); if (sbi->s_lvid_bh) { struct logicalVolIntegrityDesc *lvid = (struct logicalVolIntegrityDesc *) sbi->s_lvid_bh->b_data; - if (le32_to_cpu(lvid->numOfPartitions) > sbi->s_partition) { + if (le32_to_cpu(lvid->numOfPartitions) > part) { accum = le32_to_cpu( - lvid->freeSpaceTable[sbi->s_partition]); + lvid->freeSpaceTable[part]); if (accum == 0xFFFFFFFF) accum = 0; } @@ -2312,15 +2541,11 @@ static unsigned int udf_count_free(struct super_block *sb) if (accum) return accum; - map = &sbi->s_partmaps[sbi->s_partition]; + map = &sbi->s_partmaps[part]; if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_BITMAP) { accum += udf_count_free_bitmap(sb, map->s_uspace.s_bitmap); } - if (map->s_partition_flags & UDF_PART_FLAG_FREED_BITMAP) { - accum += udf_count_free_bitmap(sb, - map->s_fspace.s_bitmap); - } if (accum) return accum; @@ -2328,10 +2553,11 @@ static unsigned int udf_count_free(struct super_block *sb) accum += udf_count_free_table(sb, map->s_uspace.s_table); } - if (map->s_partition_flags & UDF_PART_FLAG_FREED_TABLE) { - accum += udf_count_free_table(sb, - map->s_fspace.s_table); - } - return accum; } + +MODULE_AUTHOR("Ben Fennema"); +MODULE_DESCRIPTION("Universal Disk Format Filesystem"); +MODULE_LICENSE("GPL"); +module_init(init_udf_fs) +module_exit(exit_udf_fs) diff --git a/fs/udf/symlink.c b/fs/udf/symlink.c index d7c6dbe4194b..fe03745d09b1 100644 --- a/fs/udf/symlink.c +++ b/fs/udf/symlink.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * symlink.c * @@ -5,11 +6,6 @@ * Symlink handling routines for the OSTA-UDF(tm) filesystem. * * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. - * * (C) 1998-2001 Ben Fennema * (C) 1999 Stelias Computing Inc * @@ -20,106 +16,156 @@ */ #include "udfdecl.h" -#include <asm/uaccess.h> +#include <linux/uaccess.h> #include <linux/errno.h> #include <linux/fs.h> #include <linux/time.h> #include <linux/mm.h> #include <linux/stat.h> #include <linux/pagemap.h> -#include <linux/buffer_head.h> #include "udf_i.h" -static void udf_pc_to_char(struct super_block *sb, unsigned char *from, - int fromlen, unsigned char *to) +static int udf_pc_to_char(struct super_block *sb, unsigned char *from, + int fromlen, unsigned char *to, int tolen) { struct pathComponent *pc; int elen = 0; + int comp_len; unsigned char *p = to; + /* Reserve one byte for terminating \0 */ + tolen--; while (elen < fromlen) { pc = (struct pathComponent *)(from + elen); + elen += sizeof(struct pathComponent); switch (pc->componentType) { case 1: /* * Symlink points to some place which should be agreed * upon between originator and receiver of the media. Ignore. */ - if (pc->lengthComponentIdent > 0) + if (pc->lengthComponentIdent > 0) { + elen += pc->lengthComponentIdent; break; - /* Fall through */ + } + fallthrough; case 2: + if (tolen == 0) + return -ENAMETOOLONG; p = to; *p++ = '/'; + tolen--; break; case 3: + if (tolen < 3) + return -ENAMETOOLONG; memcpy(p, "../", 3); p += 3; + tolen -= 3; break; case 4: + if (tolen < 2) + return -ENAMETOOLONG; memcpy(p, "./", 2); p += 2; + tolen -= 2; /* that would be . - just ignore */ break; case 5: - p += udf_get_filename(sb, pc->componentIdent, p, - pc->lengthComponentIdent); + elen += pc->lengthComponentIdent; + if (elen > fromlen) + return -EIO; + comp_len = udf_get_filename(sb, pc->componentIdent, + pc->lengthComponentIdent, + p, tolen); + if (comp_len < 0) + return comp_len; + + p += comp_len; + tolen -= comp_len; + if (tolen == 0) + return -ENAMETOOLONG; *p++ = '/'; + tolen--; break; } - elen += sizeof(struct pathComponent) + pc->lengthComponentIdent; } if (p > to + 1) p[-1] = '\0'; else p[0] = '\0'; + return 0; } -static int udf_symlink_filler(struct file *file, struct page *page) +static int udf_symlink_filler(struct file *file, struct folio *folio) { - struct inode *inode = page->mapping->host; + struct inode *inode = folio->mapping->host; struct buffer_head *bh = NULL; unsigned char *symlink; - int err = -EIO; - unsigned char *p = kmap(page); - struct udf_inode_info *iinfo; - uint32_t pos; + int err = 0; + unsigned char *p = folio_address(folio); + struct udf_inode_info *iinfo = UDF_I(inode); - iinfo = UDF_I(inode); - pos = udf_block_map(inode, 0); + /* We don't support symlinks longer than one block */ + if (inode->i_size > inode->i_sb->s_blocksize) { + err = -ENAMETOOLONG; + goto out; + } - down_read(&iinfo->i_data_sem); if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { - symlink = iinfo->i_ext.i_data + iinfo->i_lenEAttr; + symlink = iinfo->i_data + iinfo->i_lenEAttr; } else { - bh = sb_bread(inode->i_sb, pos); - - if (!bh) + bh = udf_bread(inode, 0, 0, &err); + if (!bh) { + if (!err) + err = -EFSCORRUPTED; goto out; - + } symlink = bh->b_data; } - udf_pc_to_char(inode->i_sb, symlink, inode->i_size, p); + err = udf_pc_to_char(inode->i_sb, symlink, inode->i_size, p, PAGE_SIZE); brelse(bh); - - up_read(&iinfo->i_data_sem); - SetPageUptodate(page); - kunmap(page); - unlock_page(page); - return 0; - out: - up_read(&iinfo->i_data_sem); - SetPageError(page); - kunmap(page); - unlock_page(page); + folio_end_read(folio, err == 0); return err; } +static int udf_symlink_getattr(struct mnt_idmap *idmap, + const struct path *path, struct kstat *stat, + u32 request_mask, unsigned int flags) +{ + struct dentry *dentry = path->dentry; + struct inode *inode = d_backing_inode(dentry); + struct folio *folio; + + generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat); + folio = read_mapping_folio(inode->i_mapping, 0, NULL); + if (IS_ERR(folio)) + return PTR_ERR(folio); + /* + * UDF uses non-trivial encoding of symlinks so i_size does not match + * number of characters reported by readlink(2) which apparently some + * applications expect. Also POSIX says that "The value returned in the + * st_size field shall be the length of the contents of the symbolic + * link, and shall not count a trailing null if one is present." So + * let's report the length of string returned by readlink(2) for + * st_size. + */ + stat->size = strlen(folio_address(folio)); + folio_put(folio); + + return 0; +} + /* * symlinks can't do much... */ const struct address_space_operations udf_symlink_aops = { - .readpage = udf_symlink_filler, + .read_folio = udf_symlink_filler, +}; + +const struct inode_operations udf_symlink_inode_operations = { + .get_link = page_get_link, + .getattr = udf_symlink_getattr, }; diff --git a/fs/udf/truncate.c b/fs/udf/truncate.c index 8a9657d7f7c6..b4071c9cf8c9 100644 --- a/fs/udf/truncate.c +++ b/fs/udf/truncate.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * truncate.c * @@ -5,11 +6,6 @@ * Truncate handling routines for the OSTA-UDF(tm) filesystem. * * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. - * * (C) 1999-2004 Ben Fennema * (C) 1999 Stelias Computing Inc * @@ -22,7 +18,6 @@ #include "udfdecl.h" #include <linux/fs.h> #include <linux/mm.h> -#include <linux/buffer_head.h> #include "udf_i.h" #include "udf_sb.h" @@ -49,7 +44,7 @@ static void extent_trunc(struct inode *inode, struct extent_position *epos, if (elen != nelen) { udf_write_aext(inode, epos, &neloc, nelen, 0); - if (last_block - first_block > 0) { + if (last_block > first_block) { if (etype == (EXT_RECORDED_ALLOCATED >> 30)) mark_inode_dirty(inode); @@ -74,6 +69,7 @@ void udf_truncate_tail_extent(struct inode *inode) int8_t etype = -1, netype; int adsize; struct udf_inode_info *iinfo = UDF_I(inode); + int ret; if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB || inode->i_size == iinfo->i_lenExtents) @@ -90,7 +86,10 @@ void udf_truncate_tail_extent(struct inode *inode) BUG(); /* Find the last extent in the file */ - while ((netype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1) { + while (1) { + ret = udf_next_aext(inode, &epos, &eloc, &elen, &netype, 1); + if (ret <= 0) + break; etype = netype; lbcount += elen; if (lbcount > inode->i_size) { @@ -106,7 +105,8 @@ void udf_truncate_tail_extent(struct inode *inode) epos.offset -= adsize; extent_trunc(inode, &epos, &eloc, etype, elen, nelen); epos.offset += adsize; - if (udf_next_aext(inode, &epos, &eloc, &elen, 1) != -1) + if (udf_next_aext(inode, &epos, &eloc, &elen, + &netype, 1) > 0) udf_err(inode->i_sb, "Extent after EOF in inode %u\n", (unsigned)inode->i_ino); @@ -115,66 +115,60 @@ void udf_truncate_tail_extent(struct inode *inode) } /* This inode entry is in-memory only and thus we don't have to mark * the inode dirty */ - iinfo->i_lenExtents = inode->i_size; + if (ret >= 0) + iinfo->i_lenExtents = inode->i_size; brelse(epos.bh); } void udf_discard_prealloc(struct inode *inode) { - struct extent_position epos = { NULL, 0, {0, 0} }; + struct extent_position epos = {}; + struct extent_position prev_epos = {}; struct kernel_lb_addr eloc; uint32_t elen; uint64_t lbcount = 0; - int8_t etype = -1, netype; - int adsize; + int8_t etype = -1; struct udf_inode_info *iinfo = UDF_I(inode); + int bsize = i_blocksize(inode); + int8_t tmpetype = -1; + int ret; if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB || - inode->i_size == iinfo->i_lenExtents) + ALIGN(inode->i_size, bsize) == ALIGN(iinfo->i_lenExtents, bsize)) return; - 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 - adsize = 0; - epos.block = iinfo->i_location; /* Find the last extent in the file */ - while ((netype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1) { - etype = netype; + while (1) { + ret = udf_next_aext(inode, &epos, &eloc, &elen, &tmpetype, 0); + if (ret < 0) + goto out; + if (ret == 0) + break; + brelse(prev_epos.bh); + prev_epos = epos; + if (prev_epos.bh) + get_bh(prev_epos.bh); + + ret = udf_next_aext(inode, &epos, &eloc, &elen, &etype, 1); + if (ret < 0) + goto out; lbcount += elen; } + if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30)) { - epos.offset -= adsize; lbcount -= elen; - extent_trunc(inode, &epos, &eloc, etype, elen, 0); - if (!epos.bh) { - iinfo->i_lenAlloc = - epos.offset - - udf_file_entry_alloc_offset(inode); - mark_inode_dirty(inode); - } else { - struct allocExtDesc *aed = - (struct allocExtDesc *)(epos.bh->b_data); - aed->lengthAllocDescs = - cpu_to_le32(epos.offset - - sizeof(struct allocExtDesc)); - if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT) || - UDF_SB(inode->i_sb)->s_udfrev >= 0x0201) - udf_update_tag(epos.bh->b_data, epos.offset); - else - udf_update_tag(epos.bh->b_data, - sizeof(struct allocExtDesc)); - mark_buffer_dirty_inode(epos.bh, inode); - } + udf_delete_aext(inode, prev_epos); + udf_free_blocks(inode->i_sb, inode, &eloc, 0, + DIV_ROUND_UP(elen, bsize)); } /* This inode entry is in-memory only and thus we don't have to mark * the inode dirty */ iinfo->i_lenExtents = lbcount; +out: brelse(epos.bh); + brelse(prev_epos.bh); } static void udf_update_alloc_ext_desc(struct inode *inode, @@ -200,7 +194,7 @@ static void udf_update_alloc_ext_desc(struct inode *inode, * for making file shorter. For making file longer, udf_extend_file() has to * be used. */ -void udf_truncate_extents(struct inode *inode) +int udf_truncate_extents(struct inode *inode) { struct extent_position epos; struct kernel_lb_addr eloc, neloc = {}; @@ -211,6 +205,7 @@ void udf_truncate_extents(struct inode *inode) loff_t byte_offset; int adsize; struct udf_inode_info *iinfo = UDF_I(inode); + int ret = 0; if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT) adsize = sizeof(struct short_ad); @@ -219,13 +214,15 @@ void udf_truncate_extents(struct inode *inode) else BUG(); - etype = inode_bmap(inode, first_block, &epos, &eloc, &elen, &offset); + ret = inode_bmap(inode, first_block, &epos, &eloc, &elen, &offset, &etype); + if (ret < 0) + return ret; byte_offset = (offset << sb->s_blocksize_bits) + (inode->i_size & (sb->s_blocksize - 1)); - if (etype == -1) { + if (ret == 0) { /* We should extend the file? */ WARN_ON(byte_offset); - return; + return 0; } epos.offset -= adsize; extent_trunc(inode, &epos, &eloc, etype, elen, byte_offset); @@ -240,9 +237,9 @@ void udf_truncate_extents(struct inode *inode) else lenalloc -= sizeof(struct allocExtDesc); - while ((etype = udf_current_aext(inode, &epos, &eloc, - &elen, 0)) != -1) { - if (etype == (EXT_NEXT_EXTENT_ALLOCDECS >> 30)) { + while ((ret = udf_current_aext(inode, &epos, &eloc, + &elen, &etype, 0)) > 0) { + if (etype == (EXT_NEXT_EXTENT_ALLOCDESCS >> 30)) { udf_write_aext(inode, &epos, &neloc, nelen, 0); if (indirect_ext_len) { /* We managed to free all extents in the @@ -259,8 +256,11 @@ void udf_truncate_extents(struct inode *inode) brelse(epos.bh); epos.offset = sizeof(struct allocExtDesc); epos.block = eloc; - epos.bh = udf_tread(sb, + epos.bh = sb_bread(sb, udf_get_lb_pblock(sb, &eloc, 0)); + /* Error reading indirect block? */ + if (!epos.bh) + return -EIO; if (elen) indirect_ext_len = (elen + sb->s_blocksize - 1) >> @@ -273,6 +273,11 @@ void udf_truncate_extents(struct inode *inode) } } + if (ret < 0) { + brelse(epos.bh); + return ret; + } + if (indirect_ext_len) { BUG_ON(!epos.bh); udf_free_blocks(sb, NULL, &epos.block, 0, indirect_ext_len); @@ -284,4 +289,5 @@ void udf_truncate_extents(struct inode *inode) iinfo->i_lenExtents = inode->i_size; brelse(epos.bh); + return 0; } diff --git a/fs/udf/udf_i.h b/fs/udf/udf_i.h index b5cd8ed2aa12..312b7c9ef10e 100644 --- a/fs/udf/udf_i.h +++ b/fs/udf/udf_i.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _UDF_I_H #define _UDF_I_H @@ -27,7 +28,7 @@ struct udf_ext_cache { */ struct udf_inode_info { - struct timespec i_crtime; + struct timespec64 i_crtime; /* Physical address of inode */ struct kernel_lb_addr i_location; __u64 i_unique; @@ -37,16 +38,17 @@ struct udf_inode_info { __u32 i_next_alloc_block; __u32 i_next_alloc_goal; __u32 i_checkpoint; + __u32 i_extraPerms; unsigned i_alloc_type : 3; unsigned i_efe : 1; /* extendedFileEntry */ unsigned i_use : 1; /* unallocSpaceEntry */ unsigned i_strat4096 : 1; - unsigned reserved : 26; - union { - struct short_ad *i_sad; - struct long_ad *i_lad; - __u8 *i_data; - } i_ext; + unsigned i_streamdir : 1; + unsigned i_hidden : 1; /* hidden system inode */ + unsigned reserved : 24; + __u8 *i_data; + struct kernel_lb_addr i_locStreamdir; + __u64 i_lenStreams; struct rw_semaphore i_data_sem; struct udf_ext_cache cached_extent; /* Spinlock for protecting extent cache */ @@ -56,7 +58,7 @@ struct udf_inode_info { static inline struct udf_inode_info *UDF_I(struct inode *inode) { - return list_entry(inode, struct udf_inode_info, vfs_inode); + return container_of(inode, struct udf_inode_info, vfs_inode); } #endif /* _UDF_I_H) */ diff --git a/fs/udf/udf_sb.h b/fs/udf/udf_sb.h index ed401e94aa8c..08ec8756b948 100644 --- a/fs/udf/udf_sb.h +++ b/fs/udf/udf_sb.h @@ -1,13 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef __LINUX_UDF_SB_H #define __LINUX_UDF_SB_H #include <linux/mutex.h> #include <linux/bitops.h> +#include <linux/magic.h> -/* Since UDF 2.01 is ISO 13346 based... */ -#define UDF_SUPER_MAGIC 0x15013346 - -#define UDF_MAX_READ_VERSION 0x0250 +/* + * Even UDF 2.6 media should have version <= 0x250 but apparently there are + * some broken filesystems with version set to 0x260. Accommodate those. + */ +#define UDF_MAX_READ_VERSION 0x0260 #define UDF_MAX_WRITE_VERSION 0x0201 #define UDF_FLAG_USE_EXTENDED_FE 0 @@ -20,23 +23,20 @@ #define UDF_FLAG_STRICT 5 #define UDF_FLAG_UNDELETE 6 #define UDF_FLAG_UNHIDE 7 -#define UDF_FLAG_VARCONV 8 -#define UDF_FLAG_NLS_MAP 9 -#define UDF_FLAG_UTF8 10 +#define UDF_FLAG_NOVRS 8 #define UDF_FLAG_UID_FORGET 11 /* save -1 for uid to disk */ -#define UDF_FLAG_UID_IGNORE 12 /* use sb uid instead of on disk uid */ -#define UDF_FLAG_GID_FORGET 13 -#define UDF_FLAG_GID_IGNORE 14 -#define UDF_FLAG_UID_SET 15 -#define UDF_FLAG_GID_SET 16 -#define UDF_FLAG_SESSION_SET 17 -#define UDF_FLAG_LASTBLOCK_SET 18 -#define UDF_FLAG_BLOCKSIZE_SET 19 +#define UDF_FLAG_GID_FORGET 12 +#define UDF_FLAG_UID_SET 13 +#define UDF_FLAG_GID_SET 14 +#define UDF_FLAG_SESSION_SET 15 +#define UDF_FLAG_LASTBLOCK_SET 16 +#define UDF_FLAG_BLOCKSIZE_SET 17 +#define UDF_FLAG_INCONSISTENT 18 +#define UDF_FLAG_RW_INCOMPAT 19 /* Set when we find RW incompatible + * feature */ #define UDF_PART_FLAG_UNALLOC_BITMAP 0x0001 #define UDF_PART_FLAG_UNALLOC_TABLE 0x0002 -#define UDF_PART_FLAG_FREED_BITMAP 0x0004 -#define UDF_PART_FLAG_FREED_TABLE 0x0008 #define UDF_PART_FLAG_READ_ONLY 0x0010 #define UDF_PART_FLAG_WRITE_ONCE 0x0020 #define UDF_PART_FLAG_REWRITABLE 0x0040 @@ -52,17 +52,22 @@ #define UDF_INVALID_MODE ((umode_t)-1) -#pragma pack(1) /* XXX(hch): Why? This file just defines in-core structures */ - #define MF_DUPLICATE_MD 0x01 #define MF_MIRROR_FE_LOADED 0x02 +#define EFSCORRUPTED EUCLEAN + struct udf_meta_data { __u32 s_meta_file_loc; __u32 s_mirror_file_loc; __u32 s_bitmap_file_loc; __u32 s_alloc_unit_size; __u16 s_align_unit_size; + /* + * Partition Reference Number of the associated physical / sparable + * partition + */ + __u16 s_phys_partition_ref; int s_flags; struct inode *s_metadata_fe; struct inode *s_mirror_fe; @@ -82,7 +87,7 @@ struct udf_virtual_data { struct udf_bitmap { __u32 s_extPosition; int s_nr_groups; - struct buffer_head *s_block_bitmap[0]; + struct buffer_head *s_block_bitmap[] __counted_by(s_nr_groups); }; struct udf_part_map { @@ -90,10 +95,6 @@ struct udf_part_map { struct udf_bitmap *s_bitmap; struct inode *s_table; } s_uspace; - union { - struct udf_bitmap *s_bitmap; - struct inode *s_table; - } s_fspace; __u32 s_partition_root; __u32 s_partition_len; __u16 s_partition_type; @@ -135,7 +136,7 @@ struct udf_sb_info { rwlock_t s_cred_lock; /* Root Info */ - struct timespec s_record_time; + struct timespec64 s_record_time; /* Fileset Info */ __u16 s_serial_number; @@ -162,7 +163,7 @@ static inline struct udf_sb_info *UDF_SB(struct super_block *sb) return sb->s_fs_info; } -struct logicalVolIntegrityDescImpUse *udf_sb_lvidiu(struct udf_sb_info *sbi); +struct logicalVolIntegrityDescImpUse *udf_sb_lvidiu(struct super_block *sb); int udf_compute_nr_groups(struct super_block *sb, u32 partition); diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h index be7dabbbcb49..d159f20d61e8 100644 --- a/fs/udf/udfdecl.h +++ b/fs/udf/udfdecl.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef __UDF_DECL_H #define __UDF_DECL_H @@ -15,7 +16,6 @@ #include "udfend.h" #include "udf_i.h" -#define UDF_PREALLOCATE #define UDF_DEFAULT_PREALLOC_BLOCKS 8 extern __printf(3, 4) void _udf_err(struct super_block *sb, @@ -31,26 +31,17 @@ extern __printf(3, 4) void _udf_warn(struct super_block *sb, #define udf_info(fmt, ...) \ pr_info("INFO " fmt, ##__VA_ARGS__) -#undef UDFFS_DEBUG - -#ifdef UDFFS_DEBUG -#define udf_debug(fmt, ...) \ - printk(KERN_DEBUG pr_fmt("%s:%d:%s: " fmt), \ - __FILE__, __LINE__, __func__, ##__VA_ARGS__) -#else #define udf_debug(fmt, ...) \ - no_printk(fmt, ##__VA_ARGS__) -#endif - -#define udf_fixed_to_variable(x) ( ( ( (x) >> 5 ) * 39 ) + ( (x) & 0x0000001F ) ) -#define udf_variable_to_fixed(x) ( ( ( (x) / 39 ) << 5 ) + ( (x) % 39 ) ) + pr_debug("%s:%d:%s: " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define UDF_EXTENT_LENGTH_MASK 0x3FFFFFFF #define UDF_EXTENT_FLAG_MASK 0xC0000000 +#define UDF_INVALID_ID ((uint32_t)-1) + #define UDF_NAME_PAD 4 -#define UDF_NAME_LEN 256 -#define UDF_PATH_LEN 1023 +#define UDF_NAME_LEN 254 +#define UDF_NAME_LEN_CS0 255 static inline size_t udf_file_entry_alloc_offset(struct inode *inode) { @@ -74,6 +65,8 @@ static inline size_t udf_ext0_offset(struct inode *inode) /* computes tag checksum */ u8 udf_tag_checksum(const struct tag *t); +typedef uint32_t udf_pblk_t; + struct dentry; struct inode; struct task_struct; @@ -87,14 +80,24 @@ extern const struct inode_operations udf_file_inode_operations; extern const struct file_operations udf_file_operations; extern const struct inode_operations udf_symlink_inode_operations; extern const struct address_space_operations udf_aops; -extern const struct address_space_operations udf_adinicb_aops; extern const struct address_space_operations udf_symlink_aops; -struct udf_fileident_bh { - struct buffer_head *sbh; - struct buffer_head *ebh; - int soffset; - int eoffset; +struct udf_fileident_iter { + struct inode *dir; /* Directory we are working with */ + loff_t pos; /* Logical position in a dir */ + struct buffer_head *bh[2]; /* Buffer containing 'pos' and possibly + * next buffer if entry straddles + * blocks */ + struct kernel_lb_addr eloc; /* Start of extent containing 'pos' */ + uint32_t elen; /* Length of extent containing 'pos' */ + sector_t loffset; /* Block offset of 'pos' within above + * extent */ + struct extent_position epos; /* Position after the above extent */ + struct fileIdentDesc fi; /* Copied directory entry */ + uint8_t *name; /* Pointer to entry name */ + uint8_t *namebuf; /* Storage for entry name in case + * the name is split between two blocks + */ }; struct udf_vds_record { @@ -107,12 +110,6 @@ struct generic_desc { __le32 volDescSeqNum; }; -struct ustr { - uint8_t u_cmpID; - uint8_t u_name[UDF_NAME_LEN - 2]; - uint8_t u_len; -}; - /* super.c */ @@ -131,38 +128,57 @@ struct inode *udf_find_metadata_inode_efe(struct super_block *sb, u32 meta_file_loc, u32 partition_num); /* namei.c */ -extern int udf_write_fi(struct inode *inode, struct fileIdentDesc *, - struct fileIdentDesc *, struct udf_fileident_bh *, - uint8_t *, uint8_t *); +static inline unsigned int udf_dir_entry_len(struct fileIdentDesc *cfi) +{ + return ALIGN(sizeof(struct fileIdentDesc) + + le16_to_cpu(cfi->lengthOfImpUse) + cfi->lengthFileIdent, + UDF_NAME_PAD); +} /* file.c */ extern long udf_ioctl(struct file *, unsigned int, unsigned long); + /* inode.c */ -extern struct inode *udf_iget(struct super_block *, struct kernel_lb_addr *); +extern struct inode *__udf_iget(struct super_block *, struct kernel_lb_addr *, + bool hidden_inode); +static inline struct inode *udf_iget_special(struct super_block *sb, + struct kernel_lb_addr *ino) +{ + return __udf_iget(sb, ino, true); +} +static inline struct inode *udf_iget(struct super_block *sb, + struct kernel_lb_addr *ino) +{ + return __udf_iget(sb, ino, false); +} extern int udf_expand_file_adinicb(struct inode *); -extern struct buffer_head *udf_expand_dir_adinicb(struct inode *, int *, int *); -extern struct buffer_head *udf_bread(struct inode *, int, int, int *); +extern struct buffer_head *udf_bread(struct inode *inode, udf_pblk_t block, + int create, int *err); extern int udf_setsize(struct inode *, loff_t); -extern void udf_read_inode(struct inode *); extern void udf_evict_inode(struct inode *); extern int udf_write_inode(struct inode *, struct writeback_control *wbc); -extern long udf_block_map(struct inode *, sector_t); -extern int8_t inode_bmap(struct inode *, sector_t, struct extent_position *, - struct kernel_lb_addr *, uint32_t *, sector_t *); +extern int inode_bmap(struct inode *inode, sector_t block, + struct extent_position *pos, struct kernel_lb_addr *eloc, + uint32_t *elen, sector_t *offset, int8_t *etype); +int udf_get_block(struct inode *, sector_t, struct buffer_head *, int); +extern int udf_setup_indirect_aext(struct inode *inode, udf_pblk_t block, + struct extent_position *epos); +extern int __udf_add_aext(struct inode *inode, struct extent_position *epos, + struct kernel_lb_addr *eloc, uint32_t elen, int inc); extern int udf_add_aext(struct inode *, struct extent_position *, struct kernel_lb_addr *, uint32_t, int); extern void udf_write_aext(struct inode *, struct extent_position *, struct kernel_lb_addr *, uint32_t, int); -extern int8_t udf_delete_aext(struct inode *, struct extent_position, - struct kernel_lb_addr, uint32_t); -extern int8_t udf_next_aext(struct inode *, struct extent_position *, - struct kernel_lb_addr *, uint32_t *, int); -extern int8_t udf_current_aext(struct inode *, struct extent_position *, - struct kernel_lb_addr *, uint32_t *, int); +extern int8_t udf_delete_aext(struct inode *, struct extent_position); +extern int udf_next_aext(struct inode *inode, struct extent_position *epos, + struct kernel_lb_addr *eloc, uint32_t *elen, + int8_t *etype, int inc); +extern int udf_current_aext(struct inode *inode, struct extent_position *epos, + struct kernel_lb_addr *eloc, uint32_t *elen, + int8_t *etype, int inc); +extern void udf_update_extra_perms(struct inode *inode, umode_t mode); /* misc.c */ -extern struct buffer_head *udf_tgetblk(struct super_block *, int); -extern struct buffer_head *udf_tread(struct super_block *, int); extern struct genericFormat *udf_add_extendedattr(struct inode *, uint32_t, uint32_t, uint8_t); extern struct genericFormat *udf_get_extendedattr(struct inode *, uint32_t, @@ -177,7 +193,7 @@ extern void udf_new_tag(char *, uint16_t, uint16_t, uint16_t, uint32_t, int); /* lowlevel.c */ extern unsigned int udf_get_last_session(struct super_block *); -extern unsigned long udf_get_last_block(struct super_block *); +udf_pblk_t udf_get_last_block(struct super_block *); /* partition.c */ extern uint32_t udf_get_pblock(struct super_block *, uint32_t, uint16_t, @@ -201,44 +217,44 @@ udf_get_lb_pblock(struct super_block *sb, struct kernel_lb_addr *loc, } /* unicode.c */ -extern int udf_get_filename(struct super_block *, uint8_t *, uint8_t *, int); -extern int udf_put_filename(struct super_block *, const uint8_t *, uint8_t *, - int); -extern int udf_build_ustr(struct ustr *, dstring *, int); -extern int udf_CS0toUTF8(struct ustr *, const struct ustr *); +extern int udf_get_filename(struct super_block *, const uint8_t *, int, + uint8_t *, int); +extern int udf_put_filename(struct super_block *, const uint8_t *, int, + uint8_t *, int); +extern int udf_dstrCS0toChar(struct super_block *, uint8_t *, int, + const uint8_t *, int); /* ialloc.c */ extern void udf_free_inode(struct inode *); -extern struct inode *udf_new_inode(struct inode *, umode_t, int *); +extern struct inode *udf_new_inode(struct inode *, umode_t); /* truncate.c */ extern void udf_truncate_tail_extent(struct inode *); extern void udf_discard_prealloc(struct inode *); -extern void udf_truncate_extents(struct inode *); +extern int udf_truncate_extents(struct inode *); /* balloc.c */ extern void udf_free_blocks(struct super_block *, struct inode *, struct kernel_lb_addr *, uint32_t, uint32_t); extern int udf_prealloc_blocks(struct super_block *, struct inode *, uint16_t, uint32_t, uint32_t); -extern int udf_new_block(struct super_block *, struct inode *, uint16_t, - uint32_t, int *); +extern udf_pblk_t udf_new_block(struct super_block *sb, struct inode *inode, + uint16_t partition, uint32_t goal, int *err); /* directory.c */ -extern struct fileIdentDesc *udf_fileident_read(struct inode *, loff_t *, - struct udf_fileident_bh *, - struct fileIdentDesc *, - struct extent_position *, - struct kernel_lb_addr *, uint32_t *, - sector_t *); -extern struct fileIdentDesc *udf_get_fileident(void *buffer, int bufsize, - int *offset); +int udf_fiiter_init(struct udf_fileident_iter *iter, struct inode *dir, + loff_t pos); +int udf_fiiter_advance(struct udf_fileident_iter *iter); +void udf_fiiter_release(struct udf_fileident_iter *iter); +void udf_fiiter_write_fi(struct udf_fileident_iter *iter, uint8_t *impuse); +void udf_fiiter_update_elen(struct udf_fileident_iter *iter, uint32_t new_elen); +int udf_fiiter_append_blk(struct udf_fileident_iter *iter); extern struct long_ad *udf_get_filelongad(uint8_t *, int, uint32_t *, int); extern struct short_ad *udf_get_fileshortad(uint8_t *, int, uint32_t *, int); /* udftime.c */ -extern struct timespec *udf_disk_stamp_to_time(struct timespec *dest, +extern void udf_disk_stamp_to_time(struct timespec64 *dest, struct timestamp src); -extern struct timestamp *udf_time_to_disk_stamp(struct timestamp *dest, struct timespec src); +extern void udf_time_to_disk_stamp(struct timestamp *dest, struct timespec64 src); #endif /* __UDF_DECL_H */ diff --git a/fs/udf/udfend.h b/fs/udf/udfend.h index 6a9f3a9cc428..a4363ac2cfeb 100644 --- a/fs/udf/udfend.h +++ b/fs/udf/udfend.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef __UDF_ENDIAN_H #define __UDF_ENDIAN_H diff --git a/fs/udf/udftime.c b/fs/udf/udftime.c index 1f11483eba6a..78ecc633606f 100644 --- a/fs/udf/udftime.c +++ b/fs/udf/udftime.c @@ -1,21 +1,7 @@ +// SPDX-License-Identifier: LGPL-2.0+ /* Copyright (C) 1993, 1994, 1995, 1996, 1997 Free Software Foundation, Inc. This file is part of the GNU C Library. - Contributed by Paul Eggert (eggert@twinsun.com). - - The GNU C Library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. - - The GNU C Library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with the GNU C Library; see the file COPYING.LIB. If not, - write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. */ + Contributed by Paul Eggert (eggert@twinsun.com). */ /* * dgb 10/02/98: ripped this from glibc source to help convert timestamps @@ -38,58 +24,11 @@ #include <linux/types.h> #include <linux/kernel.h> +#include <linux/time.h> -#define EPOCH_YEAR 1970 - -#ifndef __isleap -/* Nonzero if YEAR is a leap year (every 4 years, - except every 100th isn't, and every 400th is). */ -#define __isleap(year) \ - ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0)) -#endif - -/* How many days come before each month (0-12). */ -static const unsigned short int __mon_yday[2][13] = { - /* Normal years. */ - {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, - /* Leap years. */ - {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366} -}; - -#define MAX_YEAR_SECONDS 69 -#define SPD 0x15180 /*3600*24 */ -#define SPY(y, l, s) (SPD * (365 * y + l) + s) - -static time_t year_seconds[MAX_YEAR_SECONDS] = { -/*1970*/ SPY(0, 0, 0), SPY(1, 0, 0), SPY(2, 0, 0), SPY(3, 1, 0), -/*1974*/ SPY(4, 1, 0), SPY(5, 1, 0), SPY(6, 1, 0), SPY(7, 2, 0), -/*1978*/ SPY(8, 2, 0), SPY(9, 2, 0), SPY(10, 2, 0), SPY(11, 3, 0), -/*1982*/ SPY(12, 3, 0), SPY(13, 3, 0), SPY(14, 3, 0), SPY(15, 4, 0), -/*1986*/ SPY(16, 4, 0), SPY(17, 4, 0), SPY(18, 4, 0), SPY(19, 5, 0), -/*1990*/ SPY(20, 5, 0), SPY(21, 5, 0), SPY(22, 5, 0), SPY(23, 6, 0), -/*1994*/ SPY(24, 6, 0), SPY(25, 6, 0), SPY(26, 6, 0), SPY(27, 7, 0), -/*1998*/ SPY(28, 7, 0), SPY(29, 7, 0), SPY(30, 7, 0), SPY(31, 8, 0), -/*2002*/ SPY(32, 8, 0), SPY(33, 8, 0), SPY(34, 8, 0), SPY(35, 9, 0), -/*2006*/ SPY(36, 9, 0), SPY(37, 9, 0), SPY(38, 9, 0), SPY(39, 10, 0), -/*2010*/ SPY(40, 10, 0), SPY(41, 10, 0), SPY(42, 10, 0), SPY(43, 11, 0), -/*2014*/ SPY(44, 11, 0), SPY(45, 11, 0), SPY(46, 11, 0), SPY(47, 12, 0), -/*2018*/ SPY(48, 12, 0), SPY(49, 12, 0), SPY(50, 12, 0), SPY(51, 13, 0), -/*2022*/ SPY(52, 13, 0), SPY(53, 13, 0), SPY(54, 13, 0), SPY(55, 14, 0), -/*2026*/ SPY(56, 14, 0), SPY(57, 14, 0), SPY(58, 14, 0), SPY(59, 15, 0), -/*2030*/ SPY(60, 15, 0), SPY(61, 15, 0), SPY(62, 15, 0), SPY(63, 16, 0), -/*2034*/ SPY(64, 16, 0), SPY(65, 16, 0), SPY(66, 16, 0), SPY(67, 17, 0), -/*2038*/ SPY(68, 17, 0) -}; - -extern struct timezone sys_tz; - -#define SECS_PER_HOUR (60 * 60) -#define SECS_PER_DAY (SECS_PER_HOUR * 24) - -struct timespec * -udf_disk_stamp_to_time(struct timespec *dest, struct timestamp src) +void +udf_disk_stamp_to_time(struct timespec64 *dest, struct timestamp src) { - int yday; u16 typeAndTimezone = le16_to_cpu(src.typeAndTimezone); u16 year = le16_to_cpu(src.year); uint8_t type = typeAndTimezone >> 12; @@ -104,69 +43,47 @@ udf_disk_stamp_to_time(struct timespec *dest, struct timestamp src) } else offset = 0; - if ((year < EPOCH_YEAR) || - (year >= EPOCH_YEAR + MAX_YEAR_SECONDS)) { - return NULL; - } - dest->tv_sec = year_seconds[year - EPOCH_YEAR]; + dest->tv_sec = mktime64(year, src.month, src.day, src.hour, src.minute, + src.second); dest->tv_sec -= offset * 60; - yday = ((__mon_yday[__isleap(year)][src.month - 1]) + src.day - 1); - dest->tv_sec += (((yday * 24) + src.hour) * 60 + src.minute) * 60 + src.second; - dest->tv_nsec = 1000 * (src.centiseconds * 10000 + + /* + * Sanitize nanosecond field since reportedly some filesystems are + * recorded with bogus sub-second values. + */ + if (src.centiseconds < 100 && src.hundredsOfMicroseconds < 100 && + src.microseconds < 100) { + dest->tv_nsec = 1000 * (src.centiseconds * 10000 + src.hundredsOfMicroseconds * 100 + src.microseconds); - return dest; + } else { + dest->tv_nsec = 0; + } } -struct timestamp * -udf_time_to_disk_stamp(struct timestamp *dest, struct timespec ts) +void +udf_time_to_disk_stamp(struct timestamp *dest, struct timespec64 ts) { - long int days, rem, y; - const unsigned short int *ip; + time64_t seconds; int16_t offset; + struct tm tm; offset = -sys_tz.tz_minuteswest; - if (!dest) - return NULL; - dest->typeAndTimezone = cpu_to_le16(0x1000 | (offset & 0x0FFF)); - ts.tv_sec += offset * 60; - days = ts.tv_sec / SECS_PER_DAY; - rem = ts.tv_sec % SECS_PER_DAY; - dest->hour = rem / SECS_PER_HOUR; - rem %= SECS_PER_HOUR; - dest->minute = rem / 60; - dest->second = rem % 60; - y = 1970; - -#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0)) -#define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400)) - - while (days < 0 || days >= (__isleap(y) ? 366 : 365)) { - long int yg = y + days / 365 - (days % 365 < 0); - - /* Adjust DAYS and Y to match the guessed year. */ - days -= ((yg - y) * 365 - + LEAPS_THRU_END_OF(yg - 1) - - LEAPS_THRU_END_OF(y - 1)); - y = yg; - } - dest->year = cpu_to_le16(y); - ip = __mon_yday[__isleap(y)]; - for (y = 11; days < (long int)ip[y]; --y) - continue; - days -= ip[y]; - dest->month = y + 1; - dest->day = days + 1; - + seconds = ts.tv_sec + offset * 60; + time64_to_tm(seconds, 0, &tm); + dest->year = cpu_to_le16(tm.tm_year + 1900); + dest->month = tm.tm_mon + 1; + dest->day = tm.tm_mday; + dest->hour = tm.tm_hour; + dest->minute = tm.tm_min; + dest->second = tm.tm_sec; dest->centiseconds = ts.tv_nsec / 10000000; dest->hundredsOfMicroseconds = (ts.tv_nsec / 1000 - dest->centiseconds * 10000) / 100; dest->microseconds = (ts.tv_nsec / 1000 - dest->centiseconds * 10000 - dest->hundredsOfMicroseconds * 100); - return dest; } /* EOF */ diff --git a/fs/udf/unicode.c b/fs/udf/unicode.c index 44b815e57f94..32c7f3d27f74 100644 --- a/fs/udf/unicode.c +++ b/fs/udf/unicode.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * unicode.c * @@ -11,11 +12,6 @@ * UTF-8 is explained in the IETF RFC XXXX. * ftp://ftp.internic.net/rfc/rfcxxxx.txt * - * COPYRIGHT - * This file is distributed under the terms of the GNU General Public - * License (GPL). Copies of the GPL can be obtained from: - * ftp://prep.ai.mit.edu/pub/gnu/GPL - * Each contributing author retains all rights to their own work. */ #include "udfdecl.h" @@ -28,466 +24,375 @@ #include "udf_sb.h" -static int udf_translate_to_linux(uint8_t *, uint8_t *, int, uint8_t *, int); +#define PLANE_SIZE 0x10000 +#define UNICODE_MAX 0x10ffff +#define SURROGATE_MASK 0xfffff800 +#define SURROGATE_PAIR 0x0000d800 +#define SURROGATE_LOW 0x00000400 +#define SURROGATE_CHAR_BITS 10 +#define SURROGATE_CHAR_MASK ((1 << SURROGATE_CHAR_BITS) - 1) -static int udf_char_to_ustr(struct ustr *dest, const uint8_t *src, int strlen) -{ - if ((!dest) || (!src) || (!strlen) || (strlen > UDF_NAME_LEN - 2)) - return 0; - - memset(dest, 0, sizeof(struct ustr)); - memcpy(dest->u_name, src, strlen); - dest->u_cmpID = 0x08; - dest->u_len = strlen; - - return strlen; -} +#define ILLEGAL_CHAR_MARK '_' +#define EXT_MARK '.' +#define CRC_MARK '#' +#define EXT_SIZE 5 +/* Number of chars we need to store generated CRC to make filename unique */ +#define CRC_LEN 5 -/* - * udf_build_ustr - */ -int udf_build_ustr(struct ustr *dest, dstring *ptr, int size) +static unicode_t get_utf16_char(const uint8_t *str_i, int str_i_max_len, + int str_i_idx, int u_ch, unicode_t *ret) { - int usesize; + unicode_t c; + int start_idx = str_i_idx; + + /* Expand OSTA compressed Unicode to Unicode */ + c = str_i[str_i_idx++]; + if (u_ch > 1) + c = (c << 8) | str_i[str_i_idx++]; + if ((c & SURROGATE_MASK) == SURROGATE_PAIR) { + unicode_t next; + + /* Trailing surrogate char */ + if (str_i_idx >= str_i_max_len) { + c = UNICODE_MAX + 1; + goto out; + } - if (!dest || !ptr || !size) - return -1; - BUG_ON(size < 2); + /* Low surrogate must follow the high one... */ + if (c & SURROGATE_LOW) { + c = UNICODE_MAX + 1; + goto out; + } - usesize = min_t(size_t, ptr[size - 1], sizeof(dest->u_name)); - usesize = min(usesize, size - 2); - dest->u_cmpID = ptr[0]; - dest->u_len = usesize; - memcpy(dest->u_name, ptr + 1, usesize); - memset(dest->u_name + usesize, 0, sizeof(dest->u_name) - usesize); + WARN_ON_ONCE(u_ch != 2); + next = str_i[str_i_idx++] << 8; + next |= str_i[str_i_idx++]; + if ((next & SURROGATE_MASK) != SURROGATE_PAIR || + !(next & SURROGATE_LOW)) { + c = UNICODE_MAX + 1; + goto out; + } - return 0; + c = PLANE_SIZE + + ((c & SURROGATE_CHAR_MASK) << SURROGATE_CHAR_BITS) + + (next & SURROGATE_CHAR_MASK); + } +out: + *ret = c; + return str_i_idx - start_idx; } -/* - * udf_build_ustr_exact - */ -static int udf_build_ustr_exact(struct ustr *dest, dstring *ptr, int exactsize) -{ - if ((!dest) || (!ptr) || (!exactsize)) - return -1; - memset(dest, 0, sizeof(struct ustr)); - dest->u_cmpID = ptr[0]; - dest->u_len = exactsize - 1; - memcpy(dest->u_name, ptr + 1, exactsize - 1); +static int udf_name_conv_char(uint8_t *str_o, int str_o_max_len, + int *str_o_idx, + const uint8_t *str_i, int str_i_max_len, + int *str_i_idx, + int u_ch, int *needsCRC, + int (*conv_f)(wchar_t, unsigned char *, int), + int translate) +{ + unicode_t c; + int illChar = 0; + int len, gotch = 0; + + while (!gotch && *str_i_idx < str_i_max_len) { + if (*str_o_idx >= str_o_max_len) { + *needsCRC = 1; + return gotch; + } - return 0; + len = get_utf16_char(str_i, str_i_max_len, *str_i_idx, u_ch, + &c); + /* These chars cannot be converted. Replace them. */ + if (c == 0 || c > UNICODE_MAX || (conv_f && c > MAX_WCHAR_T) || + (translate && c == '/')) { + illChar = 1; + if (!translate) + gotch = 1; + } else if (illChar) + break; + else + gotch = 1; + *str_i_idx += len; + } + if (illChar) { + *needsCRC = 1; + c = ILLEGAL_CHAR_MARK; + gotch = 1; + } + if (gotch) { + if (conv_f) { + len = conv_f(c, &str_o[*str_o_idx], + str_o_max_len - *str_o_idx); + } else { + len = utf32_to_utf8(c, &str_o[*str_o_idx], + str_o_max_len - *str_o_idx); + if (len < 0) + len = -ENAMETOOLONG; + } + /* Valid character? */ + if (len >= 0) + *str_o_idx += len; + else if (len == -ENAMETOOLONG) { + *needsCRC = 1; + gotch = 0; + } else { + str_o[(*str_o_idx)++] = ILLEGAL_CHAR_MARK; + *needsCRC = 1; + } + } + return gotch; } -/* - * udf_ocu_to_utf8 - * - * PURPOSE - * Convert OSTA Compressed Unicode to the UTF-8 equivalent. - * - * PRE-CONDITIONS - * utf Pointer to UTF-8 output buffer. - * ocu Pointer to OSTA Compressed Unicode input buffer - * of size UDF_NAME_LEN bytes. - * both of type "struct ustr *" - * - * POST-CONDITIONS - * <return> Zero on success. - * - * HISTORY - * November 12, 1997 - Andrew E. Mileski - * Written, tested, and released. - */ -int udf_CS0toUTF8(struct ustr *utf_o, const struct ustr *ocu_i) +static int udf_name_from_CS0(struct super_block *sb, + uint8_t *str_o, int str_max_len, + const uint8_t *ocu, int ocu_len, + int translate) { - const uint8_t *ocu; - uint8_t cmp_id, ocu_len; - int i; + uint32_t c; + uint8_t cmp_id; + int idx, len; + int u_ch; + int needsCRC = 0; + int ext_i_len, ext_max_len; + int str_o_len = 0; /* Length of resulting output */ + int ext_o_len = 0; /* Extension output length */ + int ext_crc_len = 0; /* Extension output length if used with CRC */ + int i_ext = -1; /* Extension position in input buffer */ + int o_crc = 0; /* Rightmost possible output pos for CRC+ext */ + unsigned short valueCRC; + uint8_t ext[EXT_SIZE * NLS_MAX_CHARSET_SIZE + 1]; + uint8_t crc[CRC_LEN]; + int (*conv_f)(wchar_t, unsigned char *, int); - ocu_len = ocu_i->u_len; - if (ocu_len == 0) { - memset(utf_o, 0, sizeof(struct ustr)); + if (str_max_len <= 0) return 0; - } - cmp_id = ocu_i->u_cmpID; - if (cmp_id != 8 && cmp_id != 16) { - memset(utf_o, 0, sizeof(struct ustr)); - pr_err("unknown compression code (%d) stri=%s\n", - cmp_id, ocu_i->u_name); + if (ocu_len == 0) { + memset(str_o, 0, str_max_len); return 0; } - ocu = ocu_i->u_name; - utf_o->u_len = 0; - for (i = 0; (i < ocu_len) && (utf_o->u_len <= (UDF_NAME_LEN - 3));) { - - /* Expand OSTA compressed Unicode to Unicode */ - uint32_t c = ocu[i++]; - if (cmp_id == 16) - c = (c << 8) | ocu[i++]; - - /* Compress Unicode to UTF-8 */ - if (c < 0x80U) - utf_o->u_name[utf_o->u_len++] = (uint8_t)c; - else if (c < 0x800U) { - utf_o->u_name[utf_o->u_len++] = - (uint8_t)(0xc0 | (c >> 6)); - utf_o->u_name[utf_o->u_len++] = - (uint8_t)(0x80 | (c & 0x3f)); - } else { - utf_o->u_name[utf_o->u_len++] = - (uint8_t)(0xe0 | (c >> 12)); - utf_o->u_name[utf_o->u_len++] = - (uint8_t)(0x80 | - ((c >> 6) & 0x3f)); - utf_o->u_name[utf_o->u_len++] = - (uint8_t)(0x80 | (c & 0x3f)); - } + if (UDF_SB(sb)->s_nls_map) + conv_f = UDF_SB(sb)->s_nls_map->uni2char; + else + conv_f = NULL; + + cmp_id = ocu[0]; + if (cmp_id != 8 && cmp_id != 16) { + memset(str_o, 0, str_max_len); + pr_err("unknown compression code (%u)\n", cmp_id); + return -EINVAL; } - utf_o->u_cmpID = 8; + u_ch = cmp_id >> 3; - return utf_o->u_len; -} + ocu++; + ocu_len--; -/* - * - * udf_utf8_to_ocu - * - * PURPOSE - * Convert UTF-8 to the OSTA Compressed Unicode equivalent. - * - * DESCRIPTION - * This routine is only called by udf_lookup(). - * - * PRE-CONDITIONS - * ocu Pointer to OSTA Compressed Unicode output - * buffer of size UDF_NAME_LEN bytes. - * utf Pointer to UTF-8 input buffer. - * utf_len Length of UTF-8 input buffer in bytes. - * - * POST-CONDITIONS - * <return> Zero on success. - * - * HISTORY - * November 12, 1997 - Andrew E. Mileski - * Written, tested, and released. - */ -static int udf_UTF8toCS0(dstring *ocu, struct ustr *utf, int length) -{ - unsigned c, i, max_val, utf_char; - int utf_cnt, u_len; - - memset(ocu, 0, sizeof(dstring) * length); - ocu[0] = 8; - max_val = 0xffU; + if (ocu_len % u_ch) { + pr_err("incorrect filename length (%d)\n", ocu_len + 1); + return -EINVAL; + } -try_again: - u_len = 0U; - utf_char = 0U; - utf_cnt = 0U; - for (i = 0U; i < utf->u_len; i++) { - c = (uint8_t)utf->u_name[i]; - - /* Complete a multi-byte UTF-8 character */ - if (utf_cnt) { - utf_char = (utf_char << 6) | (c & 0x3fU); - if (--utf_cnt) - continue; - } else { - /* Check for a multi-byte UTF-8 character */ - if (c & 0x80U) { - /* Start a multi-byte UTF-8 character */ - if ((c & 0xe0U) == 0xc0U) { - utf_char = c & 0x1fU; - utf_cnt = 1; - } else if ((c & 0xf0U) == 0xe0U) { - utf_char = c & 0x0fU; - utf_cnt = 2; - } else if ((c & 0xf8U) == 0xf0U) { - utf_char = c & 0x07U; - utf_cnt = 3; - } else if ((c & 0xfcU) == 0xf8U) { - utf_char = c & 0x03U; - utf_cnt = 4; - } else if ((c & 0xfeU) == 0xfcU) { - utf_char = c & 0x01U; - utf_cnt = 5; - } else { - goto error_out; - } - continue; - } else { - /* Single byte UTF-8 character (most common) */ - utf_char = c; + if (translate) { + /* Look for extension */ + for (idx = ocu_len - u_ch, ext_i_len = 0; + (idx >= 0) && (ext_i_len < EXT_SIZE); + idx -= u_ch, ext_i_len++) { + c = ocu[idx]; + if (u_ch > 1) + c = (c << 8) | ocu[idx + 1]; + + if (c == EXT_MARK) { + if (ext_i_len) + i_ext = idx; + break; } } - - /* Choose no compression if necessary */ - if (utf_char > max_val) { - if (max_val == 0xffU) { - max_val = 0xffffU; - ocu[0] = (uint8_t)0x10U; - goto try_again; + if (i_ext >= 0) { + /* Convert extension */ + ext_max_len = min_t(int, sizeof(ext), str_max_len); + ext[ext_o_len++] = EXT_MARK; + idx = i_ext + u_ch; + while (udf_name_conv_char(ext, ext_max_len, &ext_o_len, + ocu, ocu_len, &idx, + u_ch, &needsCRC, + conv_f, translate)) { + if ((ext_o_len + CRC_LEN) < str_max_len) + ext_crc_len = ext_o_len; } - goto error_out; } - - if (max_val == 0xffffU) - ocu[++u_len] = (uint8_t)(utf_char >> 8); - ocu[++u_len] = (uint8_t)(utf_char & 0xffU); } - if (utf_cnt) { -error_out: - ocu[++u_len] = '?'; - printk(KERN_DEBUG pr_fmt("bad UTF-8 character\n")); + idx = 0; + while (1) { + if (translate && (idx == i_ext)) { + if (str_o_len > (str_max_len - ext_o_len)) + needsCRC = 1; + break; + } + + if (!udf_name_conv_char(str_o, str_max_len, &str_o_len, + ocu, ocu_len, &idx, + u_ch, &needsCRC, conv_f, translate)) + break; + + if (translate && + (str_o_len <= (str_max_len - ext_o_len - CRC_LEN))) + o_crc = str_o_len; } - ocu[length - 1] = (uint8_t)u_len + 1; + if (translate) { + if (str_o_len > 0 && str_o_len <= 2 && str_o[0] == '.' && + (str_o_len == 1 || str_o[1] == '.')) + needsCRC = 1; + if (needsCRC) { + str_o_len = o_crc; + valueCRC = crc_itu_t(0, ocu, ocu_len); + crc[0] = CRC_MARK; + crc[1] = hex_asc_upper_hi(valueCRC >> 8); + crc[2] = hex_asc_upper_lo(valueCRC >> 8); + crc[3] = hex_asc_upper_hi(valueCRC); + crc[4] = hex_asc_upper_lo(valueCRC); + len = min_t(int, CRC_LEN, str_max_len - str_o_len); + memcpy(&str_o[str_o_len], crc, len); + str_o_len += len; + ext_o_len = ext_crc_len; + } + if (ext_o_len > 0) { + memcpy(&str_o[str_o_len], ext, ext_o_len); + str_o_len += ext_o_len; + } + } - return u_len + 1; + return str_o_len; } -static int udf_CS0toNLS(struct nls_table *nls, struct ustr *utf_o, - const struct ustr *ocu_i) +static int udf_name_to_CS0(struct super_block *sb, + uint8_t *ocu, int ocu_max_len, + const uint8_t *str_i, int str_len) { - const uint8_t *ocu; - uint8_t cmp_id, ocu_len; int i, len; + unsigned int max_val; + int u_len, u_ch; + unicode_t uni_char; + int (*conv_f)(const unsigned char *, int, wchar_t *); - - ocu_len = ocu_i->u_len; - if (ocu_len == 0) { - memset(utf_o, 0, sizeof(struct ustr)); + if (ocu_max_len <= 0) return 0; - } - cmp_id = ocu_i->u_cmpID; - if (cmp_id != 8 && cmp_id != 16) { - memset(utf_o, 0, sizeof(struct ustr)); - pr_err("unknown compression code (%d) stri=%s\n", - cmp_id, ocu_i->u_name); - return 0; - } + if (UDF_SB(sb)->s_nls_map) + conv_f = UDF_SB(sb)->s_nls_map->char2uni; + else + conv_f = NULL; - ocu = ocu_i->u_name; - utf_o->u_len = 0; - for (i = 0; (i < ocu_len) && (utf_o->u_len <= (UDF_NAME_LEN - 3));) { - /* Expand OSTA compressed Unicode to Unicode */ - uint32_t c = ocu[i++]; - if (cmp_id == 16) - c = (c << 8) | ocu[i++]; - - len = nls->uni2char(c, &utf_o->u_name[utf_o->u_len], - UDF_NAME_LEN - utf_o->u_len); - /* Valid character? */ - if (len >= 0) - utf_o->u_len += len; - else - utf_o->u_name[utf_o->u_len++] = '?'; - } - utf_o->u_cmpID = 8; - - return utf_o->u_len; -} - -static int udf_NLStoCS0(struct nls_table *nls, dstring *ocu, struct ustr *uni, - int length) -{ - int len; - unsigned i, max_val; - uint16_t uni_char; - int u_len; - - memset(ocu, 0, sizeof(dstring) * length); + memset(ocu, 0, ocu_max_len); ocu[0] = 8; - max_val = 0xffU; + max_val = 0xff; + u_ch = 1; try_again: - u_len = 0U; - for (i = 0U; i < uni->u_len; i++) { - len = nls->char2uni(&uni->u_name[i], uni->u_len - i, &uni_char); - if (!len) - continue; + u_len = 1; + for (i = 0; i < str_len; i += len) { + /* Name didn't fit? */ + if (u_len + u_ch > ocu_max_len) + return 0; + if (conv_f) { + wchar_t wchar; + + len = conv_f(&str_i[i], str_len - i, &wchar); + if (len > 0) + uni_char = wchar; + } else { + len = utf8_to_utf32(&str_i[i], str_len - i, + &uni_char); + } /* Invalid character, deal with it */ - if (len < 0) { + if (len <= 0 || uni_char > UNICODE_MAX) { len = 1; uni_char = '?'; } if (uni_char > max_val) { - max_val = 0xffffU; - ocu[0] = (uint8_t)0x10U; - goto try_again; + unicode_t c; + + if (max_val == 0xff) { + max_val = 0xffff; + ocu[0] = 0x10; + u_ch = 2; + goto try_again; + } + /* + * Use UTF-16 encoding for chars outside we + * cannot encode directly. + */ + if (u_len + 2 * u_ch > ocu_max_len) + return 0; + + uni_char -= PLANE_SIZE; + c = SURROGATE_PAIR | + ((uni_char >> SURROGATE_CHAR_BITS) & + SURROGATE_CHAR_MASK); + ocu[u_len++] = (uint8_t)(c >> 8); + ocu[u_len++] = (uint8_t)(c & 0xff); + uni_char = SURROGATE_PAIR | SURROGATE_LOW | + (uni_char & SURROGATE_CHAR_MASK); } - if (max_val == 0xffffU) - ocu[++u_len] = (uint8_t)(uni_char >> 8); - ocu[++u_len] = (uint8_t)(uni_char & 0xffU); - i += len - 1; + if (max_val == 0xffff) + ocu[u_len++] = (uint8_t)(uni_char >> 8); + ocu[u_len++] = (uint8_t)(uni_char & 0xff); } - ocu[length - 1] = (uint8_t)u_len + 1; - return u_len + 1; + return u_len; } -int udf_get_filename(struct super_block *sb, uint8_t *sname, uint8_t *dname, - int flen) +/* + * Convert CS0 dstring to output charset. Warning: This function may truncate + * input string if it is too long as it is used for informational strings only + * and it is better to truncate the string than to refuse mounting a media. + */ +int udf_dstrCS0toChar(struct super_block *sb, uint8_t *utf_o, int o_len, + const uint8_t *ocu_i, int i_len) { - struct ustr *filename, *unifilename; - int len = 0; - - filename = kmalloc(sizeof(struct ustr), GFP_NOFS); - if (!filename) - return 0; - - unifilename = kmalloc(sizeof(struct ustr), GFP_NOFS); - if (!unifilename) - goto out1; - - if (udf_build_ustr_exact(unifilename, sname, flen)) - goto out2; - - if (UDF_QUERY_FLAG(sb, UDF_FLAG_UTF8)) { - if (!udf_CS0toUTF8(filename, unifilename)) { - udf_debug("Failed in udf_get_filename: sname = %s\n", - sname); - goto out2; - } - } else if (UDF_QUERY_FLAG(sb, UDF_FLAG_NLS_MAP)) { - if (!udf_CS0toNLS(UDF_SB(sb)->s_nls_map, filename, - unifilename)) { - udf_debug("Failed in udf_get_filename: sname = %s\n", - sname); - goto out2; + int s_len = 0; + + if (i_len > 0) { + s_len = ocu_i[i_len - 1]; + if (s_len >= i_len) { + pr_warn("incorrect dstring lengths (%d/%d)," + " truncating\n", s_len, i_len); + s_len = i_len - 1; + /* 2-byte encoding? Need to round properly... */ + if (ocu_i[0] == 16) + s_len -= (s_len - 1) & 2; } - } else - goto out2; - - len = udf_translate_to_linux(dname, filename->u_name, filename->u_len, - unifilename->u_name, unifilename->u_len); -out2: - kfree(unifilename); -out1: - kfree(filename); - return len; + } + + return udf_name_from_CS0(sb, utf_o, o_len, ocu_i, s_len, 0); } -int udf_put_filename(struct super_block *sb, const uint8_t *sname, - uint8_t *dname, int flen) +int udf_get_filename(struct super_block *sb, const uint8_t *sname, int slen, + uint8_t *dname, int dlen) { - struct ustr unifilename; - int namelen; + int ret; - if (!udf_char_to_ustr(&unifilename, sname, flen)) - return 0; + if (!slen) + return -EIO; - if (UDF_QUERY_FLAG(sb, UDF_FLAG_UTF8)) { - namelen = udf_UTF8toCS0(dname, &unifilename, UDF_NAME_LEN); - if (!namelen) - return 0; - } else if (UDF_QUERY_FLAG(sb, UDF_FLAG_NLS_MAP)) { - namelen = udf_NLStoCS0(UDF_SB(sb)->s_nls_map, dname, - &unifilename, UDF_NAME_LEN); - if (!namelen) - return 0; - } else + if (dlen <= 0) return 0; - return namelen; + ret = udf_name_from_CS0(sb, dname, dlen, sname, slen, 1); + /* Zero length filename isn't valid... */ + if (ret == 0) + ret = -EINVAL; + return ret; } -#define ILLEGAL_CHAR_MARK '_' -#define EXT_MARK '.' -#define CRC_MARK '#' -#define EXT_SIZE 5 - -static int udf_translate_to_linux(uint8_t *newName, uint8_t *udfName, - int udfLen, uint8_t *fidName, - int fidNameLen) +int udf_put_filename(struct super_block *sb, const uint8_t *sname, int slen, + uint8_t *dname, int dlen) { - int index, newIndex = 0, needsCRC = 0; - int extIndex = 0, newExtIndex = 0, hasExt = 0; - unsigned short valueCRC; - uint8_t curr; - const uint8_t hexChar[] = "0123456789ABCDEF"; - - if (udfName[0] == '.' && - (udfLen == 1 || (udfLen == 2 && udfName[1] == '.'))) { - needsCRC = 1; - newIndex = udfLen; - memcpy(newName, udfName, udfLen); - } else { - for (index = 0; index < udfLen; index++) { - curr = udfName[index]; - if (curr == '/' || curr == 0) { - needsCRC = 1; - curr = ILLEGAL_CHAR_MARK; - while (index + 1 < udfLen && - (udfName[index + 1] == '/' || - udfName[index + 1] == 0)) - index++; - } - if (curr == EXT_MARK && - (udfLen - index - 1) <= EXT_SIZE) { - if (udfLen == index + 1) - hasExt = 0; - else { - hasExt = 1; - extIndex = index; - newExtIndex = newIndex; - } - } - if (newIndex < 256) - newName[newIndex++] = curr; - else - needsCRC = 1; - } - } - if (needsCRC) { - uint8_t ext[EXT_SIZE]; - int localExtIndex = 0; - - if (hasExt) { - int maxFilenameLen; - for (index = 0; - index < EXT_SIZE && extIndex + index + 1 < udfLen; - index++) { - curr = udfName[extIndex + index + 1]; - - if (curr == '/' || curr == 0) { - needsCRC = 1; - curr = ILLEGAL_CHAR_MARK; - while (extIndex + index + 2 < udfLen && - (index + 1 < EXT_SIZE && - (udfName[extIndex + index + 2] == '/' || - udfName[extIndex + index + 2] == 0))) - index++; - } - ext[localExtIndex++] = curr; - } - maxFilenameLen = 250 - localExtIndex; - if (newIndex > maxFilenameLen) - newIndex = maxFilenameLen; - else - newIndex = newExtIndex; - } else if (newIndex > 250) - newIndex = 250; - newName[newIndex++] = CRC_MARK; - valueCRC = crc_itu_t(0, fidName, fidNameLen); - newName[newIndex++] = hexChar[(valueCRC & 0xf000) >> 12]; - newName[newIndex++] = hexChar[(valueCRC & 0x0f00) >> 8]; - newName[newIndex++] = hexChar[(valueCRC & 0x00f0) >> 4]; - newName[newIndex++] = hexChar[(valueCRC & 0x000f)]; - - if (hasExt) { - newName[newIndex++] = EXT_MARK; - for (index = 0; index < localExtIndex; index++) - newName[newIndex++] = ext[index]; - } - } - - return newIndex; + return udf_name_to_CS0(sb, dname, dlen, sname, slen); } + |
