summaryrefslogtreecommitdiffstats
path: root/fs/udf
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /fs/udf
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'fs/udf')
-rw-r--r--fs/udf/Kconfig19
-rw-r--r--fs/udf/Makefile10
-rw-r--r--fs/udf/balloc.c739
-rw-r--r--fs/udf/dir.c138
-rw-r--r--fs/udf/directory.c533
-rw-r--r--fs/udf/ecma_167.h816
-rw-r--r--fs/udf/file.c250
-rw-r--r--fs/udf/ialloc.c113
-rw-r--r--fs/udf/inode.c2386
-rw-r--r--fs/udf/lowlevel.c62
-rw-r--r--fs/udf/misc.c285
-rw-r--r--fs/udf/namei.c1017
-rw-r--r--fs/udf/osta_udf.h305
-rw-r--r--fs/udf/partition.c338
-rw-r--r--fs/udf/super.c2509
-rw-r--r--fs/udf/symlink.c181
-rw-r--r--fs/udf/truncate.c268
-rw-r--r--fs/udf/udf_i.h64
-rw-r--r--fs/udf/udf_sb.h184
-rw-r--r--fs/udf/udfdecl.h257
-rw-r--r--fs/udf/udfend.h78
-rw-r--r--fs/udf/udftime.c84
-rw-r--r--fs/udf/unicode.c398
23 files changed, 11034 insertions, 0 deletions
diff --git a/fs/udf/Kconfig b/fs/udf/Kconfig
new file mode 100644
index 0000000000..8f7ce30d47
--- /dev/null
+++ b/fs/udf/Kconfig
@@ -0,0 +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 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.
diff --git a/fs/udf/Makefile b/fs/udf/Makefile
new file mode 100644
index 0000000000..63981cd333
--- /dev/null
+++ b/fs/udf/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the linux udf-filesystem routines.
+#
+
+obj-$(CONFIG_UDF_FS) += udf.o
+
+udf-objs := balloc.o dir.o file.o ialloc.o inode.o lowlevel.o namei.o \
+ partition.o super.o truncate.o symlink.o \
+ directory.o misc.o udftime.o unicode.o
diff --git a/fs/udf/balloc.c b/fs/udf/balloc.c
new file mode 100644
index 0000000000..ab3ffc3559
--- /dev/null
+++ b/fs/udf/balloc.c
@@ -0,0 +1,739 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * balloc.c
+ *
+ * PURPOSE
+ * Block allocation handling routines for the OSTA-UDF(tm) filesystem.
+ *
+ * COPYRIGHT
+ * (C) 1999-2001 Ben Fennema
+ * (C) 1999 Stelias Computing Inc
+ *
+ * HISTORY
+ *
+ * 02/24/99 blf Created.
+ *
+ */
+
+#include "udfdecl.h"
+
+#include <linux/bitops.h>
+
+#include "udf_i.h"
+#include "udf_sb.h"
+
+#define udf_clear_bit __test_and_clear_bit_le
+#define udf_set_bit __test_and_set_bit_le
+#define udf_test_bit test_bit_le
+#define udf_find_next_one_bit find_next_bit_le
+
+static int read_block_bitmap(struct super_block *sb,
+ struct udf_bitmap *bitmap, unsigned int block,
+ unsigned long bitmap_nr)
+{
+ struct buffer_head *bh = NULL;
+ 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 = sb_bread(sb, udf_get_lb_pblock(sb, &loc, block));
+ bitmap->s_block_bitmap[bitmap_nr] = bh;
+ if (!bh)
+ return -EIO;
+
+ /* 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))
+ return -EFSCORRUPTED;
+ return 0;
+}
+
+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 (%u) > nr_groups (%d)\n",
+ block_group, nr_groups);
+ }
+
+ if (bitmap->s_block_bitmap[block_group])
+ return block_group;
+
+ retval = read_block_bitmap(sb, bitmap, block_group, block_group);
+ if (retval < 0)
+ return retval;
+
+ 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;
+
+ if (!bitmap->s_block_bitmap[slot])
+ return -EIO;
+
+ return slot;
+}
+
+static void udf_add_free_space(struct super_block *sb, u16 partition, u32 cnt)
+{
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ struct logicalVolIntegrityDesc *lvid;
+
+ if (!sbi->s_lvid_bh)
+ return;
+
+ lvid = (struct logicalVolIntegrityDesc *)sbi->s_lvid_bh->b_data;
+ le32_add_cpu(&lvid->freeSpaceTable[partition], cnt);
+ udf_updated_lvid(sb);
+}
+
+static void udf_bitmap_free_blocks(struct super_block *sb,
+ struct udf_bitmap *bitmap,
+ struct kernel_lb_addr *bloc,
+ uint32_t offset,
+ uint32_t count)
+{
+ 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;
+ unsigned long i;
+ int bitmap_nr;
+ 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("%u < %d || %u + %u > %u\n",
+ bloc->logicalBlockNum, 0,
+ bloc->logicalBlockNum, count,
+ partmap->s_partition_len);
+ goto error_return;
+ }
+
+ block = bloc->logicalBlockNum + offset +
+ (sizeof(struct spaceBitmapDesc) << 3);
+
+ do {
+ overflow = 0;
+ block_group = block >> (sb->s_blocksize_bits + 3);
+ bit = block % (sb->s_blocksize << 3);
+
+ /*
+ * Check to see if we are freeing blocks across a group boundary.
+ */
+ if (bit + count > (sb->s_blocksize << 3)) {
+ overflow = bit + count - (sb->s_blocksize << 3);
+ count -= overflow;
+ }
+ bitmap_nr = load_block_bitmap(sb, bitmap, block_group);
+ if (bitmap_nr < 0)
+ goto error_return;
+
+ 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 %lu already set\n", bit + i);
+ udf_debug("byte=%2x\n",
+ ((__u8 *)bh->b_data)[(bit + i) >> 3]);
+ }
+ }
+ udf_add_free_space(sb, sbi->s_partition, count);
+ mark_buffer_dirty(bh);
+ if (overflow) {
+ block += count;
+ count = overflow;
+ }
+ } while (overflow);
+
+error_return:
+ mutex_unlock(&sbi->s_alloc_mutex);
+}
+
+static int udf_bitmap_prealloc_blocks(struct super_block *sb,
+ struct udf_bitmap *bitmap,
+ uint16_t partition, uint32_t first_block,
+ uint32_t block_count)
+{
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ int alloc_count = 0;
+ int bit, block, block_group;
+ int bitmap_nr;
+ struct buffer_head *bh;
+ __u32 part_len;
+
+ mutex_lock(&sbi->s_alloc_mutex);
+ part_len = sbi->s_partmaps[partition].s_partition_len;
+ if (first_block >= part_len)
+ goto out;
+
+ if (first_block + block_count > part_len)
+ block_count = part_len - first_block;
+
+ do {
+ block = first_block + (sizeof(struct spaceBitmapDesc) << 3);
+ block_group = block >> (sb->s_blocksize_bits + 3);
+
+ bitmap_nr = load_block_bitmap(sb, bitmap, block_group);
+ if (bitmap_nr < 0)
+ goto out;
+ bh = bitmap->s_block_bitmap[bitmap_nr];
+
+ bit = block % (sb->s_blocksize << 3);
+
+ while (bit < (sb->s_blocksize << 3) && block_count > 0) {
+ if (!udf_clear_bit(bit, bh->b_data))
+ goto out;
+ block_count--;
+ alloc_count++;
+ bit++;
+ block++;
+ }
+ mark_buffer_dirty(bh);
+ } while (block_count > 0);
+
+out:
+ udf_add_free_space(sb, partition, -alloc_count);
+ mutex_unlock(&sbi->s_alloc_mutex);
+ return alloc_count;
+}
+
+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;
+ udf_pblk_t block;
+ int block_group, group_start;
+ int end_goal, nr_groups, bitmap_nr, i;
+ struct buffer_head *bh = NULL;
+ char *ptr;
+ udf_pblk_t newblock = 0;
+
+ *err = -ENOSPC;
+ mutex_lock(&sbi->s_alloc_mutex);
+
+repeat:
+ if (goal >= sbi->s_partmaps[partition].s_partition_len)
+ goal = 0;
+
+ nr_groups = bitmap->s_nr_groups;
+ block = goal + (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)
+ goto error_return;
+ bh = bitmap->s_block_bitmap[bitmap_nr];
+ ptr = memscan((char *)bh->b_data + group_start, 0xFF,
+ sb->s_blocksize - group_start);
+
+ if ((ptr - ((char *)bh->b_data)) < sb->s_blocksize) {
+ bit = block % (sb->s_blocksize << 3);
+ if (udf_test_bit(bit, bh->b_data))
+ goto got_block;
+
+ end_goal = (bit + 63) & ~63;
+ bit = udf_find_next_one_bit(bh->b_data, end_goal, bit);
+ if (bit < end_goal)
+ goto got_block;
+
+ ptr = memscan((char *)bh->b_data + (bit >> 3), 0xFF,
+ sb->s_blocksize - ((bit + 7) >> 3));
+ newbit = (ptr - ((char *)bh->b_data)) << 3;
+ if (newbit < sb->s_blocksize << 3) {
+ bit = newbit;
+ goto search_back;
+ }
+
+ newbit = udf_find_next_one_bit(bh->b_data,
+ sb->s_blocksize << 3, bit);
+ if (newbit < sb->s_blocksize << 3) {
+ bit = newbit;
+ goto got_block;
+ }
+ }
+
+ for (i = 0; i < (nr_groups * 2); i++) {
+ block_group++;
+ if (block_group >= nr_groups)
+ block_group = 0;
+ group_start = block_group ? 0 : sizeof(struct spaceBitmapDesc);
+
+ bitmap_nr = load_block_bitmap(sb, bitmap, block_group);
+ if (bitmap_nr < 0)
+ goto error_return;
+ bh = bitmap->s_block_bitmap[bitmap_nr];
+ if (i < nr_groups) {
+ ptr = memscan((char *)bh->b_data + group_start, 0xFF,
+ sb->s_blocksize - group_start);
+ if ((ptr - ((char *)bh->b_data)) < sb->s_blocksize) {
+ bit = (ptr - ((char *)bh->b_data)) << 3;
+ break;
+ }
+ } else {
+ bit = udf_find_next_one_bit(bh->b_data,
+ sb->s_blocksize << 3,
+ group_start << 3);
+ if (bit < sb->s_blocksize << 3)
+ break;
+ }
+ }
+ if (i >= (nr_groups * 2)) {
+ mutex_unlock(&sbi->s_alloc_mutex);
+ return newblock;
+ }
+ if (bit < sb->s_blocksize << 3)
+ goto search_back;
+ else
+ bit = udf_find_next_one_bit(bh->b_data, sb->s_blocksize << 3,
+ group_start << 3);
+ if (bit >= sb->s_blocksize << 3) {
+ mutex_unlock(&sbi->s_alloc_mutex);
+ return 0;
+ }
+
+search_back:
+ i = 0;
+ while (i < 7 && bit > (group_start << 3) &&
+ udf_test_bit(bit - 1, bh->b_data)) {
+ ++i;
+ --bit;
+ }
+
+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;
+ }
+
+ mark_buffer_dirty(bh);
+
+ udf_add_free_space(sb, partition, -1);
+ mutex_unlock(&sbi->s_alloc_mutex);
+ *err = 0;
+ return newblock;
+
+error_return:
+ *err = -EIO;
+ mutex_unlock(&sbi->s_alloc_mutex);
+ return 0;
+}
+
+static void udf_table_free_blocks(struct super_block *sb,
+ struct inode *table,
+ struct kernel_lb_addr *bloc,
+ uint32_t offset,
+ 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;
+ struct udf_inode_info *iinfo;
+
+ 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("%u < %d || %u + %u > %u\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);
+
+ start = bloc->logicalBlockNum + offset;
+ end = bloc->logicalBlockNum + offset + count - 1;
+
+ epos.offset = oepos.offset = sizeof(struct unallocSpaceEntry);
+ elen = 0;
+ epos.block = oepos.block = iinfo->i_location;
+ epos.bh = oepos.bh = NULL;
+
+ while (count &&
+ (etype = udf_next_aext(table, &epos, &eloc, &elen, 1)) != -1) {
+ if (((eloc.logicalBlockNum +
+ (elen >> sb->s_blocksize_bits)) == start)) {
+ if ((0x3FFFFFFF - elen) <
+ (count << sb->s_blocksize_bits)) {
+ uint32_t tmp = ((0x3FFFFFFF - elen) >>
+ sb->s_blocksize_bits);
+ count -= tmp;
+ start += tmp;
+ elen = (etype << 30) |
+ (0x40000000 - sb->s_blocksize);
+ } else {
+ elen = (etype << 30) |
+ (elen +
+ (count << sb->s_blocksize_bits));
+ start += count;
+ count = 0;
+ }
+ udf_write_aext(table, &oepos, &eloc, elen, 1);
+ } else if (eloc.logicalBlockNum == (end + 1)) {
+ if ((0x3FFFFFFF - elen) <
+ (count << sb->s_blocksize_bits)) {
+ uint32_t tmp = ((0x3FFFFFFF - elen) >>
+ sb->s_blocksize_bits);
+ count -= tmp;
+ end -= tmp;
+ eloc.logicalBlockNum -= tmp;
+ elen = (etype << 30) |
+ (0x40000000 - sb->s_blocksize);
+ } else {
+ eloc.logicalBlockNum = start;
+ elen = (etype << 30) |
+ (elen +
+ (count << sb->s_blocksize_bits));
+ end -= count;
+ count = 0;
+ }
+ udf_write_aext(table, &oepos, &eloc, elen, 1);
+ }
+
+ if (epos.bh != oepos.bh) {
+ oepos.block = epos.block;
+ brelse(oepos.bh);
+ get_bh(epos.bh);
+ oepos.bh = epos.bh;
+ oepos.offset = 0;
+ } else {
+ oepos.offset = epos.offset;
+ }
+ }
+
+ if (count) {
+ /*
+ * NOTE: we CANNOT use udf_add_aext here, as it can try to
+ * allocate a new block, and since we hold the super block
+ * lock already very bad things would happen :)
+ *
+ * We copy the behavior of udf_add_aext, but instead of
+ * trying to allocate a new block close to the existing one,
+ * we just steal a block from the extent we are trying to add.
+ *
+ * It would be nice if the blocks were close together, but it
+ * isn't required.
+ */
+
+ int adsize;
+
+ eloc.logicalBlockNum = start;
+ elen = EXT_RECORDED_ALLOCATED |
+ (count << sb->s_blocksize_bits);
+
+ 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 {
+ brelse(oepos.bh);
+ brelse(epos.bh);
+ goto error_return;
+ }
+
+ if (epos.offset + (2 * adsize) > sb->s_blocksize) {
+ /* Steal a block from the extent being free'd */
+ udf_setup_indirect_aext(table, eloc.logicalBlockNum,
+ &epos);
+
+ eloc.logicalBlockNum++;
+ elen -= sb->s_blocksize;
+ }
+
+ /* It's possible that stealing the block emptied the extent */
+ if (elen)
+ __udf_add_aext(table, &epos, &eloc, elen, 1);
+ }
+
+ brelse(epos.bh);
+ brelse(oepos.bh);
+
+error_return:
+ mutex_unlock(&sbi->s_alloc_mutex);
+ return;
+}
+
+static int udf_table_prealloc_blocks(struct super_block *sb,
+ struct inode *table, uint16_t partition,
+ uint32_t first_block, uint32_t block_count)
+{
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ int alloc_count = 0;
+ uint32_t elen, adsize;
+ struct kernel_lb_addr eloc;
+ struct extent_position epos;
+ int8_t etype = -1;
+ struct udf_inode_info *iinfo;
+
+ if (first_block >= sbi->s_partmaps[partition].s_partition_len)
+ return 0;
+
+ iinfo = UDF_I(table);
+ 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
+ return 0;
+
+ mutex_lock(&sbi->s_alloc_mutex);
+ epos.offset = sizeof(struct unallocSpaceEntry);
+ epos.block = iinfo->i_location;
+ epos.bh = NULL;
+ eloc.logicalBlockNum = 0xFFFFFFFF;
+
+ while (first_block != eloc.logicalBlockNum &&
+ (etype = udf_next_aext(table, &epos, &eloc, &elen, 1)) != -1) {
+ udf_debug("eloc=%u, elen=%u, first_block=%u\n",
+ eloc.logicalBlockNum, elen, first_block);
+ ; /* empty loop body */
+ }
+
+ if (first_block == eloc.logicalBlockNum) {
+ epos.offset -= adsize;
+
+ alloc_count = (elen >> sb->s_blocksize_bits);
+ if (alloc_count > block_count) {
+ alloc_count = block_count;
+ eloc.logicalBlockNum += alloc_count;
+ elen -= (alloc_count << sb->s_blocksize_bits);
+ udf_write_aext(table, &epos, &eloc,
+ (etype << 30) | elen, 1);
+ } else
+ udf_delete_aext(table, epos);
+ } else {
+ alloc_count = 0;
+ }
+
+ brelse(epos.bh);
+
+ if (alloc_count)
+ udf_add_free_space(sb, partition, -alloc_count);
+ mutex_unlock(&sbi->s_alloc_mutex);
+ return alloc_count;
+}
+
+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;
+ udf_pblk_t newblock = 0;
+ uint32_t adsize;
+ uint32_t elen, goal_elen = 0;
+ struct kernel_lb_addr eloc, goal_eloc;
+ struct extent_position epos, goal_epos;
+ int8_t etype;
+ struct udf_inode_info *iinfo = UDF_I(table);
+
+ *err = -ENOSPC;
+
+ 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
+ return newblock;
+
+ mutex_lock(&sbi->s_alloc_mutex);
+ if (goal >= sbi->s_partmaps[partition].s_partition_len)
+ goal = 0;
+
+ /* We search for the closest matching block to goal. If we find
+ a exact hit, we stop. Otherwise we keep going till we run out
+ of extents. We store the buffer_head, bloc, and extoffset
+ of the current closest match and use that when we are done.
+ */
+ epos.offset = sizeof(struct unallocSpaceEntry);
+ epos.block = iinfo->i_location;
+ epos.bh = goal_epos.bh = NULL;
+
+ while (spread &&
+ (etype = udf_next_aext(table, &epos, &eloc, &elen, 1)) != -1) {
+ if (goal >= eloc.logicalBlockNum) {
+ if (goal < eloc.logicalBlockNum +
+ (elen >> sb->s_blocksize_bits))
+ nspread = 0;
+ else
+ nspread = goal - eloc.logicalBlockNum -
+ (elen >> sb->s_blocksize_bits);
+ } else {
+ nspread = eloc.logicalBlockNum - goal;
+ }
+
+ if (nspread < spread) {
+ spread = nspread;
+ if (goal_epos.bh != epos.bh) {
+ brelse(goal_epos.bh);
+ goal_epos.bh = epos.bh;
+ get_bh(goal_epos.bh);
+ }
+ goal_epos.block = epos.block;
+ goal_epos.offset = epos.offset - adsize;
+ goal_eloc = eloc;
+ goal_elen = (etype << 30) | elen;
+ }
+ }
+
+ brelse(epos.bh);
+
+ if (spread == 0xFFFFFFFF) {
+ brelse(goal_epos.bh);
+ mutex_unlock(&sbi->s_alloc_mutex);
+ return 0;
+ }
+
+ /* Only allocate blocks from the beginning of the extent.
+ That way, we only delete (empty) extents, never have to insert an
+ extent because of splitting */
+ /* This works, but very poorly.... */
+
+ newblock = goal_eloc.logicalBlockNum;
+ goal_eloc.logicalBlockNum++;
+ goal_elen -= sb->s_blocksize;
+
+ if (goal_elen)
+ udf_write_aext(table, &goal_epos, &goal_eloc, goal_elen, 1);
+ else
+ udf_delete_aext(table, goal_epos);
+ brelse(goal_epos.bh);
+
+ udf_add_free_space(sb, partition, -1);
+
+ mutex_unlock(&sbi->s_alloc_mutex);
+ *err = 0;
+ return newblock;
+}
+
+void udf_free_blocks(struct super_block *sb, struct inode *inode,
+ struct kernel_lb_addr *bloc, uint32_t offset,
+ uint32_t count)
+{
+ uint16_t partition = bloc->partitionReferenceNum;
+ struct udf_part_map *map = &UDF_SB(sb)->s_partmaps[partition];
+
+ if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_BITMAP) {
+ udf_bitmap_free_blocks(sb, map->s_uspace.s_bitmap,
+ bloc, offset, count);
+ } else if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_TABLE) {
+ udf_table_free_blocks(sb, map->s_uspace.s_table,
+ bloc, offset, count);
+ }
+
+ if (inode) {
+ inode_sub_bytes(inode,
+ ((sector_t)count) << sb->s_blocksize_bits);
+ }
+}
+
+inline int udf_prealloc_blocks(struct super_block *sb,
+ struct inode *inode,
+ uint16_t partition, uint32_t first_block,
+ uint32_t block_count)
+{
+ struct udf_part_map *map = &UDF_SB(sb)->s_partmaps[partition];
+ int allocated;
+
+ if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_BITMAP)
+ allocated = udf_bitmap_prealloc_blocks(sb,
+ map->s_uspace.s_bitmap,
+ partition, first_block,
+ block_count);
+ else if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_TABLE)
+ allocated = udf_table_prealloc_blocks(sb,
+ map->s_uspace.s_table,
+ partition, first_block,
+ block_count);
+ else
+ return 0;
+
+ if (inode && allocated > 0)
+ inode_add_bytes(inode, allocated << sb->s_blocksize_bits);
+ return allocated;
+}
+
+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];
+ udf_pblk_t block;
+
+ if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_BITMAP)
+ block = udf_bitmap_new_block(sb,
+ map->s_uspace.s_bitmap,
+ partition, goal, err);
+ else if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_TABLE)
+ block = udf_table_new_block(sb,
+ map->s_uspace.s_table,
+ partition, goal, err);
+ else {
+ *err = -EIO;
+ return 0;
+ }
+ if (inode && block)
+ inode_add_bytes(inode, sb->s_blocksize);
+ return block;
+}
diff --git a/fs/udf/dir.c b/fs/udf/dir.c
new file mode 100644
index 0000000000..f6533f9385
--- /dev/null
+++ b/fs/udf/dir.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * dir.c
+ *
+ * PURPOSE
+ * Directory handling routines for the OSTA-UDF(tm) filesystem.
+ *
+ * COPYRIGHT
+ * (C) 1998-2004 Ben Fennema
+ *
+ * HISTORY
+ *
+ * 10/05/98 dgb Split directory operations into its own file
+ * Implemented directory reads via do_udf_readdir
+ * 10/06/98 Made directory operations work!
+ * 11/17/98 Rewrote directory to support ICBTAG_FLAG_AD_LONG
+ * 11/25/98 blf Rewrote directory handling (readdir+lookup) to support reading
+ * across blocks.
+ * 12/12/98 Split out the lookup code to namei.c. bulk of directory
+ * code now in directory.c:udf_fileident_read.
+ */
+
+#include "udfdecl.h"
+
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/slab.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);
+ loff_t nf_pos, emit_pos = 0;
+ int flen;
+ unsigned char *fname = NULL;
+ 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))
+ return 0;
+ ctx->pos = 1;
+ }
+ nf_pos = (ctx->pos - 1) << 2;
+ if (nf_pos >= dir->i_size)
+ goto out;
+
+ /*
+ * 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, file->f_version)) {
+ emit_pos = nf_pos;
+ nf_pos = 0;
+ } else {
+ pos_valid = true;
+ }
+
+ fname = kmalloc(UDF_NAME_LEN, GFP_NOFS);
+ if (!fname) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ 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;
+
+ /* Still not at offset where user asked us to read from? */
+ if (iter.pos < emit_pos)
+ continue;
+
+ /* Update file position only if we got past the current one */
+ pos_valid = true;
+ ctx->pos = (iter.pos >> 2) + 1;
+
+ if (iter.fi.fileCharacteristics & FID_FILE_CHAR_DELETED) {
+ if (!UDF_QUERY_FLAG(sb, UDF_FLAG_UNDELETE))
+ continue;
+ }
+
+ if (iter.fi.fileCharacteristics & FID_FILE_CHAR_HIDDEN) {
+ if (!UDF_QUERY_FLAG(sb, UDF_FLAG_UNHIDE))
+ continue;
+ }
+
+ if (iter.fi.fileCharacteristics & FID_FILE_CHAR_PARENT) {
+ if (!dir_emit_dotdot(file, ctx))
+ goto out_iter;
+ continue;
+ }
+
+ flen = udf_get_filename(sb, iter.name,
+ iter.fi.lengthFileIdent, fname, UDF_NAME_LEN);
+ if (flen < 0)
+ continue;
+
+ 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_iter;
+ }
+
+ if (!ret) {
+ ctx->pos = (iter.pos >> 2) + 1;
+ pos_valid = true;
+ }
+out_iter:
+ udf_fiiter_release(&iter);
+out:
+ if (pos_valid)
+ file->f_version = inode_query_iversion(dir);
+ kfree(fname);
+
+ return ret;
+}
+
+/* readdir and lookup functions */
+const struct file_operations udf_dir_operations = {
+ .llseek = generic_file_llseek,
+ .read = generic_read_dir,
+ .iterate_shared = udf_readdir,
+ .unlocked_ioctl = udf_ioctl,
+ .fsync = generic_file_fsync,
+};
diff --git a/fs/udf/directory.c b/fs/udf/directory.c
new file mode 100644
index 0000000000..93153665eb
--- /dev/null
+++ b/fs/udf/directory.c
@@ -0,0 +1,533 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * directory.c
+ *
+ * PURPOSE
+ * Directory related functions
+ *
+ */
+
+#include "udfdecl.h"
+#include "udf_i.h"
+
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/bio.h>
+#include <linux/crc-itu-t.h>
+#include <linux/iversion.h>
+
+static int udf_verify_fi(struct udf_fileident_iter *iter)
+{
+ 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) {
+ 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;
+ }
+
+ 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;
+}
+
+/* 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]);
+ }
+}
+
+static struct buffer_head *udf_fiiter_bread_blk(struct udf_fileident_iter *iter)
+{
+ udf_pblk_t blk;
+
+ 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);
+}
+
+/*
+ * 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)
+{
+ iter->loffset++;
+ if (iter->loffset < DIV_ROUND_UP(iter->elen, 1<<iter->dir->i_blkbits))
+ return 0;
+
+ iter->loffset = 0;
+ if (udf_next_aext(iter->dir, &iter->epos, &iter->eloc, &iter->elen, 1)
+ != (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;
+}
+
+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;
+
+ /* 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;
+ }
+ }
+ /* 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;
+}
+
+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;
+
+ 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 (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
+ err = udf_copy_fi(iter);
+ goto out;
+ }
+
+ if (inode_bmap(dir, iter->pos >> dir->i_blkbits, &iter->epos,
+ &iter->eloc, &iter->elen, &iter->loffset) !=
+ (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;
+}
+
+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);
+}
+
+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;
+}
+
+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);
+ }
+}
+
+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;
+}
+
+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);
+ }
+
+ 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);
+
+ udf_copy_to_bufs(buf1, len1, buf2, len2, fioff, fi, sizeof(struct tag));
+}
+
+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;
+
+ 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;
+ }
+ }
+
+ udf_copy_fi_to_bufs(buf1, len1, buf2, len2, off, &iter->fi, impuse,
+ iter->name == iter->namebuf ? iter->name : NULL);
+
+ 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);
+ }
+ inode_inc_iversion(iter->dir);
+}
+
+void udf_fiiter_update_elen(struct udf_fileident_iter *iter, uint32_t new_elen)
+{
+ 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);
+}
+
+/* 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;
+
+ 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;
+ }
+ if (inode_bmap(iter->dir, block, &iter->epos, &iter->eloc, &iter->elen,
+ &iter->loffset) != (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 (!(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,
+ int inc)
+{
+ struct short_ad *sa;
+
+ if ((!ptr) || (!offset)) {
+ pr_err("%s: invalidparms\n", __func__);
+ return NULL;
+ }
+
+ if ((*offset + sizeof(struct short_ad)) > maxoffset)
+ return NULL;
+ else {
+ sa = (struct short_ad *)ptr;
+ if (sa->extLength == 0)
+ return NULL;
+ }
+
+ if (inc)
+ *offset += sizeof(struct short_ad);
+ return sa;
+}
+
+struct long_ad *udf_get_filelongad(uint8_t *ptr, int maxoffset, uint32_t *offset, int inc)
+{
+ struct long_ad *la;
+
+ if ((!ptr) || (!offset)) {
+ pr_err("%s: invalidparms\n", __func__);
+ return NULL;
+ }
+
+ if ((*offset + sizeof(struct long_ad)) > maxoffset)
+ return NULL;
+ else {
+ la = (struct long_ad *)ptr;
+ if (la->extLength == 0)
+ return NULL;
+ }
+
+ if (inc)
+ *offset += sizeof(struct long_ad);
+ return la;
+}
diff --git a/fs/udf/ecma_167.h b/fs/udf/ecma_167.h
new file mode 100644
index 0000000000..de17a97e86
--- /dev/null
+++ b/fs/udf/ecma_167.h
@@ -0,0 +1,816 @@
+/*
+ * ecma_167.h
+ *
+ * This file is based on ECMA-167 3rd edition (June 1997)
+ * https://www.ecma.ch
+ *
+ * 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
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU Public License ("GPL").
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * 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];
+} __packed;
+
+/* Character Set Type (ECMA 167r3 1/7.2.1.1) */
+#define CHARSPEC_TYPE_CS0 0x00 /* (1/7.2.2) */
+#define CHARSPEC_TYPE_CS1 0x01 /* (1/7.2.3) */
+#define CHARSPEC_TYPE_CS2 0x02 /* (1/7.2.4) */
+#define CHARSPEC_TYPE_CS3 0x03 /* (1/7.2.5) */
+#define CHARSPEC_TYPE_CS4 0x04 /* (1/7.2.6) */
+#define CHARSPEC_TYPE_CS5 0x05 /* (1/7.2.7) */
+#define CHARSPEC_TYPE_CS6 0x06 /* (1/7.2.8) */
+#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) */
+struct timestamp {
+ __le16 typeAndTimezone;
+ __le16 year;
+ uint8_t month;
+ uint8_t day;
+ uint8_t hour;
+ uint8_t minute;
+ uint8_t second;
+ uint8_t centiseconds;
+ uint8_t hundredsOfMicroseconds;
+ uint8_t microseconds;
+} __packed;
+
+/* Type and Time Zone (ECMA 167r3 1/7.3.1) */
+#define TIMESTAMP_TYPE_MASK 0xF000
+#define TIMESTAMP_TYPE_CUT 0x0000
+#define TIMESTAMP_TYPE_LOCAL 0x1000
+#define TIMESTAMP_TYPE_AGREEMENT 0x2000
+#define TIMESTAMP_TIMEZONE_MASK 0x0FFF
+
+/* Entity identifier (ECMA 167r3 1/7.4) */
+struct regid {
+ uint8_t flags;
+ uint8_t ident[23];
+ uint8_t identSuffix[8];
+} __packed;
+
+/* Flags (ECMA 167r3 1/7.4.1) */
+#define ENTITYID_FLAGS_DIRTY 0x01
+#define ENTITYID_FLAGS_PROTECTED 0x02
+
+/* Volume Structure Descriptor (ECMA 167r3 2/9.1) */
+#define VSD_STD_ID_LEN 5
+struct volStructDesc {
+ uint8_t structType;
+ uint8_t stdIdent[VSD_STD_ID_LEN];
+ uint8_t structVersion;
+ uint8_t structData[2041];
+} __packed;
+
+/* Standard Identifier (EMCA 167r2 2/9.1.2) */
+#define VSD_STD_ID_NSR02 "NSR02" /* (3/9.1) */
+
+/* Standard Identifier (ECMA 167r3 2/9.1.2) */
+#define VSD_STD_ID_BEA01 "BEA01" /* (2/9.2) */
+#define VSD_STD_ID_BOOT2 "BOOT2" /* (2/9.4) */
+#define VSD_STD_ID_CD001 "CD001" /* (ECMA-119) */
+#define VSD_STD_ID_CDW02 "CDW02" /* (ECMA-168) */
+#define VSD_STD_ID_NSR03 "NSR03" /* (3/9.1) */
+#define VSD_STD_ID_TEA01 "TEA01" /* (2/9.3) */
+
+/* Beginning Extended Area Descriptor (ECMA 167r3 2/9.2) */
+struct beginningExtendedAreaDesc {
+ uint8_t structType;
+ uint8_t stdIdent[VSD_STD_ID_LEN];
+ uint8_t structVersion;
+ uint8_t structData[2041];
+} __packed;
+
+/* Terminating Extended Area Descriptor (ECMA 167r3 2/9.3) */
+struct terminatingExtendedAreaDesc {
+ uint8_t structType;
+ uint8_t stdIdent[VSD_STD_ID_LEN];
+ uint8_t structVersion;
+ uint8_t structData[2041];
+} __packed;
+
+/* Boot Descriptor (ECMA 167r3 2/9.4) */
+struct bootDesc {
+ uint8_t structType;
+ uint8_t stdIdent[VSD_STD_ID_LEN];
+ uint8_t structVersion;
+ uint8_t reserved1;
+ struct regid archType;
+ struct regid bootIdent;
+ __le32 bootExtLocation;
+ __le32 bootExtLength;
+ __le64 loadAddress;
+ __le64 startAddress;
+ struct timestamp descCreationDateAndTime;
+ __le16 flags;
+ uint8_t reserved2[32];
+ uint8_t bootUse[1906];
+} __packed;
+
+/* Flags (ECMA 167r3 2/9.4.12) */
+#define BOOT_FLAGS_ERASE 0x01
+
+/* Extent Descriptor (ECMA 167r3 3/7.1) */
+struct extent_ad {
+ __le32 extLength;
+ __le32 extLocation;
+} __packed;
+
+struct kernel_extent_ad {
+ uint32_t extLength;
+ uint32_t extLocation;
+};
+
+/* Descriptor Tag (ECMA 167r3 3/7.2) */
+struct tag {
+ __le16 tagIdent;
+ __le16 descVersion;
+ uint8_t tagChecksum;
+ uint8_t reserved;
+ __le16 tagSerialNum;
+ __le16 descCRC;
+ __le16 descCRCLength;
+ __le32 tagLocation;
+} __packed;
+
+/* Tag Identifier (ECMA 167r3 3/7.2.1) */
+#define TAG_IDENT_PVD 0x0001
+#define TAG_IDENT_AVDP 0x0002
+#define TAG_IDENT_VDP 0x0003
+#define TAG_IDENT_IUVD 0x0004
+#define TAG_IDENT_PD 0x0005
+#define TAG_IDENT_LVD 0x0006
+#define TAG_IDENT_USD 0x0007
+#define TAG_IDENT_TD 0x0008
+#define TAG_IDENT_LVID 0x0009
+
+/* NSR Descriptor (ECMA 167r3 3/9.1) */
+struct NSRDesc {
+ uint8_t structType;
+ uint8_t stdIdent[VSD_STD_ID_LEN];
+ uint8_t structVersion;
+ uint8_t reserved;
+ uint8_t structData[2040];
+} __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 {
+ struct tag descTag;
+ __le32 volDescSeqNum;
+ __le32 primaryVolDescNum;
+ dstring volIdent[32];
+ __le16 volSeqNum;
+ __le16 maxVolSeqNum;
+ __le16 interchangeLvl;
+ __le16 maxInterchangeLvl;
+ __le32 charSetList;
+ __le32 maxCharSetList;
+ dstring volSetIdent[128];
+ struct charspec descCharSet;
+ struct charspec explanatoryCharSet;
+ struct extent_ad volAbstract;
+ struct extent_ad volCopyright;
+ struct regid appIdent;
+ struct timestamp recordingDateAndTime;
+ struct regid impIdent;
+ uint8_t impUse[64];
+ __le32 predecessorVolDescSeqLocation;
+ __le16 flags;
+ uint8_t reserved[22];
+} __packed;
+
+/* Flags (ECMA 167r3 3/10.1.21) */
+#define PVD_FLAGS_VSID_COMMON 0x0001
+
+/* Anchor Volume Descriptor Pointer (ECMA 167r3 3/10.2) */
+struct anchorVolDescPtr {
+ struct tag descTag;
+ struct extent_ad mainVolDescSeqExt;
+ struct extent_ad reserveVolDescSeqExt;
+ uint8_t reserved[480];
+} __packed;
+
+/* Volume Descriptor Pointer (ECMA 167r3 3/10.3) */
+struct volDescPtr {
+ struct tag descTag;
+ __le32 volDescSeqNum;
+ struct extent_ad nextVolDescSeqExt;
+ uint8_t reserved[484];
+} __packed;
+
+/* Implementation Use Volume Descriptor (ECMA 167r3 3/10.4) */
+struct impUseVolDesc {
+ struct tag descTag;
+ __le32 volDescSeqNum;
+ struct regid impIdent;
+ uint8_t impUse[460];
+} __packed;
+
+/* Partition Descriptor (ECMA 167r3 3/10.5) */
+struct partitionDesc {
+ struct tag descTag;
+ __le32 volDescSeqNum;
+ __le16 partitionFlags;
+ __le16 partitionNumber;
+ struct regid partitionContents;
+ uint8_t partitionContentsUse[128];
+ __le32 accessType;
+ __le32 partitionStartingLocation;
+ __le32 partitionLength;
+ struct regid impIdent;
+ uint8_t impUse[128];
+ uint8_t reserved[156];
+} __packed;
+
+/* Partition Flags (ECMA 167r3 3/10.5.3) */
+#define PD_PARTITION_FLAGS_ALLOC 0x0001
+
+/* Partition Contents (ECMA 167r2 3/10.5.3) */
+#define PD_PARTITION_CONTENTS_NSR02 "+NSR02"
+
+/* Partition Contents (ECMA 167r3 3/10.5.5) */
+#define PD_PARTITION_CONTENTS_FDC01 "+FDC01"
+#define PD_PARTITION_CONTENTS_CD001 "+CD001"
+#define PD_PARTITION_CONTENTS_CDW02 "+CDW02"
+#define PD_PARTITION_CONTENTS_NSR03 "+NSR03"
+
+/* Access Type (ECMA 167r3 3/10.5.7) */
+#define PD_ACCESS_TYPE_NONE 0x00000000
+#define PD_ACCESS_TYPE_READ_ONLY 0x00000001
+#define PD_ACCESS_TYPE_WRITE_ONCE 0x00000002
+#define PD_ACCESS_TYPE_REWRITABLE 0x00000003
+#define PD_ACCESS_TYPE_OVERWRITABLE 0x00000004
+
+/* Logical Volume Descriptor (ECMA 167r3 3/10.6) */
+struct logicalVolDesc {
+ struct tag descTag;
+ __le32 volDescSeqNum;
+ struct charspec descCharSet;
+ dstring logicalVolIdent[128];
+ __le32 logicalBlockSize;
+ struct regid domainIdent;
+ uint8_t logicalVolContentsUse[16];
+ __le32 mapTableLength;
+ __le32 numPartitionMaps;
+ struct regid impIdent;
+ uint8_t impUse[128];
+ struct extent_ad integritySeqExt;
+ uint8_t partitionMaps[];
+} __packed;
+
+/* Generic Partition Map (ECMA 167r3 3/10.7.1) */
+struct genericPartitionMap {
+ uint8_t partitionMapType;
+ uint8_t partitionMapLength;
+ uint8_t partitionMapping[];
+} __packed;
+
+/* Partition Map Type (ECMA 167r3 3/10.7.1.1) */
+#define GP_PARTITION_MAP_TYPE_UNDEF 0x00
+#define GP_PARTITION_MAP_TYPE_1 0x01
+#define GP_PARTITION_MAP_TYPE_2 0x02
+
+/* Type 1 Partition Map (ECMA 167r3 3/10.7.2) */
+struct genericPartitionMap1 {
+ uint8_t partitionMapType;
+ uint8_t partitionMapLength;
+ __le16 volSeqNum;
+ __le16 partitionNum;
+} __packed;
+
+/* Type 2 Partition Map (ECMA 167r3 3/10.7.3) */
+struct genericPartitionMap2 {
+ uint8_t partitionMapType;
+ uint8_t partitionMapLength;
+ uint8_t partitionIdent[62];
+} __packed;
+
+/* Unallocated Space Descriptor (ECMA 167r3 3/10.8) */
+struct unallocSpaceDesc {
+ struct tag descTag;
+ __le32 volDescSeqNum;
+ __le32 numAllocDescs;
+ struct extent_ad allocDescs[];
+} __packed;
+
+/* Terminating Descriptor (ECMA 167r3 3/10.9) */
+struct terminatingDesc {
+ struct tag descTag;
+ uint8_t reserved[496];
+} __packed;
+
+/* Logical Volume Integrity Descriptor (ECMA 167r3 3/10.10) */
+struct logicalVolIntegrityDesc {
+ struct tag descTag;
+ struct timestamp recordingDateAndTime;
+ __le32 integrityType;
+ struct extent_ad nextIntegrityExt;
+ uint8_t logicalVolContentsUse[32];
+ __le32 numOfPartitions;
+ __le32 lengthOfImpUse;
+ __le32 freeSpaceTable[];
+ /* __le32 sizeTable[]; */
+ /* uint8_t impUse[]; */
+} __packed;
+
+/* Integrity Type (ECMA 167r3 3/10.10.3) */
+#define LVID_INTEGRITY_TYPE_OPEN 0x00000000
+#define LVID_INTEGRITY_TYPE_CLOSE 0x00000001
+
+/* Recorded Address (ECMA 167r3 4/7.1) */
+struct lb_addr {
+ __le32 logicalBlockNum;
+ __le16 partitionReferenceNum;
+} __packed;
+
+/* ... and its in-core analog */
+struct kernel_lb_addr {
+ uint32_t logicalBlockNum;
+ uint16_t partitionReferenceNum;
+};
+
+/* Short Allocation Descriptor (ECMA 167r3 4/14.14.1) */
+struct short_ad {
+ __le32 extLength;
+ __le32 extPosition;
+} __packed;
+
+/* Long Allocation Descriptor (ECMA 167r3 4/14.14.2) */
+struct long_ad {
+ __le32 extLength;
+ struct lb_addr extLocation;
+ uint8_t impUse[6];
+} __packed;
+
+struct kernel_long_ad {
+ uint32_t extLength;
+ struct kernel_lb_addr extLocation;
+ uint8_t impUse[6];
+};
+
+/* Extended Allocation Descriptor (ECMA 167r3 4/14.14.3) */
+struct ext_ad {
+ __le32 extLength;
+ __le32 recordedLength;
+ __le32 informationLength;
+ struct lb_addr extLocation;
+} __packed;
+
+struct kernel_ext_ad {
+ uint32_t extLength;
+ uint32_t recordedLength;
+ uint32_t informationLength;
+ struct kernel_lb_addr extLocation;
+};
+
+/* Descriptor Tag (ECMA 167r3 4/7.2 - See 3/7.2) */
+
+/* Tag Identifier (ECMA 167r3 4/7.2.1) */
+#define TAG_IDENT_FSD 0x0100
+#define TAG_IDENT_FID 0x0101
+#define TAG_IDENT_AED 0x0102
+#define TAG_IDENT_IE 0x0103
+#define TAG_IDENT_TE 0x0104
+#define TAG_IDENT_FE 0x0105
+#define TAG_IDENT_EAHD 0x0106
+#define TAG_IDENT_USE 0x0107
+#define TAG_IDENT_SBD 0x0108
+#define TAG_IDENT_PIE 0x0109
+#define TAG_IDENT_EFE 0x010A
+
+/* File Set Descriptor (ECMA 167r3 4/14.1) */
+struct fileSetDesc {
+ struct tag descTag;
+ struct timestamp recordingDateAndTime;
+ __le16 interchangeLvl;
+ __le16 maxInterchangeLvl;
+ __le32 charSetList;
+ __le32 maxCharSetList;
+ __le32 fileSetNum;
+ __le32 fileSetDescNum;
+ struct charspec logicalVolIdentCharSet;
+ dstring logicalVolIdent[128];
+ struct charspec fileSetCharSet;
+ dstring fileSetIdent[32];
+ dstring copyrightFileIdent[32];
+ dstring abstractFileIdent[32];
+ struct long_ad rootDirectoryICB;
+ struct regid domainIdent;
+ struct long_ad nextExt;
+ struct long_ad streamDirectoryICB;
+ uint8_t reserved[32];
+} __packed;
+
+/* Partition Header Descriptor (ECMA 167r3 4/14.3) */
+struct partitionHeaderDesc {
+ struct short_ad unallocSpaceTable;
+ struct short_ad unallocSpaceBitmap;
+ struct short_ad partitionIntegrityTable;
+ struct short_ad freedSpaceTable;
+ struct short_ad freedSpaceBitmap;
+ uint8_t reserved[88];
+} __packed;
+
+/* File Identifier Descriptor (ECMA 167r3 4/14.4) */
+struct fileIdentDesc {
+ struct tag descTag;
+ __le16 fileVersionNum;
+ uint8_t fileCharacteristics;
+ uint8_t lengthFileIdent;
+ struct long_ad icb;
+ __le16 lengthOfImpUse;
+ uint8_t impUse[];
+ /* uint8_t fileIdent[]; */
+ /* uint8_t padding[]; */
+} __packed;
+
+/* File Characteristics (ECMA 167r3 4/14.4.3) */
+#define FID_FILE_CHAR_HIDDEN 0x01
+#define FID_FILE_CHAR_DIRECTORY 0x02
+#define FID_FILE_CHAR_DELETED 0x04
+#define FID_FILE_CHAR_PARENT 0x08
+#define FID_FILE_CHAR_METADATA 0x10
+
+/* Allocation Ext Descriptor (ECMA 167r3 4/14.5) */
+struct allocExtDesc {
+ struct tag descTag;
+ __le32 previousAllocExtLocation;
+ __le32 lengthAllocDescs;
+} __packed;
+
+/* ICB Tag (ECMA 167r3 4/14.6) */
+struct icbtag {
+ __le32 priorRecordedNumDirectEntries;
+ __le16 strategyType;
+ __le16 strategyParameter;
+ __le16 numEntries;
+ uint8_t reserved;
+ uint8_t fileType;
+ struct lb_addr parentICBLocation;
+ __le16 flags;
+} __packed;
+
+/* Strategy Type (ECMA 167r3 4/14.6.2) */
+#define ICBTAG_STRATEGY_TYPE_UNDEF 0x0000
+#define ICBTAG_STRATEGY_TYPE_1 0x0001
+#define ICBTAG_STRATEGY_TYPE_2 0x0002
+#define ICBTAG_STRATEGY_TYPE_3 0x0003
+#define ICBTAG_STRATEGY_TYPE_4 0x0004
+
+/* File Type (ECMA 167r3 4/14.6.6) */
+#define ICBTAG_FILE_TYPE_UNDEF 0x00
+#define ICBTAG_FILE_TYPE_USE 0x01
+#define ICBTAG_FILE_TYPE_PIE 0x02
+#define ICBTAG_FILE_TYPE_IE 0x03
+#define ICBTAG_FILE_TYPE_DIRECTORY 0x04
+#define ICBTAG_FILE_TYPE_REGULAR 0x05
+#define ICBTAG_FILE_TYPE_BLOCK 0x06
+#define ICBTAG_FILE_TYPE_CHAR 0x07
+#define ICBTAG_FILE_TYPE_EA 0x08
+#define ICBTAG_FILE_TYPE_FIFO 0x09
+#define ICBTAG_FILE_TYPE_SOCKET 0x0A
+#define ICBTAG_FILE_TYPE_TE 0x0B
+#define ICBTAG_FILE_TYPE_SYMLINK 0x0C
+#define ICBTAG_FILE_TYPE_STREAMDIR 0x0D
+
+/* Flags (ECMA 167r3 4/14.6.8) */
+#define ICBTAG_FLAG_AD_MASK 0x0007
+#define ICBTAG_FLAG_AD_SHORT 0x0000
+#define ICBTAG_FLAG_AD_LONG 0x0001
+#define ICBTAG_FLAG_AD_EXTENDED 0x0002
+#define ICBTAG_FLAG_AD_IN_ICB 0x0003
+#define ICBTAG_FLAG_SORTED 0x0008
+#define ICBTAG_FLAG_NONRELOCATABLE 0x0010
+#define ICBTAG_FLAG_ARCHIVE 0x0020
+#define ICBTAG_FLAG_SETUID 0x0040
+#define ICBTAG_FLAG_SETGID 0x0080
+#define ICBTAG_FLAG_STICKY 0x0100
+#define ICBTAG_FLAG_CONTIGUOUS 0x0200
+#define ICBTAG_FLAG_SYSTEM 0x0400
+#define ICBTAG_FLAG_TRANSFORMED 0x0800
+#define ICBTAG_FLAG_MULTIVERSIONS 0x1000
+#define ICBTAG_FLAG_STREAM 0x2000
+
+/* Indirect Entry (ECMA 167r3 4/14.7) */
+struct indirectEntry {
+ struct tag descTag;
+ struct icbtag icbTag;
+ struct long_ad indirectICB;
+} __packed;
+
+/* Terminal Entry (ECMA 167r3 4/14.8) */
+struct terminalEntry {
+ struct tag descTag;
+ struct icbtag icbTag;
+} __packed;
+
+/* File Entry (ECMA 167r3 4/14.9) */
+struct fileEntry {
+ struct tag descTag;
+ struct icbtag icbTag;
+ __le32 uid;
+ __le32 gid;
+ __le32 permissions;
+ __le16 fileLinkCount;
+ uint8_t recordFormat;
+ uint8_t recordDisplayAttr;
+ __le32 recordLength;
+ __le64 informationLength;
+ __le64 logicalBlocksRecorded;
+ struct timestamp accessTime;
+ struct timestamp modificationTime;
+ struct timestamp attrTime;
+ __le32 checkpoint;
+ struct long_ad extendedAttrICB;
+ struct regid impIdent;
+ __le64 uniqueID;
+ __le32 lengthExtendedAttr;
+ __le32 lengthAllocDescs;
+ uint8_t extendedAttr[];
+ /* uint8_t allocDescs[]; */
+} __packed;
+
+/* Permissions (ECMA 167r3 4/14.9.5) */
+#define FE_PERM_O_EXEC 0x00000001U
+#define FE_PERM_O_WRITE 0x00000002U
+#define FE_PERM_O_READ 0x00000004U
+#define FE_PERM_O_CHATTR 0x00000008U
+#define FE_PERM_O_DELETE 0x00000010U
+#define FE_PERM_G_EXEC 0x00000020U
+#define FE_PERM_G_WRITE 0x00000040U
+#define FE_PERM_G_READ 0x00000080U
+#define FE_PERM_G_CHATTR 0x00000100U
+#define FE_PERM_G_DELETE 0x00000200U
+#define FE_PERM_U_EXEC 0x00000400U
+#define FE_PERM_U_WRITE 0x00000800U
+#define FE_PERM_U_READ 0x00001000U
+#define FE_PERM_U_CHATTR 0x00002000U
+#define FE_PERM_U_DELETE 0x00004000U
+
+/* Record Format (ECMA 167r3 4/14.9.7) */
+#define FE_RECORD_FMT_UNDEF 0x00
+#define FE_RECORD_FMT_FIXED_PAD 0x01
+#define FE_RECORD_FMT_FIXED 0x02
+#define FE_RECORD_FMT_VARIABLE8 0x03
+#define FE_RECORD_FMT_VARIABLE16 0x04
+#define FE_RECORD_FMT_VARIABLE16_MSB 0x05
+#define FE_RECORD_FMT_VARIABLE32 0x06
+#define FE_RECORD_FMT_PRINT 0x07
+#define FE_RECORD_FMT_LF 0x08
+#define FE_RECORD_FMT_CR 0x09
+#define FE_RECORD_FMT_CRLF 0x0A
+#define FE_RECORD_FMT_LFCR 0x0B
+
+/* Record Display Attributes (ECMA 167r3 4/14.9.8) */
+#define FE_RECORD_DISPLAY_ATTR_UNDEF 0x00
+#define FE_RECORD_DISPLAY_ATTR_1 0x01
+#define FE_RECORD_DISPLAY_ATTR_2 0x02
+#define FE_RECORD_DISPLAY_ATTR_3 0x03
+
+/* Extended Attribute Header Descriptor (ECMA 167r3 4/14.10.1) */
+struct extendedAttrHeaderDesc {
+ struct tag descTag;
+ __le32 impAttrLocation;
+ __le32 appAttrLocation;
+} __packed;
+
+/* Generic Format (ECMA 167r3 4/14.10.2) */
+struct genericFormat {
+ __le32 attrType;
+ uint8_t attrSubtype;
+ uint8_t reserved[3];
+ __le32 attrLength;
+ uint8_t attrData[];
+} __packed;
+
+/* Character Set Information (ECMA 167r3 4/14.10.3) */
+struct charSetInfo {
+ __le32 attrType;
+ uint8_t attrSubtype;
+ uint8_t reserved[3];
+ __le32 attrLength;
+ __le32 escapeSeqLength;
+ uint8_t charSetType;
+ uint8_t escapeSeq[];
+} __packed;
+
+/* Alternate Permissions (ECMA 167r3 4/14.10.4) */
+struct altPerms {
+ __le32 attrType;
+ uint8_t attrSubtype;
+ uint8_t reserved[3];
+ __le32 attrLength;
+ __le16 ownerIdent;
+ __le16 groupIdent;
+ __le16 permission;
+} __packed;
+
+/* File Times Extended Attribute (ECMA 167r3 4/14.10.5) */
+struct fileTimesExtAttr {
+ __le32 attrType;
+ uint8_t attrSubtype;
+ uint8_t reserved[3];
+ __le32 attrLength;
+ __le32 dataLength;
+ __le32 fileTimeExistence;
+ uint8_t fileTimes;
+} __packed;
+
+/* FileTimeExistence (ECMA 167r3 4/14.10.5.6) */
+#define FTE_CREATION 0x00000001
+#define FTE_DELETION 0x00000004
+#define FTE_EFFECTIVE 0x00000008
+#define FTE_BACKUP 0x00000002
+
+/* Information Times Extended Attribute (ECMA 167r3 4/14.10.6) */
+struct infoTimesExtAttr {
+ __le32 attrType;
+ uint8_t attrSubtype;
+ uint8_t reserved[3];
+ __le32 attrLength;
+ __le32 dataLength;
+ __le32 infoTimeExistence;
+ uint8_t infoTimes[];
+} __packed;
+
+/* Device Specification (ECMA 167r3 4/14.10.7) */
+struct deviceSpec {
+ __le32 attrType;
+ uint8_t attrSubtype;
+ uint8_t reserved[3];
+ __le32 attrLength;
+ __le32 impUseLength;
+ __le32 majorDeviceIdent;
+ __le32 minorDeviceIdent;
+ uint8_t impUse[];
+} __packed;
+
+/* Implementation Use Extended Attr (ECMA 167r3 4/14.10.8) */
+struct impUseExtAttr {
+ __le32 attrType;
+ uint8_t attrSubtype;
+ uint8_t reserved[3];
+ __le32 attrLength;
+ __le32 impUseLength;
+ struct regid impIdent;
+ uint8_t impUse[];
+} __packed;
+
+/* Application Use Extended Attribute (ECMA 167r3 4/14.10.9) */
+struct appUseExtAttr {
+ __le32 attrType;
+ uint8_t attrSubtype;
+ uint8_t reserved[3];
+ __le32 attrLength;
+ __le32 appUseLength;
+ struct regid appIdent;
+ uint8_t appUse[];
+} __packed;
+
+#define EXTATTR_CHAR_SET 1
+#define EXTATTR_ALT_PERMS 3
+#define EXTATTR_FILE_TIMES 5
+#define EXTATTR_INFO_TIMES 6
+#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[];
+} __packed;
+
+/* Space Bitmap Descriptor (ECMA 167r3 4/14.12) */
+struct spaceBitmapDesc {
+ struct tag descTag;
+ __le32 numOfBits;
+ __le32 numOfBytes;
+ uint8_t bitmap[];
+} __packed;
+
+/* Partition Integrity Entry (ECMA 167r3 4/14.13) */
+struct partitionIntegrityEntry {
+ struct tag descTag;
+ struct icbtag icbTag;
+ struct timestamp recordingDateAndTime;
+ uint8_t integrityType;
+ uint8_t reserved[175];
+ struct regid impIdent;
+ uint8_t impUse[256];
+} __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_ALLOCDESCS 0xC0000000
+
+/* Long Allocation Descriptor (ECMA 167r3 4/14.14.2) */
+
+/* Extended Allocation Descriptor (ECMA 167r3 4/14.14.3) */
+
+/* Logical Volume Header Descriptor (ECMA 167r3 4/14.15) */
+struct logicalVolHeaderDesc {
+ __le64 uniqueID;
+ uint8_t reserved[24];
+} __packed;
+
+/* Path Component (ECMA 167r3 4/14.16.1) */
+struct pathComponent {
+ uint8_t componentType;
+ uint8_t lengthComponentIdent;
+ __le16 componentFileVersionNum;
+ dchars componentIdent[];
+} __packed;
+
+/* File Entry (ECMA 167r3 4/14.17) */
+struct extendedFileEntry {
+ struct tag descTag;
+ struct icbtag icbTag;
+ __le32 uid;
+ __le32 gid;
+ __le32 permissions;
+ __le16 fileLinkCount;
+ uint8_t recordFormat;
+ uint8_t recordDisplayAttr;
+ __le32 recordLength;
+ __le64 informationLength;
+ __le64 objectSize;
+ __le64 logicalBlocksRecorded;
+ struct timestamp accessTime;
+ struct timestamp modificationTime;
+ struct timestamp createTime;
+ struct timestamp attrTime;
+ __le32 checkpoint;
+ __le32 reserved;
+ struct long_ad extendedAttrICB;
+ struct long_ad streamDirectoryICB;
+ struct regid impIdent;
+ __le64 uniqueID;
+ __le32 lengthExtendedAttr;
+ __le32 lengthAllocDescs;
+ uint8_t extendedAttr[];
+ /* uint8_t allocDescs[]; */
+} __packed;
+
+#endif /* _ECMA_167_H */
diff --git a/fs/udf/file.c b/fs/udf/file.c
new file mode 100644
index 0000000000..0ceac4b593
--- /dev/null
+++ b/fs/udf/file.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * file.c
+ *
+ * PURPOSE
+ * File handling routines for the OSTA-UDF(tm) filesystem.
+ *
+ * COPYRIGHT
+ * (C) 1998-1999 Dave Boynton
+ * (C) 1998-2004 Ben Fennema
+ * (C) 1999-2000 Stelias Computing Inc
+ *
+ * HISTORY
+ *
+ * 10/02/98 dgb Attempt to integrate into udf.o
+ * 10/07/98 Switched to using generic_readpage, etc., like isofs
+ * And it works!
+ * 12/06/98 blf Added udf_file_read. uses generic_file_read for all cases but
+ * ICBTAG_FLAG_AD_IN_ICB.
+ * 04/06/99 64 bit file handling on 32 bit systems taken from ext2 file.c
+ * 05/12/99 Preliminary file write support
+ */
+
+#include "udfdecl.h"
+#include <linux/fs.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/uio.h>
+
+#include "udf_i.h"
+#include "udf_sb.h"
+
+static vm_fault_t udf_page_mkwrite(struct vm_fault *vmf)
+{
+ struct vm_area_struct *vma = vmf->vma;
+ struct inode *inode = file_inode(vma->vm_file);
+ struct address_space *mapping = inode->i_mapping;
+ struct page *page = 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);
+ lock_page(page);
+ size = i_size_read(inode);
+ if (page->mapping != inode->i_mapping || page_offset(page) >= size) {
+ unlock_page(page);
+ 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 (page->index == size >> PAGE_SHIFT)
+ end = size & ~PAGE_MASK;
+ else
+ end = PAGE_SIZE;
+ err = __block_write_begin(page, 0, end, udf_get_block);
+ if (err) {
+ unlock_page(page);
+ ret = vmf_fs_error(err);
+ goto out_unlock;
+ }
+
+ block_commit_write(page, 0, end);
+out_dirty:
+ set_page_dirty(page);
+ wait_for_stable_page(page);
+out_unlock:
+ filemap_invalidate_unlock_shared(mapping);
+ sb_end_pagefault(inode->i_sb);
+ return ret;
+}
+
+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_write_iter(struct kiocb *iocb, struct iov_iter *from)
+{
+ ssize_t retval;
+ struct file *file = iocb->ki_filp;
+ struct inode *inode = file_inode(file);
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ inode_lock(inode);
+
+ 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);
+
+ if (retval > 0) {
+ mark_inode_dirty(inode);
+ retval = generic_write_sync(iocb, retval);
+ }
+
+ return retval;
+}
+
+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;
+
+ if (file_permission(filp, MAY_READ) != 0) {
+ udf_debug("no permission to access inode %lu\n", inode->i_ino);
+ return -EPERM;
+ }
+
+ if (!arg && ((cmd == UDF_GETVOLIDENT) || (cmd == UDF_GETEASIZE) ||
+ (cmd == UDF_RELOCATE_BLOCKS) || (cmd == UDF_GETEABLOCK))) {
+ udf_debug("invalid argument to udf_ioctl\n");
+ return -EINVAL;
+ }
+
+ switch (cmd) {
+ case UDF_GETVOLIDENT:
+ if (copy_to_user((char __user *)arg,
+ UDF_SB(inode->i_sb)->s_volume_ident, 32))
+ return -EFAULT;
+ return 0;
+ case UDF_RELOCATE_BLOCKS:
+ 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);
+ return result;
+ case UDF_GETEASIZE:
+ return put_user(UDF_I(inode)->i_lenEAttr, (int __user *)arg);
+ case UDF_GETEABLOCK:
+ return copy_to_user((char __user *)arg,
+ UDF_I(inode)->i_data,
+ UDF_I(inode)->i_lenEAttr) ? -EFAULT : 0;
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+static int udf_release_file(struct inode *inode, struct file *filp)
+{
+ 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_iter = generic_file_read_iter,
+ .unlocked_ioctl = udf_ioctl,
+ .open = generic_file_open,
+ .mmap = udf_file_mmap,
+ .write_iter = udf_file_write_iter,
+ .release = udf_release_file,
+ .fsync = generic_file_fsync,
+ .splice_read = filemap_splice_read,
+ .splice_write = iter_file_splice_write,
+ .llseek = generic_file_llseek,
+};
+
+static int udf_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct iattr *attr)
+{
+ struct inode *inode = d_inode(dentry);
+ struct super_block *sb = inode->i_sb;
+ int error;
+
+ 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)) {
+ error = udf_setsize(inode, attr->ia_size);
+ if (error)
+ return error;
+ }
+
+ 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;
+}
+
+const struct inode_operations udf_file_inode_operations = {
+ .setattr = udf_setattr,
+};
diff --git a/fs/udf/ialloc.c b/fs/udf/ialloc.c
new file mode 100644
index 0000000000..6b558cbbeb
--- /dev/null
+++ b/fs/udf/ialloc.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ialloc.c
+ *
+ * PURPOSE
+ * Inode allocation handling routines for the OSTA-UDF(tm) filesystem.
+ *
+ * COPYRIGHT
+ * (C) 1998-2001 Ben Fennema
+ *
+ * HISTORY
+ *
+ * 02/24/99 blf Created.
+ *
+ */
+
+#include "udfdecl.h"
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include "udf_i.h"
+#include "udf_sb.h"
+
+void udf_free_inode(struct inode *inode)
+{
+ 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)
+{
+ struct super_block *sb = dir->i_sb;
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ struct inode *inode;
+ 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)
+ 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_data = kzalloc(inode->i_sb->s_blocksize -
+ sizeof(struct extendedFileEntry),
+ GFP_KERNEL);
+ } else {
+ iinfo->i_efe = 0;
+ iinfo->i_data = kzalloc(inode->i_sb->s_blocksize -
+ sizeof(struct fileEntry),
+ GFP_KERNEL);
+ }
+ if (!iinfo->i_data) {
+ make_bad_inode(inode);
+ iput(inode);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ err = -ENOSPC;
+ block = udf_new_block(dir->i_sb, NULL,
+ dinfo->i_location.partitionReferenceNum,
+ start, &err);
+ if (err) {
+ make_bad_inode(inode);
+ iput(inode);
+ return ERR_PTR(err);
+ }
+
+ iinfo->i_unique = lvid_get_unique_id(sb);
+ inode->i_generation = iinfo->i_unique;
+
+ 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 =
+ dinfo->i_location.partitionReferenceNum;
+ inode->i_ino = udf_get_lb_pblock(sb, &iinfo->i_location, 0);
+ inode->i_blocks = 0;
+ iinfo->i_lenEAttr = 0;
+ 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_set_ctime_current(inode);
+ iinfo->i_crtime = inode->i_mtime;
+ if (unlikely(insert_inode_locked(inode) < 0)) {
+ make_bad_inode(inode);
+ iput(inode);
+ return ERR_PTR(-EIO);
+ }
+ mark_inode_dirty(inode);
+
+ return inode;
+}
diff --git a/fs/udf/inode.c b/fs/udf/inode.c
new file mode 100644
index 0000000000..a17a6184cc
--- /dev/null
+++ b/fs/udf/inode.c
@@ -0,0 +1,2386 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * inode.c
+ *
+ * PURPOSE
+ * Inode handling routines for the OSTA-UDF(tm) filesystem.
+ *
+ * COPYRIGHT
+ * (C) 1998 Dave Boynton
+ * (C) 1998-2004 Ben Fennema
+ * (C) 1999-2000 Stelias Computing Inc
+ *
+ * HISTORY
+ *
+ * 10/04/98 dgb Added rudimentary directory functions
+ * 10/07/98 Fully working udf_block_map! It works!
+ * 11/25/98 bmap altered to better support extents
+ * 12/06/98 blf partition support in udf_iget, udf_block_map
+ * and udf_read_inode
+ * 12/12/98 rewrote udf_block_map to handle next extents and descs across
+ * block boundaries (which is not actually allowed)
+ * 12/20/98 added support for strategy 4096
+ * 03/07/99 rewrote udf_block_map (again)
+ * New funcs, inode_bmap, udf_next_aext
+ * 04/19/99 Support for writing device EA's for major/minor #
+ */
+
+#include "udfdecl.h"
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/pagemap.h>
+#include <linux/writeback.h>
+#include <linux/slab.h>
+#include <linux/crc-itu-t.h>
+#include <linux/mpage.h>
+#include <linux/uio.h>
+#include <linux/bio.h>
+
+#include "udf_i.h"
+#include "udf_sb.h"
+
+#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 int udf_sync_inode(struct inode *inode);
+static int udf_alloc_i_data(struct inode *inode, size_t size);
+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 *, 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)
+{
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ if (iinfo->cached_extent.lstart != -1) {
+ brelse(iinfo->cached_extent.epos.bh);
+ iinfo->cached_extent.lstart = -1;
+ }
+}
+
+/* Invalidate extent cache */
+static void udf_clear_extent_cache(struct inode *inode)
+{
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ spin_lock(&iinfo->i_extent_cache_lock);
+ __udf_clear_extent_cache(inode);
+ spin_unlock(&iinfo->i_extent_cache_lock);
+}
+
+/* Return contents of extent cache */
+static int udf_read_extent_cache(struct inode *inode, loff_t bcount,
+ loff_t *lbcount, struct extent_position *pos)
+{
+ struct udf_inode_info *iinfo = UDF_I(inode);
+ int ret = 0;
+
+ spin_lock(&iinfo->i_extent_cache_lock);
+ if ((iinfo->cached_extent.lstart <= bcount) &&
+ (iinfo->cached_extent.lstart != -1)) {
+ /* Cache hit */
+ *lbcount = iinfo->cached_extent.lstart;
+ memcpy(pos, &iinfo->cached_extent.epos,
+ sizeof(struct extent_position));
+ if (pos->bh)
+ get_bh(pos->bh);
+ ret = 1;
+ }
+ spin_unlock(&iinfo->i_extent_cache_lock);
+ return ret;
+}
+
+/* Add extent to extent cache */
+static void udf_update_extent_cache(struct inode *inode, loff_t estart,
+ struct extent_position *pos)
+{
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ spin_lock(&iinfo->i_extent_cache_lock);
+ /* Invalidate previously cached extent */
+ __udf_clear_extent_cache(inode);
+ if (pos->bh)
+ get_bh(pos->bh);
+ memcpy(&iinfo->cached_extent.epos, pos, sizeof(*pos));
+ iinfo->cached_extent.lstart = estart;
+ 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);
+}
+
+void udf_evict_inode(struct inode *inode)
+{
+ struct udf_inode_info *iinfo = UDF_I(inode);
+ int want_delete = 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);
+ kfree(iinfo->i_data);
+ iinfo->i_data = NULL;
+ udf_clear_extent_cache(inode);
+ if (want_delete) {
+ udf_free_inode(inode);
+ }
+}
+
+static void udf_write_failed(struct address_space *mapping, loff_t to)
+{
+ struct inode *inode = mapping->host;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+ loff_t isize = inode->i_size;
+
+ if (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);
+ udf_truncate_extents(inode);
+ up_write(&iinfo->i_data_sem);
+ }
+ }
+}
+
+static int udf_adinicb_writepage(struct folio *folio,
+ struct writeback_control *wbc, void *data)
+{
+ struct inode *inode = folio->mapping->host;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ 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 inode *inode = mapping->host;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB)
+ return mpage_writepages(mapping, wbc, udf_get_block_wb);
+ return write_cache_pages(mapping, wbc, udf_adinicb_writepage, NULL);
+}
+
+static void udf_adinicb_readpage(struct page *page)
+{
+ struct inode *inode = page->mapping->host;
+ char *kaddr;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+ loff_t isize = i_size_read(inode);
+
+ kaddr = kmap_local_page(page);
+ memcpy(kaddr, iinfo->i_data + iinfo->i_lenEAttr, isize);
+ memset(kaddr + isize, 0, PAGE_SIZE - isize);
+ flush_dcache_page(page);
+ SetPageUptodate(page);
+ kunmap_local(kaddr);
+}
+
+static int udf_read_folio(struct file *file, struct folio *folio)
+{
+ struct udf_inode_info *iinfo = UDF_I(file_inode(file));
+
+ if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
+ udf_adinicb_readpage(&folio->page);
+ folio_unlock(folio);
+ return 0;
+ }
+ return mpage_read_folio(folio, udf_get_block);
+}
+
+static void udf_readahead(struct readahead_control *rac)
+{
+ 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,
+ struct page **pagep, void **fsdata)
+{
+ struct udf_inode_info *iinfo = UDF_I(file_inode(file));
+ struct page *page;
+ int ret;
+
+ if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) {
+ ret = block_write_begin(mapping, pos, len, pagep,
+ udf_get_block);
+ if (unlikely(ret))
+ udf_write_failed(mapping, pos + len);
+ return ret;
+ }
+ if (WARN_ON_ONCE(pos >= PAGE_SIZE))
+ return -EIO;
+ page = grab_cache_page_write_begin(mapping, 0);
+ if (!page)
+ return -ENOMEM;
+ *pagep = page;
+ if (!PageUptodate(page))
+ udf_adinicb_readpage(page);
+ return 0;
+}
+
+static int udf_write_end(struct file *file, struct address_space *mapping,
+ loff_t pos, unsigned len, unsigned copied,
+ struct page *page, void *fsdata)
+{
+ struct inode *inode = file_inode(file);
+ loff_t last_pos;
+
+ if (UDF_I(inode)->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB)
+ return generic_write_end(file, mapping, pos, len, copied, page,
+ fsdata);
+ last_pos = pos + copied;
+ if (last_pos > inode->i_size)
+ i_size_write(inode, last_pos);
+ set_page_dirty(page);
+ unlock_page(page);
+ put_page(page);
+
+ 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;
+
+ /* 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 = {
+ .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 = 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_mutex held
+ */
+int udf_expand_file_adinicb(struct inode *inode)
+{
+ struct page *page;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+ int err;
+
+ 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;
+ up_write(&iinfo->i_data_sem);
+ mark_inode_dirty(inode);
+ return 0;
+ }
+
+ page = find_or_create_page(inode->i_mapping, 0, GFP_NOFS);
+ if (!page)
+ return -ENOMEM;
+
+ if (!PageUptodate(page))
+ udf_adinicb_readpage(page);
+ down_write(&iinfo->i_data_sem);
+ 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;
+ set_page_dirty(page);
+ unlock_page(page);
+ up_write(&iinfo->i_data_sem);
+ err = filemap_fdatawrite(inode->i_mapping);
+ if (err) {
+ /* Restore everything back so that we don't lose data... */
+ lock_page(page);
+ down_write(&iinfo->i_data_sem);
+ memcpy_to_page(page, 0, iinfo->i_data + iinfo->i_lenEAttr,
+ inode->i_size);
+ unlock_page(page);
+ iinfo->i_alloc_type = ICBTAG_FLAG_AD_IN_ICB;
+ iinfo->i_lenAlloc = inode->i_size;
+ up_write(&iinfo->i_data_sem);
+ }
+ put_page(page);
+ mark_inode_dirty(inode);
+
+ return err;
+}
+
+#define UDF_MAP_CREATE 0x01 /* Mapping can allocate new blocks */
+#define UDF_MAP_NOPREALLOC 0x02 /* Do not preallocate blocks */
+
+#define UDF_BLK_MAPPED 0x01 /* Block was successfully mapped */
+#define UDF_BLK_NEW 0x02 /* Block was freshly allocated */
+
+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 */
+};
+
+static int udf_map_block(struct inode *inode, struct udf_map_rq *map)
+{
+ int err;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ 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 = {};
+
+ down_read(&iinfo->i_data_sem);
+ if (inode_bmap(inode, map->lblk, &epos, &eloc, &elen, &offset)
+ == (EXT_RECORDED_ALLOCATED >> 30)) {
+ map->pblk = udf_get_lb_pblock(inode->i_sb, &eloc,
+ offset);
+ map->oflags |= UDF_BLK_MAPPED;
+ }
+ up_read(&iinfo->i_data_sem);
+ brelse(epos.bh);
+
+ return 0;
+ }
+
+ 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);
+ err = inode_getblk(inode, map);
+ up_write(&iinfo->i_data_sem);
+ return err;
+}
+
+static int __udf_get_block(struct inode *inode, sector_t block,
+ struct buffer_head *bh_result, int flags)
+{
+ int err;
+ struct udf_map_rq map = {
+ .lblk = block,
+ .iflags = flags,
+ };
+
+ 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;
+}
+
+int udf_get_block(struct inode *inode, sector_t block,
+ struct buffer_head *bh_result, int create)
+{
+ int flags = create ? UDF_MAP_CREATE : 0;
+
+ /*
+ * 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);
+}
+
+/*
+ * 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)
+{
+ return __udf_get_block(inode, block, bh_result, 0);
+}
+
+/* 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,
+ loff_t new_block_bytes)
+{
+ uint32_t add;
+ int count = 0, fake = !(last_ext->extLength & UDF_EXTENT_LENGTH_MASK);
+ struct super_block *sb = inode->i_sb;
+ 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 (!new_block_bytes && fake)
+ return 0;
+
+ iinfo = UDF_I(inode);
+ /* Round the last extent up to a multiple of block size */
+ if (last_ext->extLength & (sb->s_blocksize - 1)) {
+ last_ext->extLength =
+ (last_ext->extLength & UDF_EXTENT_FLAG_MASK) |
+ (((last_ext->extLength & UDF_EXTENT_LENGTH_MASK) +
+ sb->s_blocksize - 1) & ~(sb->s_blocksize - 1));
+ iinfo->i_lenExtents =
+ (iinfo->i_lenExtents + sb->s_blocksize - 1) &
+ ~(sb->s_blocksize - 1);
+ }
+
+ 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);
+ if (add > new_block_bytes)
+ add = new_block_bytes;
+ new_block_bytes -= add;
+ last_ext->extLength += add;
+ }
+
+ if (fake) {
+ err = udf_add_aext(inode, last_pos, &last_ext->extLocation,
+ last_ext->extLength, 1);
+ if (err < 0)
+ goto out_err;
+ count++;
+ } else {
+ struct kernel_lb_addr tmploc;
+ uint32_t tmplen;
+
+ 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)
+ udf_next_aext(inode, last_pos, &tmploc, &tmplen, 0);
+ }
+ iinfo->i_lenExtents += add;
+
+ /* Managed to do everything necessary? */
+ 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;
+ last_ext->extLength = EXT_NOT_RECORDED_NOT_ALLOCATED | add;
+
+ /* Create enough extents to cover the whole hole */
+ 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)
+ goto out_err;
+ iinfo->i_lenExtents += add;
+ count++;
+ }
+ if (new_block_bytes) {
+ last_ext->extLength = EXT_NOT_RECORDED_NOT_ALLOCATED |
+ new_block_bytes;
+ err = udf_add_aext(inode, last_pos, &last_ext->extLocation,
+ last_ext->extLength, 1);
+ if (err)
+ goto out_err;
+ iinfo->i_lenExtents += new_block_bytes;
+ count++;
+ }
+
+out:
+ /* 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);
+ else if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_LONG)
+ last_pos->offset -= sizeof(struct long_ad);
+ else
+ 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)
+{
+
+ struct extent_position epos;
+ struct kernel_lb_addr eloc;
+ uint32_t elen;
+ int8_t etype;
+ struct super_block *sb = inode->i_sb;
+ sector_t first_block = newsize >> sb->s_blocksize_bits, offset;
+ loff_t new_elen;
+ int adsize;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+ struct kernel_long_ad extent;
+ int err = 0;
+ bool within_last_ext;
+
+ if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT)
+ adsize = sizeof(struct short_ad);
+ else if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_LONG)
+ adsize = sizeof(struct long_ad);
+ else
+ BUG();
+
+ 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);
+
+ etype = inode_bmap(inode, first_block, &epos, &eloc, &elen, &offset);
+ within_last_ext = (etype != -1);
+ /* We don't expect extents past EOF... */
+ WARN_ON_ONCE(within_last_ext &&
+ elen > ((loff_t)offset + 1) << inode->i_blkbits);
+
+ if ((!epos.bh && epos.offset == udf_file_entry_alloc_offset(inode)) ||
+ (epos.bh && epos.offset == sizeof(struct allocExtDesc))) {
+ /* File has no extents at all or has empty last
+ * indirect extent! Create a fake extent... */
+ extent.extLocation.logicalBlockNum = 0;
+ extent.extLocation.partitionReferenceNum = 0;
+ extent.extLength = EXT_NOT_RECORDED_NOT_ALLOCATED;
+ } else {
+ epos.offset -= adsize;
+ etype = udf_next_aext(inode, &epos, &extent.extLocation,
+ &extent.extLength, 0);
+ extent.extLength |= etype << 30;
+ }
+
+ 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;
+out:
+ brelse(epos.bh);
+ up_write(&iinfo->i_data_sem);
+ return err;
+}
+
+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;
+ int count = 0, startnum = 0, endnum = 0;
+ uint32_t elen = 0, tmpelen;
+ struct kernel_lb_addr eloc, tmpeloc;
+ int c = 1;
+ loff_t lbcount = 0, b_off = 0;
+ udf_pblk_t newblocknum;
+ sector_t offset = 0;
+ int8_t etype;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+ udf_pblk_t goal = 0, pgoal = iinfo->i_location.logicalBlockNum;
+ int lastblock = 0;
+ bool isBeyondEOF;
+ int ret = 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)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
+ current extent, and the previous extent */
+ do {
+ if (prev_epos.bh != cur_epos.bh) {
+ brelse(prev_epos.bh);
+ get_bh(cur_epos.bh);
+ prev_epos.bh = cur_epos.bh;
+ }
+ if (cur_epos.bh != next_epos.bh) {
+ brelse(cur_epos.bh);
+ get_bh(next_epos.bh);
+ cur_epos.bh = next_epos.bh;
+ }
+
+ lbcount += elen;
+
+ prev_epos.block = cur_epos.block;
+ cur_epos.block = next_epos.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)
+ break;
+
+ c = !c;
+
+ laarr[c].extLength = (etype << 30) | elen;
+ laarr[c].extLocation = eloc;
+
+ if (etype != (EXT_NOT_RECORDED_NOT_ALLOCATED >> 30))
+ pgoal = eloc.logicalBlockNum +
+ ((elen + inode->i_sb->s_blocksize - 1) >>
+ inode->i_sb->s_blocksize_bits);
+
+ count++;
+ } while (lbcount + elen <= b_off);
+
+ b_off -= lbcount;
+ offset = b_off >> inode->i_sb->s_blocksize_bits;
+ /*
+ * 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);
+
+ /* 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 (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);
+ }
+ map->oflags = UDF_BLK_MAPPED;
+ map->pblk = udf_get_lb_pblock(inode->i_sb, &eloc, offset);
+ goto out_free;
+ }
+
+ /* Are we beyond EOF and preallocated extent? */
+ if (etype == -1) {
+ loff_t hole_len;
+
+ isBeyondEOF = true;
+ if (count) {
+ if (c)
+ laarr[0] = laarr[1];
+ startnum = 1;
+ } else {
+ /* Create a fake extent when there's not one */
+ memset(&laarr[0].extLocation, 0x00,
+ sizeof(struct kernel_lb_addr));
+ laarr[0].extLength = EXT_NOT_RECORDED_NOT_ALLOCATED;
+ /* Will udf_do_extend_file() create real extent from
+ a fake one? */
+ startnum = (offset > 0);
+ }
+ /* Create extents for the hole between EOF and offset */
+ 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;
+ /*
+ * 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 = false;
+ endnum = startnum = ((count > 2) ? 2 : count);
+
+ /* if the current extent is in position 0,
+ swap it with the previous */
+ if (!c && count != 1) {
+ laarr[2] = laarr[0];
+ laarr[0] = laarr[1];
+ laarr[1] = laarr[2];
+ c = 1;
+ }
+
+ /* 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) {
+ laarr[c + 1].extLength = (etype << 30) | elen;
+ laarr[c + 1].extLocation = eloc;
+ count++;
+ startnum++;
+ endnum++;
+ } else
+ lastblock = 1;
+ }
+
+ /* if the current extent is not recorded but allocated, get the
+ * block in the extent corresponding to the requested 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 == map->lblk)
+ goal = iinfo->i_next_alloc_goal;
+
+ if (!goal) {
+ if (!(goal = pgoal)) /* XXX: what was intended here? */
+ goal = iinfo->i_location.logicalBlockNum + 1;
+ }
+
+ newblocknum = udf_new_block(inode->i_sb, inode,
+ iinfo->i_location.partitionReferenceNum,
+ goal, &ret);
+ if (!newblocknum)
+ goto out_free;
+ if (isBeyondEOF)
+ iinfo->i_lenExtents += inode->i_sb->s_blocksize;
+ }
+
+ /* if the extent the requsted block is located in contains multiple
+ * blocks, split the extent into at most three extents. blocks prior
+ * to requested block, requested block, and blocks after requested
+ * block */
+ udf_split_extents(inode, &c, offset, newblocknum, laarr, &endnum);
+
+ if (!(map->iflags & UDF_MAP_NOPREALLOC))
+ udf_prealloc_extents(inode, c, lastblock, laarr, &endnum);
+
+ /* merge any continuous blocks in laarr */
+ udf_merge_extents(inode, laarr, &endnum);
+
+ /* 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 */
+ ret = udf_update_extents(inode, laarr, startnum, endnum, &prev_epos);
+ if (ret < 0)
+ goto out_free;
+
+ map->pblk = udf_get_pblock(inode->i_sb, newblocknum,
+ iinfo->i_location.partitionReferenceNum, 0);
+ if (!map->pblk) {
+ ret = -EFSCORRUPTED;
+ goto out_free;
+ }
+ 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);
+ 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,
+ 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;
+
+ if ((laarr[*c].extLength >> 30) == (EXT_NOT_RECORDED_ALLOCATED >> 30) ||
+ (laarr[*c].extLength >> 30) ==
+ (EXT_NOT_RECORDED_NOT_ALLOCATED >> 30)) {
+ int curr = *c;
+ int blen = ((laarr[curr].extLength & UDF_EXTENT_LENGTH_MASK) +
+ blocksize - 1) >> blocksize_bits;
+ int8_t etype = (laarr[curr].extLength >> 30);
+
+ if (blen == 1)
+ ;
+ else if (!offset || blen == offset + 1) {
+ laarr[curr + 2] = laarr[curr + 1];
+ laarr[curr + 1] = laarr[curr];
+ } else {
+ laarr[curr + 3] = laarr[curr + 1];
+ laarr[curr + 2] = laarr[curr + 1] = laarr[curr];
+ }
+
+ if (offset) {
+ if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30)) {
+ udf_free_blocks(inode->i_sb, inode,
+ &laarr[curr].extLocation,
+ 0, offset);
+ laarr[curr].extLength =
+ EXT_NOT_RECORDED_NOT_ALLOCATED |
+ (offset << blocksize_bits);
+ laarr[curr].extLocation.logicalBlockNum = 0;
+ laarr[curr].extLocation.
+ partitionReferenceNum = 0;
+ } else
+ laarr[curr].extLength = (etype << 30) |
+ (offset << blocksize_bits);
+ curr++;
+ (*c)++;
+ (*endnum)++;
+ }
+
+ laarr[curr].extLocation.logicalBlockNum = newblocknum;
+ if (etype == (EXT_NOT_RECORDED_NOT_ALLOCATED >> 30))
+ laarr[curr].extLocation.partitionReferenceNum =
+ UDF_I(inode)->i_location.partitionReferenceNum;
+ laarr[curr].extLength = EXT_RECORDED_ALLOCATED |
+ blocksize;
+ curr++;
+
+ if (blen != offset + 1) {
+ if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30))
+ laarr[curr].extLocation.logicalBlockNum +=
+ offset + 1;
+ laarr[curr].extLength = (etype << 30) |
+ ((blen - (offset + 1)) << blocksize_bits);
+ curr++;
+ (*endnum)++;
+ }
+ }
+}
+
+static void udf_prealloc_extents(struct inode *inode, int c, int lastblock,
+ struct kernel_long_ad *laarr,
+ int *endnum)
+{
+ int start, length = 0, currlength = 0, i;
+
+ if (*endnum >= (c + 1)) {
+ if (!lastblock)
+ return;
+ else
+ start = c;
+ } else {
+ if ((laarr[c + 1].extLength >> 30) ==
+ (EXT_NOT_RECORDED_ALLOCATED >> 30)) {
+ start = c + 1;
+ length = currlength =
+ (((laarr[c + 1].extLength &
+ UDF_EXTENT_LENGTH_MASK) +
+ inode->i_sb->s_blocksize - 1) >>
+ inode->i_sb->s_blocksize_bits);
+ } else
+ start = c;
+ }
+
+ for (i = start + 1; i <= *endnum; i++) {
+ if (i == *endnum) {
+ if (lastblock)
+ length += UDF_DEFAULT_PREALLOC_BLOCKS;
+ } else if ((laarr[i].extLength >> 30) ==
+ (EXT_NOT_RECORDED_NOT_ALLOCATED >> 30)) {
+ length += (((laarr[i].extLength &
+ UDF_EXTENT_LENGTH_MASK) +
+ inode->i_sb->s_blocksize - 1) >>
+ inode->i_sb->s_blocksize_bits);
+ } else
+ break;
+ }
+
+ if (length) {
+ int next = laarr[start].extLocation.logicalBlockNum +
+ (((laarr[start].extLength & UDF_EXTENT_LENGTH_MASK) +
+ inode->i_sb->s_blocksize - 1) >>
+ inode->i_sb->s_blocksize_bits);
+ int numalloc = udf_prealloc_blocks(inode->i_sb, inode,
+ laarr[start].extLocation.partitionReferenceNum,
+ next, (UDF_DEFAULT_PREALLOC_BLOCKS > length ?
+ length : UDF_DEFAULT_PREALLOC_BLOCKS) -
+ currlength);
+ if (numalloc) {
+ if (start == (c + 1))
+ laarr[start].extLength +=
+ (numalloc <<
+ inode->i_sb->s_blocksize_bits);
+ else {
+ memmove(&laarr[c + 2], &laarr[c + 1],
+ sizeof(struct long_ad) * (*endnum - (c + 1)));
+ (*endnum)++;
+ laarr[c + 1].extLocation.logicalBlockNum = next;
+ laarr[c + 1].extLocation.partitionReferenceNum =
+ laarr[c].extLocation.
+ partitionReferenceNum;
+ laarr[c + 1].extLength =
+ EXT_NOT_RECORDED_ALLOCATED |
+ (numalloc <<
+ inode->i_sb->s_blocksize_bits);
+ start = c + 1;
+ }
+
+ for (i = start + 1; numalloc && i < *endnum; i++) {
+ int elen = ((laarr[i].extLength &
+ UDF_EXTENT_LENGTH_MASK) +
+ inode->i_sb->s_blocksize - 1) >>
+ inode->i_sb->s_blocksize_bits;
+
+ if (elen > numalloc) {
+ laarr[i].extLength -=
+ (numalloc <<
+ inode->i_sb->s_blocksize_bits);
+ numalloc = 0;
+ } else {
+ numalloc -= elen;
+ if (*endnum > (i + 1))
+ memmove(&laarr[i],
+ &laarr[i + 1],
+ sizeof(struct long_ad) *
+ (*endnum - (i + 1)));
+ i--;
+ (*endnum)--;
+ }
+ }
+ UDF_I(inode)->i_lenExtents +=
+ numalloc << inode->i_sb->s_blocksize_bits;
+ }
+ }
+}
+
+static void udf_merge_extents(struct inode *inode, struct kernel_long_ad *laarr,
+ int *endnum)
+{
+ int i;
+ unsigned long blocksize = inode->i_sb->s_blocksize;
+ unsigned char blocksize_bits = inode->i_sb->s_blocksize_bits;
+
+ for (i = 0; i < (*endnum - 1); i++) {
+ struct kernel_long_ad *li /*l[i]*/ = &laarr[i];
+ struct kernel_long_ad *lip1 /*l[i plus 1]*/ = &laarr[i + 1];
+
+ if (((li->extLength >> 30) == (lip1->extLength >> 30)) &&
+ (((li->extLength >> 30) ==
+ (EXT_NOT_RECORDED_NOT_ALLOCATED >> 30)) ||
+ ((lip1->extLocation.logicalBlockNum -
+ li->extLocation.logicalBlockNum) ==
+ (((li->extLength & UDF_EXTENT_LENGTH_MASK) +
+ blocksize - 1) >> blocksize_bits)))) {
+
+ if (((li->extLength & UDF_EXTENT_LENGTH_MASK) +
+ (lip1->extLength & UDF_EXTENT_LENGTH_MASK) +
+ blocksize - 1) <= UDF_EXTENT_LENGTH_MASK) {
+ li->extLength = lip1->extLength +
+ (((li->extLength &
+ UDF_EXTENT_LENGTH_MASK) +
+ blocksize - 1) & ~(blocksize - 1));
+ if (*endnum > (i + 2))
+ memmove(&laarr[i + 1], &laarr[i + 2],
+ sizeof(struct long_ad) *
+ (*endnum - (i + 2)));
+ i--;
+ (*endnum)--;
+ }
+ } else if (((li->extLength >> 30) ==
+ (EXT_NOT_RECORDED_ALLOCATED >> 30)) &&
+ ((lip1->extLength >> 30) ==
+ (EXT_NOT_RECORDED_NOT_ALLOCATED >> 30))) {
+ udf_free_blocks(inode->i_sb, inode, &li->extLocation, 0,
+ ((li->extLength &
+ UDF_EXTENT_LENGTH_MASK) +
+ blocksize - 1) >> blocksize_bits);
+ li->extLocation.logicalBlockNum = 0;
+ li->extLocation.partitionReferenceNum = 0;
+
+ 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;
+ } else {
+ li->extLength = lip1->extLength +
+ (((li->extLength &
+ UDF_EXTENT_LENGTH_MASK) +
+ blocksize - 1) & ~(blocksize - 1));
+ if (*endnum > (i + 2))
+ memmove(&laarr[i + 1], &laarr[i + 2],
+ sizeof(struct long_ad) *
+ (*endnum - (i + 2)));
+ i--;
+ (*endnum)--;
+ }
+ } else if ((li->extLength >> 30) ==
+ (EXT_NOT_RECORDED_ALLOCATED >> 30)) {
+ udf_free_blocks(inode->i_sb, inode,
+ &li->extLocation, 0,
+ ((li->extLength &
+ UDF_EXTENT_LENGTH_MASK) +
+ blocksize - 1) >> blocksize_bits);
+ li->extLocation.logicalBlockNum = 0;
+ li->extLocation.partitionReferenceNum = 0;
+ li->extLength = (li->extLength &
+ UDF_EXTENT_LENGTH_MASK) |
+ EXT_NOT_RECORDED_NOT_ALLOCATED;
+ }
+ }
+}
+
+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;
+ int err;
+
+ if (startnum > endnum) {
+ for (i = 0; i < (startnum - endnum); i++)
+ udf_delete_aext(inode, *epos);
+ } else if (startnum < endnum) {
+ for (i = 0; i < (endnum - startnum); i++) {
+ 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;
+ udf_next_aext(inode, epos, &laarr[i].extLocation,
+ &laarr[i].extLength, 1);
+ start++;
+ }
+ }
+
+ for (i = start; i < endnum; i++) {
+ udf_next_aext(inode, epos, &tmploc, &tmplen, 0);
+ udf_write_aext(inode, epos, &laarr[i].extLocation,
+ laarr[i].extLength, 1);
+ }
+ return 0;
+}
+
+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),
+ };
+
+ *err = udf_map_block(inode, &map);
+ if (*err || !(map.oflags & UDF_BLK_MAPPED))
+ return NULL;
+
+ 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;
+ }
+
+ if (bh_read(bh, 0) >= 0)
+ return bh;
+
+ brelse(bh);
+ *err = -EIO;
+ return NULL;
+}
+
+int udf_setsize(struct inode *inode, loff_t newsize)
+{
+ int err = 0;
+ struct udf_inode_info *iinfo;
+ 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;
+
+ filemap_invalidate_lock(inode->i_mapping);
+ iinfo = UDF_I(inode);
+ if (newsize > inode->i_size) {
+ if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
+ if (bsize >=
+ (udf_file_entry_alloc_offset(inode) + newsize)) {
+ down_write(&iinfo->i_data_sem);
+ iinfo->i_lenAlloc = newsize;
+ up_write(&iinfo->i_data_sem);
+ goto set_size;
+ }
+ err = udf_expand_file_adinicb(inode);
+ if (err)
+ goto out_unlock;
+ }
+ err = udf_extend_file(inode, newsize);
+ if (err)
+ goto out_unlock;
+set_size:
+ truncate_setsize(inode, newsize);
+ } 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_data + iinfo->i_lenEAttr + newsize,
+ 0x00, bsize - newsize -
+ udf_file_entry_alloc_offset(inode));
+ iinfo->i_lenAlloc = newsize;
+ truncate_setsize(inode, newsize);
+ up_write(&iinfo->i_data_sem);
+ goto update_time;
+ }
+ err = block_truncate_page(inode->i_mapping, newsize,
+ udf_get_block);
+ if (err)
+ goto out_unlock;
+ truncate_setsize(inode, newsize);
+ down_write(&iinfo->i_data_sem);
+ udf_clear_extent_cache(inode);
+ err = udf_truncate_extents(inode);
+ up_write(&iinfo->i_data_sem);
+ if (err)
+ goto out_unlock;
+ }
+update_time:
+ inode->i_mtime = inode_set_ctime_current(inode);
+ if (IS_SYNC(inode))
+ udf_sync_inode(inode);
+ else
+ mark_inode_dirty(inode);
+out_unlock:
+ filemap_invalidate_unlock(inode->i_mapping);
+ return err;
+}
+
+/*
+ * 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 ctime;
+
+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!
+ * Note: get_new_inode() sets the following on a new inode:
+ * i_sb = sb
+ * i_no = ino
+ * i_flags = sb->s_flags
+ * i_state = 0
+ * clean_inode(): zero fills and sets
+ * i_count = 1
+ * i_nlink = 1
+ * i_op = NULL;
+ */
+ bh = udf_read_ptagged(inode->i_sb, iloc, 0, &ident);
+ if (!bh) {
+ 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 %lu) failed ident=%u\n",
+ inode->i_ino, ident);
+ 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, iloc, 1, &ident);
+ if (ident == TAG_IDENT_IE && ibh) {
+ 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) {
+ 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(bh);
+ goto reread;
+ }
+ }
+ brelse(ibh);
+ } else if (fe->icbTag.strategyType != cpu_to_le16(4)) {
+ udf_err(inode->i_sb, "unsupported strategy type: %u\n",
+ le16_to_cpu(fe->icbTag.strategyType));
+ goto out;
+ }
+ if (fe->icbTag.strategyType == cpu_to_le16(4))
+ iinfo->i_strat4096 = 0;
+ else /* if (fe->icbTag.strategyType == cpu_to_le16(4096)) */
+ iinfo->i_strat4096 = 1;
+
+ 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;
+ iinfo->i_lenAlloc = 0;
+ iinfo->i_next_alloc_block = 0;
+ iinfo->i_next_alloc_goal = 0;
+ if (fe->descTag.tagIdent == cpu_to_le16(TAG_IDENT_EFE)) {
+ iinfo->i_efe = 1;
+ iinfo->i_use = 0;
+ 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;
+ ret = udf_alloc_i_data(inode, bs - sizeof(struct fileEntry));
+ if (ret)
+ goto out;
+ memcpy(iinfo->i_data,
+ bh->b_data + 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);
+ ret = udf_alloc_i_data(inode, bs -
+ sizeof(struct unallocSpaceEntry));
+ 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);
+ uid = le32_to_cpu(fe->uid);
+ if (uid == UDF_INVALID_ID ||
+ UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_UID_SET))
+ inode->i_uid = sbi->s_uid;
+ else
+ i_uid_write(inode, uid);
+
+ gid = le32_to_cpu(fe->gid);
+ if (gid == UDF_INVALID_ID ||
+ UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_GID_SET))
+ 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)
+ inode->i_mode = sbi->s_fmode;
+ else if (fe->icbTag.fileType == ICBTAG_FILE_TYPE_DIRECTORY &&
+ sbi->s_dmode != UDF_INVALID_MODE)
+ inode->i_mode = sbi->s_dmode;
+ 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 (!hidden_inode) {
+ ret = -ESTALE;
+ goto out;
+ }
+ link_count = 1;
+ }
+ set_nlink(inode, link_count);
+
+ inode->i_size = le64_to_cpu(fe->informationLength);
+ iinfo->i_lenExtents = inode->i_size;
+
+ if (iinfo->i_efe == 0) {
+ inode->i_blocks = le64_to_cpu(fe->logicalBlocksRecorded) <<
+ (inode->i_sb->s_blocksize_bits - 9);
+
+ udf_disk_stamp_to_time(&inode->i_atime, fe->accessTime);
+ udf_disk_stamp_to_time(&inode->i_mtime, fe->modificationTime);
+ udf_disk_stamp_to_time(&ctime, fe->attrTime);
+ inode_set_ctime_to_ts(inode, ctime);
+
+ 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);
+
+ udf_disk_stamp_to_time(&inode->i_atime, efe->accessTime);
+ udf_disk_stamp_to_time(&inode->i_mtime, efe->modificationTime);
+ udf_disk_stamp_to_time(&iinfo->i_crtime, efe->createTime);
+ udf_disk_stamp_to_time(&ctime, efe->attrTime);
+ inode_set_ctime_to_ts(inode, ctime);
+
+ 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) {
+ case ICBTAG_FILE_TYPE_DIRECTORY:
+ inode->i_op = &udf_dir_inode_operations;
+ inode->i_fop = &udf_dir_operations;
+ inode->i_mode |= S_IFDIR;
+ inc_nlink(inode);
+ break;
+ case ICBTAG_FILE_TYPE_REALTIME:
+ case ICBTAG_FILE_TYPE_REGULAR:
+ case ICBTAG_FILE_TYPE_UNDEF:
+ case ICBTAG_FILE_TYPE_VAT20:
+ 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;
+ break;
+ case ICBTAG_FILE_TYPE_BLOCK:
+ inode->i_mode |= S_IFBLK;
+ break;
+ case ICBTAG_FILE_TYPE_CHAR:
+ inode->i_mode |= S_IFCHR;
+ break;
+ case ICBTAG_FILE_TYPE_FIFO:
+ init_special_inode(inode, inode->i_mode | S_IFIFO, 0);
+ break;
+ case ICBTAG_FILE_TYPE_SOCKET:
+ init_special_inode(inode, inode->i_mode | S_IFSOCK, 0);
+ break;
+ case ICBTAG_FILE_TYPE_SYMLINK:
+ inode->i_data.a_ops = &udf_symlink_aops;
+ inode->i_op = &udf_symlink_inode_operations;
+ inode_nohighmem(inode);
+ inode->i_mode = S_IFLNK | 0777;
+ break;
+ case ICBTAG_FILE_TYPE_MAIN:
+ udf_debug("METADATA FILE-----\n");
+ break;
+ case ICBTAG_FILE_TYPE_MIRROR:
+ udf_debug("METADATA MIRROR FILE-----\n");
+ break;
+ case ICBTAG_FILE_TYPE_BITMAP:
+ udf_debug("METADATA BITMAP FILE-----\n");
+ break;
+ default:
+ udf_err(inode->i_sb, "(ino %lu) failed unknown file type=%u\n",
+ inode->i_ino, fe->icbTag.fileType);
+ goto out;
+ }
+ if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) {
+ struct deviceSpec *dsea =
+ (struct deviceSpec *)udf_get_extendedattr(inode, 12, 1);
+ if (dsea) {
+ init_special_inode(inode, inode->i_mode,
+ MKDEV(le32_to_cpu(dsea->majorDeviceIdent),
+ le32_to_cpu(dsea->minorDeviceIdent)));
+ /* Developer ID ??? */
+ } else
+ 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_data = kmalloc(size, GFP_KERNEL);
+ if (!iinfo->i_data)
+ return -ENOMEM;
+ return 0;
+}
+
+static umode_t udf_convert_permissions(struct fileEntry *fe)
+{
+ umode_t mode;
+ uint32_t permissions;
+ uint32_t flags;
+
+ permissions = le32_to_cpu(fe->permissions);
+ flags = le16_to_cpu(fe->icbTag.flags);
+
+ 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);
+
+ 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);
+}
+
+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;
+ struct fileEntry *fe;
+ struct extendedFileEntry *efe;
+ uint64_t lb_recorded;
+ uint32_t udfperms;
+ uint16_t icbflags;
+ uint16_t crclen;
+ int err = 0;
+ struct udf_sb_info *sbi = UDF_SB(inode->i_sb);
+ unsigned char blocksize_bits = inode->i_sb->s_blocksize_bits;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ 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 -EIO;
+ }
+
+ lock_buffer(bh);
+ memset(bh->b_data, 0, inode->i_sb->s_blocksize);
+ fe = (struct fileEntry *)bh->b_data;
+ efe = (struct extendedFileEntry *)bh->b_data;
+
+ if (iinfo->i_use) {
+ struct unallocSpaceEntry *use =
+ (struct unallocSpaceEntry *)bh->b_data;
+
+ use->lengthAllocDescs = cpu_to_le32(iinfo->i_lenAlloc);
+ memcpy(bh->b_data + sizeof(struct unallocSpaceEntry),
+ iinfo->i_data, inode->i_sb->s_blocksize -
+ sizeof(struct unallocSpaceEntry));
+ use->descTag.tagIdent = cpu_to_le16(TAG_IDENT_USE);
+ crclen = sizeof(struct unallocSpaceEntry);
+
+ goto finish;
+ }
+
+ if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_UID_FORGET))
+ 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(UDF_INVALID_ID);
+ else
+ fe->gid = cpu_to_le32(i_gid_read(inode));
+
+ udfperms = ((inode->i_mode & 0007)) |
+ ((inode->i_mode & 0070) << 2) |
+ ((inode->i_mode & 0700) << 4);
+
+ udfperms |= iinfo->i_extraPerms;
+ fe->permissions = cpu_to_le32(udfperms);
+
+ if (S_ISDIR(inode->i_mode) && inode->i_nlink > 0)
+ fe->fileLinkCount = cpu_to_le16(inode->i_nlink - 1);
+ 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);
+
+ if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) {
+ struct regid *eid;
+ struct deviceSpec *dsea =
+ (struct deviceSpec *)udf_get_extendedattr(inode, 12, 1);
+ if (!dsea) {
+ dsea = (struct deviceSpec *)
+ udf_add_extendedattr(inode,
+ sizeof(struct deviceSpec) +
+ sizeof(struct regid), 12, 0x3);
+ dsea->attrType = cpu_to_le32(12);
+ dsea->attrSubtype = 1;
+ dsea->attrLength = cpu_to_le32(
+ sizeof(struct deviceSpec) +
+ sizeof(struct regid));
+ dsea->impUseLength = cpu_to_le32(sizeof(struct regid));
+ }
+ eid = (struct regid *)dsea->impUse;
+ 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;
+ dsea->majorDeviceIdent = cpu_to_le32(imajor(inode));
+ dsea->minorDeviceIdent = cpu_to_le32(iminor(inode));
+ }
+
+ if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB)
+ lb_recorded = 0; /* No extents => no blocks! */
+ else
+ lb_recorded =
+ (inode->i_blocks + (1 << (blocksize_bits - 9)) - 1) >>
+ (blocksize_bits - 9);
+
+ if (iinfo->i_efe == 0) {
+ memcpy(bh->b_data + sizeof(struct fileEntry),
+ 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_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;
+ fe->impIdent.identSuffix[1] = UDF_OS_ID_LINUX;
+ fe->uniqueID = cpu_to_le64(iinfo->i_unique);
+ fe->lengthExtendedAttr = cpu_to_le32(iinfo->i_lenEAttr);
+ fe->lengthAllocDescs = cpu_to_le32(iinfo->i_lenAlloc);
+ fe->checkpoint = cpu_to_le32(iinfo->i_checkpoint);
+ fe->descTag.tagIdent = cpu_to_le16(TAG_IDENT_FE);
+ crclen = sizeof(struct fileEntry);
+ } else {
+ memcpy(bh->b_data + sizeof(struct extendedFileEntry),
+ iinfo->i_data,
+ inode->i_sb->s_blocksize -
+ sizeof(struct extendedFileEntry));
+ efe->objectSize =
+ cpu_to_le64(inode->i_size + iinfo->i_lenStreams);
+ efe->logicalBlocksRecorded = cpu_to_le64(lb_recorded);
+
+ if (iinfo->i_streamdir) {
+ struct long_ad *icb_lad = &efe->streamDirectoryICB;
+
+ icb_lad->extLocation =
+ cpu_to_lelb(iinfo->i_locStreamdir);
+ icb_lad->extLength =
+ cpu_to_le32(inode->i_sb->s_blocksize);
+ }
+
+ udf_adjust_time(iinfo, inode->i_atime);
+ udf_adjust_time(iinfo, inode->i_mtime);
+ 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->createTime, iinfo->i_crtime);
+ udf_time_to_disk_stamp(&efe->attrTime, inode_get_ctime(inode));
+
+ 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;
+ efe->uniqueID = cpu_to_le64(iinfo->i_unique);
+ efe->lengthExtendedAttr = cpu_to_le32(iinfo->i_lenEAttr);
+ efe->lengthAllocDescs = cpu_to_le32(iinfo->i_lenAlloc);
+ efe->checkpoint = cpu_to_le32(iinfo->i_checkpoint);
+ 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);
+ fe->icbTag.numEntries = cpu_to_le16(2);
+ } else {
+ fe->icbTag.strategyType = cpu_to_le16(4);
+ fe->icbTag.numEntries = cpu_to_le16(1);
+ }
+
+ 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;
+ else if (S_ISLNK(inode->i_mode))
+ fe->icbTag.fileType = ICBTAG_FILE_TYPE_SYMLINK;
+ else if (S_ISBLK(inode->i_mode))
+ fe->icbTag.fileType = ICBTAG_FILE_TYPE_BLOCK;
+ else if (S_ISCHR(inode->i_mode))
+ fe->icbTag.fileType = ICBTAG_FILE_TYPE_CHAR;
+ else if (S_ISFIFO(inode->i_mode))
+ fe->icbTag.fileType = ICBTAG_FILE_TYPE_FIFO;
+ else if (S_ISSOCK(inode->i_mode))
+ fe->icbTag.fileType = ICBTAG_FILE_TYPE_SOCKET;
+
+ icbflags = iinfo->i_alloc_type |
+ ((inode->i_mode & S_ISUID) ? ICBTAG_FLAG_SETUID : 0) |
+ ((inode->i_mode & S_ISGID) ? ICBTAG_FLAG_SETGID : 0) |
+ ((inode->i_mode & S_ISVTX) ? ICBTAG_FLAG_STICKY : 0) |
+ (le16_to_cpu(fe->icbTag.flags) &
+ ~(ICBTAG_FLAG_AD_MASK | ICBTAG_FLAG_SETUID |
+ ICBTAG_FLAG_SETGID | ICBTAG_FLAG_STICKY));
+
+ fe->icbTag.flags = cpu_to_le16(icbflags);
+ if (sbi->s_udfrev >= 0x0200)
+ fe->descTag.descVersion = cpu_to_le16(3);
+ else
+ fe->descTag.descVersion = cpu_to_le16(2);
+ fe->descTag.tagSerialNum = cpu_to_le16(sbi->s_serial_number);
+ fe->descTag.tagLocation = cpu_to_le32(
+ iinfo->i_location.logicalBlockNum);
+ crclen += iinfo->i_lenEAttr + iinfo->i_lenAlloc - sizeof(struct tag);
+ fe->descTag.descCRCLength = cpu_to_le16(crclen);
+ fe->descTag.descCRC = cpu_to_le16(crc_itu_t(0, (char *)fe + sizeof(struct tag),
+ crclen));
+ fe->descTag.tagChecksum = udf_tag_checksum(&fe->descTag);
+
+ set_buffer_uptodate(bh);
+ unlock_buffer(bh);
+
+ /* write the data blocks */
+ mark_buffer_dirty(bh);
+ if (do_sync) {
+ sync_dirty_buffer(bh);
+ if (buffer_write_io_error(bh)) {
+ udf_warn(inode->i_sb, "IO error syncing udf inode [%08lx]\n",
+ inode->i_ino);
+ err = -EIO;
+ }
+ }
+ brelse(bh);
+
+ return err;
+}
+
+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 ERR_PTR(-ENOMEM);
+
+ if (!(inode->i_state & I_NEW)) {
+ if (UDF_I(inode)->i_hidden != hidden_inode) {
+ iput(inode);
+ return ERR_PTR(-EFSCORRUPTED);
+ }
+ return inode;
+ }
+
+ 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;
+}
+
+int udf_setup_indirect_aext(struct inode *inode, udf_pblk_t block,
+ struct extent_position *epos)
+{
+ struct super_block *sb = inode->i_sb;
+ struct buffer_head *bh;
+ struct allocExtDesc *aed;
+ struct extent_position nepos;
+ struct kernel_lb_addr neloc;
+ int ver, adsize;
+
+ 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;
+
+ 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;
+ int cp_type;
+
+ epos->offset -= adsize;
+ cp_type = udf_current_aext(inode, epos, &cp_loc, &cp_len, 0);
+ 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;
+}
+
+/*
+ * 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);
+ else if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_LONG)
+ adsize = sizeof(struct long_ad);
+ else
+ return -EIO;
+
+ 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);
+
+ if (!epos->bh) {
+ iinfo->i_lenAlloc += adsize;
+ mark_inode_dirty(inode);
+ } else {
+ aed = (struct allocExtDesc *)epos->bh->b_data;
+ le32_add_cpu(&aed->lengthAllocDescs, adsize);
+ 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 + (inc ? 0 : adsize));
+ else
+ udf_update_tag(epos->bh->b_data,
+ sizeof(struct allocExtDesc));
+ mark_buffer_dirty_inode(epos->bh, inode);
+ }
+
+ 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)
+{
+ int adsize;
+ uint8_t *ptr;
+ struct short_ad *sad;
+ struct long_ad *lad;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ if (!epos->bh)
+ ptr = iinfo->i_data + epos->offset -
+ udf_file_entry_alloc_offset(inode) +
+ iinfo->i_lenEAttr;
+ else
+ ptr = epos->bh->b_data + epos->offset;
+
+ switch (iinfo->i_alloc_type) {
+ case ICBTAG_FLAG_AD_SHORT:
+ sad = (struct short_ad *)ptr;
+ sad->extLength = cpu_to_le32(elen);
+ sad->extPosition = cpu_to_le32(eloc->logicalBlockNum);
+ adsize = sizeof(struct short_ad);
+ break;
+ case ICBTAG_FLAG_AD_LONG:
+ lad = (struct long_ad *)ptr;
+ lad->extLength = cpu_to_le32(elen);
+ lad->extLocation = cpu_to_lelb(*eloc);
+ memset(lad->impUse, 0x00, sizeof(lad->impUse));
+ adsize = sizeof(struct long_ad);
+ break;
+ default:
+ return;
+ }
+
+ if (epos->bh) {
+ if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT) ||
+ UDF_SB(inode->i_sb)->s_udfrev >= 0x0201) {
+ struct allocExtDesc *aed =
+ (struct allocExtDesc *)epos->bh->b_data;
+ udf_update_tag(epos->bh->b_data,
+ le32_to_cpu(aed->lengthAllocDescs) +
+ sizeof(struct allocExtDesc));
+ }
+ mark_buffer_dirty_inode(epos->bh, inode);
+ } else {
+ mark_inode_dirty(inode);
+ }
+
+ if (inc)
+ epos->offset += adsize;
+}
+
+/*
+ * 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
+
+int8_t udf_next_aext(struct inode *inode, struct extent_position *epos,
+ struct kernel_lb_addr *eloc, uint32_t *elen, int inc)
+{
+ int8_t etype;
+ unsigned int indirections = 0;
+
+ while ((etype = udf_current_aext(inode, epos, eloc, elen, inc)) ==
+ (EXT_NEXT_EXTENT_ALLOCDESCS >> 30)) {
+ udf_pblk_t block;
+
+ if (++indirections > UDF_MAX_INDIR_EXTS) {
+ udf_err(inode->i_sb,
+ "too many indirect extents in inode %lu\n",
+ inode->i_ino);
+ return -1;
+ }
+
+ epos->block = *eloc;
+ epos->offset = sizeof(struct allocExtDesc);
+ brelse(epos->bh);
+ block = udf_get_lb_pblock(inode->i_sb, &epos->block, 0);
+ epos->bh = sb_bread(inode->i_sb, block);
+ if (!epos->bh) {
+ udf_debug("reading block %u failed!\n", block);
+ return -1;
+ }
+ }
+
+ return etype;
+}
+
+int8_t udf_current_aext(struct inode *inode, struct extent_position *epos,
+ struct kernel_lb_addr *eloc, uint32_t *elen, int inc)
+{
+ int alen;
+ int8_t etype;
+ uint8_t *ptr;
+ struct short_ad *sad;
+ struct long_ad *lad;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ if (!epos->bh) {
+ if (!epos->offset)
+ epos->offset = udf_file_entry_alloc_offset(inode);
+ 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 {
+ 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);
+ }
+
+ 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;
+ eloc->logicalBlockNum = le32_to_cpu(sad->extPosition);
+ eloc->partitionReferenceNum =
+ iinfo->i_location.partitionReferenceNum;
+ *elen = le32_to_cpu(sad->extLength) & UDF_EXTENT_LENGTH_MASK;
+ break;
+ 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;
+ *eloc = lelb_to_cpu(lad->extLocation);
+ *elen = le32_to_cpu(lad->extLength) & UDF_EXTENT_LENGTH_MASK;
+ break;
+ default:
+ udf_debug("alloc_type = %u unsupported\n", iinfo->i_alloc_type);
+ return -1;
+ }
+
+ return etype;
+}
+
+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 err;
+
+ if (epos.bh)
+ get_bh(epos.bh);
+
+ while ((etype = udf_next_aext(inode, &epos, &oeloc, &oelen, 0)) != -1) {
+ udf_write_aext(inode, &epos, &neloc, nelen, 1);
+ neloc = oeloc;
+ nelen = (etype << 30) | oelen;
+ }
+ err = udf_add_aext(inode, &epos, &neloc, nelen, 1);
+ brelse(epos.bh);
+
+ return err;
+}
+
+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;
+
+ if (epos.bh) {
+ get_bh(epos.bh);
+ get_bh(epos.bh);
+ }
+
+ iinfo = UDF_I(inode);
+ 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;
+
+ oepos = epos;
+ if (udf_next_aext(inode, &epos, &eloc, &elen, 1) == -1)
+ return -1;
+
+ while ((etype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1) {
+ udf_write_aext(inode, &oepos, &eloc, (etype << 30) | elen, 1);
+ if (oepos.bh != epos.bh) {
+ oepos.block = epos.block;
+ brelse(oepos.bh);
+ get_bh(epos.bh);
+ oepos.bh = epos.bh;
+ oepos.offset = epos.offset - adsize;
+ }
+ }
+ memset(&eloc, 0x00, sizeof(struct kernel_lb_addr));
+ elen = 0;
+
+ if (epos.bh != oepos.bh) {
+ udf_free_blocks(inode->i_sb, inode, &epos.block, 0, 1);
+ udf_write_aext(inode, &oepos, &eloc, elen, 1);
+ udf_write_aext(inode, &oepos, &eloc, elen, 1);
+ if (!oepos.bh) {
+ iinfo->i_lenAlloc -= (adsize * 2);
+ mark_inode_dirty(inode);
+ } else {
+ aed = (struct allocExtDesc *)oepos.bh->b_data;
+ le32_add_cpu(&aed->lengthAllocDescs, -(2 * adsize));
+ if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT) ||
+ UDF_SB(inode->i_sb)->s_udfrev >= 0x0201)
+ udf_update_tag(oepos.bh->b_data,
+ oepos.offset - (2 * adsize));
+ else
+ udf_update_tag(oepos.bh->b_data,
+ sizeof(struct allocExtDesc));
+ mark_buffer_dirty_inode(oepos.bh, inode);
+ }
+ } else {
+ udf_write_aext(inode, &oepos, &eloc, elen, 1);
+ if (!oepos.bh) {
+ iinfo->i_lenAlloc -= adsize;
+ mark_inode_dirty(inode);
+ } else {
+ aed = (struct allocExtDesc *)oepos.bh->b_data;
+ le32_add_cpu(&aed->lengthAllocDescs, -adsize);
+ if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT) ||
+ UDF_SB(inode->i_sb)->s_udfrev >= 0x0201)
+ udf_update_tag(oepos.bh->b_data,
+ epos.offset - adsize);
+ else
+ udf_update_tag(oepos.bh->b_data,
+ sizeof(struct allocExtDesc));
+ mark_buffer_dirty_inode(oepos.bh, inode);
+ }
+ }
+
+ brelse(epos.bh);
+ brelse(oepos.bh);
+
+ 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)
+{
+ unsigned char blocksize_bits = inode->i_sb->s_blocksize_bits;
+ loff_t lbcount = 0, bcount = (loff_t) block << blocksize_bits;
+ int8_t etype;
+ struct udf_inode_info *iinfo;
+
+ iinfo = UDF_I(inode);
+ if (!udf_read_extent_cache(inode, bcount, &lbcount, pos)) {
+ pos->offset = 0;
+ pos->block = iinfo->i_location;
+ pos->bh = NULL;
+ }
+ *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;
+ }
+ lbcount += *elen;
+ } while (lbcount <= bcount);
+ /* update extent cache */
+ udf_update_extent_cache(inode, lbcount - *elen, pos);
+ *offset = (bcount + *elen - lbcount) >> blocksize_bits;
+
+ return etype;
+}
diff --git a/fs/udf/lowlevel.c b/fs/udf/lowlevel.c
new file mode 100644
index 0000000000..9d847a7a09
--- /dev/null
+++ b/fs/udf/lowlevel.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * lowlevel.c
+ *
+ * PURPOSE
+ * Low Level Device Routines for the UDF filesystem
+ *
+ * COPYRIGHT
+ * (C) 1999-2001 Ben Fennema
+ *
+ * HISTORY
+ *
+ * 03/26/99 blf Created.
+ */
+
+#include "udfdecl.h"
+
+#include <linux/blkdev.h>
+#include <linux/cdrom.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;
+
+ if (!cdi) {
+ udf_debug("CDROMMULTISESSION not supported.\n");
+ return 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 */
+ return ms_info.addr.lba;
+ }
+ return 0;
+}
+
+udf_pblk_t udf_get_last_block(struct super_block *sb)
+{
+ struct cdrom_device_info *cdi = disk_to_cdi(sb->s_bdev->bd_disk);
+ unsigned long lblock = 0;
+
+ /*
+ * The cdrom layer call failed or returned obviously bogus value?
+ * Try using the device size...
+ */
+ 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;
+ return 0;
+}
diff --git a/fs/udf/misc.c b/fs/udf/misc.c
new file mode 100644
index 0000000000..0788593b6a
--- /dev/null
+++ b/fs/udf/misc.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * misc.c
+ *
+ * PURPOSE
+ * Miscellaneous routines for the OSTA-UDF(tm) filesystem.
+ *
+ * COPYRIGHT
+ * (C) 1998 Dave Boynton
+ * (C) 1998-2004 Ben Fennema
+ * (C) 1999-2000 Stelias Computing Inc
+ *
+ * HISTORY
+ *
+ * 04/19/99 blf partial support for reading/writing specific EA's
+ */
+
+#include "udfdecl.h"
+
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/crc-itu-t.h>
+
+#include "udf_i.h"
+#include "udf_sb.h"
+
+struct genericFormat *udf_add_extendedattr(struct inode *inode, uint32_t size,
+ uint32_t type, uint8_t loc)
+{
+ uint8_t *ea = NULL, *ad = NULL;
+ int offset;
+ uint16_t crclen;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ ea = iinfo->i_data;
+ if (iinfo->i_lenEAttr) {
+ ad = iinfo->i_data + iinfo->i_lenEAttr;
+ } else {
+ ad = ea;
+ size += sizeof(struct extendedAttrHeaderDesc);
+ }
+
+ offset = inode->i_sb->s_blocksize - udf_file_entry_alloc_offset(inode) -
+ iinfo->i_lenAlloc;
+
+ /* TODO - Check for FreeEASpace */
+
+ if (loc & 0x01 && offset >= size) {
+ struct extendedAttrHeaderDesc *eahd;
+ eahd = (struct extendedAttrHeaderDesc *)ea;
+
+ if (iinfo->i_lenAlloc)
+ memmove(&ad[size], ad, iinfo->i_lenAlloc);
+
+ if (iinfo->i_lenEAttr) {
+ /* check checksum/crc */
+ if (eahd->descTag.tagIdent !=
+ cpu_to_le16(TAG_IDENT_EAHD) ||
+ le32_to_cpu(eahd->descTag.tagLocation) !=
+ iinfo->i_location.logicalBlockNum)
+ return NULL;
+ } else {
+ struct udf_sb_info *sbi = UDF_SB(inode->i_sb);
+
+ size -= sizeof(struct extendedAttrHeaderDesc);
+ iinfo->i_lenEAttr +=
+ sizeof(struct extendedAttrHeaderDesc);
+ eahd->descTag.tagIdent = cpu_to_le16(TAG_IDENT_EAHD);
+ if (sbi->s_udfrev >= 0x0200)
+ eahd->descTag.descVersion = cpu_to_le16(3);
+ else
+ eahd->descTag.descVersion = cpu_to_le16(2);
+ eahd->descTag.tagSerialNum =
+ cpu_to_le16(sbi->s_serial_number);
+ eahd->descTag.tagLocation = cpu_to_le32(
+ iinfo->i_location.logicalBlockNum);
+ eahd->impAttrLocation = cpu_to_le32(0xFFFFFFFF);
+ eahd->appAttrLocation = cpu_to_le32(0xFFFFFFFF);
+ }
+
+ offset = iinfo->i_lenEAttr;
+ if (type < 2048) {
+ if (le32_to_cpu(eahd->appAttrLocation) <
+ iinfo->i_lenEAttr) {
+ uint32_t aal =
+ le32_to_cpu(eahd->appAttrLocation);
+ memmove(&ea[offset - aal + size],
+ &ea[aal], offset - aal);
+ offset -= aal;
+ eahd->appAttrLocation =
+ cpu_to_le32(aal + size);
+ }
+ if (le32_to_cpu(eahd->impAttrLocation) <
+ iinfo->i_lenEAttr) {
+ uint32_t ial =
+ le32_to_cpu(eahd->impAttrLocation);
+ memmove(&ea[offset - ial + size],
+ &ea[ial], offset - ial);
+ offset -= ial;
+ eahd->impAttrLocation =
+ cpu_to_le32(ial + size);
+ }
+ } else if (type < 65536) {
+ if (le32_to_cpu(eahd->appAttrLocation) <
+ iinfo->i_lenEAttr) {
+ uint32_t aal =
+ le32_to_cpu(eahd->appAttrLocation);
+ memmove(&ea[offset - aal + size],
+ &ea[aal], offset - aal);
+ offset -= aal;
+ eahd->appAttrLocation =
+ cpu_to_le32(aal + size);
+ }
+ }
+ /* rewrite CRC + checksum of eahd */
+ crclen = sizeof(struct extendedAttrHeaderDesc) - sizeof(struct tag);
+ eahd->descTag.descCRCLength = cpu_to_le16(crclen);
+ eahd->descTag.descCRC = cpu_to_le16(crc_itu_t(0, (char *)eahd +
+ sizeof(struct tag), crclen));
+ eahd->descTag.tagChecksum = udf_tag_checksum(&eahd->descTag);
+ iinfo->i_lenEAttr += size;
+ return (struct genericFormat *)&ea[offset];
+ }
+
+ return NULL;
+}
+
+struct genericFormat *udf_get_extendedattr(struct inode *inode, uint32_t type,
+ uint8_t subtype)
+{
+ struct genericFormat *gaf;
+ uint8_t *ea = NULL;
+ uint32_t offset;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ ea = iinfo->i_data;
+
+ if (iinfo->i_lenEAttr) {
+ struct extendedAttrHeaderDesc *eahd;
+ eahd = (struct extendedAttrHeaderDesc *)ea;
+
+ /* check checksum/crc */
+ if (eahd->descTag.tagIdent !=
+ cpu_to_le16(TAG_IDENT_EAHD) ||
+ le32_to_cpu(eahd->descTag.tagLocation) !=
+ iinfo->i_location.logicalBlockNum)
+ return NULL;
+
+ if (type < 2048)
+ offset = sizeof(struct extendedAttrHeaderDesc);
+ else if (type < 65536)
+ offset = le32_to_cpu(eahd->impAttrLocation);
+ else
+ offset = le32_to_cpu(eahd->appAttrLocation);
+
+ 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 += attrLength;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * udf_read_tagged
+ *
+ * PURPOSE
+ * Read the first block of a tagged descriptor.
+ *
+ * HISTORY
+ * July 1, 1997 - Andrew E. Mileski
+ * Written, tested, and released.
+ */
+struct buffer_head *udf_read_tagged(struct super_block *sb, uint32_t block,
+ uint32_t location, uint16_t *ident)
+{
+ struct tag *tag_p;
+ struct buffer_head *bh = NULL;
+ u8 checksum;
+
+ /* Read the block */
+ if (block == 0xFFFFFFFF)
+ return NULL;
+
+ bh = sb_bread(sb, block);
+ if (!bh) {
+ udf_err(sb, "read failed, block=%u, location=%u\n",
+ block, location);
+ return NULL;
+ }
+
+ tag_p = (struct tag *)(bh->b_data);
+
+ *ident = le16_to_cpu(tag_p->tagIdent);
+
+ if (location != le32_to_cpu(tag_p->tagLocation)) {
+ udf_debug("location mismatch block %u, tag %u != %u\n",
+ block, le32_to_cpu(tag_p->tagLocation), location);
+ goto error_out;
+ }
+
+ /* Verify the tag checksum */
+ checksum = udf_tag_checksum(tag_p);
+ if (checksum != tag_p->tagChecksum) {
+ udf_err(sb, "tag checksum failed, block %u: 0x%02x != 0x%02x\n",
+ block, checksum, tag_p->tagChecksum);
+ goto error_out;
+ }
+
+ /* Verify the tag version */
+ if (tag_p->descVersion != cpu_to_le16(0x0002U) &&
+ tag_p->descVersion != cpu_to_le16(0x0003U)) {
+ udf_err(sb, "tag version 0x%04x != 0x0002 || 0x0003, block %u\n",
+ le16_to_cpu(tag_p->descVersion), block);
+ goto error_out;
+ }
+
+ /* Verify the descriptor CRC */
+ if (le16_to_cpu(tag_p->descCRCLength) + sizeof(struct tag) > sb->s_blocksize ||
+ le16_to_cpu(tag_p->descCRC) == crc_itu_t(0,
+ bh->b_data + sizeof(struct tag),
+ le16_to_cpu(tag_p->descCRCLength)))
+ return bh;
+
+ 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:
+ brelse(bh);
+ return NULL;
+}
+
+struct buffer_head *udf_read_ptagged(struct super_block *sb,
+ struct kernel_lb_addr *loc,
+ uint32_t offset, uint16_t *ident)
+{
+ return udf_read_tagged(sb, udf_get_lb_pblock(sb, loc, offset),
+ loc->logicalBlockNum + offset, ident);
+}
+
+void udf_update_tag(char *data, int length)
+{
+ struct tag *tptr = (struct tag *)data;
+ length -= sizeof(struct tag);
+
+ tptr->descCRCLength = cpu_to_le16(length);
+ tptr->descCRC = cpu_to_le16(crc_itu_t(0, data + sizeof(struct tag), length));
+ tptr->tagChecksum = udf_tag_checksum(tptr);
+}
+
+void udf_new_tag(char *data, uint16_t ident, uint16_t version, uint16_t snum,
+ uint32_t loc, int length)
+{
+ struct tag *tptr = (struct tag *)data;
+ tptr->tagIdent = cpu_to_le16(ident);
+ tptr->descVersion = cpu_to_le16(version);
+ tptr->tagSerialNum = cpu_to_le16(snum);
+ tptr->tagLocation = cpu_to_le32(loc);
+ udf_update_tag(data, length);
+}
+
+u8 udf_tag_checksum(const struct tag *t)
+{
+ u8 *data = (u8 *)t;
+ u8 checksum = 0;
+ int i;
+ for (i = 0; i < sizeof(struct tag); ++i)
+ if (i != 4) /* position of checksum */
+ checksum += data[i];
+ return checksum;
+}
diff --git a/fs/udf/namei.c b/fs/udf/namei.c
new file mode 100644
index 0000000000..ae55ab8859
--- /dev/null
+++ b/fs/udf/namei.c
@@ -0,0 +1,1017 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * namei.c
+ *
+ * PURPOSE
+ * Inode name handling routines for the OSTA-UDF(tm) filesystem.
+ *
+ * COPYRIGHT
+ * (C) 1998-2004 Ben Fennema
+ * (C) 1999-2000 Stelias Computing Inc
+ *
+ * HISTORY
+ *
+ * 12/12/98 blf Created. Split out the lookup code from dir.c
+ * 04/19/99 blf link, mknod, symlink support
+ */
+
+#include "udfdecl.h"
+
+#include "udf_i.h"
+#include "udf_sb.h"
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/slab.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)
+{
+ if (len1 != len2)
+ return 0;
+
+ return !memcmp(name1, name2, len1);
+}
+
+/**
+ * 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)
+{
+ int flen;
+ unsigned char *fname = NULL;
+ struct super_block *sb = dir->i_sb;
+ int isdotdot = child->len == 2 &&
+ child->name[0] == '.' && child->name[1] == '.';
+ int ret;
+
+ fname = kmalloc(UDF_NAME_LEN, GFP_NOFS);
+ if (!fname)
+ return -ENOMEM;
+
+ 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 (iter->fi.fileCharacteristics & FID_FILE_CHAR_HIDDEN) {
+ if (!UDF_QUERY_FLAG(sb, UDF_FLAG_UNHIDE))
+ continue;
+ }
+
+ if ((iter->fi.fileCharacteristics & FID_FILE_CHAR_PARENT) &&
+ isdotdot)
+ goto out_ok;
+
+ if (!iter->fi.lengthFileIdent)
+ continue;
+
+ 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:
+ udf_fiiter_release(iter);
+out_ok:
+ kfree(fname);
+
+ return ret;
+}
+
+static struct dentry *udf_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ struct inode *inode = NULL;
+ struct udf_fileident_iter iter;
+ int err;
+
+ if (dentry->d_name.len > UDF_NAME_LEN)
+ return ERR_PTR(-ENAMETOOLONG);
+
+ err = udf_fiiter_find_entry(dir, &dentry->d_name, &iter);
+ if (err < 0 && err != -ENOENT)
+ return ERR_PTR(err);
+
+ if (err == 0) {
+ struct kernel_lb_addr loc;
+
+ loc = lelb_to_cpu(iter.fi.icb.extLocation);
+ udf_fiiter_release(&iter);
+
+ inode = udf_iget(dir->i_sb, &loc);
+ if (IS_ERR(inode))
+ return ERR_CAST(inode);
+ }
+
+ return d_splice_alias(inode, dentry);
+}
+
+static int udf_expand_dir_adinicb(struct inode *inode, udf_pblk_t *block)
+{
+ udf_pblk_t newblock;
+ struct buffer_head *dbh = NULL;
+ struct kernel_lb_addr eloc;
+ 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;
+
+ if (!inode->i_size) {
+ iinfo->i_alloc_type = alloctype;
+ mark_inode_dirty(inode);
+ return 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);
+
+ /* 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);
+
+ return 0;
+}
+
+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];
+
+ if (dentry) {
+ if (!dentry->d_name.len)
+ return -EINVAL;
+ 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;
+ }
+ }
+ }
+ if (ret) {
+ udf_fiiter_release(iter);
+ return ret;
+ }
+ if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB &&
+ 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;
+ }
+
+ /* Get blocknumber to use for entry tag */
+ if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
+ block = dinfo->i_location.logicalBlockNum;
+ } else {
+ 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;
+
+ ret = udf_fiiter_append_blk(iter);
+ if (ret) {
+ udf_fiiter_release(iter);
+ return ret;
+ }
+
+ /* 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 *)(&iter->fi), TAG_IDENT_FID, 2, 1, block,
+ sizeof(struct tag));
+ 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 {
+ /* Truncate last extent to proper size */
+ udf_fiiter_update_elen(iter, iter->elen -
+ (dinfo->i_lenExtents - dir->i_size));
+ }
+ mark_inode_dirty(dir);
+
+ return 0;
+}
+
+static void udf_fiiter_delete_entry(struct udf_fileident_iter *iter)
+{
+ iter->fi.fileCharacteristics |= FID_FILE_CHAR_DELETED;
+
+ if (UDF_QUERY_FLAG(iter->dir->i_sb, UDF_FLAG_STRICT))
+ memset(&iter->fi.icb, 0x00, sizeof(struct long_ad));
+
+ udf_fiiter_write_fi(iter, NULL);
+}
+
+static void udf_add_fid_counter(struct super_block *sb, bool dir, int val)
+{
+ struct logicalVolIntegrityDescImpUse *lvidiu = udf_sb_lvidiu(sb);
+
+ if (!lvidiu)
+ return;
+ mutex_lock(&UDF_SB(sb)->s_alloc_mutex);
+ if (dir)
+ le32_add_cpu(&lvidiu->numDirs, val);
+ else
+ 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;
+
+ err = udf_fiiter_add_entry(dir, dentry, &iter);
+ if (err) {
+ inode_dec_link_count(inode);
+ discard_new_inode(inode);
+ return err;
+ }
+ 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_fiiter_write_fi(&iter, NULL);
+ dir->i_mtime = 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_create(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, umode_t mode, bool excl)
+{
+ 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);
+
+ 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 mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, umode_t mode, dev_t rdev)
+{
+ struct inode *inode;
+
+ if (!old_valid_dev(rdev))
+ return -EINVAL;
+
+ inode = udf_new_inode(dir, mode);
+ if (IS_ERR(inode))
+ return PTR_ERR(inode);
+
+ init_special_inode(inode, mode, rdev);
+ return udf_add_nondir(dentry, inode);
+}
+
+static int udf_mkdir(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
+{
+ struct inode *inode;
+ struct udf_fileident_iter iter;
+ int err;
+ struct udf_inode_info *dinfo = UDF_I(dir);
+ struct udf_inode_info *iinfo;
+
+ inode = udf_new_inode(dir, S_IFDIR | mode);
+ if (IS_ERR(inode))
+ return PTR_ERR(inode);
+
+ iinfo = UDF_I(inode);
+ inode->i_op = &udf_dir_inode_operations;
+ inode->i_fop = &udf_dir_operations;
+ err = udf_fiiter_add_entry(inode, NULL, &iter);
+ if (err) {
+ clear_nlink(inode);
+ discard_new_inode(inode);
+ return err;
+ }
+ set_nlink(inode, 2);
+ 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);
+ iter.fi.fileCharacteristics =
+ FID_FILE_CHAR_DIRECTORY | FID_FILE_CHAR_PARENT;
+ udf_fiiter_write_fi(&iter, NULL);
+ udf_fiiter_release(&iter);
+ mark_inode_dirty(inode);
+
+ err = udf_fiiter_add_entry(dir, dentry, &iter);
+ if (err) {
+ clear_nlink(inode);
+ discard_new_inode(inode);
+ return err;
+ }
+ 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);
+ 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);
+ dir->i_mtime = inode_set_ctime_current(dir);
+ mark_inode_dirty(dir);
+ d_instantiate_new(dentry, inode);
+
+ return 0;
+}
+
+static int empty_dir(struct inode *dir)
+{
+ 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;
+ }
+ }
+ udf_fiiter_release(&iter);
+
+ return 1;
+}
+
+static int udf_rmdir(struct inode *dir, struct dentry *dentry)
+{
+ int ret;
+ struct inode *inode = d_inode(dentry);
+ struct udf_fileident_iter iter;
+ struct kernel_lb_addr tloc;
+
+ ret = udf_fiiter_find_entry(dir, &dentry->d_name, &iter);
+ if (ret)
+ goto out;
+
+ 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;
+ ret = -ENOTEMPTY;
+ if (!empty_dir(inode))
+ goto end_rmdir;
+ udf_fiiter_delete_entry(&iter);
+ if (inode->i_nlink != 2)
+ 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);
+ udf_add_fid_counter(dir->i_sb, true, -1);
+ dir->i_mtime = inode_set_ctime_to_ts(dir,
+ inode_set_ctime_current(inode));
+ mark_inode_dirty(dir);
+ ret = 0;
+end_rmdir:
+ udf_fiiter_release(&iter);
+out:
+ return ret;
+}
+
+static int udf_unlink(struct inode *dir, struct dentry *dentry)
+{
+ int ret;
+ struct inode *inode = d_inode(dentry);
+ struct udf_fileident_iter iter;
+ struct kernel_lb_addr tloc;
+
+ ret = udf_fiiter_find_entry(dir, &dentry->d_name, &iter);
+ if (ret)
+ goto out;
+
+ 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), %u\n",
+ inode->i_ino, inode->i_nlink);
+ set_nlink(inode, 1);
+ }
+ udf_fiiter_delete_entry(&iter);
+ dir->i_mtime = inode_set_ctime_current(dir);
+ mark_inode_dirty(dir);
+ inode_dec_link_count(inode);
+ udf_add_fid_counter(dir->i_sb, false, -1);
+ inode_set_ctime_to_ts(inode, inode_get_ctime(dir));
+ ret = 0;
+end_unlink:
+ udf_fiiter_release(&iter);
+out:
+ return ret;
+}
+
+static int udf_symlink(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, const char *symname)
+{
+ struct inode *inode = udf_new_inode(dir, S_IFLNK | 0777);
+ struct pathComponent *pc;
+ const char *compstart;
+ struct extent_position epos = {};
+ int eoffset, elen = 0;
+ uint8_t *ea;
+ int err;
+ udf_pblk_t block;
+ unsigned char *name = NULL;
+ int namelen;
+ struct udf_inode_info *iinfo;
+ struct super_block *sb = dir->i_sb;
+
+ if (IS_ERR(inode))
+ return PTR_ERR(inode);
+
+ iinfo = UDF_I(inode);
+ down_write(&iinfo->i_data_sem);
+ name = kmalloc(UDF_NAME_LEN_CS0, GFP_NOFS);
+ if (!name) {
+ err = -ENOMEM;
+ goto out_no_entry;
+ }
+
+ 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;
+ uint32_t bsize;
+
+ block = udf_new_block(sb, inode,
+ iinfo->i_location.partitionReferenceNum,
+ iinfo->i_location.logicalBlockNum, &err);
+ if (!block)
+ goto out_no_entry;
+ epos.block = iinfo->i_location;
+ epos.offset = udf_file_entry_alloc_offset(inode);
+ epos.bh = NULL;
+ eloc.logicalBlockNum = block;
+ eloc.partitionReferenceNum =
+ iinfo->i_location.partitionReferenceNum;
+ bsize = sb->s_blocksize;
+ iinfo->i_lenExtents = bsize;
+ 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 = 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);
+ unlock_buffer(epos.bh);
+ mark_buffer_dirty_inode(epos.bh, inode);
+ ea = epos.bh->b_data + udf_ext0_offset(inode);
+ } else
+ ea = iinfo->i_data + iinfo->i_lenEAttr;
+
+ eoffset = sb->s_blocksize - udf_ext0_offset(inode);
+ pc = (struct pathComponent *)ea;
+
+ if (*symname == '/') {
+ do {
+ symname++;
+ } while (*symname == '/');
+
+ pc->componentType = 1;
+ pc->lengthComponentIdent = 0;
+ pc->componentFileVersionNum = 0;
+ elen += sizeof(struct pathComponent);
+ }
+
+ err = -ENAMETOOLONG;
+
+ while (*symname) {
+ if (elen + sizeof(struct pathComponent) > eoffset)
+ goto out_no_entry;
+
+ pc = (struct pathComponent *)(ea + elen);
+
+ compstart = symname;
+
+ do {
+ symname++;
+ } while (*symname && *symname != '/');
+
+ pc->componentType = 5;
+ pc->lengthComponentIdent = 0;
+ pc->componentFileVersionNum = 0;
+ if (compstart[0] == '.') {
+ if ((symname - compstart) == 1)
+ pc->componentType = 4;
+ else if ((symname - compstart) == 2 &&
+ compstart[1] == '.')
+ pc->componentType = 3;
+ }
+
+ if (pc->componentType == 5) {
+ namelen = udf_put_filename(sb, compstart,
+ symname - compstart,
+ name, UDF_NAME_LEN_CS0);
+ if (!namelen)
+ goto out_no_entry;
+
+ if (elen + sizeof(struct pathComponent) + namelen >
+ eoffset)
+ goto out_no_entry;
+ else
+ pc->lengthComponentIdent = namelen;
+
+ memcpy(pc->componentIdent, name, namelen);
+ }
+
+ elen += sizeof(struct pathComponent) + pc->lengthComponentIdent;
+
+ if (*symname) {
+ do {
+ symname++;
+ } while (*symname == '/');
+ }
+ }
+
+ brelse(epos.bh);
+ inode->i_size = elen;
+ if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB)
+ iinfo->i_lenAlloc = inode->i_size;
+ else
+ udf_truncate_tail_extent(inode);
+ mark_inode_dirty(inode);
+ up_write(&iinfo->i_data_sem);
+
+ err = udf_add_nondir(dentry, inode);
+out:
+ kfree(name);
+ return err;
+
+out_no_entry:
+ up_write(&iinfo->i_data_sem);
+ inode_dec_link_count(inode);
+ discard_new_inode(inode);
+ goto out;
+}
+
+static int udf_link(struct dentry *old_dentry, struct inode *dir,
+ struct dentry *dentry)
+{
+ struct inode *inode = d_inode(old_dentry);
+ struct udf_fileident_iter iter;
+ int err;
+
+ err = udf_fiiter_add_entry(dir, dentry, &iter);
+ if (err)
+ return err;
+ 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 *)iter.fi.icb.impUse)->impUse =
+ cpu_to_le32(lvid_get_unique_id(inode->i_sb));
+ }
+ udf_fiiter_write_fi(&iter, NULL);
+ udf_fiiter_release(&iter);
+
+ inc_nlink(inode);
+ udf_add_fid_counter(dir->i_sb, false, 1);
+ inode_set_ctime_current(inode);
+ mark_inode_dirty(inode);
+ dir->i_mtime = inode_set_ctime_current(dir);
+ mark_inode_dirty(dir);
+ ihold(inode);
+ d_instantiate(dentry, inode);
+
+ return 0;
+}
+
+/* Anybody can rename anything with this: the permission checks are left to the
+ * higher-level routines.
+ */
+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 = d_inode(old_dentry);
+ struct inode *new_inode = d_inode(new_dentry);
+ struct udf_fileident_iter oiter, niter, diriter;
+ bool has_diriter = false;
+ int retval;
+ struct kernel_lb_addr tloc;
+
+ 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)) {
+ if (new_inode) {
+ retval = -ENOTEMPTY;
+ if (!empty_dir(new_inode))
+ goto out_oiter;
+ }
+ 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 (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) {
+ 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;
+ }
+ /* 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.
+ */
+ inode_set_ctime_current(old_inode);
+ mark_inode_dirty(old_inode);
+
+ /*
+ * ok, that's it
+ */
+ 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 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) {
+ 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_mtime = inode_set_ctime_current(old_dir);
+ new_dir->i_mtime = inode_set_ctime_current(new_dir);
+ mark_inode_dirty(old_dir);
+ mark_inode_dirty(new_dir);
+
+ if (has_diriter) {
+ diriter.fi.icb.extLocation =
+ cpu_to_lelb(UDF_I(new_dir)->i_location);
+ udf_update_tag((char *)&diriter.fi,
+ udf_dir_entry_len(&diriter.fi));
+ udf_fiiter_write_fi(&diriter, NULL);
+ udf_fiiter_release(&diriter);
+
+ inode_dec_link_count(old_dir);
+ if (new_inode)
+ inode_dec_link_count(new_inode);
+ else {
+ inc_nlink(new_dir);
+ mark_inode_dirty(new_dir);
+ }
+ }
+ return 0;
+out_oiter:
+ if (has_diriter)
+ udf_fiiter_release(&diriter);
+ udf_fiiter_release(&oiter);
+
+ return retval;
+}
+
+static struct dentry *udf_get_parent(struct dentry *child)
+{
+ struct kernel_lb_addr tloc;
+ struct inode *inode = NULL;
+ struct udf_fileident_iter iter;
+ int err;
+
+ err = udf_fiiter_find_entry(d_inode(child), &dotdot_name, &iter);
+ if (err)
+ return ERR_PTR(err);
+
+ tloc = lelb_to_cpu(iter.fi.icb.extLocation);
+ udf_fiiter_release(&iter);
+ inode = udf_iget(child->d_sb, &tloc);
+ if (IS_ERR(inode))
+ return ERR_CAST(inode);
+
+ return d_obtain_alias(inode);
+}
+
+
+static struct dentry *udf_nfs_get_inode(struct super_block *sb, u32 block,
+ u16 partref, __u32 generation)
+{
+ struct inode *inode;
+ struct kernel_lb_addr loc;
+
+ if (block == 0)
+ return ERR_PTR(-ESTALE);
+
+ loc.logicalBlockNum = block;
+ loc.partitionReferenceNum = partref;
+ inode = udf_iget(sb, &loc);
+
+ if (IS_ERR(inode))
+ return ERR_CAST(inode);
+
+ if (generation && inode->i_generation != generation) {
+ iput(inode);
+ return ERR_PTR(-ESTALE);
+ }
+ return d_obtain_alias(inode);
+}
+
+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_type != FILEID_UDF_WITH_PARENT &&
+ fh_type != FILEID_UDF_WITHOUT_PARENT))
+ return NULL;
+
+ return udf_nfs_get_inode(sb, fid->udf.block, fid->udf.partref,
+ fid->udf.generation);
+}
+
+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)
+ return NULL;
+
+ return udf_nfs_get_inode(sb, fid->udf.parent_block,
+ fid->udf.parent_partref,
+ fid->udf.parent_generation);
+}
+static int udf_encode_fh(struct inode *inode, __u32 *fh, int *lenp,
+ struct inode *parent)
+{
+ int len = *lenp;
+ struct kernel_lb_addr location = UDF_I(inode)->i_location;
+ struct fid *fid = (struct fid *)fh;
+ int type = FILEID_UDF_WITHOUT_PARENT;
+
+ if (parent && (len < 5)) {
+ *lenp = 5;
+ return FILEID_INVALID;
+ } else if (len < 3) {
+ *lenp = 3;
+ return FILEID_INVALID;
+ }
+
+ *lenp = 3;
+ fid->udf.block = location.logicalBlockNum;
+ fid->udf.partref = location.partitionReferenceNum;
+ fid->udf.parent_partref = 0;
+ fid->udf.generation = inode->i_generation;
+
+ if (parent) {
+ location = UDF_I(parent)->i_location;
+ fid->udf.parent_block = location.logicalBlockNum;
+ fid->udf.parent_partref = location.partitionReferenceNum;
+ fid->udf.parent_generation = inode->i_generation;
+ *lenp = 5;
+ type = FILEID_UDF_WITH_PARENT;
+ }
+
+ return type;
+}
+
+const struct export_operations udf_export_ops = {
+ .encode_fh = udf_encode_fh,
+ .fh_to_dentry = udf_fh_to_dentry,
+ .fh_to_parent = udf_fh_to_parent,
+ .get_parent = udf_get_parent,
+};
+
+const struct inode_operations udf_dir_inode_operations = {
+ .lookup = udf_lookup,
+ .create = udf_create,
+ .link = udf_link,
+ .unlink = udf_unlink,
+ .symlink = udf_symlink,
+ .mkdir = udf_mkdir,
+ .rmdir = udf_rmdir,
+ .mknod = udf_mknod,
+ .rename = udf_rename,
+ .tmpfile = udf_tmpfile,
+};
diff --git a/fs/udf/osta_udf.h b/fs/udf/osta_udf.h
new file mode 100644
index 0000000000..157de0ec0c
--- /dev/null
+++ b/fs/udf/osta_udf.h
@@ -0,0 +1,305 @@
+/*
+ * osta_udf.h
+ *
+ * This file is based on OSTA UDF(tm) 2.60 (March 1, 2005)
+ * http://www.osta.org
+ *
+ * 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
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU Public License ("GPL").
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * 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.60 2.1.2) */
+#define UDF_CHAR_SET_TYPE 0
+#define UDF_CHAR_SET_INFO "OSTA Compressed Unicode"
+
+/* 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.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];
+} __packed;
+
+struct impIdentSuffix {
+ uint8_t OSClass;
+ uint8_t OSIdentifier;
+ uint8_t impUse[6];
+} __packed;
+
+struct appIdentSuffix {
+ uint8_t impUse[8];
+} __packed;
+
+/* 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;
+ __le32 numDirs;
+ __le16 minUDFReadRev;
+ __le16 minUDFWriteRev;
+ __le16 maxUDFWriteRev;
+ uint8_t impUse[];
+} __packed;
+
+/* 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];
+ dstring LVInfo1[36];
+ dstring LVInfo2[36];
+ dstring LVInfo3[36];
+ struct regid impIdent;
+ uint8_t impUse[128];
+} __packed;
+
+struct udfPartitionMap2 {
+ uint8_t partitionMapType;
+ uint8_t partitionMapLength;
+ uint8_t reserved1[2];
+ struct regid partIdent;
+ __le16 volSeqNum;
+ __le16 partitionNum;
+} __packed;
+
+/* Virtual Partition Map (UDF 2.60 2.2.8) */
+struct virtualPartitionMap {
+ uint8_t partitionMapType;
+ uint8_t partitionMapLength;
+ uint8_t reserved1[2];
+ struct regid partIdent;
+ __le16 volSeqNum;
+ __le16 partitionNum;
+ uint8_t reserved2[24];
+} __packed;
+
+/* Sparable Partition Map (UDF 2.60 2.2.9) */
+struct sparablePartitionMap {
+ uint8_t partitionMapType;
+ uint8_t partitionMapLength;
+ uint8_t reserved1[2];
+ struct regid partIdent;
+ __le16 volSeqNum;
+ __le16 partitionNum;
+ __le16 packetLength;
+ uint8_t numSparingTables;
+ uint8_t reserved2[1];
+ __le32 sizeSparingTable;
+ __le32 locSparingTable[4];
+} __packed;
+
+/* Metadata Partition Map (UDF 2.60 2.2.10) */
+struct metadataPartitionMap {
+ uint8_t partitionMapType;
+ uint8_t partitionMapLength;
+ uint8_t reserved1[2];
+ struct regid partIdent;
+ __le16 volSeqNum;
+ __le16 partitionNum;
+ __le32 metadataFileLoc;
+ __le32 metadataMirrorFileLoc;
+ __le32 metadataBitmapFileLoc;
+ __le32 allocUnitSize;
+ __le16 alignUnitSize;
+ uint8_t flags;
+ uint8_t reserved2[5];
+} __packed;
+
+/* Virtual Allocation Table (UDF 2.60 2.2.11) */
+struct virtualAllocationTable20 {
+ __le16 lengthHeader;
+ __le16 lengthImpUse;
+ dstring logicalVolIdent[128];
+ __le32 previousVATICBLoc;
+ __le32 numFiles;
+ __le32 numDirs;
+ __le16 minUDFReadRev;
+ __le16 minUDFWriteRev;
+ __le16 maxUDFWriteRev;
+ __le16 reserved;
+ uint8_t impUse[];
+ /* __le32 vatEntry[]; */
+} __packed;
+
+#define ICBTAG_FILE_TYPE_VAT20 0xF8U
+
+/* Sparing Table (UDF 2.60 2.2.12) */
+struct sparingEntry {
+ __le32 origLocation;
+ __le32 mappedLocation;
+} __packed;
+
+struct sparingTable {
+ struct tag descTag;
+ struct regid sparingIdent;
+ __le16 reallocationTableLen;
+ __le16 reserved;
+ __le32 sequenceNum;
+ struct sparingEntry mapEntry[];
+} __packed;
+
+/* 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 long_ad ICB - ADImpUse (UDF 2.60 2.2.4.3) */
+struct allocDescImpUse {
+ __le16 flags;
+ uint8_t impUse[4];
+} __packed;
+
+#define AD_IU_EXT_ERASED 0x0001
+
+/* Real-Time Files (UDF 2.60 6.11) */
+#define ICBTAG_FILE_TYPE_REALTIME 0xF9U
+
+/* 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[];
+} __packed;
+
+/* 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];
+} __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.60 3.3.4.6) */
+/* FreeAppEASpace (UDF 2.60 3.3.4.6.1) */
+struct freeAppEASpace {
+ __le16 headerChecksum;
+ uint8_t freeEASpace[];
+} __packed;
+
+/* 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"
+
+/* 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
+#define UDF_OS_CLASS_MAC 0x03U
+#define UDF_OS_CLASS_UNIX 0x04U
+#define UDF_OS_CLASS_WIN9X 0x05U
+#define UDF_OS_CLASS_WINNT 0x06U
+#define UDF_OS_CLASS_OS400 0x07U
+#define UDF_OS_CLASS_BEOS 0x08U
+#define UDF_OS_CLASS_WINCE 0x09U
+
+#define UDF_OS_ID_UNDEF 0x00U
+#define UDF_OS_ID_DOS 0x00U
+#define UDF_OS_ID_OS2 0x00U
+#define UDF_OS_ID_MAC 0x00U
+#define UDF_OS_ID_MAX_OSX 0x01U
+#define UDF_OS_ID_UNIX 0x00U
+#define UDF_OS_ID_AIX 0x01U
+#define UDF_OS_ID_SOLARIS 0x02U
+#define UDF_OS_ID_HPUX 0x03U
+#define UDF_OS_ID_IRIX 0x04U
+#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
+#define UDF_OS_ID_BEOS 0x00U
+#define UDF_OS_ID_WINCE 0x00U
+
+#endif /* _OSTA_UDF_H */
diff --git a/fs/udf/partition.c b/fs/udf/partition.c
new file mode 100644
index 0000000000..af877991ed
--- /dev/null
+++ b/fs/udf/partition.c
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * partition.c
+ *
+ * PURPOSE
+ * Partition handling routines for the OSTA-UDF(tm) filesystem.
+ *
+ * COPYRIGHT
+ * (C) 1998-2001 Ben Fennema
+ *
+ * HISTORY
+ *
+ * 12/06/98 blf Created file.
+ *
+ */
+
+#include "udfdecl.h"
+#include "udf_sb.h"
+#include "udf_i.h"
+
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/mutex.h>
+
+uint32_t udf_get_pblock(struct super_block *sb, uint32_t block,
+ uint16_t partition, uint32_t offset)
+{
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ struct udf_part_map *map;
+ if (partition >= sbi->s_partitions) {
+ udf_debug("block=%u, partition=%u, offset=%u: invalid partition\n",
+ block, partition, offset);
+ return 0xFFFFFFFF;
+ }
+ map = &sbi->s_partmaps[partition];
+ if (map->s_partition_func)
+ return map->s_partition_func(sb, block, partition, offset);
+ else
+ return map->s_partition_root + block + offset;
+}
+
+uint32_t udf_get_pblock_virt15(struct super_block *sb, uint32_t block,
+ uint16_t partition, uint32_t offset)
+{
+ struct buffer_head *bh = NULL;
+ uint32_t newblock;
+ uint32_t index;
+ uint32_t loc;
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ 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 (%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_data +
+ vdata->s_start_offset))[block]);
+ goto translate;
+ }
+ index = (sb->s_blocksize - vdata->s_start_offset) / sizeof(uint32_t);
+ if (block >= index) {
+ block -= index;
+ newblock = 1 + (block / (sb->s_blocksize / sizeof(uint32_t)));
+ index = block % (sb->s_blocksize / sizeof(uint32_t));
+ } else {
+ newblock = 0;
+ index = vdata->s_start_offset / sizeof(uint32_t) + block;
+ }
+
+ bh = udf_bread(sbi->s_vat_inode, newblock, 0, &err);
+ if (!bh) {
+ udf_debug("get_pblock(UDF_VIRTUAL_MAP:%p,%u,%u)\n",
+ sb, block, partition);
+ return 0xFFFFFFFF;
+ }
+
+ loc = le32_to_cpu(((__le32 *)bh->b_data)[index]);
+
+ brelse(bh);
+
+translate:
+ if (iinfo->i_location.partitionReferenceNum == partition) {
+ udf_debug("recursive call to udf_get_pblock!\n");
+ return 0xFFFFFFFF;
+ }
+
+ return udf_get_pblock(sb, loc,
+ iinfo->i_location.partitionReferenceNum,
+ offset);
+}
+
+inline uint32_t udf_get_pblock_virt20(struct super_block *sb, uint32_t block,
+ uint16_t partition, uint32_t offset)
+{
+ return udf_get_pblock_virt15(sb, block, partition, offset);
+}
+
+uint32_t udf_get_pblock_spar15(struct super_block *sb, uint32_t block,
+ uint16_t partition, uint32_t offset)
+{
+ int i;
+ struct sparingTable *st = NULL;
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ struct udf_part_map *map;
+ uint32_t packet;
+ struct udf_sparing_data *sdata;
+
+ map = &sbi->s_partmaps[partition];
+ sdata = &map->s_type_specific.s_sparing;
+ packet = (block + offset) & ~(sdata->s_packet_len - 1);
+
+ for (i = 0; i < 4; i++) {
+ if (sdata->s_spar_map[i] != NULL) {
+ st = (struct sparingTable *)
+ sdata->s_spar_map[i]->b_data;
+ break;
+ }
+ }
+
+ if (st) {
+ for (i = 0; i < le16_to_cpu(st->reallocationTableLen); i++) {
+ struct sparingEntry *entry = &st->mapEntry[i];
+ u32 origLoc = le32_to_cpu(entry->origLocation);
+ if (origLoc >= 0xFFFFFFF0)
+ break;
+ else if (origLoc == packet)
+ return le32_to_cpu(entry->mappedLocation) +
+ ((block + offset) &
+ (sdata->s_packet_len - 1));
+ else if (origLoc > packet)
+ break;
+ }
+ }
+
+ return map->s_partition_root + block + offset;
+}
+
+int udf_relocate_blocks(struct super_block *sb, long old_block, long *new_block)
+{
+ struct udf_sparing_data *sdata;
+ struct sparingTable *st = NULL;
+ struct sparingEntry mapEntry;
+ uint32_t packet;
+ int i, j, k, l;
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ u16 reallocationTableLen;
+ struct buffer_head *bh;
+ int ret = 0;
+
+ mutex_lock(&sbi->s_alloc_mutex);
+ for (i = 0; i < sbi->s_partitions; i++) {
+ struct udf_part_map *map = &sbi->s_partmaps[i];
+ if (old_block > map->s_partition_root &&
+ old_block < map->s_partition_root + map->s_partition_len) {
+ sdata = &map->s_type_specific.s_sparing;
+ packet = (old_block - map->s_partition_root) &
+ ~(sdata->s_packet_len - 1);
+
+ for (j = 0; j < 4; j++)
+ if (sdata->s_spar_map[j] != NULL) {
+ st = (struct sparingTable *)
+ sdata->s_spar_map[j]->b_data;
+ break;
+ }
+
+ if (!st) {
+ ret = 1;
+ goto out;
+ }
+
+ reallocationTableLen =
+ le16_to_cpu(st->reallocationTableLen);
+ for (k = 0; k < reallocationTableLen; k++) {
+ struct sparingEntry *entry = &st->mapEntry[k];
+ u32 origLoc = le32_to_cpu(entry->origLocation);
+
+ if (origLoc == 0xFFFFFFFF) {
+ for (; j < 4; j++) {
+ int len;
+ bh = sdata->s_spar_map[j];
+ if (!bh)
+ continue;
+
+ st = (struct sparingTable *)
+ bh->b_data;
+ entry->origLocation =
+ cpu_to_le32(packet);
+ len =
+ sizeof(struct sparingTable) +
+ reallocationTableLen *
+ sizeof(struct sparingEntry);
+ udf_update_tag((char *)st, len);
+ mark_buffer_dirty(bh);
+ }
+ *new_block = le32_to_cpu(
+ entry->mappedLocation) +
+ ((old_block -
+ map->s_partition_root) &
+ (sdata->s_packet_len - 1));
+ ret = 0;
+ goto out;
+ } else if (origLoc == packet) {
+ *new_block = le32_to_cpu(
+ entry->mappedLocation) +
+ ((old_block -
+ map->s_partition_root) &
+ (sdata->s_packet_len - 1));
+ ret = 0;
+ goto out;
+ } else if (origLoc > packet)
+ break;
+ }
+
+ for (l = k; l < reallocationTableLen; l++) {
+ struct sparingEntry *entry = &st->mapEntry[l];
+ u32 origLoc = le32_to_cpu(entry->origLocation);
+
+ if (origLoc != 0xFFFFFFFF)
+ continue;
+
+ for (; j < 4; j++) {
+ bh = sdata->s_spar_map[j];
+ if (!bh)
+ continue;
+
+ st = (struct sparingTable *)bh->b_data;
+ mapEntry = st->mapEntry[l];
+ mapEntry.origLocation =
+ cpu_to_le32(packet);
+ memmove(&st->mapEntry[k + 1],
+ &st->mapEntry[k],
+ (l - k) *
+ sizeof(struct sparingEntry));
+ st->mapEntry[k] = mapEntry;
+ udf_update_tag((char *)st,
+ sizeof(struct sparingTable) +
+ reallocationTableLen *
+ sizeof(struct sparingEntry));
+ mark_buffer_dirty(bh);
+ }
+ *new_block =
+ le32_to_cpu(
+ st->mapEntry[k].mappedLocation) +
+ ((old_block - map->s_partition_root) &
+ (sdata->s_packet_len - 1));
+ ret = 0;
+ goto out;
+ }
+
+ ret = 1;
+ goto out;
+ } /* if old_block */
+ }
+
+ if (i == sbi->s_partitions) {
+ /* outside of partitions */
+ /* for now, fail =) */
+ ret = 1;
+ }
+
+out:
+ mutex_unlock(&sbi->s_alloc_mutex);
+ return ret;
+}
+
+static uint32_t udf_try_read_meta(struct inode *inode, uint32_t block,
+ uint16_t partition, uint32_t offset)
+{
+ struct super_block *sb = inode->i_sb;
+ struct udf_part_map *map;
+ struct kernel_lb_addr eloc;
+ uint32_t elen;
+ sector_t ext_offset;
+ struct extent_position epos = {};
+ uint32_t phyblock;
+
+ if (inode_bmap(inode, block, &epos, &eloc, &elen, &ext_offset) !=
+ (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_type_specific.s_metadata.s_phys_partition_ref,
+ ext_offset + offset);
+ }
+
+ brelse(epos.bh);
+ return phyblock;
+}
+
+uint32_t udf_get_pblock_meta25(struct super_block *sb, uint32_t block,
+ uint16_t partition, uint32_t offset)
+{
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ struct udf_part_map *map;
+ struct udf_meta_data *mdata;
+ uint32_t retblk;
+ struct inode *inode;
+
+ udf_debug("READING from METADATA\n");
+
+ map = &sbi->s_partmaps[partition];
+ mdata = &map->s_type_specific.s_metadata;
+ inode = mdata->s_metadata_fe ? : mdata->s_mirror_fe;
+
+ 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,
+ mdata->s_phys_partition_ref);
+ if (IS_ERR(mdata->s_mirror_fe))
+ mdata->s_mirror_fe = NULL;
+ mdata->s_flags |= MF_MIRROR_FE_LOADED;
+ }
+
+ inode = mdata->s_mirror_fe;
+ if (!inode)
+ return 0xFFFFFFFF;
+ retblk = udf_try_read_meta(inode, block, partition, offset);
+ }
+
+ return retblk;
+}
diff --git a/fs/udf/super.c b/fs/udf/super.c
new file mode 100644
index 0000000000..928a04d9d9
--- /dev/null
+++ b/fs/udf/super.c
@@ -0,0 +1,2509 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * super.c
+ *
+ * PURPOSE
+ * Super block routines for the OSTA-UDF(tm) filesystem.
+ *
+ * DESCRIPTION
+ * OSTA-UDF(tm) = Optical Storage Technology Association
+ * Universal Disk Format.
+ *
+ * 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/
+ * https://www.ecma.ch/
+ * https://www.iso.org/
+ *
+ * COPYRIGHT
+ * (C) 1998 Dave Boynton
+ * (C) 1998-2004 Ben Fennema
+ * (C) 2000 Stelias Computing Inc
+ *
+ * HISTORY
+ *
+ * 09/24/98 dgb changed to allow compiling outside of kernel, and
+ * added some debugging.
+ * 10/01/98 dgb updated to allow (some) possibility of compiling w/2.0.34
+ * 10/16/98 attempting some multi-session support
+ * 10/17/98 added freespace count for "df"
+ * 11/11/98 gr added novrs option
+ * 11/26/98 dgb added fileset,anchor mount options
+ * 12/06/98 blf really hosed things royally. vat/sparing support. sequenced
+ * vol descs. rewrote option handling based on isofs
+ * 12/20/98 find the free space bitmap (if it exists)
+ */
+
+#include "udfdecl.h"
+
+#include <linux/blkdev.h>
+#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/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 "udf_sb.h"
+#include "udf_i.h"
+
+#include <linux/init.h>
+#include <linux/uaccess.h>
+
+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 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 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 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 *);
+
+struct logicalVolIntegrityDescImpUse *udf_sb_lvidiu(struct super_block *sb)
+{
+ 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)
+{
+ return mount_bdev(fs_type, flags, dev_name, data, udf_fill_super);
+}
+
+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,
+};
+MODULE_ALIAS_FS("udf");
+
+static struct kmem_cache *udf_inode_cachep;
+
+static struct inode *udf_alloc_inode(struct super_block *sb)
+{
+ struct udf_inode_info *ei;
+ 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_free_in_core_inode(struct inode *inode)
+{
+ kmem_cache_free(udf_inode_cachep, UDF_I(inode));
+}
+
+static void init_once(void *foo)
+{
+ struct udf_inode_info *ei = foo;
+
+ ei->i_data = NULL;
+ inode_init_once(&ei->vfs_inode);
+}
+
+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;
+ return 0;
+}
+
+static void destroy_inodecache(void)
+{
+ /*
+ * Make sure all delayed rcu free inodes are flushed before we
+ * destroy cache.
+ */
+ rcu_barrier();
+ kmem_cache_destroy(udf_inode_cachep);
+}
+
+/* Superblock operations */
+static const struct super_operations udf_sb_ops = {
+ .alloc_inode = udf_alloc_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 flags;
+ umode_t umask;
+ kgid_t gid;
+ kuid_t uid;
+ umode_t fmode;
+ umode_t dmode;
+ struct nls_table *nls_map;
+};
+
+static int __init init_udf_fs(void)
+{
+ int err;
+
+ err = init_inodecache();
+ if (err)
+ goto out1;
+ err = register_filesystem(&udf_fstype);
+ if (err)
+ goto out;
+
+ return 0;
+
+out:
+ destroy_inodecache();
+
+out1:
+ return err;
+}
+
+static void __exit exit_udf_fs(void)
+{
+ unregister_filesystem(&udf_fstype);
+ destroy_inodecache();
+}
+
+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(*sbi->s_partmaps), GFP_KERNEL);
+ if (!sbi->s_partmaps) {
+ sbi->s_partitions = 0;
+ return -ENOMEM;
+ }
+
+ sbi->s_partitions = count;
+ return 0;
+}
+
+static void udf_sb_free_bitmap(struct udf_bitmap *bitmap)
+{
+ int i;
+ int nr_groups = bitmap->s_nr_groups;
+
+ for (i = 0; i < nr_groups; i++)
+ brelse(bitmap->s_block_bitmap[i]);
+
+ kvfree(bitmap);
+}
+
+static void udf_free_partition(struct udf_part_map *map)
+{
+ int i;
+ struct udf_meta_data *mdata;
+
+ if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_TABLE)
+ iput(map->s_uspace.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_type == UDF_SPARABLE_MAP15)
+ for (i = 0; i < 4; i++)
+ brelse(map->s_type_specific.s_sparing.s_spar_map[i]);
+ else if (map->s_partition_type == UDF_METADATA_MAP25) {
+ mdata = &map->s_type_specific.s_metadata;
+ iput(mdata->s_metadata_fe);
+ mdata->s_metadata_fe = NULL;
+
+ iput(mdata->s_mirror_fe);
+ mdata->s_mirror_fe = NULL;
+
+ iput(mdata->s_bitmap_fe);
+ mdata->s_bitmap_fe = NULL;
+ }
+}
+
+static void udf_sb_free_partitions(struct super_block *sb)
+{
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ int i;
+
+ if (!sbi->s_partmaps)
+ return;
+ for (i = 0; i < sbi->s_partitions; i++)
+ udf_free_partition(&sbi->s_partmaps[i]);
+ kfree(sbi->s_partmaps);
+ sbi->s_partmaps = NULL;
+}
+
+static int udf_show_options(struct seq_file *seq, struct dentry *root)
+{
+ struct super_block *sb = root->d_sb;
+ struct udf_sb_info *sbi = UDF_SB(sb);
+
+ if (!UDF_QUERY_FLAG(sb, UDF_FLAG_STRICT))
+ seq_puts(seq, ",nostrict");
+ if (UDF_QUERY_FLAG(sb, UDF_FLAG_BLOCKSIZE_SET))
+ seq_printf(seq, ",bs=%lu", sb->s_blocksize);
+ if (UDF_QUERY_FLAG(sb, UDF_FLAG_UNHIDE))
+ seq_puts(seq, ",unhide");
+ if (UDF_QUERY_FLAG(sb, UDF_FLAG_UNDELETE))
+ seq_puts(seq, ",undelete");
+ if (!UDF_QUERY_FLAG(sb, UDF_FLAG_USE_AD_IN_ICB))
+ seq_puts(seq, ",noadinicb");
+ if (UDF_QUERY_FLAG(sb, UDF_FLAG_USE_SHORT_AD))
+ 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_GID_FORGET))
+ seq_puts(seq, ",gid=forget");
+ 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))
+ seq_printf(seq, ",gid=%u", from_kgid(&init_user_ns, sbi->s_gid));
+ if (sbi->s_umask != 0)
+ seq_printf(seq, ",umask=%ho", sbi->s_umask);
+ if (sbi->s_fmode != UDF_INVALID_MODE)
+ seq_printf(seq, ",mode=%ho", sbi->s_fmode);
+ 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=%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);
+ 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
+ *
+ * PURPOSE
+ * Parse mount options.
+ *
+ * DESCRIPTION
+ * The following mount options are supported:
+ *
+ * gid= Set the default group.
+ * umask= Set the default umask.
+ * mode= Set the default file permissions.
+ * dmode= Set the default directory permissions.
+ * uid= Set the default user.
+ * bs= Set the block size.
+ * unhide Show otherwise hidden files.
+ * undelete Show deleted files in lists.
+ * adinicb Embed data in the inode (default)
+ * noadinicb Don't embed data in the inode
+ * shortad Use short ad's
+ * longad Use long ad's (default)
+ * nostrict Unset strict conformance
+ * iocharset= Set the NLS character set
+ *
+ * The remaining are for debugging and disaster recovery:
+ *
+ * novrs Skip volume sequence recognition
+ *
+ * The following expect a offset from 0.
+ *
+ * session= Set the CDROM session (default= last session)
+ * anchor= Override standard anchor location. (default= 256)
+ * volume= Override the VolumeDesc location. (unused)
+ * partition= Override the PartitionDesc location. (unused)
+ * lastblock= Set the last block of the filesystem/
+ *
+ * The following expect a offset from the partition root.
+ *
+ * fileset= Override the fileset block location. (unused)
+ * rootdir= Override the root directory location. (unused)
+ * WARNING: overriding the rootdir to a non-directory may
+ * yield highly unpredictable results.
+ *
+ * PRE-CONDITIONS
+ * options Pointer to mount options string.
+ * uopts Pointer to mount options variable.
+ *
+ * POST-CONDITIONS
+ * <return> 1 Mount options parsed okay.
+ * <return> 0 Error parsing mount options.
+ *
+ * HISTORY
+ * July 1, 1997 - Andrew E. Mileski
+ * Written, tested, and released.
+ */
+
+enum {
+ Opt_novrs, Opt_nostrict, Opt_bs, Opt_unhide, Opt_undelete,
+ 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}
+};
+
+static int udf_parse_options(char *options, struct udf_options *uopt,
+ bool remount)
+{
+ char *p;
+ int option;
+ unsigned int uv;
+
+ uopt->novrs = 0;
+ uopt->session = 0xFFFFFFFF;
+ uopt->lastblock = 0;
+ uopt->anchor = 0;
+
+ if (!options)
+ return 1;
+
+ while ((p = strsep(&options, ",")) != NULL) {
+ substring_t args[MAX_OPT_ARGS];
+ int token;
+ unsigned n;
+ 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;
+ n = option;
+ if (n != 512 && n != 1024 && n != 2048 && n != 4096)
+ return 0;
+ 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_noadinicb:
+ uopt->flags &= ~(1 << UDF_FLAG_USE_AD_IN_ICB);
+ break;
+ case Opt_adinicb:
+ 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_uint(args, &uv))
+ return 0;
+ uopt->gid = make_kgid(current_user_ns(), uv);
+ if (!gid_valid(uopt->gid))
+ return 0;
+ uopt->flags |= (1 << UDF_FLAG_GID_SET);
+ break;
+ case Opt_uid:
+ if (match_uint(args, &uv))
+ return 0;
+ uopt->uid = make_kuid(current_user_ns(), uv);
+ if (!uid_valid(uopt->uid))
+ return 0;
+ 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:
+ 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(args[0].from, "utf8") != 0) {
+ uopt->nls_map = load_nls(args[0].from);
+ if (!uopt->nls_map) {
+ pr_err("iocharset %s not found\n",
+ args[0].from);
+ return 0;
+ }
+ }
+ break;
+ case Opt_uforget:
+ uopt->flags |= (1 << UDF_FLAG_UID_FORGET);
+ break;
+ case Opt_uignore:
+ case Opt_gignore:
+ /* These options are superseeded by uid=<number> */
+ 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;
+ }
+ }
+ return 1;
+}
+
+static int udf_remount_fs(struct super_block *sb, int *flags, char *options)
+{
+ struct udf_options uopt;
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ int error = 0;
+
+ if (!(*flags & SB_RDONLY) && UDF_QUERY_FLAG(sb, UDF_FLAG_RW_INCOMPAT))
+ return -EACCES;
+
+ sync_filesystem(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;
+
+ if (!udf_parse_options(options, &uopt, true))
+ return -EINVAL;
+
+ 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;
+ write_unlock(&sbi->s_cred_lock);
+
+ if ((bool)(*flags & SB_RDONLY) == sb_rdonly(sb))
+ goto out_unlock;
+
+ if (*flags & SB_RDONLY)
+ udf_close_lvid(sb);
+ else
+ udf_open_lvid(sb);
+
+out_unlock:
+ return error;
+}
+
+/*
+ * 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 = VSD_FIRST_SECTOR_OFFSET;
+ int sectorsize;
+ struct buffer_head *bh = NULL;
+ int nsr = 0;
+ struct udf_sb_info *sbi;
+ loff_t session_offset;
+
+ sbi = UDF_SB(sb);
+ if (sb->s_blocksize < sizeof(struct volStructDesc))
+ sectorsize = sizeof(struct volStructDesc);
+ else
+ sectorsize = sb->s_blocksize;
+
+ session_offset = (loff_t)sbi->s_session << sb->s_blocksize_bits;
+ sector += session_offset;
+
+ 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). 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 = sb_bread(sb, sector >> sb->s_blocksize_bits);
+ if (!bh)
+ break;
+
+ vsd = (struct volStructDesc *)(bh->b_data +
+ (sector & (sb->s_blocksize - 1)));
+ nsr = identify_vsd(vsd);
+ /* Found NSR or end? */
+ if (nsr) {
+ brelse(bh);
+ break;
+ }
+ /*
+ * 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 (nsr > 0)
+ return 1;
+ else if (!bh && sector - session_offset == VSD_FIRST_SECTOR_OFFSET)
+ return -1;
+ else
+ return 0;
+}
+
+static int udf_verify_domain_identifier(struct super_block *sb,
+ struct regid *ident, char *dname)
+{
+ struct domainIdentSuffix *suffix;
+
+ 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;
+
+force_ro:
+ if (!sb_rdonly(sb))
+ return -EACCES;
+ UDF_SET_FLAG(sb, UDF_FLAG_RW_INCOMPAT);
+ return 0;
+}
+
+static int udf_load_fileset(struct super_block *sb, struct fileSetDesc *fset,
+ struct kernel_lb_addr *root)
+{
+ int ret;
+
+ ret = udf_verify_domain_identifier(sb, &fset->domainIdent, "file set");
+ if (ret < 0)
+ return ret;
+
+ *root = lelb_to_cpu(fset->rootDirectoryICB.extLocation);
+ UDF_SB(sb)->s_serial_number = le16_to_cpu(fset->descTag.tagSerialNum);
+
+ udf_debug("Rootdir at block=%u, partition=%u\n",
+ root->logicalBlockNum, root->partitionReferenceNum);
+ return 0;
+}
+
+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 -EINVAL;
+ }
+
+ 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;
+ uint8_t *outstr;
+ struct buffer_head *bh;
+ uint16_t ident;
+ int ret;
+ struct timestamp *ts;
+
+ outstr = kmalloc(128, GFP_NOFS);
+ if (!outstr)
+ return -ENOMEM;
+
+ bh = udf_read_tagged(sb, block, block, &ident);
+ if (!bh) {
+ ret = -EAGAIN;
+ goto out2;
+ }
+
+ if (ident != TAG_IDENT_PVD) {
+ ret = -EIO;
+ goto out_bh;
+ }
+
+ pvoldesc = (struct primaryVolDesc *)bh->b_data;
+
+ 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) {
+ strcpy(UDF_SB(sb)->s_volume_ident, "InvalidName");
+ pr_warn("incorrect volume identification, setting to "
+ "'InvalidName'\n");
+ } else {
+ strncpy(UDF_SB(sb)->s_volume_ident, outstr, ret);
+ }
+ udf_debug("volIdent[] = '%s'\n", UDF_SB(sb)->s_volume_ident);
+
+ 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);
+
+ ret = 0;
+out_bh:
+ brelse(bh);
+out2:
+ kfree(outstr);
+ return ret;
+}
+
+struct inode *udf_find_metadata_inode_efe(struct super_block *sb,
+ u32 meta_file_loc, u32 partition_ref)
+{
+ struct kernel_lb_addr addr;
+ struct inode *metadata_fe;
+
+ addr.logicalBlockNum = meta_file_loc;
+ addr.partitionReferenceNum = partition_ref;
+
+ metadata_fe = udf_iget_special(sb, &addr);
+
+ if (IS_ERR(metadata_fe)) {
+ udf_warn(sb, "metadata inode efe not found\n");
+ 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);
+ return ERR_PTR(-EIO);
+ }
+
+ return metadata_fe;
+}
+
+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 = %u part = %u\n",
+ mdata->s_meta_file_loc, mdata->s_phys_partition_ref);
+
+ 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 = %u part = %u\n",
+ mdata->s_mirror_file_loc, mdata->s_phys_partition_ref);
+
+ fe = udf_find_metadata_inode_efe(sb, mdata->s_mirror_file_loc,
+ mdata->s_phys_partition_ref);
+
+ if (IS_ERR(fe)) {
+ udf_err(sb, "Both metadata and mirror metadata inode efe can not found\n");
+ return PTR_ERR(fe);
+ }
+ mdata->s_mirror_fe = fe;
+ } else
+ mdata->s_metadata_fe = fe;
+
+
+ /*
+ * bitmap file entry
+ * Note:
+ * Load only if bitmap file location differs from 0xFFFFFFFF (DCN-5102)
+ */
+ if (mdata->s_bitmap_file_loc != 0xFFFFFFFF) {
+ addr.logicalBlockNum = mdata->s_bitmap_file_loc;
+ addr.partitionReferenceNum = mdata->s_phys_partition_ref;
+
+ udf_debug("Bitmap file location: block = %u part = %u\n",
+ addr.logicalBlockNum, addr.partitionReferenceNum);
+
+ 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");
+ return PTR_ERR(fe);
+ }
+ } else
+ mdata->s_bitmap_fe = fe;
+ }
+
+ udf_debug("udf_load_metadata_files Ok\n");
+ return 0;
+}
+
+int udf_compute_nr_groups(struct super_block *sb, u32 partition)
+{
+ struct udf_part_map *map = &UDF_SB(sb)->s_partmaps[partition];
+ return DIV_ROUND_UP(map->s_partition_len +
+ (sizeof(struct spaceBitmapDesc) << 3),
+ sb->s_blocksize * 8);
+}
+
+static struct udf_bitmap *udf_sb_alloc_bitmap(struct super_block *sb, u32 index)
+{
+ struct udf_bitmap *bitmap;
+ int nr_groups = udf_compute_nr_groups(sb, index);
+
+ 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;
+ 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 (p->accessType == cpu_to_le32(PD_ACCESS_TYPE_READ_ONLY))
+ map->s_partition_flags |= UDF_PART_FLAG_READ_ONLY;
+ if (p->accessType == cpu_to_le32(PD_ACCESS_TYPE_WRITE_ONCE))
+ map->s_partition_flags |= UDF_PART_FLAG_WRITE_ONCE;
+ if (p->accessType == cpu_to_le32(PD_ACCESS_TYPE_REWRITABLE))
+ map->s_partition_flags |= UDF_PART_FLAG_REWRITABLE;
+ 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 %u, block length %u\n",
+ p_index, map->s_partition_type,
+ map->s_partition_root, map->s_partition_len);
+
+ 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;
+ if (phd->unallocSpaceTable.extLength) {
+ struct kernel_lb_addr loc = {
+ .logicalBlockNum = le32_to_cpu(
+ phd->unallocSpaceTable.extPosition),
+ .partitionReferenceNum = p_index,
+ };
+ struct inode *inode;
+
+ inode = udf_iget_special(sb, &loc);
+ if (IS_ERR(inode)) {
+ udf_debug("cannot load unallocSpaceTable (part %d)\n",
+ p_index);
+ return PTR_ERR(inode);
+ }
+ map->s_uspace.s_table = inode;
+ map->s_partition_flags |= UDF_PART_FLAG_UNALLOC_TABLE;
+ 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 -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) @ %u\n",
+ p_index, bitmap->s_extPosition);
+ }
+
+ return 0;
+}
+
+static void udf_find_vat_block(struct super_block *sb, int p_index,
+ int type1_index, sector_t start_block)
+{
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ 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
+ * it a few blocks before so try a bit harder...
+ */
+ ino.partitionReferenceNum = type1_index;
+ for (vat_block = start_block;
+ vat_block >= map->s_partition_root &&
+ vat_block >= start_block - 3; vat_block--) {
+ ino.logicalBlockNum = vat_block - map->s_partition_root;
+ inode = udf_iget_special(sb, &ino);
+ if (!IS_ERR(inode)) {
+ sbi->s_vat_inode = inode;
+ break;
+ }
+ }
+}
+
+static int udf_load_vat(struct super_block *sb, int p_index, int type1_index)
+{
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ struct udf_part_map *map = &sbi->s_partmaps[p_index];
+ struct buffer_head *bh = NULL;
+ struct udf_inode_info *vati;
+ struct virtualAllocationTable20 *vat20;
+ 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 &&
+ sbi->s_last_block != blocks - 1) {
+ pr_notice("Failed to read VAT inode from the last recorded block (%lu), retrying with the last block of the device (%lu).\n",
+ (unsigned long)sbi->s_last_block,
+ (unsigned long)blocks - 1);
+ udf_find_vat_block(sb, p_index, type1_index, blocks - 1);
+ }
+ if (!sbi->s_vat_inode)
+ return -EIO;
+
+ if (map->s_partition_type == UDF_VIRTUAL_MAP15) {
+ map->s_type_specific.s_virtual.s_start_offset = 0;
+ map->s_type_specific.s_virtual.s_num_entries =
+ (sbi->s_vat_inode->i_size - 36) >> 2;
+ } 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) {
+ 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_data;
+ }
+
+ map->s_type_specific.s_virtual.s_start_offset =
+ le16_to_cpu(vat20->lengthHeader);
+ map->s_type_specific.s_virtual.s_num_entries =
+ (sbi->s_vat_inode->i_size -
+ map->s_type_specific.s_virtual.
+ s_start_offset) >> 2;
+ brelse(bh);
+ }
+ 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;
+ struct partitionDesc *p;
+ struct udf_part_map *map;
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ int i, type1_idx;
+ uint16_t partitionNumber;
+ uint16_t ident;
+ int ret;
+
+ bh = udf_read_tagged(sb, block, block, &ident);
+ if (!bh)
+ 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 and SPARABLE partitions */
+ for (i = 0; i < sbi->s_partitions; i++) {
+ map = &sbi->s_partmaps[i];
+ 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 ||
+ map->s_partition_type == UDF_SPARABLE_MAP15))
+ break;
+ }
+
+ if (i >= sbi->s_partitions) {
+ 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];
+
+ if (map->s_partition_num == partitionNumber &&
+ (map->s_partition_type == UDF_VIRTUAL_MAP15 ||
+ map->s_partition_type == UDF_VIRTUAL_MAP20 ||
+ map->s_partition_type == UDF_METADATA_MAP25))
+ break;
+ }
+
+ if (i >= sbi->s_partitions) {
+ ret = 0;
+ goto out_bh;
+ }
+
+ ret = udf_fill_partdesc_info(sb, p, i);
+ if (ret < 0)
+ goto out_bh;
+
+ if (map->s_partition_type == UDF_METADATA_MAP25) {
+ 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 {
+ /*
+ * If we have a partition with virtual map, we don't handle
+ * writing to it (we overwrite blocks instead of relocating
+ * them).
+ */
+ 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);
+ return ret;
+}
+
+static int udf_load_sparable_map(struct super_block *sb,
+ struct udf_part_map *map,
+ struct sparablePartitionMap *spm)
+{
+ uint32_t loc;
+ uint16_t ident;
+ struct sparingTable *st;
+ struct udf_sparing_data *sdata = &map->s_type_specific.s_sparing;
+ int i;
+ struct buffer_head *bh;
+
+ map->s_partition_type = UDF_SPARABLE_MAP15;
+ sdata->s_packet_len = le16_to_cpu(spm->packetLength);
+ if (!is_power_of_2(sdata->s_packet_len)) {
+ udf_err(sb, "error loading logical volume descriptor: "
+ "Invalid packet length %u\n",
+ (unsigned)sdata->s_packet_len);
+ return -EIO;
+ }
+ if (spm->numSparingTables > 4) {
+ udf_err(sb, "error loading logical volume descriptor: "
+ "Too many sparing tables (%d)\n",
+ (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]);
+ bh = udf_read_tagged(sb, loc, loc, &ident);
+ if (!bh)
+ continue;
+
+ st = (struct sparingTable *)bh->b_data;
+ if (ident != 0 ||
+ strncmp(st->sparingIdent.ident, UDF_ID_SPARING,
+ strlen(UDF_ID_SPARING)) ||
+ sizeof(*st) + le16_to_cpu(st->reallocationTableLen) >
+ sb->s_blocksize) {
+ brelse(bh);
+ continue;
+ }
+
+ sdata->s_spar_map[i] = bh;
+ }
+ map->s_partition_func = udf_get_pblock_spar15;
+ return 0;
+}
+
+static int udf_load_logicalvol(struct super_block *sb, sector_t block,
+ struct kernel_lb_addr *fileset)
+{
+ struct logicalVolDesc *lvd;
+ int i, offset;
+ uint8_t type;
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ struct genericPartitionMap *gpm;
+ uint16_t ident;
+ struct buffer_head *bh;
+ unsigned int table_len;
+ int ret;
+
+ bh = udf_read_tagged(sb, block, block, &ident);
+ if (!bh)
+ return -EAGAIN;
+ BUG_ON(ident != TAG_IDENT_LVD);
+ lvd = (struct logicalVolDesc *)bh->b_data;
+ table_len = le32_to_cpu(lvd->mapTableLength);
+ if (table_len > sb->s_blocksize - sizeof(*lvd)) {
+ udf_err(sb, "error loading logical volume descriptor: "
+ "Partition table too long (%u > %lu)\n", table_len,
+ sb->s_blocksize - sizeof(*lvd));
+ ret = -EIO;
+ goto out_bh;
+ }
+
+ ret = udf_verify_domain_identifier(sb, &lvd->domainIdent,
+ "logical volume");
+ if (ret)
+ goto out_bh;
+ ret = udf_sb_alloc_partition_maps(sb, le32_to_cpu(lvd->numPartitionMaps));
+ if (ret)
+ goto out_bh;
+
+ for (i = 0, offset = 0;
+ i < sbi->s_partitions && offset < table_len;
+ i++, offset += gpm->partitionMapLength) {
+ struct udf_part_map *map = &sbi->s_partmaps[i];
+ gpm = (struct genericPartitionMap *)
+ &(lvd->partitionMaps[offset]);
+ type = gpm->partitionMapType;
+ if (type == 1) {
+ struct genericPartitionMap1 *gpm1 =
+ (struct genericPartitionMap1 *)gpm;
+ map->s_partition_type = UDF_TYPE1_MAP15;
+ map->s_volumeseqnum = le16_to_cpu(gpm1->volSeqNum);
+ map->s_partition_num = le16_to_cpu(gpm1->partitionNum);
+ map->s_partition_func = NULL;
+ } else if (type == 2) {
+ struct udfPartitionMap2 *upm2 =
+ (struct udfPartitionMap2 *)gpm;
+ if (!strncmp(upm2->partIdent.ident, UDF_ID_VIRTUAL,
+ strlen(UDF_ID_VIRTUAL))) {
+ u16 suf =
+ le16_to_cpu(((__le16 *)upm2->partIdent.
+ identSuffix)[0]);
+ if (suf < 0x0200) {
+ map->s_partition_type =
+ UDF_VIRTUAL_MAP15;
+ map->s_partition_func =
+ udf_get_pblock_virt15;
+ } else {
+ map->s_partition_type =
+ UDF_VIRTUAL_MAP20;
+ map->s_partition_func =
+ udf_get_pblock_virt20;
+ }
+ } else if (!strncmp(upm2->partIdent.ident,
+ UDF_ID_SPARABLE,
+ strlen(UDF_ID_SPARABLE))) {
+ 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))) {
+ struct udf_meta_data *mdata =
+ &map->s_type_specific.s_metadata;
+ struct metadataPartitionMap *mdm =
+ (struct metadataPartitionMap *)
+ &(lvd->partitionMaps[offset]);
+ udf_debug("Parsing Logical vol part %d type %u id=%s\n",
+ i, type, UDF_ID_METADATA);
+
+ map->s_partition_type = UDF_METADATA_MAP25;
+ map->s_partition_func = udf_get_pblock_meta25;
+
+ mdata->s_meta_file_loc =
+ le32_to_cpu(mdm->metadataFileLoc);
+ mdata->s_mirror_file_loc =
+ le32_to_cpu(mdm->metadataMirrorFileLoc);
+ mdata->s_bitmap_file_loc =
+ le32_to_cpu(mdm->metadataBitmapFileLoc);
+ mdata->s_alloc_unit_size =
+ le32_to_cpu(mdm->allocUnitSize);
+ mdata->s_align_unit_size =
+ le16_to_cpu(mdm->alignUnitSize);
+ if (mdm->flags & 0x01)
+ mdata->s_flags |= MF_DUPLICATE_MD;
+
+ udf_debug("Metadata Ident suffix=0x%x\n",
+ le16_to_cpu(*(__le16 *)
+ mdm->partIdent.identSuffix));
+ udf_debug("Metadata part num=%u\n",
+ le16_to_cpu(mdm->partitionNum));
+ udf_debug("Metadata part alloc unit size=%u\n",
+ le32_to_cpu(mdm->allocUnitSize));
+ udf_debug("Metadata file loc=%u\n",
+ le32_to_cpu(mdm->metadataFileLoc));
+ udf_debug("Mirror file loc=%u\n",
+ le32_to_cpu(mdm->metadataMirrorFileLoc));
+ udf_debug("Bitmap file loc=%u\n",
+ le32_to_cpu(mdm->metadataBitmapFileLoc));
+ udf_debug("Flags: %d %u\n",
+ mdata->s_flags, mdm->flags);
+ } else {
+ udf_debug("Unknown ident: %s\n",
+ upm2->partIdent.ident);
+ continue;
+ }
+ map->s_volumeseqnum = le16_to_cpu(upm2->volSeqNum);
+ map->s_partition_num = le16_to_cpu(upm2->partitionNum);
+ }
+ udf_debug("Partition (%d:%u) type %u on volume %u\n",
+ i, map->s_partition_num, type, map->s_volumeseqnum);
+ }
+
+ if (fileset) {
+ struct long_ad *la = (struct long_ad *)&(lvd->logicalVolContentsUse[0]);
+
+ *fileset = lelb_to_cpu(la->extLocation);
+ 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;
+}
+
+/*
+ * 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, *final_bh;
+ uint16_t ident;
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ struct logicalVolIntegrityDesc *lvid;
+ int indirections = 0;
+ u32 parts, impuselen;
+
+ 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;
+ }
+
+ brelse(final_bh);
+ final_bh = bh;
+
+ loc.extLength -= sb->s_blocksize;
+ loc.extLocation++;
+ }
+
+ if (!final_bh)
+ return;
+
+ brelse(sbi->s_lvid_bh);
+ sbi->s_lvid_bh = final_bh;
+
+ lvid = (struct logicalVolIntegrityDesc *)final_bh->b_data;
+ if (lvid->nextIntegrityExt.extLength == 0)
+ goto check;
+
+ loc = leea_to_cpu(lvid->nextIntegrityExt);
+ }
+
+ udf_warn(sb, "Too many LVID indirections (max %u), ignoring.\n",
+ UDF_MAX_LVID_NESTING);
+out_err:
+ brelse(sbi->s_lvid_bh);
+ sbi->s_lvid_bh = NULL;
+ return;
+check:
+ 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) {
+ udf_warn(sb, "Corrupted LVID (parts=%u, impuselen=%u), "
+ "ignoring.\n", parts, impuselen);
+ goto out_err;
+ }
+}
+
+/*
+ * 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
+ *
+ * Returns <0 on error, 0 on success. -EAGAIN is special - try next descriptor
+ * sequence
+ */
+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 *curr;
+ struct generic_desc *gd;
+ struct volDescPtr *vdp;
+ bool done = false;
+ uint32_t vdsn;
+ uint16_t ident;
+ 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)
+ 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_VDP: /* ISO 13346 3/10.3 */
+ 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 */
+ case TAG_IDENT_LVD: /* ISO 13346 3/10.6 */
+ case TAG_IDENT_USD: /* ISO 13346 3/10.8 */
+ 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 */
+ done = true;
+ break;
+ }
+ brelse(bh);
+ }
+ /*
+ * Now read interesting descriptors again and process them
+ * in a suitable order
+ */
+ if (!data.vds[VDS_POS_PRIMARY_VOL_DESC].block) {
+ udf_err(sb, "Primary Volume Descriptor not found!\n");
+ ret = -EAGAIN;
+ goto out;
+ }
+ ret = udf_load_pvoldesc(sb, data.vds[VDS_POS_PRIMARY_VOL_DESC].block);
+ if (ret < 0)
+ goto out;
+
+ 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;
+ }
+
+ /* 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;
+ sector_t main_s, main_e, reserve_s, reserve_e;
+ int ret;
+
+ anchor = (struct anchorVolDescPtr *)bh->b_data;
+
+ /* Locate the main sequence */
+ 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 - 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 - 1;
+
+ /* Process the main & reserve sequences */
+ /* responsible for finding the PartitionDesc(s) */
+ ret = udf_process_sequence(sb, main_s, main_e, fileset);
+ if (ret != -EAGAIN)
+ return ret;
+ udf_sb_free_partitions(sb);
+ 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)
+{
+ struct buffer_head *bh;
+ uint16_t ident;
+ int ret;
+
+ bh = udf_read_tagged(sb, block, block, &ident);
+ if (!bh)
+ return -EAGAIN;
+ if (ident != TAG_IDENT_AVDP) {
+ brelse(bh);
+ return -EAGAIN;
+ }
+ ret = udf_load_sequence(sb, bh, fileset);
+ brelse(bh);
+ return ret;
+}
+
+/*
+ * 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)
+{
+ 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) {
+ ret = udf_check_anchor_block(sb, sbi->s_anchor, fileset);
+ if (ret != -EAGAIN)
+ return ret;
+ }
+ /*
+ * according to spec, anchor is in either:
+ * block 256
+ * lastblock-256
+ * lastblock
+ * however, if the disc isn't closed, it could be 512.
+ */
+ 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;
+
+ for (i = 0; i < last_count; i++) {
+ if (last[i] >= sb_bdev_nr_blocks(sb))
+ continue;
+ ret = udf_check_anchor_block(sb, last[i], fileset);
+ if (ret != -EAGAIN) {
+ if (!ret)
+ *lastblock = last[i];
+ return ret;
+ }
+ if (last[i] < 256)
+ continue;
+ 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 */
+ return udf_check_anchor_block(sb, sbi->s_session + 512, fileset);
+}
+
+/*
+ * Check Volume Structure Descriptor, find Anchor block and load Volume
+ * 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);
+ int nsr = 0;
+ int ret;
+
+ if (!sb_set_blocksize(sb, uopt->blocksize)) {
+ if (!silent)
+ udf_warn(sb, "Bad block size\n");
+ return -EINVAL;
+ }
+ sbi->s_last_block = uopt->lastblock;
+ if (!uopt->novrs) {
+ /* Check that it is NSR02 compliant */
+ nsr = udf_check_vsd(sb);
+ if (!nsr) {
+ if (!silent)
+ udf_warn(sb, "No VRS found\n");
+ return -EINVAL;
+ }
+ 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 {
+ udf_debug("Validity check skipped because of novrs option\n");
+ }
+
+ /* Look for anchor block and load Volume Descriptor Sequence */
+ sbi->s_anchor = uopt->anchor;
+ 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 ret;
+ }
+ 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)
+{
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ struct buffer_head *bh = sbi->s_lvid_bh;
+ struct logicalVolIntegrityDesc *lvid;
+ struct logicalVolIntegrityDescImpUse *lvidiu;
+
+ if (!bh)
+ return;
+ lvid = (struct logicalVolIntegrityDesc *)bh->b_data;
+ 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;
+ 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);
+
+ udf_finalize_lvid(lvid);
+ mark_buffer_dirty(bh);
+ sbi->s_lvid_dirty = 0;
+ mutex_unlock(&sbi->s_alloc_mutex);
+ /* Make opening of filesystem visible on the media immediately */
+ sync_dirty_buffer(bh);
+}
+
+static void udf_close_lvid(struct super_block *sb)
+{
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ struct buffer_head *bh = sbi->s_lvid_bh;
+ struct logicalVolIntegrityDesc *lvid;
+ struct logicalVolIntegrityDescImpUse *lvidiu;
+
+ if (!bh)
+ return;
+ lvid = (struct logicalVolIntegrityDesc *)bh->b_data;
+ 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;
+ 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);
+ if (!UDF_QUERY_FLAG(sb, UDF_FLAG_INCONSISTENT))
+ lvid->integrityType = cpu_to_le32(LVID_INTEGRITY_TYPE_CLOSE);
+
+ /*
+ * 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);
+ /* Make closing of filesystem visible on the media immediately */
+ sync_dirty_buffer(bh);
+}
+
+u64 lvid_get_unique_id(struct super_block *sb)
+{
+ struct buffer_head *bh;
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ struct logicalVolIntegrityDesc *lvid;
+ struct logicalVolHeaderDesc *lvhd;
+ u64 uniqueID;
+ u64 ret;
+
+ bh = sbi->s_lvid_bh;
+ if (!bh)
+ return 0;
+
+ lvid = (struct logicalVolIntegrityDesc *)bh->b_data;
+ lvhd = (struct logicalVolHeaderDesc *)lvid->logicalVolContentsUse;
+
+ mutex_lock(&sbi->s_alloc_mutex);
+ ret = uniqueID = le64_to_cpu(lvhd->uniqueID);
+ if (!(++uniqueID & 0xFFFFFFFF))
+ uniqueID += 16;
+ lvhd->uniqueID = cpu_to_le64(uniqueID);
+ udf_updated_lvid(sb);
+ mutex_unlock(&sbi->s_alloc_mutex);
+
+ return ret;
+}
+
+static int udf_fill_super(struct super_block *sb, void *options, int silent)
+{
+ int ret = -EINVAL;
+ struct inode *inode = NULL;
+ struct udf_options uopt;
+ struct kernel_lb_addr rootdir, fileset;
+ struct udf_sb_info *sbi;
+ bool lvid_open = false;
+
+ 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;
+
+ sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
+ if (!sbi)
+ return -ENOMEM;
+
+ sb->s_fs_info = sbi;
+
+ mutex_init(&sbi->s_alloc_mutex);
+
+ if (!udf_parse_options((char *)options, &uopt, false))
+ goto parse_options_failure;
+
+ 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;
+ rwlock_init(&sbi->s_cred_lock);
+
+ if (uopt.session == 0xFFFFFFFF)
+ sbi->s_session = udf_get_last_session(sb);
+ else
+ sbi->s_session = uopt.session;
+
+ udf_debug("Multi-session=%d\n", sbi->s_session);
+
+ /* Fill in the rest of the superblock */
+ sb->s_op = &udf_sb_ops;
+ sb->s_export_op = &udf_export_ops;
+
+ 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);
+ } else {
+ 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 < 0) {
+ if (ret == -EAGAIN) {
+ udf_warn(sb, "No partition found (1)\n");
+ ret = -EINVAL;
+ }
+ goto error_out;
+ }
+
+ udf_debug("Lastblock=%u\n", sbi->s_last_block);
+
+ if (sbi->s_lvid_bh) {
+ struct logicalVolIntegrityDescImpUse *lvidiu =
+ 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",
+ minUDFReadRev,
+ UDF_MAX_READ_VERSION);
+ ret = -EINVAL;
+ goto error_out;
+ } 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;
+
+ if (minUDFReadRev >= UDF_VERS_USE_EXTENDED_FE)
+ UDF_SET_FLAG(sb, UDF_FLAG_USE_EXTENDED_FE);
+ if (minUDFReadRev >= UDF_VERS_USE_STREAMS)
+ UDF_SET_FLAG(sb, UDF_FLAG_USE_STREAMS);
+ }
+
+ 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) {
+ if (!sb_rdonly(sb)) {
+ ret = -EACCES;
+ goto error_out;
+ }
+ UDF_SET_FLAG(sb, UDF_FLAG_RW_INCOMPAT);
+ }
+
+ ret = udf_find_fileset(sb, &fileset, &rootdir);
+ if (ret < 0) {
+ udf_warn(sb, "No fileset found\n");
+ goto error_out;
+ }
+
+ if (!silent) {
+ struct timestamp ts;
+ udf_time_to_disk_stamp(&ts, sbi->s_record_time);
+ udf_info("Mounting volume '%s', timestamp %04u/%02u/%02u %02u:%02u (%x)\n",
+ sbi->s_volume_ident,
+ le16_to_cpu(ts.year), ts.month, ts.day,
+ ts.hour, ts.minute, le16_to_cpu(ts.typeAndTimezone));
+ }
+ 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 (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;
+ }
+
+ /* Allocate a dentry for the root inode */
+ 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 = UDF_MAX_FILESIZE;
+ sb->s_max_links = UDF_MAX_LINKS;
+ return 0;
+
+error_out:
+ iput(sbi->s_vat_inode);
+parse_options_failure:
+ 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 ret;
+}
+
+void _udf_err(struct super_block *sb, const char *function,
+ const char *fmt, ...)
+{
+ struct va_format vaf;
+ va_list args;
+
+ va_start(args, fmt);
+
+ vaf.fmt = fmt;
+ vaf.va = &args;
+
+ pr_err("error (device %s): %s: %pV", sb->s_id, function, &vaf);
+
+ va_end(args);
+}
+
+void _udf_warn(struct super_block *sb, const char *function,
+ const char *fmt, ...)
+{
+ struct va_format vaf;
+ va_list args;
+
+ va_start(args, fmt);
+
+ vaf.fmt = fmt;
+ vaf.va = &args;
+
+ pr_warn("warning (device %s): %s: %pV", sb->s_id, function, &vaf);
+
+ va_end(args);
+}
+
+static void udf_put_super(struct super_block *sb)
+{
+ struct udf_sb_info *sbi;
+
+ sbi = UDF_SB(sb);
+
+ 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;
+}
+
+static int udf_sync_fs(struct super_block *sb, int wait)
+{
+ struct udf_sb_info *sbi = UDF_SB(sb);
+
+ 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(bh);
+ sbi->s_lvid_dirty = 0;
+ }
+ mutex_unlock(&sbi->s_alloc_mutex);
+
+ return 0;
+}
+
+static int udf_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+ struct super_block *sb = dentry->d_sb;
+ struct udf_sb_info *sbi = UDF_SB(sb);
+ struct logicalVolIntegrityDescImpUse *lvidiu;
+ u64 id = huge_encode_dev(sb->s_bdev->bd_dev);
+
+ 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;
+ buf->f_fsid = u64_to_fsid(id);
+
+ return 0;
+}
+
+static unsigned int udf_count_free_bitmap(struct super_block *sb,
+ struct udf_bitmap *bitmap)
+{
+ struct buffer_head *bh = NULL;
+ unsigned int accum = 0;
+ int index;
+ udf_pblk_t block = 0, newblock;
+ struct kernel_lb_addr loc;
+ uint32_t bytes;
+ uint8_t *ptr;
+ uint16_t ident;
+ struct spaceBitmapDesc *bm;
+
+ loc.logicalBlockNum = bitmap->s_extPosition;
+ loc.partitionReferenceNum = UDF_SB(sb)->s_partition;
+ bh = udf_read_ptagged(sb, &loc, 0, &ident);
+
+ if (!bh) {
+ udf_err(sb, "udf_count_free failed\n");
+ goto out;
+ } else if (ident != TAG_IDENT_SBD) {
+ brelse(bh);
+ udf_err(sb, "udf_count_free failed\n");
+ goto out;
+ }
+
+ bm = (struct spaceBitmapDesc *)bh->b_data;
+ bytes = le32_to_cpu(bm->numOfBytes);
+ index = sizeof(struct spaceBitmapDesc); /* offset in first block only */
+ ptr = (uint8_t *)bh->b_data;
+
+ while (bytes > 0) {
+ u32 cur_bytes = min_t(u32, bytes, sb->s_blocksize - index);
+ accum += bitmap_weight((const unsigned long *)(ptr + index),
+ cur_bytes * 8);
+ bytes -= cur_bytes;
+ if (bytes) {
+ brelse(bh);
+ newblock = udf_get_lb_pblock(sb, &loc, ++block);
+ bh = sb_bread(sb, newblock);
+ if (!bh) {
+ udf_debug("read failed\n");
+ goto out;
+ }
+ index = 0;
+ ptr = (uint8_t *)bh->b_data;
+ }
+ }
+ brelse(bh);
+out:
+ return accum;
+}
+
+static unsigned int udf_count_free_table(struct super_block *sb,
+ struct inode *table)
+{
+ unsigned int accum = 0;
+ uint32_t elen;
+ struct kernel_lb_addr eloc;
+ struct extent_position epos;
+
+ mutex_lock(&UDF_SB(sb)->s_alloc_mutex);
+ epos.block = UDF_I(table)->i_location;
+ epos.offset = sizeof(struct unallocSpaceEntry);
+ epos.bh = NULL;
+
+ while (udf_next_aext(table, &epos, &eloc, &elen, 1) != -1)
+ accum += (elen >> table->i_sb->s_blocksize_bits);
+
+ brelse(epos.bh);
+ mutex_unlock(&UDF_SB(sb)->s_alloc_mutex);
+
+ return accum;
+}
+
+static unsigned int udf_count_free(struct super_block *sb)
+{
+ unsigned int accum = 0;
+ 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;
+ }
+
+ if (sbi->s_lvid_bh) {
+ struct logicalVolIntegrityDesc *lvid =
+ (struct logicalVolIntegrityDesc *)
+ sbi->s_lvid_bh->b_data;
+ if (le32_to_cpu(lvid->numOfPartitions) > part) {
+ accum = le32_to_cpu(
+ lvid->freeSpaceTable[part]);
+ if (accum == 0xFFFFFFFF)
+ accum = 0;
+ }
+ }
+
+ if (accum)
+ return accum;
+
+ 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 (accum)
+ return accum;
+
+ if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_TABLE) {
+ accum += udf_count_free_table(sb,
+ map->s_uspace.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
new file mode 100644
index 0000000000..f7eaf7b145
--- /dev/null
+++ b/fs/udf/symlink.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * symlink.c
+ *
+ * PURPOSE
+ * Symlink handling routines for the OSTA-UDF(tm) filesystem.
+ *
+ * COPYRIGHT
+ * (C) 1998-2001 Ben Fennema
+ * (C) 1999 Stelias Computing Inc
+ *
+ * HISTORY
+ *
+ * 04/16/99 blf Created.
+ *
+ */
+
+#include "udfdecl.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 "udf_i.h"
+
+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) {
+ elen += pc->lengthComponentIdent;
+ break;
+ }
+ 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:
+ 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;
+ }
+ }
+ if (p > to + 1)
+ p[-1] = '\0';
+ else
+ p[0] = '\0';
+ return 0;
+}
+
+static int udf_symlink_filler(struct file *file, struct folio *folio)
+{
+ struct page *page = &folio->page;
+ struct inode *inode = page->mapping->host;
+ struct buffer_head *bh = NULL;
+ unsigned char *symlink;
+ int err = 0;
+ unsigned char *p = page_address(page);
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ /* We don't support symlinks longer than one block */
+ if (inode->i_size > inode->i_sb->s_blocksize) {
+ err = -ENAMETOOLONG;
+ goto out_unlock;
+ }
+
+ if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
+ symlink = iinfo->i_data + iinfo->i_lenEAttr;
+ } else {
+ bh = udf_bread(inode, 0, 0, &err);
+ if (!bh) {
+ if (!err)
+ err = -EFSCORRUPTED;
+ goto out_err;
+ }
+ symlink = bh->b_data;
+ }
+
+ err = udf_pc_to_char(inode->i_sb, symlink, inode->i_size, p, PAGE_SIZE);
+ brelse(bh);
+ if (err)
+ goto out_err;
+
+ SetPageUptodate(page);
+ unlock_page(page);
+ return 0;
+
+out_err:
+ SetPageError(page);
+out_unlock:
+ unlock_page(page);
+ 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 page *page;
+
+ generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat);
+ page = read_mapping_page(inode->i_mapping, 0, NULL);
+ if (IS_ERR(page))
+ return PTR_ERR(page);
+ /*
+ * 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(page_address(page));
+ put_page(page);
+
+ return 0;
+}
+
+/*
+ * symlinks can't do much...
+ */
+const struct address_space_operations udf_symlink_aops = {
+ .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
new file mode 100644
index 0000000000..a686c10fd7
--- /dev/null
+++ b/fs/udf/truncate.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * truncate.c
+ *
+ * PURPOSE
+ * Truncate handling routines for the OSTA-UDF(tm) filesystem.
+ *
+ * COPYRIGHT
+ * (C) 1999-2004 Ben Fennema
+ * (C) 1999 Stelias Computing Inc
+ *
+ * HISTORY
+ *
+ * 02/24/99 blf Created.
+ *
+ */
+
+#include "udfdecl.h"
+#include <linux/fs.h>
+#include <linux/mm.h>
+
+#include "udf_i.h"
+#include "udf_sb.h"
+
+static void extent_trunc(struct inode *inode, struct extent_position *epos,
+ struct kernel_lb_addr *eloc, int8_t etype, uint32_t elen,
+ uint32_t nelen)
+{
+ struct kernel_lb_addr neloc = {};
+ int last_block = (elen + inode->i_sb->s_blocksize - 1) >>
+ inode->i_sb->s_blocksize_bits;
+ int first_block = (nelen + inode->i_sb->s_blocksize - 1) >>
+ inode->i_sb->s_blocksize_bits;
+
+ if (nelen) {
+ if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30)) {
+ udf_free_blocks(inode->i_sb, inode, eloc, 0,
+ last_block);
+ etype = (EXT_NOT_RECORDED_NOT_ALLOCATED >> 30);
+ } else
+ neloc = *eloc;
+ nelen = (etype << 30) | nelen;
+ }
+
+ if (elen != nelen) {
+ udf_write_aext(inode, epos, &neloc, nelen, 0);
+ if (last_block > first_block) {
+ if (etype == (EXT_RECORDED_ALLOCATED >> 30))
+ mark_inode_dirty(inode);
+
+ if (etype != (EXT_NOT_RECORDED_NOT_ALLOCATED >> 30))
+ udf_free_blocks(inode->i_sb, inode, eloc,
+ first_block,
+ last_block - first_block);
+ }
+ }
+}
+
+/*
+ * Truncate the last extent to match i_size. This function assumes
+ * that preallocation extent is already truncated.
+ */
+void udf_truncate_tail_extent(struct inode *inode)
+{
+ struct extent_position epos = {};
+ struct kernel_lb_addr eloc;
+ uint32_t elen, nelen;
+ uint64_t lbcount = 0;
+ int8_t etype = -1, netype;
+ int adsize;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB ||
+ inode->i_size == iinfo->i_lenExtents)
+ return;
+ /* Are we going to delete the file anyway? */
+ if (inode->i_nlink == 0)
+ 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
+ BUG();
+
+ /* Find the last extent in the file */
+ while ((netype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1) {
+ etype = netype;
+ lbcount += elen;
+ if (lbcount > inode->i_size) {
+ if (lbcount - inode->i_size >= inode->i_sb->s_blocksize)
+ udf_warn(inode->i_sb,
+ "Too long extent after EOF in inode %u: i_size: %lld lbcount: %lld extent %u+%u\n",
+ (unsigned)inode->i_ino,
+ (long long)inode->i_size,
+ (long long)lbcount,
+ (unsigned)eloc.logicalBlockNum,
+ (unsigned)elen);
+ nelen = elen - (lbcount - inode->i_size);
+ epos.offset -= adsize;
+ extent_trunc(inode, &epos, &eloc, etype, elen, nelen);
+ epos.offset += adsize;
+ if (udf_next_aext(inode, &epos, &eloc, &elen, 1) != -1)
+ udf_err(inode->i_sb,
+ "Extent after EOF in inode %u\n",
+ (unsigned)inode->i_ino);
+ break;
+ }
+ }
+ /* This inode entry is in-memory only and thus we don't have to mark
+ * the inode dirty */
+ iinfo->i_lenExtents = inode->i_size;
+ brelse(epos.bh);
+}
+
+void udf_discard_prealloc(struct inode *inode)
+{
+ 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;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+ int bsize = i_blocksize(inode);
+
+ if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB ||
+ ALIGN(inode->i_size, bsize) == ALIGN(iinfo->i_lenExtents, bsize))
+ return;
+
+ epos.block = iinfo->i_location;
+
+ /* Find the last extent in the file */
+ while (udf_next_aext(inode, &epos, &eloc, &elen, 0) != -1) {
+ brelse(prev_epos.bh);
+ prev_epos = epos;
+ if (prev_epos.bh)
+ get_bh(prev_epos.bh);
+
+ etype = udf_next_aext(inode, &epos, &eloc, &elen, 1);
+ lbcount += elen;
+ }
+ if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30)) {
+ lbcount -= elen;
+ 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;
+ brelse(epos.bh);
+ brelse(prev_epos.bh);
+}
+
+static void udf_update_alloc_ext_desc(struct inode *inode,
+ struct extent_position *epos,
+ u32 lenalloc)
+{
+ struct super_block *sb = inode->i_sb;
+ struct udf_sb_info *sbi = UDF_SB(sb);
+
+ struct allocExtDesc *aed = (struct allocExtDesc *) (epos->bh->b_data);
+ int len = sizeof(struct allocExtDesc);
+
+ aed->lengthAllocDescs = cpu_to_le32(lenalloc);
+ if (!UDF_QUERY_FLAG(sb, UDF_FLAG_STRICT) || sbi->s_udfrev >= 0x0201)
+ len += lenalloc;
+
+ udf_update_tag(epos->bh->b_data, len);
+ mark_buffer_dirty_inode(epos->bh, inode);
+}
+
+/*
+ * Truncate extents of inode to inode->i_size. This function can be used only
+ * for making file shorter. For making file longer, udf_extend_file() has to
+ * be used.
+ */
+int udf_truncate_extents(struct inode *inode)
+{
+ struct extent_position epos;
+ struct kernel_lb_addr eloc, neloc = {};
+ uint32_t elen, nelen = 0, indirect_ext_len = 0, lenalloc;
+ int8_t etype;
+ struct super_block *sb = inode->i_sb;
+ sector_t first_block = inode->i_size >> sb->s_blocksize_bits, offset;
+ loff_t byte_offset;
+ int adsize;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+
+ if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT)
+ adsize = sizeof(struct short_ad);
+ else if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_LONG)
+ adsize = sizeof(struct long_ad);
+ else
+ BUG();
+
+ etype = inode_bmap(inode, first_block, &epos, &eloc, &elen, &offset);
+ byte_offset = (offset << sb->s_blocksize_bits) +
+ (inode->i_size & (sb->s_blocksize - 1));
+ if (etype == -1) {
+ /* We should extend the file? */
+ WARN_ON(byte_offset);
+ return 0;
+ }
+ epos.offset -= adsize;
+ extent_trunc(inode, &epos, &eloc, etype, elen, byte_offset);
+ epos.offset += adsize;
+ if (byte_offset)
+ lenalloc = epos.offset;
+ else
+ lenalloc = epos.offset - adsize;
+
+ if (!epos.bh)
+ lenalloc -= udf_file_entry_alloc_offset(inode);
+ else
+ lenalloc -= sizeof(struct allocExtDesc);
+
+ while ((etype = udf_current_aext(inode, &epos, &eloc,
+ &elen, 0)) != -1) {
+ 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
+ * indirect extent - free it too */
+ BUG_ON(!epos.bh);
+ udf_free_blocks(sb, NULL, &epos.block,
+ 0, indirect_ext_len);
+ } else if (!epos.bh) {
+ iinfo->i_lenAlloc = lenalloc;
+ mark_inode_dirty(inode);
+ } else
+ udf_update_alloc_ext_desc(inode,
+ &epos, lenalloc);
+ brelse(epos.bh);
+ epos.offset = sizeof(struct allocExtDesc);
+ epos.block = eloc;
+ 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) >>
+ sb->s_blocksize_bits;
+ else
+ indirect_ext_len = 1;
+ } else {
+ extent_trunc(inode, &epos, &eloc, etype, elen, 0);
+ epos.offset += adsize;
+ }
+ }
+
+ if (indirect_ext_len) {
+ BUG_ON(!epos.bh);
+ udf_free_blocks(sb, NULL, &epos.block, 0, indirect_ext_len);
+ } else if (!epos.bh) {
+ iinfo->i_lenAlloc = lenalloc;
+ mark_inode_dirty(inode);
+ } else
+ udf_update_alloc_ext_desc(inode, &epos, lenalloc);
+ 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
new file mode 100644
index 0000000000..312b7c9ef1
--- /dev/null
+++ b/fs/udf/udf_i.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _UDF_I_H
+#define _UDF_I_H
+
+struct extent_position {
+ struct buffer_head *bh;
+ uint32_t offset;
+ struct kernel_lb_addr block;
+};
+
+struct udf_ext_cache {
+ /* Extent position */
+ struct extent_position epos;
+ /* Start logical offset in bytes */
+ loff_t lstart;
+};
+
+/*
+ * The i_data_sem and i_mutex serve for protection of allocation information
+ * of a regular files and symlinks. This includes all extents belonging to
+ * the file/symlink, a fact whether data are in-inode or in external data
+ * blocks, preallocation, goal block information... When extents are read,
+ * i_mutex or i_data_sem must be held (for reading is enough in case of
+ * i_data_sem). When extents are changed, i_data_sem must be held for writing
+ * and also i_mutex must be held.
+ *
+ * For directories i_mutex is used for all the necessary protection.
+ */
+
+struct udf_inode_info {
+ struct timespec64 i_crtime;
+ /* Physical address of inode */
+ struct kernel_lb_addr i_location;
+ __u64 i_unique;
+ __u32 i_lenEAttr;
+ __u32 i_lenAlloc;
+ __u64 i_lenExtents;
+ __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 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 */
+ spinlock_t i_extent_cache_lock;
+ struct inode vfs_inode;
+};
+
+static inline struct udf_inode_info *UDF_I(struct inode *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
new file mode 100644
index 0000000000..9af6ff7f97
--- /dev/null
+++ b/fs/udf/udf_sb.h
@@ -0,0 +1,184 @@
+/* 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>
+
+/*
+ * 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
+#define UDF_VERS_USE_EXTENDED_FE 0x0200
+#define UDF_FLAG_USE_STREAMS 1
+#define UDF_VERS_USE_STREAMS 0x0200
+#define UDF_FLAG_USE_SHORT_AD 2
+#define UDF_FLAG_USE_AD_IN_ICB 3
+#define UDF_FLAG_USE_FILE_CTIME_EA 4
+#define UDF_FLAG_STRICT 5
+#define UDF_FLAG_UNDELETE 6
+#define UDF_FLAG_UNHIDE 7
+#define UDF_FLAG_UID_FORGET 11 /* save -1 for uid to disk */
+#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_READ_ONLY 0x0010
+#define UDF_PART_FLAG_WRITE_ONCE 0x0020
+#define UDF_PART_FLAG_REWRITABLE 0x0040
+#define UDF_PART_FLAG_OVERWRITABLE 0x0080
+
+#define UDF_MAX_BLOCK_LOADED 8
+
+#define UDF_TYPE1_MAP15 0x1511U
+#define UDF_VIRTUAL_MAP15 0x1512U
+#define UDF_VIRTUAL_MAP20 0x2012U
+#define UDF_SPARABLE_MAP15 0x1522U
+#define UDF_METADATA_MAP25 0x2511U
+
+#define UDF_INVALID_MODE ((umode_t)-1)
+
+#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;
+ struct inode *s_bitmap_fe;
+};
+
+struct udf_sparing_data {
+ __u16 s_packet_len;
+ struct buffer_head *s_spar_map[4];
+};
+
+struct udf_virtual_data {
+ __u32 s_num_entries;
+ __u16 s_start_offset;
+};
+
+struct udf_bitmap {
+ __u32 s_extPosition;
+ int s_nr_groups;
+ struct buffer_head *s_block_bitmap[];
+};
+
+struct udf_part_map {
+ union {
+ struct udf_bitmap *s_bitmap;
+ struct inode *s_table;
+ } s_uspace;
+ __u32 s_partition_root;
+ __u32 s_partition_len;
+ __u16 s_partition_type;
+ __u16 s_partition_num;
+ union {
+ struct udf_sparing_data s_sparing;
+ struct udf_virtual_data s_virtual;
+ struct udf_meta_data s_metadata;
+ } s_type_specific;
+ __u32 (*s_partition_func)(struct super_block *, __u32, __u16, __u32);
+ __u16 s_volumeseqnum;
+ __u16 s_partition_flags;
+};
+
+#pragma pack()
+
+struct udf_sb_info {
+ struct udf_part_map *s_partmaps;
+ __u8 s_volume_ident[32];
+
+ /* Overall info */
+ __u16 s_partitions;
+ __u16 s_partition;
+
+ /* Sector headers */
+ __s32 s_session;
+ __u32 s_anchor;
+ __u32 s_last_block;
+
+ struct buffer_head *s_lvid_bh;
+
+ /* Default permissions */
+ umode_t s_umask;
+ kgid_t s_gid;
+ kuid_t s_uid;
+ umode_t s_fmode;
+ umode_t s_dmode;
+ /* Lock protecting consistency of above permission settings */
+ rwlock_t s_cred_lock;
+
+ /* Root Info */
+ struct timespec64 s_record_time;
+
+ /* Fileset Info */
+ __u16 s_serial_number;
+
+ /* highest UDF revision we have recorded to this media */
+ __u16 s_udfrev;
+
+ /* Miscellaneous flags */
+ unsigned long s_flags;
+
+ /* Encoding info */
+ struct nls_table *s_nls_map;
+
+ /* VAT inode */
+ struct inode *s_vat_inode;
+
+ struct mutex s_alloc_mutex;
+ /* Protected by s_alloc_mutex */
+ unsigned int s_lvid_dirty;
+};
+
+static inline struct udf_sb_info *UDF_SB(struct super_block *sb)
+{
+ return sb->s_fs_info;
+}
+
+struct logicalVolIntegrityDescImpUse *udf_sb_lvidiu(struct super_block *sb);
+
+int udf_compute_nr_groups(struct super_block *sb, u32 partition);
+
+static inline int UDF_QUERY_FLAG(struct super_block *sb, int flag)
+{
+ return test_bit(flag, &UDF_SB(sb)->s_flags);
+}
+
+static inline void UDF_SET_FLAG(struct super_block *sb, int flag)
+{
+ set_bit(flag, &UDF_SB(sb)->s_flags);
+}
+
+static inline void UDF_CLEAR_FLAG(struct super_block *sb, int flag)
+{
+ clear_bit(flag, &UDF_SB(sb)->s_flags);
+}
+
+#endif /* __LINUX_UDF_SB_H */
diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h
new file mode 100644
index 0000000000..88692512a4
--- /dev/null
+++ b/fs/udf/udfdecl.h
@@ -0,0 +1,257 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __UDF_DECL_H
+#define __UDF_DECL_H
+
+#define pr_fmt(fmt) "UDF-fs: " fmt
+
+#include "ecma_167.h"
+#include "osta_udf.h"
+
+#include <linux/fs.h>
+#include <linux/types.h>
+#include <linux/buffer_head.h>
+#include <linux/udf_fs_i.h>
+
+#include "udf_sb.h"
+#include "udfend.h"
+#include "udf_i.h"
+
+#define UDF_DEFAULT_PREALLOC_BLOCKS 8
+
+extern __printf(3, 4) void _udf_err(struct super_block *sb,
+ const char *function, const char *fmt, ...);
+#define udf_err(sb, fmt, ...) \
+ _udf_err(sb, __func__, fmt, ##__VA_ARGS__)
+
+extern __printf(3, 4) void _udf_warn(struct super_block *sb,
+ const char *function, const char *fmt, ...);
+#define udf_warn(sb, fmt, ...) \
+ _udf_warn(sb, __func__, fmt, ##__VA_ARGS__)
+
+#define udf_info(fmt, ...) \
+ pr_info("INFO " fmt, ##__VA_ARGS__)
+
+#define udf_debug(fmt, ...) \
+ 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 254
+#define UDF_NAME_LEN_CS0 255
+
+static inline size_t udf_file_entry_alloc_offset(struct inode *inode)
+{
+ struct udf_inode_info *iinfo = UDF_I(inode);
+ if (iinfo->i_use)
+ return sizeof(struct unallocSpaceEntry);
+ else if (iinfo->i_efe)
+ return sizeof(struct extendedFileEntry) + iinfo->i_lenEAttr;
+ else
+ return sizeof(struct fileEntry) + iinfo->i_lenEAttr;
+}
+
+static inline size_t udf_ext0_offset(struct inode *inode)
+{
+ if (UDF_I(inode)->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB)
+ return udf_file_entry_alloc_offset(inode);
+ else
+ return 0;
+}
+
+/* computes tag checksum */
+u8 udf_tag_checksum(const struct tag *t);
+
+typedef uint32_t udf_pblk_t;
+
+struct dentry;
+struct inode;
+struct task_struct;
+struct buffer_head;
+struct super_block;
+
+extern const struct export_operations udf_export_ops;
+extern const struct inode_operations udf_dir_inode_operations;
+extern const struct file_operations udf_dir_operations;
+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_symlink_aops;
+
+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 {
+ uint32_t block;
+ uint32_t volDescSeqNum;
+};
+
+struct generic_desc {
+ struct tag descTag;
+ __le32 volDescSeqNum;
+};
+
+
+/* super.c */
+
+static inline void udf_updated_lvid(struct super_block *sb)
+{
+ struct buffer_head *bh = UDF_SB(sb)->s_lvid_bh;
+
+ BUG_ON(!bh);
+ WARN_ON_ONCE(((struct logicalVolIntegrityDesc *)
+ bh->b_data)->integrityType !=
+ cpu_to_le32(LVID_INTEGRITY_TYPE_OPEN));
+ UDF_SB(sb)->s_lvid_dirty = 1;
+}
+extern u64 lvid_get_unique_id(struct super_block *sb);
+struct inode *udf_find_metadata_inode_efe(struct super_block *sb,
+ u32 meta_file_loc, u32 partition_num);
+
+/* namei.c */
+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 *,
+ 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_bread(struct inode *inode, udf_pblk_t block,
+ int create, int *err);
+extern int udf_setsize(struct inode *, loff_t);
+extern void udf_evict_inode(struct inode *);
+extern int udf_write_inode(struct inode *, struct writeback_control *wbc);
+extern int8_t inode_bmap(struct inode *, sector_t, struct extent_position *,
+ struct kernel_lb_addr *, uint32_t *, sector_t *);
+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);
+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 void udf_update_extra_perms(struct inode *inode, umode_t mode);
+
+/* misc.c */
+extern struct genericFormat *udf_add_extendedattr(struct inode *, uint32_t,
+ uint32_t, uint8_t);
+extern struct genericFormat *udf_get_extendedattr(struct inode *, uint32_t,
+ uint8_t);
+extern struct buffer_head *udf_read_tagged(struct super_block *, uint32_t,
+ uint32_t, uint16_t *);
+extern struct buffer_head *udf_read_ptagged(struct super_block *,
+ struct kernel_lb_addr *, uint32_t,
+ uint16_t *);
+extern void udf_update_tag(char *, int);
+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 *);
+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,
+ uint32_t);
+extern uint32_t udf_get_pblock_virt15(struct super_block *, uint32_t, uint16_t,
+ uint32_t);
+extern uint32_t udf_get_pblock_virt20(struct super_block *, uint32_t, uint16_t,
+ uint32_t);
+extern uint32_t udf_get_pblock_spar15(struct super_block *, uint32_t, uint16_t,
+ uint32_t);
+extern uint32_t udf_get_pblock_meta25(struct super_block *, uint32_t, uint16_t,
+ uint32_t);
+extern int udf_relocate_blocks(struct super_block *, long, long *);
+
+static inline uint32_t
+udf_get_lb_pblock(struct super_block *sb, struct kernel_lb_addr *loc,
+ uint32_t offset)
+{
+ return udf_get_pblock(sb, loc->logicalBlockNum,
+ loc->partitionReferenceNum, offset);
+}
+
+/* unicode.c */
+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);
+
+/* truncate.c */
+extern void udf_truncate_tail_extent(struct inode *);
+extern void udf_discard_prealloc(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 udf_pblk_t udf_new_block(struct super_block *sb, struct inode *inode,
+ uint16_t partition, uint32_t goal, int *err);
+
+/* directory.c */
+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 void udf_disk_stamp_to_time(struct timespec64 *dest,
+ struct timestamp 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
new file mode 100644
index 0000000000..a4363ac2cf
--- /dev/null
+++ b/fs/udf/udfend.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __UDF_ENDIAN_H
+#define __UDF_ENDIAN_H
+
+#include <asm/byteorder.h>
+#include <linux/string.h>
+
+static inline struct kernel_lb_addr lelb_to_cpu(struct lb_addr in)
+{
+ struct kernel_lb_addr out;
+
+ out.logicalBlockNum = le32_to_cpu(in.logicalBlockNum);
+ out.partitionReferenceNum = le16_to_cpu(in.partitionReferenceNum);
+
+ return out;
+}
+
+static inline struct lb_addr cpu_to_lelb(struct kernel_lb_addr in)
+{
+ struct lb_addr out;
+
+ out.logicalBlockNum = cpu_to_le32(in.logicalBlockNum);
+ out.partitionReferenceNum = cpu_to_le16(in.partitionReferenceNum);
+
+ return out;
+}
+
+static inline struct short_ad lesa_to_cpu(struct short_ad in)
+{
+ struct short_ad out;
+
+ out.extLength = le32_to_cpu(in.extLength);
+ out.extPosition = le32_to_cpu(in.extPosition);
+
+ return out;
+}
+
+static inline struct short_ad cpu_to_lesa(struct short_ad in)
+{
+ struct short_ad out;
+
+ out.extLength = cpu_to_le32(in.extLength);
+ out.extPosition = cpu_to_le32(in.extPosition);
+
+ return out;
+}
+
+static inline struct kernel_long_ad lela_to_cpu(struct long_ad in)
+{
+ struct kernel_long_ad out;
+
+ out.extLength = le32_to_cpu(in.extLength);
+ out.extLocation = lelb_to_cpu(in.extLocation);
+
+ return out;
+}
+
+static inline struct long_ad cpu_to_lela(struct kernel_long_ad in)
+{
+ struct long_ad out;
+
+ out.extLength = cpu_to_le32(in.extLength);
+ out.extLocation = cpu_to_lelb(in.extLocation);
+
+ return out;
+}
+
+static inline struct kernel_extent_ad leea_to_cpu(struct extent_ad in)
+{
+ struct kernel_extent_ad out;
+
+ out.extLength = le32_to_cpu(in.extLength);
+ out.extLocation = le32_to_cpu(in.extLocation);
+
+ return out;
+}
+
+#endif /* __UDF_ENDIAN_H */
diff --git a/fs/udf/udftime.c b/fs/udf/udftime.c
new file mode 100644
index 0000000000..758163af39
--- /dev/null
+++ b/fs/udf/udftime.c
@@ -0,0 +1,84 @@
+// 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). */
+
+/*
+ * dgb 10/02/98: ripped this from glibc source to help convert timestamps
+ * to unix time
+ * 10/04/98: added new table-based lookup after seeing how ugly
+ * the gnu code is
+ * blf 09/27/99: ripped out all the old code and inserted new table from
+ * John Brockmeyer (without leap second corrections)
+ * rewrote udf_stamp_to_time and fixed timezone accounting in
+ * udf_time_to_stamp.
+ */
+
+/*
+ * We don't take into account leap seconds. This may be correct or incorrect.
+ * For more NIST information (especially dealing with leap seconds), see:
+ * http://www.boulder.nist.gov/timefreq/pubs/bulletin/leapsecond.htm
+ */
+
+#include "udfdecl.h"
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/time.h>
+
+void
+udf_disk_stamp_to_time(struct timespec64 *dest, struct timestamp src)
+{
+ u16 typeAndTimezone = le16_to_cpu(src.typeAndTimezone);
+ u16 year = le16_to_cpu(src.year);
+ uint8_t type = typeAndTimezone >> 12;
+ int16_t offset;
+
+ if (type == 1) {
+ offset = typeAndTimezone << 4;
+ /* sign extent offset */
+ offset = (offset >> 4);
+ if (offset == -2047) /* unspecified offset */
+ offset = 0;
+ } else
+ offset = 0;
+
+ dest->tv_sec = mktime64(year, src.month, src.day, src.hour, src.minute,
+ src.second);
+ dest->tv_sec -= offset * 60;
+ dest->tv_nsec = 1000 * (src.centiseconds * 10000 +
+ src.hundredsOfMicroseconds * 100 + src.microseconds);
+ /*
+ * Sanitize nanosecond field since reportedly some filesystems are
+ * recorded with bogus sub-second values.
+ */
+ dest->tv_nsec %= NSEC_PER_SEC;
+}
+
+void
+udf_time_to_disk_stamp(struct timestamp *dest, struct timespec64 ts)
+{
+ time64_t seconds;
+ int16_t offset;
+ struct tm tm;
+
+ offset = -sys_tz.tz_minuteswest;
+
+ dest->typeAndTimezone = cpu_to_le16(0x1000 | (offset & 0x0FFF));
+
+ 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);
+}
+
+/* EOF */
diff --git a/fs/udf/unicode.c b/fs/udf/unicode.c
new file mode 100644
index 0000000000..32c7f3d27f
--- /dev/null
+++ b/fs/udf/unicode.c
@@ -0,0 +1,398 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * unicode.c
+ *
+ * PURPOSE
+ * Routines for converting between UTF-8 and OSTA Compressed Unicode.
+ * Also handles filename mangling
+ *
+ * DESCRIPTION
+ * OSTA Compressed Unicode is explained in the OSTA UDF specification.
+ * http://www.osta.org/
+ * UTF-8 is explained in the IETF RFC XXXX.
+ * ftp://ftp.internic.net/rfc/rfcxxxx.txt
+ *
+ */
+
+#include "udfdecl.h"
+
+#include <linux/kernel.h>
+#include <linux/string.h> /* for memset */
+#include <linux/nls.h>
+#include <linux/crc-itu-t.h>
+#include <linux/slab.h>
+
+#include "udf_sb.h"
+
+#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)
+
+#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
+
+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)
+{
+ 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;
+ }
+
+ /* Low surrogate must follow the high one... */
+ if (c & SURROGATE_LOW) {
+ c = UNICODE_MAX + 1;
+ goto out;
+ }
+
+ 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;
+ }
+
+ c = PLANE_SIZE +
+ ((c & SURROGATE_CHAR_MASK) << SURROGATE_CHAR_BITS) +
+ (next & SURROGATE_CHAR_MASK);
+ }
+out:
+ *ret = c;
+ return str_i_idx - start_idx;
+}
+
+
+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;
+ }
+
+ 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;
+}
+
+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)
+{
+ 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);
+
+ if (str_max_len <= 0)
+ return 0;
+
+ if (ocu_len == 0) {
+ memset(str_o, 0, str_max_len);
+ return 0;
+ }
+
+ 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;
+ }
+ u_ch = cmp_id >> 3;
+
+ ocu++;
+ ocu_len--;
+
+ if (ocu_len % u_ch) {
+ pr_err("incorrect filename length (%d)\n", ocu_len + 1);
+ return -EINVAL;
+ }
+
+ 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;
+ }
+ }
+ 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;
+ }
+ }
+ }
+
+ 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;
+ }
+
+ 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 str_o_len;
+}
+
+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)
+{
+ 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 *);
+
+ if (ocu_max_len <= 0)
+ return 0;
+
+ if (UDF_SB(sb)->s_nls_map)
+ conv_f = UDF_SB(sb)->s_nls_map->char2uni;
+ else
+ conv_f = NULL;
+
+ memset(ocu, 0, ocu_max_len);
+ ocu[0] = 8;
+ max_val = 0xff;
+ u_ch = 1;
+
+try_again:
+ 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 || uni_char > UNICODE_MAX) {
+ len = 1;
+ uni_char = '?';
+ }
+
+ if (uni_char > max_val) {
+ 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 == 0xffff)
+ ocu[u_len++] = (uint8_t)(uni_char >> 8);
+ ocu[u_len++] = (uint8_t)(uni_char & 0xff);
+ }
+
+ return u_len;
+}
+
+/*
+ * 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)
+{
+ 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;
+ }
+ }
+
+ return udf_name_from_CS0(sb, utf_o, o_len, ocu_i, s_len, 0);
+}
+
+int udf_get_filename(struct super_block *sb, const uint8_t *sname, int slen,
+ uint8_t *dname, int dlen)
+{
+ int ret;
+
+ if (!slen)
+ return -EIO;
+
+ if (dlen <= 0)
+ return 0;
+
+ ret = udf_name_from_CS0(sb, dname, dlen, sname, slen, 1);
+ /* Zero length filename isn't valid... */
+ if (ret == 0)
+ ret = -EINVAL;
+ return ret;
+}
+
+int udf_put_filename(struct super_block *sb, const uint8_t *sname, int slen,
+ uint8_t *dname, int dlen)
+{
+ return udf_name_to_CS0(sb, dname, dlen, sname, slen);
+}
+