From 3ae762a09cd72a08ef620c80fbb263693c3fb204 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 4 Jun 2019 14:49:25 +0100 Subject: [PATCH 01/12] fs/adfs: correct disc record structure Fill in some padding in the disc record structure, and add GCC packed and aligned attributes to ensure that it is correctly laid out. Signed-off-by: Russell King Signed-off-by: Al Viro --- include/uapi/linux/adfs_fs.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/uapi/linux/adfs_fs.h b/include/uapi/linux/adfs_fs.h index 151d93e27ed4..f1a7d67a7323 100644 --- a/include/uapi/linux/adfs_fs.h +++ b/include/uapi/linux/adfs_fs.h @@ -29,17 +29,17 @@ struct adfs_discrecord { __u8 log2sharesize:4; __u8 unused40:4; __u8 big_flag:1; - __u8 unused41:1; + __u8 unused41:7; __u8 nzones_high; + __u8 reserved43; __le32 format_version; __le32 root_size; __u8 unused52[60 - 52]; -}; +} __attribute__((packed, aligned(4))); #define ADFS_DISCRECORD (0xc00) #define ADFS_DR_OFFSET (0x1c0) #define ADFS_DR_SIZE 60 #define ADFS_DR_SIZE_BITS (ADFS_DR_SIZE << 3) - #endif /* _UAPI_ADFS_FS_H */ From 1dfdfc94730bde781c87b25fd606f6dfaffe9097 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 4 Jun 2019 14:49:30 +0100 Subject: [PATCH 02/12] fs/adfs: add helper to get discrecord from map Add a helper to get the disc record from the map, rather than open coding this in adfs_fill_super(). Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 10 ++++++++-- fs/adfs/dir_f.c | 1 - fs/adfs/dir_fplus.c | 1 - fs/adfs/map.c | 1 - fs/adfs/super.c | 3 +-- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 804c6a77c5db..5a72a0ea03bd 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -1,4 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0 */ +#include #include #include @@ -18,8 +19,6 @@ #include "dir_f.h" -struct buffer_head; - /* * adfs file system inode data in memory */ @@ -195,3 +194,10 @@ __adfs_block_map(struct super_block *sb, unsigned int object_id, return adfs_map_lookup(sb, object_id >> 8, block); } + +/* Return the disc record from the map */ +static inline +struct adfs_discrecord *adfs_map_discrecord(struct adfs_discmap *dm) +{ + return (void *)(dm[0].dm_bh->b_data + 4); +} diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index 693f69ed3de3..beedd275599f 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -9,7 +9,6 @@ * * E and F format directory handling */ -#include #include "adfs.h" #include "dir_f.h" diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index 97b9f28f459b..12ab34dad815 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -7,7 +7,6 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ -#include #include #include "adfs.h" #include "dir_fplus.h" diff --git a/fs/adfs/map.c b/fs/adfs/map.c index 6935f05202ac..5f2d9d775305 100644 --- a/fs/adfs/map.c +++ b/fs/adfs/map.c @@ -7,7 +7,6 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ -#include #include #include "adfs.h" diff --git a/fs/adfs/super.c b/fs/adfs/super.c index 2a83655c408f..533c9601a670 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -9,7 +9,6 @@ */ #include #include -#include #include #include #include @@ -463,7 +462,7 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) */ sb->s_op = &adfs_sops; - dr = (struct adfs_discrecord *)(asb->s_map[0].dm_bh->b_data + 4); + dr = adfs_map_discrecord(asb->s_map); root_obj.parent_id = root_obj.file_id = le32_to_cpu(dr->root); root_obj.name_len = 0; From 275f5b99d6d4e6fccb7cea6783460939856c1306 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 4 Jun 2019 14:49:36 +0100 Subject: [PATCH 03/12] fs/adfs: add helper to get filesystem size Add a helper to get the filesystem size from the disc record and eliminate the "s_size" member of the adfs superblock structure. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 7 ++++++- fs/adfs/super.c | 17 +++-------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 5a72a0ea03bd..ab13b5dd34a3 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -58,7 +58,6 @@ struct adfs_sb_info { __u32 s_ids_per_zone; /* max. no ids in one zone */ __u32 s_idlen; /* length of ID in map */ __u32 s_map_size; /* sector size of a map */ - unsigned long s_size; /* total size (in blocks) of this fs */ signed int s_map2blk; /* shift left by this for map->sector*/ unsigned int s_log2sharesize;/* log2 share size */ __le32 s_version; /* disc format version */ @@ -201,3 +200,9 @@ struct adfs_discrecord *adfs_map_discrecord(struct adfs_discmap *dm) { return (void *)(dm[0].dm_bh->b_data + 4); } + +static inline u64 adfs_disc_size(const struct adfs_discrecord *dr) +{ + return (u64)le32_to_cpu(dr->disc_size_high) << 32 | + le32_to_cpu(dr->disc_size); +} diff --git a/fs/adfs/super.c b/fs/adfs/super.c index 533c9601a670..7f6d00467baa 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -220,12 +220,13 @@ static int adfs_statfs(struct dentry *dentry, struct kstatfs *buf) { struct super_block *sb = dentry->d_sb; struct adfs_sb_info *sbi = ADFS_SB(sb); + struct adfs_discrecord *dr = adfs_map_discrecord(sbi->s_map); u64 id = huge_encode_dev(sb->s_bdev->bd_dev); buf->f_type = ADFS_SUPER_MAGIC; buf->f_namelen = sbi->s_namelen; buf->f_bsize = sb->s_blocksize; - buf->f_blocks = sbi->s_size; + buf->f_blocks = adfs_disc_size(dr) >> sb->s_blocksize_bits; buf->f_files = sbi->s_ids_per_zone * sbi->s_map_size; buf->f_bavail = buf->f_bfree = adfs_map_free(sb); @@ -329,8 +330,7 @@ static struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_di i = zone - 1; dm[0].dm_startblk = 0; dm[0].dm_startbit = ADFS_DR_SIZE_BITS; - dm[i].dm_endbit = (le32_to_cpu(dr->disc_size_high) << (32 - dr->log2bpmb)) + - (le32_to_cpu(dr->disc_size) >> dr->log2bpmb) + + dm[i].dm_endbit = (adfs_disc_size(dr) >> dr->log2bpmb) + (ADFS_DR_SIZE_BITS - i * zone_size); if (adfs_checkmap(sb, dm)) @@ -346,16 +346,6 @@ error_free: return ERR_PTR(-EIO); } -static inline unsigned long adfs_discsize(struct adfs_discrecord *dr, int block_bits) -{ - unsigned long discsize; - - discsize = le32_to_cpu(dr->disc_size_high) << (32 - block_bits); - discsize |= le32_to_cpu(dr->disc_size) >> block_bits; - - return discsize; -} - static int adfs_fill_super(struct super_block *sb, void *data, int silent) { struct adfs_discrecord *dr; @@ -445,7 +435,6 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) asb->s_idlen = dr->idlen; asb->s_map_size = dr->nzones | (dr->nzones_high << 8); asb->s_map2blk = dr->log2bpmb - dr->log2secsize; - asb->s_size = adfs_discsize(dr, sb->s_blocksize_bits); asb->s_version = dr->format_version; asb->s_log2sharesize = dr->log2sharesize; From cb88b5a387dd9ba9c36fd76c4cdc187cdce5974c Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 4 Jun 2019 14:49:41 +0100 Subject: [PATCH 04/12] fs/adfs: use format_version from disc_record We only use the format version in one place during filesystem mount, so it is pointless storing it in the superblock structure. Also, we should be using the version from the disc record in the map rather than the boot block. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 1 - fs/adfs/super.c | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index ab13b5dd34a3..1c31861aa115 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -60,7 +60,6 @@ struct adfs_sb_info { __u32 s_map_size; /* sector size of a map */ signed int s_map2blk; /* shift left by this for map->sector*/ unsigned int s_log2sharesize;/* log2 share size */ - __le32 s_version; /* disc format version */ unsigned int s_namelen; /* maximum number of characters in name */ }; diff --git a/fs/adfs/super.c b/fs/adfs/super.c index 7f6d00467baa..c5607685788e 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -435,7 +435,6 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) asb->s_idlen = dr->idlen; asb->s_map_size = dr->nzones | (dr->nzones_high << 8); asb->s_map2blk = dr->log2bpmb - dr->log2secsize; - asb->s_version = dr->format_version; asb->s_log2sharesize = dr->log2sharesize; asb->s_map = adfs_read_map(sb, dr); @@ -467,7 +466,7 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) * If this is a F+ disk with variable length directories, * get the root_size from the disc record. */ - if (asb->s_version) { + if (dr->format_version) { root_obj.size = le32_to_cpu(dr->root_size); asb->s_dir = &adfs_fplus_dir_ops; asb->s_namelen = ADFS_FPLUS_NAME_LEN; From 2e67080d87087fdba88059b1f63e4301ea0fad3a Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 4 Jun 2019 14:49:47 +0100 Subject: [PATCH 05/12] fs/adfs: use %pV for error messages Rather than using vsnprintf() with a temporary buffer on the stack, use %pV to print error messages. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/super.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/fs/adfs/super.c b/fs/adfs/super.c index c5607685788e..315657a3bac7 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -24,16 +24,18 @@ void __adfs_error(struct super_block *sb, const char *function, const char *fmt, ...) { - char error_buf[128]; + struct va_format vaf; va_list args; va_start(args, fmt); - vsnprintf(error_buf, sizeof(error_buf), fmt, args); - va_end(args); + vaf.fmt = fmt; + vaf.va = &args; - printk(KERN_CRIT "ADFS-fs error (device %s)%s%s: %s\n", + printk(KERN_CRIT "ADFS-fs error (device %s)%s%s: %pV\n", sb->s_id, function ? ": " : "", - function ? function : "", error_buf); + function ? function : "", &vaf); + + va_end(args); } static int adfs_checkdiscrecord(struct adfs_discrecord *dr) From ceb3b10613eba86ddf043345338e32673a27f87a Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 4 Jun 2019 14:49:52 +0100 Subject: [PATCH 06/12] fs/adfs: clean up error message printing Overhaul our message printing: - provide a consistent way to print messages: - filesystem corruption should be reported via adfs_error() - everything else should use adfs_msg() - clean up the error message printing when mounting a filesystem - fix the messages printed by the big directory format code to only use adfs_error() when there is filesystem corruption, otherwise use adfs_msg(). Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 1 + fs/adfs/dir_fplus.c | 18 ++++++++---------- fs/adfs/super.c | 45 +++++++++++++++++++++++++++++---------------- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 1c31861aa115..1e8865588a59 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -142,6 +142,7 @@ __printf(3, 4) void __adfs_error(struct super_block *sb, const char *function, const char *fmt, ...); #define adfs_error(sb, fmt...) __adfs_error(sb, __func__, fmt) +void adfs_msg(struct super_block *sb, const char *pfx, const char *fmt, ...); /* super.c */ diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index 12ab34dad815..02c54d85e77f 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -39,17 +39,15 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct h = (struct adfs_bigdirheader *)dir->bh_fplus[0]->b_data; size = le32_to_cpu(h->bigdirsize); if (size != sz) { - printk(KERN_WARNING "adfs: adfs_fplus_read:" - " directory header size %X\n" - " does not match directory size %X\n", - size, sz); + adfs_msg(sb, KERN_WARNING, + "directory header size %X does not match directory size %X", + size, sz); } if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || h->bigdirversion[2] != 0 || size & 2047 || h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) { - printk(KERN_WARNING "adfs: dir object %X has" - " malformed dir header\n", id); + adfs_error(sb, "dir %06x has malformed header", id); goto out; } @@ -60,9 +58,10 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct kcalloc(size, sizeof(struct buffer_head *), GFP_KERNEL); if (!bh_fplus) { + adfs_msg(sb, KERN_ERR, + "not enough memory for dir object %X (%d blocks)", + id, size); ret = -ENOMEM; - adfs_error(sb, "not enough memory for" - " dir object %X (%d blocks)", id, size); goto out; } dir->bh_fplus = bh_fplus; @@ -93,8 +92,7 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) || t->bigdirendmasseq != h->startmasseq || t->reserved[0] != 0 || t->reserved[1] != 0) { - printk(KERN_WARNING "adfs: dir object %X has " - "malformed dir end\n", id); + adfs_error(sb, "dir %06x has malformed tail", id); goto out; } diff --git a/fs/adfs/super.c b/fs/adfs/super.c index 315657a3bac7..6910a9afa9fd 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -38,6 +38,18 @@ void __adfs_error(struct super_block *sb, const char *function, const char *fmt, va_end(args); } +void adfs_msg(struct super_block *sb, const char *pfx, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + printk("%sADFS-fs (%s): %pV\n", pfx, sb->s_id, &vaf); + va_end(args); +} + static int adfs_checkdiscrecord(struct adfs_discrecord *dr) { int i; @@ -203,8 +215,9 @@ static int parse_options(struct super_block *sb, char *options) asb->s_ftsuffix = option; break; default: - printk("ADFS-fs: unrecognised mount option \"%s\" " - "or missing value\n", p); + adfs_msg(sb, KERN_ERR, + "unrecognised mount option \"%s\" or missing value", + p); return -EINVAL; } } @@ -377,7 +390,7 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) sb_set_blocksize(sb, BLOCK_SIZE); if (!(bh = sb_bread(sb, ADFS_DISCRECORD / BLOCK_SIZE))) { - adfs_error(sb, "unable to read superblock"); + adfs_msg(sb, KERN_ERR, "error: unable to read superblock"); ret = -EIO; goto error; } @@ -385,11 +398,8 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) b_data = bh->b_data + (ADFS_DISCRECORD % BLOCK_SIZE); if (adfs_checkbblk(b_data)) { - if (!silent) - printk("VFS: Can't find an adfs filesystem on dev " - "%s.\n", sb->s_id); ret = -EINVAL; - goto error_free_bh; + goto error_badfs; } dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET); @@ -398,33 +408,31 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) * Do some sanity checks on the ADFS disc record */ if (adfs_checkdiscrecord(dr)) { - if (!silent) - printk("VPS: Can't find an adfs filesystem on dev " - "%s.\n", sb->s_id); ret = -EINVAL; - goto error_free_bh; + goto error_badfs; } brelse(bh); if (sb_set_blocksize(sb, 1 << dr->log2secsize)) { bh = sb_bread(sb, ADFS_DISCRECORD / sb->s_blocksize); if (!bh) { - adfs_error(sb, "couldn't read superblock on " - "2nd try."); + adfs_msg(sb, KERN_ERR, + "error: couldn't read superblock on 2nd try."); ret = -EIO; goto error; } b_data = bh->b_data + (ADFS_DISCRECORD % sb->s_blocksize); if (adfs_checkbblk(b_data)) { - adfs_error(sb, "disc record mismatch, very weird!"); + adfs_msg(sb, KERN_ERR, + "error: disc record mismatch, very weird!"); ret = -EINVAL; goto error_free_bh; } dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET); } else { if (!silent) - printk(KERN_ERR "VFS: Unsupported blocksize on dev " - "%s.\n", sb->s_id); + adfs_msg(sb, KERN_ERR, + "error: unsupported blocksize"); ret = -EINVAL; goto error; } @@ -497,6 +505,11 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) } return 0; +error_badfs: + if (!silent) + adfs_msg(sb, KERN_ERR, + "error: can't find an ADFS filesystem on dev %s.", + sb->s_id); error_free_bh: brelse(bh); error: From 5ed70bb47767d1f57a5e85e585a327917ded0373 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 4 Jun 2019 14:49:57 +0100 Subject: [PATCH 07/12] fs/adfs: clean up indirect disc addresses and fragment IDs We use a variety of different names for the indirect disc address of the current object, use a variety of different types, and print it in a variety of different ways. Bring some consistency to this by naming it "indaddr", use u32 or __u32 as the type since it fits in 32-bits, and always print it with %06x (with no leading hex prefix.) When printing it was a directory identifer, use "dir %06x" otherwise use "object %06x". Do the same for fragment IDs and the parent indirect disc addresses. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 22 +++++++++++----------- fs/adfs/dir.c | 9 +++++---- fs/adfs/dir_f.c | 37 ++++++++++++++++--------------------- fs/adfs/dir_fplus.c | 2 +- fs/adfs/inode.c | 4 ++-- fs/adfs/map.c | 14 +++++--------- fs/adfs/super.c | 2 +- 7 files changed, 41 insertions(+), 49 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 1e8865588a59..9eb9bea1cef2 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -24,7 +24,7 @@ */ struct adfs_inode_info { loff_t mmu_private; - unsigned long parent_id; /* object id of parent */ + __u32 parent_id; /* parent indirect disc address */ __u32 loadaddr; /* RISC OS load address */ __u32 execaddr; /* RISC OS exec address */ unsigned int filetype; /* RISC OS file type */ @@ -86,7 +86,7 @@ struct adfs_dir { struct buffer_head **bh_fplus; unsigned int pos; - unsigned int parent_id; + __u32 parent_id; struct adfs_dirheader dirhead; union adfs_dirtail dirtail; @@ -98,7 +98,7 @@ struct adfs_dir { #define ADFS_MAX_NAME_LEN (256 + 4) /* +4 for ,xyz hex filetype suffix */ struct object_info { __u32 parent_id; /* parent object id */ - __u32 file_id; /* object id */ + __u32 indaddr; /* indirect disc addr */ __u32 loadaddr; /* load address */ __u32 execaddr; /* execution address */ __u32 size; /* size */ @@ -111,7 +111,8 @@ struct object_info { }; struct adfs_dir_ops { - int (*read)(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir); + int (*read)(struct super_block *sb, unsigned int indaddr, + unsigned int size, struct adfs_dir *dir); int (*setpos)(struct adfs_dir *dir, unsigned int fpos); int (*getnext)(struct adfs_dir *dir, struct object_info *obj); int (*update)(struct adfs_dir *dir, struct object_info *obj); @@ -134,7 +135,7 @@ int adfs_write_inode(struct inode *inode, struct writeback_control *wbc); int adfs_notify_change(struct dentry *dentry, struct iattr *attr); /* map.c */ -extern int adfs_map_lookup(struct super_block *sb, unsigned int frag_id, unsigned int offset); +int adfs_map_lookup(struct super_block *sb, u32 frag_id, unsigned int offset); extern unsigned int adfs_map_free(struct super_block *sb); /* Misc */ @@ -180,18 +181,17 @@ static inline __u32 signed_asl(__u32 val, signed int shift) * * The root directory ID should always be looked up in the map [3.4] */ -static inline int -__adfs_block_map(struct super_block *sb, unsigned int object_id, - unsigned int block) +static inline int __adfs_block_map(struct super_block *sb, u32 indaddr, + unsigned int block) { - if (object_id & 255) { + if (indaddr & 255) { unsigned int off; - off = (object_id & 255) - 1; + off = (indaddr & 255) - 1; block += off << ADFS_SB(sb)->s_log2sharesize; } - return adfs_map_lookup(sb, object_id >> 8, block); + return adfs_map_lookup(sb, indaddr >> 8, block); } /* Return the disc record from the map */ diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index fe39310c1a0a..01ffd47c7461 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -95,7 +95,7 @@ adfs_readdir(struct file *file, struct dir_context *ctx) goto unlock_out; while (ops->getnext(&dir, &obj) == 0) { if (!dir_emit(ctx, obj.name, obj.name_len, - obj.file_id, DT_UNKNOWN)) + obj.indaddr, DT_UNKNOWN)) break; ctx->pos++; } @@ -116,8 +116,8 @@ adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; struct adfs_dir dir; - printk(KERN_INFO "adfs_dir_update: object %06X in dir %06X\n", - obj->file_id, obj->parent_id); + printk(KERN_INFO "adfs_dir_update: object %06x in dir %06x\n", + obj->indaddr, obj->parent_id); if (!ops->update) { ret = -EINVAL; @@ -181,7 +181,8 @@ static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr, goto out; if (ADFS_I(inode)->parent_id != dir.parent_id) { - adfs_error(sb, "parent directory changed under me! (%lx but got %x)\n", + adfs_error(sb, + "parent directory changed under me! (%06x but got %06x)\n", ADFS_I(inode)->parent_id, dir.parent_id); ret = -EIO; goto free_out; diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index beedd275599f..67afb3808e43 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -126,12 +126,9 @@ adfs_dir_checkbyte(const struct adfs_dir *dir) return (dircheck ^ (dircheck >> 8) ^ (dircheck >> 16) ^ (dircheck >> 24)) & 0xff; } -/* - * Read and check that a directory is valid - */ -static int -adfs_dir_read(struct super_block *sb, unsigned long object_id, - unsigned int size, struct adfs_dir *dir) +/* Read and check that a directory is valid */ +static int adfs_dir_read(struct super_block *sb, u32 indaddr, + unsigned int size, struct adfs_dir *dir) { const unsigned int blocksize_bits = sb->s_blocksize_bits; int blk = 0; @@ -151,10 +148,10 @@ adfs_dir_read(struct super_block *sb, unsigned long object_id, for (blk = 0; blk < size; blk++) { int phys; - phys = __adfs_block_map(sb, object_id, blk); + phys = __adfs_block_map(sb, indaddr, blk); if (!phys) { - adfs_error(sb, "dir object %lX has a hole at offset %d", - object_id, blk); + adfs_error(sb, "dir %06x has a hole at offset %d", + indaddr, blk); goto release_buffers; } @@ -182,8 +179,7 @@ adfs_dir_read(struct super_block *sb, unsigned long object_id, return 0; bad_dir: - adfs_error(sb, "corrupted directory fragment %lX", - object_id); + adfs_error(sb, "dir %06x is corrupted", indaddr); release_buffers: for (blk -= 1; blk >= 0; blk -= 1) brelse(dir->bh[blk]); @@ -210,7 +206,7 @@ adfs_dir2obj(struct adfs_dir *dir, struct object_info *obj, } obj->name_len = name_len; - obj->file_id = adfs_readval(de->dirinddiscadd, 3); + obj->indaddr = adfs_readval(de->dirinddiscadd, 3); obj->loadaddr = adfs_readval(de->dirload, 4); obj->execaddr = adfs_readval(de->direxec, 4); obj->size = adfs_readval(de->dirlen, 4); @@ -225,7 +221,7 @@ adfs_dir2obj(struct adfs_dir *dir, struct object_info *obj, static inline void adfs_obj2dir(struct adfs_direntry *de, struct object_info *obj) { - adfs_writeval(de->dirinddiscadd, 3, obj->file_id); + adfs_writeval(de->dirinddiscadd, 3, obj->indaddr); adfs_writeval(de->dirload, 4, obj->loadaddr); adfs_writeval(de->direxec, 4, obj->execaddr); adfs_writeval(de->dirlen, 4, obj->size); @@ -311,8 +307,7 @@ __adfs_dir_put(struct adfs_dir *dir, int pos, struct object_info *obj) * the caller is responsible for holding the necessary * locks. */ -static int -adfs_dir_find_entry(struct adfs_dir *dir, unsigned long object_id) +static int adfs_dir_find_entry(struct adfs_dir *dir, u32 indaddr) { int pos, ret; @@ -324,7 +319,7 @@ adfs_dir_find_entry(struct adfs_dir *dir, unsigned long object_id) if (!__adfs_dir_get(dir, pos, &obj)) break; - if (obj.file_id == object_id) { + if (obj.indaddr == indaddr) { ret = pos; break; } @@ -333,15 +328,15 @@ adfs_dir_find_entry(struct adfs_dir *dir, unsigned long object_id) return ret; } -static int -adfs_f_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir) +static int adfs_f_read(struct super_block *sb, u32 indaddr, unsigned int size, + struct adfs_dir *dir) { int ret; - if (sz != ADFS_NEWDIR_SIZE) + if (size != ADFS_NEWDIR_SIZE) return -EIO; - ret = adfs_dir_read(sb, id, sz, dir); + ret = adfs_dir_read(sb, indaddr, size, dir); if (ret) adfs_error(sb, "unable to read directory"); else @@ -378,7 +373,7 @@ adfs_f_update(struct adfs_dir *dir, struct object_info *obj) struct super_block *sb = dir->sb; int ret, i; - ret = adfs_dir_find_entry(dir, obj->file_id); + ret = adfs_dir_find_entry(dir, obj->indaddr); if (ret < 0) { adfs_error(dir->sb, "unable to locate entry to update"); goto out; diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index 02c54d85e77f..973282fc4758 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -180,7 +180,7 @@ adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) obj->loadaddr = le32_to_cpu(bde.bigdirload); obj->execaddr = le32_to_cpu(bde.bigdirexec); obj->size = le32_to_cpu(bde.bigdirlen); - obj->file_id = le32_to_cpu(bde.bigdirindaddr); + obj->indaddr = le32_to_cpu(bde.bigdirindaddr); obj->attr = le32_to_cpu(bde.bigdirattr); obj->name_len = le32_to_cpu(bde.bigdirobnamelen); diff --git a/fs/adfs/inode.c b/fs/adfs/inode.c index 66621e96f9af..5f5af660b02e 100644 --- a/fs/adfs/inode.c +++ b/fs/adfs/inode.c @@ -250,7 +250,7 @@ adfs_iget(struct super_block *sb, struct object_info *obj) inode->i_uid = ADFS_SB(sb)->s_uid; inode->i_gid = ADFS_SB(sb)->s_gid; - inode->i_ino = obj->file_id; + inode->i_ino = obj->indaddr; inode->i_size = obj->size; set_nlink(inode, 2); inode->i_blocks = (inode->i_size + sb->s_blocksize - 1) >> @@ -358,7 +358,7 @@ int adfs_write_inode(struct inode *inode, struct writeback_control *wbc) struct object_info obj; int ret; - obj.file_id = inode->i_ino; + obj.indaddr = inode->i_ino; obj.name_len = 0; obj.parent_id = ADFS_I(inode)->parent_id; obj.loadaddr = ADFS_I(inode)->loadaddr; diff --git a/fs/adfs/map.c b/fs/adfs/map.c index 5f2d9d775305..e8f70f7c384e 100644 --- a/fs/adfs/map.c +++ b/fs/adfs/map.c @@ -66,9 +66,8 @@ static DEFINE_RWLOCK(adfs_map_lock); * output of: * gcc -D__KERNEL__ -O2 -I../../include -o - -S map.c */ -static int -lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen, - const unsigned int frag_id, unsigned int *offset) +static int lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen, + const u32 frag_id, unsigned int *offset) { const unsigned int mapsize = dm->dm_endbit; const u32 idmask = (1 << idlen) - 1; @@ -187,9 +186,8 @@ error: return 0; } -static int -scan_map(struct adfs_sb_info *asb, unsigned int zone, - const unsigned int frag_id, unsigned int mapoff) +static int scan_map(struct adfs_sb_info *asb, unsigned int zone, + const u32 frag_id, unsigned int mapoff) { const unsigned int idlen = asb->s_idlen; struct adfs_discmap *dm, *dm_end; @@ -243,9 +241,7 @@ adfs_map_free(struct super_block *sb) return signed_asl(total, asb->s_map2blk); } -int -adfs_map_lookup(struct super_block *sb, unsigned int frag_id, - unsigned int offset) +int adfs_map_lookup(struct super_block *sb, u32 frag_id, unsigned int offset) { struct adfs_sb_info *asb = ADFS_SB(sb); unsigned int zone, mapoff; diff --git a/fs/adfs/super.c b/fs/adfs/super.c index 6910a9afa9fd..4e913124fc2d 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -462,7 +462,7 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) dr = adfs_map_discrecord(asb->s_map); - root_obj.parent_id = root_obj.file_id = le32_to_cpu(dr->root); + root_obj.parent_id = root_obj.indaddr = le32_to_cpu(dr->root); root_obj.name_len = 0; /* Set root object date as 01 Jan 1987 00:00:00 */ root_obj.loadaddr = 0xfff0003f; From 421d3c0faa28890dbfb7d2a67f067f07c1123556 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 4 Jun 2019 14:50:03 +0100 Subject: [PATCH 08/12] fs/adfs: super: correct superblock flags We don't support atime updates of any kind, and we ought to set the read-only bit if we are compiled without write support. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/super.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/adfs/super.c b/fs/adfs/super.c index 4e913124fc2d..b393905abe13 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -19,6 +19,8 @@ #include "dir_f.h" #include "dir_fplus.h" +#define ADFS_SB_FLAGS SB_NOATIME + #define ADFS_DEFAULT_OWNER_MASK S_IRWXU #define ADFS_DEFAULT_OTHER_MASK (S_IRWXG | S_IRWXO) @@ -227,7 +229,7 @@ static int parse_options(struct super_block *sb, char *options) static int adfs_remount(struct super_block *sb, int *flags, char *data) { sync_filesystem(sb); - *flags |= SB_NODIRATIME; + *flags |= ADFS_SB_FLAGS; return parse_options(sb, data); } @@ -371,7 +373,7 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) struct inode *root; int ret = -EINVAL; - sb->s_flags |= SB_NODIRATIME; + sb->s_flags |= ADFS_SB_FLAGS; asb = kzalloc(sizeof(*asb), GFP_KERNEL); if (!asb) From 4c5762f5f5e31d493678b0ee6f73585355dd3638 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 4 Jun 2019 14:50:09 +0100 Subject: [PATCH 09/12] fs/adfs: super: safely update options on remount Only update the options on remount if we successfully parse all options, rather than updating those we've managed to parse. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/super.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/fs/adfs/super.c b/fs/adfs/super.c index b393905abe13..2f81c1c29757 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -170,10 +170,10 @@ static const match_table_t tokens = { {Opt_err, NULL} }; -static int parse_options(struct super_block *sb, char *options) +static int parse_options(struct super_block *sb, struct adfs_sb_info *asb, + char *options) { char *p; - struct adfs_sb_info *asb = ADFS_SB(sb); int option; if (!options) @@ -228,9 +228,18 @@ static int parse_options(struct super_block *sb, char *options) static int adfs_remount(struct super_block *sb, int *flags, char *data) { + struct adfs_sb_info temp_asb; + int ret; + sync_filesystem(sb); *flags |= ADFS_SB_FLAGS; - return parse_options(sb, data); + + temp_asb = *ADFS_SB(sb); + ret = parse_options(sb, &temp_asb, data); + if (ret == 0) + *ADFS_SB(sb) = temp_asb; + + return ret; } static int adfs_statfs(struct dentry *dentry, struct kstatfs *buf) @@ -387,7 +396,7 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) asb->s_other_mask = ADFS_DEFAULT_OTHER_MASK; asb->s_ftsuffix = 0; - if (parse_options(sb, data)) + if (parse_options(sb, asb, data)) goto error; sb_set_blocksize(sb, BLOCK_SIZE); From 5808b14a1f52554de612fee85ef517199855e310 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 4 Jun 2019 14:50:14 +0100 Subject: [PATCH 10/12] fs/adfs: super: fix use-after-free bug Fix a use-after-free bug during filesystem initialisation, where we access the disc record (which is stored in a buffer) after we have released the buffer. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/super.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/adfs/super.c b/fs/adfs/super.c index 2f81c1c29757..b1243433add7 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -378,6 +378,7 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) struct buffer_head *bh; struct object_info root_obj; unsigned char *b_data; + unsigned int blocksize; struct adfs_sb_info *asb; struct inode *root; int ret = -EINVAL; @@ -423,8 +424,10 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) goto error_badfs; } + blocksize = 1 << dr->log2secsize; brelse(bh); - if (sb_set_blocksize(sb, 1 << dr->log2secsize)) { + + if (sb_set_blocksize(sb, blocksize)) { bh = sb_bread(sb, ADFS_DISCRECORD / sb->s_blocksize); if (!bh) { adfs_msg(sb, KERN_ERR, From 8616108de152447f99570dd45b6e3b8c71615fe5 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 4 Jun 2019 14:50:19 +0100 Subject: [PATCH 11/12] fs/adfs: super: limit idlen according to directory type Limit idlen according to the directory type, as idlen (the size of a fragment ID) can not be more than 16 with the "new directory" layout. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/super.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/adfs/super.c b/fs/adfs/super.c index b1243433add7..d029ae10f8a0 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -54,6 +54,7 @@ void adfs_msg(struct super_block *sb, const char *pfx, const char *fmt, ...) static int adfs_checkdiscrecord(struct adfs_discrecord *dr) { + unsigned int max_idlen; int i; /* sector size must be 256, 512 or 1024 bytes */ @@ -73,8 +74,13 @@ static int adfs_checkdiscrecord(struct adfs_discrecord *dr) if (le32_to_cpu(dr->disc_size_high) >> dr->log2secsize) return 1; - /* idlen must be no greater than 19 v2 [1.0] */ - if (dr->idlen > 19) + /* + * Maximum idlen is limited to 16 bits for new directories by + * the three-byte storage of an indirect disc address. For + * big directories, idlen must be no greater than 19 v2 [1.0] + */ + max_idlen = dr->format_version ? 19 : 16; + if (dr->idlen > max_idlen) return 1; /* reserved bytes should be zero */ From b4ed8f75c82876342b3399942427392ba5f3bbb5 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 4 Jun 2019 14:50:24 +0100 Subject: [PATCH 12/12] fs/adfs: add time stamp and file type helpers Add some helpers to check whether the inode has a time stamp and file type, and to parse the file type from the load address. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 29 +++++++++++++++++++---------- fs/adfs/dir.c | 16 +++++----------- fs/adfs/inode.c | 8 +++----- fs/adfs/super.c | 1 - 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 9eb9bea1cef2..b7e844d2f321 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -9,6 +9,15 @@ #define ADFS_BAD_FRAG 1 #define ADFS_ROOT_FRAG 2 +#define ADFS_FILETYPE_NONE ((u16)~0) + +/* RISC OS 12-bit filetype is stored in load_address[19:8] */ +static inline u16 adfs_filetype(u32 loadaddr) +{ + return (loadaddr & 0xfff00000) == 0xfff00000 ? + (loadaddr >> 8) & 0xfff : ADFS_FILETYPE_NONE; +} + #define ADFS_NDA_OWNER_READ (1 << 0) #define ADFS_NDA_OWNER_WRITE (1 << 1) #define ADFS_NDA_LOCKED (1 << 2) @@ -27,12 +36,20 @@ struct adfs_inode_info { __u32 parent_id; /* parent indirect disc address */ __u32 loadaddr; /* RISC OS load address */ __u32 execaddr; /* RISC OS exec address */ - unsigned int filetype; /* RISC OS file type */ unsigned int attr; /* RISC OS permissions */ - unsigned int stamped:1; /* RISC OS file has date/time */ struct inode vfs_inode; }; +static inline struct adfs_inode_info *ADFS_I(struct inode *inode) +{ + return container_of(inode, struct adfs_inode_info, vfs_inode); +} + +static inline bool adfs_inode_is_stamped(struct inode *inode) +{ + return (ADFS_I(inode)->loadaddr & 0xfff00000) == 0xfff00000; +} + /* * Forward-declare this */ @@ -68,11 +85,6 @@ static inline struct adfs_sb_info *ADFS_SB(struct super_block *sb) return sb->s_fs_info; } -static inline struct adfs_inode_info *ADFS_I(struct inode *inode) -{ - return container_of(inode, struct adfs_inode_info, vfs_inode); -} - /* * Directory handling */ @@ -105,9 +117,6 @@ struct object_info { __u8 attr; /* RISC OS attributes */ unsigned int name_len; /* name length */ char name[ADFS_MAX_NAME_LEN];/* file name */ - - /* RISC OS file type (12-bit: derived from loadaddr) */ - __u16 filetype; }; struct adfs_dir_ops { diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index 01ffd47c7461..77503d12f7ee 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -38,20 +38,14 @@ void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj) if (obj->name_len <= 2 && dots == obj->name_len) obj->name[0] = '^'; - obj->filetype = -1; - /* - * object is a file and is filetyped and timestamped? - * RISC OS 12-bit filetype is stored in load_address[19:8] + * If the object is a file, and the user requested the ,xyz hex + * filetype suffix to the name, check the filetype and append. */ - if ((0 == (obj->attr & ADFS_NDA_DIRECTORY)) && - (0xfff00000 == (0xfff00000 & obj->loadaddr))) { - obj->filetype = (__u16) ((0x000fff00 & obj->loadaddr) >> 8); - - /* optionally append the ,xyz hex filetype suffix */ - if (ADFS_SB(dir->sb)->s_ftsuffix) { - __u16 filetype = obj->filetype; + if (!(obj->attr & ADFS_NDA_DIRECTORY) && ADFS_SB(dir->sb)->s_ftsuffix) { + u16 filetype = adfs_filetype(obj->loadaddr); + if (filetype != ADFS_FILETYPE_NONE) { obj->name[obj->name_len++] = ','; obj->name[obj->name_len++] = hex_asc_lo(filetype >> 8); obj->name[obj->name_len++] = hex_asc_lo(filetype >> 4); diff --git a/fs/adfs/inode.c b/fs/adfs/inode.c index 5f5af660b02e..d61c7453a4c3 100644 --- a/fs/adfs/inode.c +++ b/fs/adfs/inode.c @@ -97,7 +97,7 @@ adfs_atts2mode(struct super_block *sb, struct inode *inode) return S_IFDIR | S_IXUGO | mode; } - switch (ADFS_I(inode)->filetype) { + switch (adfs_filetype(ADFS_I(inode)->loadaddr)) { case 0xfc0: /* LinkFS */ return S_IFLNK|S_IRWXUGO; @@ -177,7 +177,7 @@ adfs_adfs2unix_time(struct timespec64 *tv, struct inode *inode) 2208988800000000000LL; s64 nsec; - if (ADFS_I(inode)->stamped == 0) + if (!adfs_inode_is_stamped(inode)) goto cur_time; high = ADFS_I(inode)->loadaddr & 0xFF; /* top 8 bits of timestamp */ @@ -216,7 +216,7 @@ adfs_unix2adfs_time(struct inode *inode, unsigned int secs) { unsigned int high, low; - if (ADFS_I(inode)->stamped) { + if (adfs_inode_is_stamped(inode)) { /* convert 32-bit seconds to 40-bit centi-seconds */ low = (secs & 255) * 100; high = (secs / 256) * 100 + (low >> 8) + 0x336e996a; @@ -266,8 +266,6 @@ adfs_iget(struct super_block *sb, struct object_info *obj) ADFS_I(inode)->loadaddr = obj->loadaddr; ADFS_I(inode)->execaddr = obj->execaddr; ADFS_I(inode)->attr = obj->attr; - ADFS_I(inode)->filetype = obj->filetype; - ADFS_I(inode)->stamped = ((obj->loadaddr & 0xfff00000) == 0xfff00000); inode->i_mode = adfs_atts2mode(sb, inode); adfs_adfs2unix_time(&inode->i_mtime, inode); diff --git a/fs/adfs/super.c b/fs/adfs/super.c index d029ae10f8a0..89b159c02b51 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -490,7 +490,6 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) root_obj.size = ADFS_NEWDIR_SIZE; root_obj.attr = ADFS_NDA_DIRECTORY | ADFS_NDA_OWNER_READ | ADFS_NDA_OWNER_WRITE | ADFS_NDA_PUBLIC_READ; - root_obj.filetype = -1; /* * If this is a F+ disk with variable length directories,