From eeeb9dd98ec353a19988b010223f29a8783127fa Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:08:18 +0000 Subject: [PATCH 01/42] fs/adfs: inode: update timestamps to centisecond precision Despite ADFS timestamps having centi-second granularity, and Linux gaining fine-grained timestamp support in v2.5.48, fs/adfs was never updated. Update fs/adfs to centi-second support, and ensure that the inode ctime always reflects what is written in underlying media. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/inode.c | 40 ++++++++++++++++++++-------------------- fs/adfs/super.c | 2 ++ 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/fs/adfs/inode.c b/fs/adfs/inode.c index 124de75413a5..18a1d478669b 100644 --- a/fs/adfs/inode.c +++ b/fs/adfs/inode.c @@ -158,6 +158,8 @@ adfs_mode2atts(struct super_block *sb, struct inode *inode) return attr; } +static const s64 nsec_unix_epoch_diff_risc_os_epoch = 2208988800000000000LL; + /* * Convert an ADFS time to Unix time. ADFS has a 40-bit centi-second time * referenced to 1 Jan 1900 (til 2248) so we need to discard 2208988800 seconds @@ -170,8 +172,6 @@ adfs_adfs2unix_time(struct timespec64 *tv, struct inode *inode) /* 01 Jan 1970 00:00:00 (Unix epoch) as nanoseconds since * 01 Jan 1900 00:00:00 (RISC OS epoch) */ - static const s64 nsec_unix_epoch_diff_risc_os_epoch = - 2208988800000000000LL; s64 nsec; if (!adfs_inode_is_stamped(inode)) @@ -204,24 +204,23 @@ adfs_adfs2unix_time(struct timespec64 *tv, struct inode *inode) return; } -/* - * Convert an Unix time to ADFS time. We only do this if the entry has a - * time/date stamp already. - */ -static void -adfs_unix2adfs_time(struct inode *inode, unsigned int secs) +/* Convert an Unix time to ADFS time for an entry that is already stamped. */ +static void adfs_unix2adfs_time(struct inode *inode, + const struct timespec64 *ts) { - unsigned int high, low; + s64 cs, nsec = timespec64_to_ns(ts); - 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; + /* convert from Unix to RISC OS epoch */ + nsec += nsec_unix_epoch_diff_risc_os_epoch; - ADFS_I(inode)->loadaddr = (high >> 24) | - (ADFS_I(inode)->loadaddr & ~0xff); - ADFS_I(inode)->execaddr = (low & 255) | (high << 8); - } + /* convert from nanoseconds to centiseconds */ + cs = div_s64(nsec, 10000000); + + cs = clamp_t(s64, cs, 0, 0xffffffffff); + + ADFS_I(inode)->loadaddr &= ~0xff; + ADFS_I(inode)->loadaddr |= (cs >> 32) & 0xff; + ADFS_I(inode)->execaddr = cs; } /* @@ -315,10 +314,11 @@ adfs_notify_change(struct dentry *dentry, struct iattr *attr) if (ia_valid & ATTR_SIZE) truncate_setsize(inode, attr->ia_size); - if (ia_valid & ATTR_MTIME) { - inode->i_mtime = attr->ia_mtime; - adfs_unix2adfs_time(inode, attr->ia_mtime.tv_sec); + if (ia_valid & ATTR_MTIME && adfs_inode_is_stamped(inode)) { + adfs_unix2adfs_time(inode, &attr->ia_mtime); + adfs_adfs2unix_time(&inode->i_mtime, inode); } + /* * FIXME: should we make these == to i_mtime since we don't * have the ability to represent them in our filesystem? diff --git a/fs/adfs/super.c b/fs/adfs/super.c index 65b04ebb51c3..e0eea9adb4e6 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -391,7 +391,9 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) asb = kzalloc(sizeof(*asb), GFP_KERNEL); if (!asb) return -ENOMEM; + sb->s_fs_info = asb; + sb->s_time_gran = 10000000; /* set default options */ asb->s_uid = GLOBAL_ROOT_UID; From 81916245ce231f7405ce444161d09b8a899a38af Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:08:23 +0000 Subject: [PATCH 02/42] fs/adfs: inode: fix adfs_mode2atts() Fix adfs_mode2atts() to actually update the file permissions on the media rather than using the current inode mode. Note also that directories do not have read/write permissions stored on the media. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/inode.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fs/adfs/inode.c b/fs/adfs/inode.c index 18a1d478669b..212a56fc7911 100644 --- a/fs/adfs/inode.c +++ b/fs/adfs/inode.c @@ -126,29 +126,29 @@ adfs_atts2mode(struct super_block *sb, struct inode *inode) * Convert Linux permission to ADFS attribute. We try to do the reverse * of atts2mode, but there is not a 1:1 translation. */ -static int -adfs_mode2atts(struct super_block *sb, struct inode *inode) +static int adfs_mode2atts(struct super_block *sb, struct inode *inode, + umode_t ia_mode) { + struct adfs_sb_info *asb = ADFS_SB(sb); umode_t mode; int attr; - struct adfs_sb_info *asb = ADFS_SB(sb); /* FIXME: should we be able to alter a link? */ if (S_ISLNK(inode->i_mode)) return ADFS_I(inode)->attr; + /* Directories do not have read/write permissions on the media */ if (S_ISDIR(inode->i_mode)) - attr = ADFS_NDA_DIRECTORY; - else - attr = 0; + return ADFS_NDA_DIRECTORY; - mode = inode->i_mode & asb->s_owner_mask; + attr = 0; + mode = ia_mode & asb->s_owner_mask; if (mode & S_IRUGO) attr |= ADFS_NDA_OWNER_READ; if (mode & S_IWUGO) attr |= ADFS_NDA_OWNER_WRITE; - mode = inode->i_mode & asb->s_other_mask; + mode = ia_mode & asb->s_other_mask; mode &= ~asb->s_owner_mask; if (mode & S_IRUGO) attr |= ADFS_NDA_PUBLIC_READ; @@ -328,7 +328,7 @@ adfs_notify_change(struct dentry *dentry, struct iattr *attr) if (ia_valid & ATTR_CTIME) inode->i_ctime = attr->ia_ctime; if (ia_valid & ATTR_MODE) { - ADFS_I(inode)->attr = adfs_mode2atts(sb, inode); + ADFS_I(inode)->attr = adfs_mode2atts(sb, inode, attr->ia_mode); inode->i_mode = adfs_atts2mode(sb, inode); } From f75d398d6ee61b04c16124e3eddd786526bc7d40 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:08:28 +0000 Subject: [PATCH 03/42] fs/adfs: map: move map reading and validation to map.c Keep all the map code together in map.c, rather than having some in super.c Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 1 + fs/adfs/map.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++ fs/adfs/super.c | 98 ------------------------------------------------ 3 files changed, 100 insertions(+), 98 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index b7e844d2f321..d23c84aeb6dd 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -146,6 +146,7 @@ int adfs_notify_change(struct dentry *dentry, struct iattr *attr); /* map.c */ int adfs_map_lookup(struct super_block *sb, u32 frag_id, unsigned int offset); extern unsigned int adfs_map_free(struct super_block *sb); +struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecord *dr); /* Misc */ __printf(3, 4) diff --git a/fs/adfs/map.c b/fs/adfs/map.c index f44d12cef5be..120e01451e75 100644 --- a/fs/adfs/map.c +++ b/fs/adfs/map.c @@ -4,6 +4,7 @@ * * Copyright (C) 1997-2002 Russell King */ +#include #include #include "adfs.h" @@ -280,3 +281,101 @@ bad_fragment: frag_id, zone, asb->s_map_size); return 0; } + +static unsigned char adfs_calczonecheck(struct super_block *sb, unsigned char *map) +{ + unsigned int v0, v1, v2, v3; + int i; + + v0 = v1 = v2 = v3 = 0; + for (i = sb->s_blocksize - 4; i; i -= 4) { + v0 += map[i] + (v3 >> 8); + v3 &= 0xff; + v1 += map[i + 1] + (v0 >> 8); + v0 &= 0xff; + v2 += map[i + 2] + (v1 >> 8); + v1 &= 0xff; + v3 += map[i + 3] + (v2 >> 8); + v2 &= 0xff; + } + v0 += v3 >> 8; + v1 += map[1] + (v0 >> 8); + v2 += map[2] + (v1 >> 8); + v3 += map[3] + (v2 >> 8); + + return v0 ^ v1 ^ v2 ^ v3; +} + +static int adfs_checkmap(struct super_block *sb, struct adfs_discmap *dm) +{ + unsigned char crosscheck = 0, zonecheck = 1; + int i; + + for (i = 0; i < ADFS_SB(sb)->s_map_size; i++) { + unsigned char *map; + + map = dm[i].dm_bh->b_data; + + if (adfs_calczonecheck(sb, map) != map[0]) { + adfs_error(sb, "zone %d fails zonecheck", i); + zonecheck = 0; + } + crosscheck ^= map[3]; + } + if (crosscheck != 0xff) + adfs_error(sb, "crosscheck != 0xff"); + return crosscheck == 0xff && zonecheck; +} + +struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecord *dr) +{ + struct adfs_discmap *dm; + unsigned int map_addr, zone_size, nzones; + int i, zone; + struct adfs_sb_info *asb = ADFS_SB(sb); + + nzones = asb->s_map_size; + zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare); + map_addr = (nzones >> 1) * zone_size - + ((nzones > 1) ? ADFS_DR_SIZE_BITS : 0); + map_addr = signed_asl(map_addr, asb->s_map2blk); + + asb->s_ids_per_zone = zone_size / (asb->s_idlen + 1); + + dm = kmalloc_array(nzones, sizeof(*dm), GFP_KERNEL); + if (dm == NULL) { + adfs_error(sb, "not enough memory"); + return ERR_PTR(-ENOMEM); + } + + for (zone = 0; zone < nzones; zone++, map_addr++) { + dm[zone].dm_startbit = 0; + dm[zone].dm_endbit = zone_size; + dm[zone].dm_startblk = zone * zone_size - ADFS_DR_SIZE_BITS; + dm[zone].dm_bh = sb_bread(sb, map_addr); + + if (!dm[zone].dm_bh) { + adfs_error(sb, "unable to read map"); + goto error_free; + } + } + + /* adjust the limits for the first and last map zones */ + i = zone - 1; + dm[0].dm_startblk = 0; + dm[0].dm_startbit = ADFS_DR_SIZE_BITS; + dm[i].dm_endbit = (adfs_disc_size(dr) >> dr->log2bpmb) + + (ADFS_DR_SIZE_BITS - i * zone_size); + + if (adfs_checkmap(sb, dm)) + return dm; + + adfs_error(sb, "map corrupted"); + +error_free: + while (--zone >= 0) + brelse(dm[zone].dm_bh); + + kfree(dm); + return ERR_PTR(-EIO); +} diff --git a/fs/adfs/super.c b/fs/adfs/super.c index e0eea9adb4e6..4091adb2c7ff 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -88,51 +88,6 @@ static int adfs_checkdiscrecord(struct adfs_discrecord *dr) return 0; } -static unsigned char adfs_calczonecheck(struct super_block *sb, unsigned char *map) -{ - unsigned int v0, v1, v2, v3; - int i; - - v0 = v1 = v2 = v3 = 0; - for (i = sb->s_blocksize - 4; i; i -= 4) { - v0 += map[i] + (v3 >> 8); - v3 &= 0xff; - v1 += map[i + 1] + (v0 >> 8); - v0 &= 0xff; - v2 += map[i + 2] + (v1 >> 8); - v1 &= 0xff; - v3 += map[i + 3] + (v2 >> 8); - v2 &= 0xff; - } - v0 += v3 >> 8; - v1 += map[1] + (v0 >> 8); - v2 += map[2] + (v1 >> 8); - v3 += map[3] + (v2 >> 8); - - return v0 ^ v1 ^ v2 ^ v3; -} - -static int adfs_checkmap(struct super_block *sb, struct adfs_discmap *dm) -{ - unsigned char crosscheck = 0, zonecheck = 1; - int i; - - for (i = 0; i < ADFS_SB(sb)->s_map_size; i++) { - unsigned char *map; - - map = dm[i].dm_bh->b_data; - - if (adfs_calczonecheck(sb, map) != map[0]) { - adfs_error(sb, "zone %d fails zonecheck", i); - zonecheck = 0; - } - crosscheck ^= map[3]; - } - if (crosscheck != 0xff) - adfs_error(sb, "crosscheck != 0xff"); - return crosscheck == 0xff && zonecheck; -} - static void adfs_put_super(struct super_block *sb) { int i; @@ -322,59 +277,6 @@ static const struct super_operations adfs_sops = { .show_options = adfs_show_options, }; -static struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecord *dr) -{ - struct adfs_discmap *dm; - unsigned int map_addr, zone_size, nzones; - int i, zone; - struct adfs_sb_info *asb = ADFS_SB(sb); - - nzones = asb->s_map_size; - zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare); - map_addr = (nzones >> 1) * zone_size - - ((nzones > 1) ? ADFS_DR_SIZE_BITS : 0); - map_addr = signed_asl(map_addr, asb->s_map2blk); - - asb->s_ids_per_zone = zone_size / (asb->s_idlen + 1); - - dm = kmalloc_array(nzones, sizeof(*dm), GFP_KERNEL); - if (dm == NULL) { - adfs_error(sb, "not enough memory"); - return ERR_PTR(-ENOMEM); - } - - for (zone = 0; zone < nzones; zone++, map_addr++) { - dm[zone].dm_startbit = 0; - dm[zone].dm_endbit = zone_size; - dm[zone].dm_startblk = zone * zone_size - ADFS_DR_SIZE_BITS; - dm[zone].dm_bh = sb_bread(sb, map_addr); - - if (!dm[zone].dm_bh) { - adfs_error(sb, "unable to read map"); - goto error_free; - } - } - - /* adjust the limits for the first and last map zones */ - i = zone - 1; - dm[0].dm_startblk = 0; - dm[0].dm_startbit = ADFS_DR_SIZE_BITS; - dm[i].dm_endbit = (adfs_disc_size(dr) >> dr->log2bpmb) + - (ADFS_DR_SIZE_BITS - i * zone_size); - - if (adfs_checkmap(sb, dm)) - return dm; - - adfs_error(sb, "map corrupted"); - -error_free: - while (--zone >= 0) - brelse(dm[zone].dm_bh); - - kfree(dm); - return ERR_PTR(-EIO); -} - static int adfs_fill_super(struct super_block *sb, void *data, int silent) { struct adfs_discrecord *dr; From e6160e469f56a23cb69e1dc37aa0d895bf29ac24 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:08:34 +0000 Subject: [PATCH 04/42] fs/adfs: map: rename adfs_map_free() to adfs_map_statfs() adfs_map_free() is not obvious whether it is freeing the map or returning the number of free blocks on the filesystem. Rename it to the more generic statfs() to make it clear that it's a statistic function. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 2 +- fs/adfs/map.c | 10 +++++++--- fs/adfs/super.c | 7 ++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index d23c84aeb6dd..45fd48fbd5e0 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -145,7 +145,7 @@ int adfs_notify_change(struct dentry *dentry, struct iattr *attr); /* map.c */ int adfs_map_lookup(struct super_block *sb, u32 frag_id, unsigned int offset); -extern unsigned int adfs_map_free(struct super_block *sb); +void adfs_map_statfs(struct super_block *sb, struct kstatfs *buf); struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecord *dr); /* Misc */ diff --git a/fs/adfs/map.c b/fs/adfs/map.c index 120e01451e75..c322d37e8f91 100644 --- a/fs/adfs/map.c +++ b/fs/adfs/map.c @@ -5,6 +5,7 @@ * Copyright (C) 1997-2002 Russell King */ #include +#include #include #include "adfs.h" @@ -221,10 +222,10 @@ found: * total_free = E(free_in_zone_n) * nzones */ -unsigned int -adfs_map_free(struct super_block *sb) +void adfs_map_statfs(struct super_block *sb, struct kstatfs *buf) { struct adfs_sb_info *asb = ADFS_SB(sb); + struct adfs_discrecord *dr = adfs_map_discrecord(asb->s_map); struct adfs_discmap *dm; unsigned int total = 0; unsigned int zone; @@ -236,7 +237,10 @@ adfs_map_free(struct super_block *sb) total += scan_free_map(asb, dm++); } while (--zone > 0); - return signed_asl(total, asb->s_map2blk); + buf->f_blocks = adfs_disc_size(dr) >> sb->s_blocksize_bits; + buf->f_files = asb->s_ids_per_zone * asb->s_map_size; + buf->f_bavail = + buf->f_bfree = signed_asl(total, asb->s_map2blk); } int adfs_map_lookup(struct super_block *sb, u32 frag_id, unsigned int offset) diff --git a/fs/adfs/super.c b/fs/adfs/super.c index 4091adb2c7ff..458824e0ca83 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -204,16 +204,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); + adfs_map_statfs(sb, buf); + buf->f_type = ADFS_SUPER_MAGIC; buf->f_namelen = sbi->s_namelen; buf->f_bsize = sb->s_blocksize; - 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); buf->f_ffree = (long)(buf->f_bfree * buf->f_files) / (long)buf->f_blocks; buf->f_fsid.val[0] = (u32)id; buf->f_fsid.val[1] = (u32)(id >> 32); From 6092b6be304494e311b65935f5e09b510cbd57cc Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:08:39 +0000 Subject: [PATCH 05/42] fs/adfs: map: break up adfs_read_map() Split up adfs_read_map() into separate helpers to layout the map, read the map, and release the map buffers. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/map.c | 80 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/fs/adfs/map.c b/fs/adfs/map.c index c322d37e8f91..4b677cd5d015 100644 --- a/fs/adfs/map.c +++ b/fs/adfs/map.c @@ -331,12 +331,63 @@ static int adfs_checkmap(struct super_block *sb, struct adfs_discmap *dm) return crosscheck == 0xff && zonecheck; } +/* + * Layout the map - the first zone contains a copy of the disc record, + * and the last zone must be limited to the size of the filesystem. + */ +static void adfs_map_layout(struct adfs_discmap *dm, unsigned int nzones, + struct adfs_discrecord *dr) +{ + unsigned int zone, zone_size; + u64 size; + + zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare); + + dm[0].dm_bh = NULL; + dm[0].dm_startblk = 0; + dm[0].dm_startbit = ADFS_DR_SIZE_BITS; + dm[0].dm_endbit = zone_size; + + for (zone = 1; zone < nzones; zone++) { + dm[zone].dm_bh = NULL; + dm[zone].dm_startblk = zone * zone_size - ADFS_DR_SIZE_BITS; + dm[zone].dm_startbit = 0; + dm[zone].dm_endbit = zone_size; + } + + size = adfs_disc_size(dr) >> dr->log2bpmb; + size -= (nzones - 1) * zone_size - ADFS_DR_SIZE_BITS; + dm[nzones - 1].dm_endbit = size; +} + +static int adfs_map_read(struct adfs_discmap *dm, struct super_block *sb, + unsigned int map_addr, unsigned int nzones) +{ + unsigned int zone; + + for (zone = 0; zone < nzones; zone++) { + dm[zone].dm_bh = sb_bread(sb, map_addr + zone); + if (!dm[zone].dm_bh) + return -EIO; + } + + return 0; +} + +static void adfs_map_relse(struct adfs_discmap *dm, unsigned int nzones) +{ + unsigned int zone; + + for (zone = 0; zone < nzones; zone++) + brelse(dm[zone].dm_bh); +} + struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecord *dr) { + struct adfs_sb_info *asb = ADFS_SB(sb); struct adfs_discmap *dm; unsigned int map_addr, zone_size, nzones; - int i, zone; - struct adfs_sb_info *asb = ADFS_SB(sb); + int ret; nzones = asb->s_map_size; zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare); @@ -352,34 +403,21 @@ struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecor return ERR_PTR(-ENOMEM); } - for (zone = 0; zone < nzones; zone++, map_addr++) { - dm[zone].dm_startbit = 0; - dm[zone].dm_endbit = zone_size; - dm[zone].dm_startblk = zone * zone_size - ADFS_DR_SIZE_BITS; - dm[zone].dm_bh = sb_bread(sb, map_addr); + adfs_map_layout(dm, nzones, dr); - if (!dm[zone].dm_bh) { - adfs_error(sb, "unable to read map"); - goto error_free; - } + ret = adfs_map_read(dm, sb, map_addr, nzones); + if (ret) { + adfs_error(sb, "unable to read map"); + goto error_free; } - /* adjust the limits for the first and last map zones */ - i = zone - 1; - dm[0].dm_startblk = 0; - dm[0].dm_startbit = ADFS_DR_SIZE_BITS; - dm[i].dm_endbit = (adfs_disc_size(dr) >> dr->log2bpmb) + - (ADFS_DR_SIZE_BITS - i * zone_size); - if (adfs_checkmap(sb, dm)) return dm; adfs_error(sb, "map corrupted"); error_free: - while (--zone >= 0) - brelse(dm[zone].dm_bh); - + adfs_map_relse(dm, nzones); kfree(dm); return ERR_PTR(-EIO); } From 7b1952676256d2cdc03d0415a4c0e6bfb64e00ff Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:08:44 +0000 Subject: [PATCH 06/42] fs/adfs: map: factor out map cleanup We have several places which deal with releasing the map buffers and freeing the map array. Provide a helper for this. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 1 + fs/adfs/map.c | 8 ++++++++ fs/adfs/super.c | 10 ++-------- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 45fd48fbd5e0..6497da8a2c8a 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -147,6 +147,7 @@ int adfs_notify_change(struct dentry *dentry, struct iattr *attr); int adfs_map_lookup(struct super_block *sb, u32 frag_id, unsigned int offset); void adfs_map_statfs(struct super_block *sb, struct kstatfs *buf); struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecord *dr); +void adfs_free_map(struct super_block *sb); /* Misc */ __printf(3, 4) diff --git a/fs/adfs/map.c b/fs/adfs/map.c index 4b677cd5d015..8ba8877110ff 100644 --- a/fs/adfs/map.c +++ b/fs/adfs/map.c @@ -421,3 +421,11 @@ error_free: kfree(dm); return ERR_PTR(-EIO); } + +void adfs_free_map(struct super_block *sb) +{ + struct adfs_sb_info *asb = ADFS_SB(sb); + + adfs_map_relse(asb->s_map, asb->s_map_size); + kfree(asb->s_map); +} diff --git a/fs/adfs/super.c b/fs/adfs/super.c index 458824e0ca83..cef16028e9f2 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -90,12 +90,9 @@ static int adfs_checkdiscrecord(struct adfs_discrecord *dr) static void adfs_put_super(struct super_block *sb) { - int i; struct adfs_sb_info *asb = ADFS_SB(sb); - for (i = 0; i < asb->s_map_size; i++) - brelse(asb->s_map[i].dm_bh); - kfree(asb->s_map); + adfs_free_map(sb); kfree_rcu(asb, rcu); } @@ -412,10 +409,7 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) root = adfs_iget(sb, &root_obj); sb->s_root = d_make_root(root); if (!sb->s_root) { - int i; - for (i = 0; i < asb->s_map_size; i++) - brelse(asb->s_map[i].dm_bh); - kfree(asb->s_map); + adfs_free_map(sb); adfs_error(sb, "get root inode failed\n"); ret = -EIO; goto error; From 197ba3c519312a0bed91aeaf34095b2c09c6431a Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:08:49 +0000 Subject: [PATCH 07/42] fs/adfs: map: incorporate map offsets into layout lookup_zone() and scan_free_map() cope in different ways with the location of the map data within a zone: 1. lookup_zone() adds a four byte offset to the map data pointer to skip over the check and free link bytes. 2. scan_free_map() needs to use the free link pointer, which is an offset from itself, so we end up adding a 32-bit offset to the end pointer (aka mapsize) which is really confusing. Rename mapsize to endbit as this is really what it is, and incorporate the 32-bit offset into the map layout. This means that both dm_startbit and dm_endbit are now bit offsets from the start of the buffer, rather than four bytes in to the buffer. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/map.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/fs/adfs/map.c b/fs/adfs/map.c index 8ba8877110ff..55bd7c20158c 100644 --- a/fs/adfs/map.c +++ b/fs/adfs/map.c @@ -68,9 +68,9 @@ static DEFINE_RWLOCK(adfs_map_lock); 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 unsigned int endbit = dm->dm_endbit; const u32 idmask = (1 << idlen) - 1; - unsigned char *map = dm->dm_bh->b_data + 4; + unsigned char *map = dm->dm_bh->b_data; unsigned int start = dm->dm_startbit; unsigned int mapptr; u32 frag; @@ -87,7 +87,7 @@ static int lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen, u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31); while (v == 0) { mapptr = (mapptr & ~31) + 32; - if (mapptr >= mapsize) + if (mapptr >= endbit) goto error; v = le32_to_cpu(_map[mapptr >> 5]); } @@ -99,7 +99,7 @@ static int lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen, goto found; again: start = mapptr; - } while (mapptr < mapsize); + } while (mapptr < endbit); return -1; error: @@ -127,7 +127,7 @@ found: static unsigned int scan_free_map(struct adfs_sb_info *asb, struct adfs_discmap *dm) { - const unsigned int mapsize = dm->dm_endbit + 32; + const unsigned int endbit = dm->dm_endbit; const unsigned int idlen = asb->s_idlen; const unsigned int frag_idlen = idlen <= 15 ? idlen : 15; const u32 idmask = (1 << frag_idlen) - 1; @@ -165,7 +165,7 @@ scan_free_map(struct adfs_sb_info *asb, struct adfs_discmap *dm) u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31); while (v == 0) { mapptr = (mapptr & ~31) + 32; - if (mapptr >= mapsize) + if (mapptr >= endbit) goto error; v = le32_to_cpu(_map[mapptr >> 5]); } @@ -345,19 +345,19 @@ static void adfs_map_layout(struct adfs_discmap *dm, unsigned int nzones, dm[0].dm_bh = NULL; dm[0].dm_startblk = 0; - dm[0].dm_startbit = ADFS_DR_SIZE_BITS; - dm[0].dm_endbit = zone_size; + dm[0].dm_startbit = 32 + ADFS_DR_SIZE_BITS; + dm[0].dm_endbit = 32 + zone_size; for (zone = 1; zone < nzones; zone++) { dm[zone].dm_bh = NULL; dm[zone].dm_startblk = zone * zone_size - ADFS_DR_SIZE_BITS; - dm[zone].dm_startbit = 0; - dm[zone].dm_endbit = zone_size; + dm[zone].dm_startbit = 32; + dm[zone].dm_endbit = 32 + zone_size; } size = adfs_disc_size(dr) >> dr->log2bpmb; size -= (nzones - 1) * zone_size - ADFS_DR_SIZE_BITS; - dm[nzones - 1].dm_endbit = size; + dm[nzones - 1].dm_endbit = 32 + size; } static int adfs_map_read(struct adfs_discmap *dm, struct super_block *sb, From 792314f8b223f77037b85e6242c67df15623cf75 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:08:54 +0000 Subject: [PATCH 08/42] fs/adfs: map: use find_next_bit_le() rather than open coding it Use find_next_bit_le() to find the end of a fragment in the map rather than open-coding this functionality. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/map.c | 70 +++++++++++++-------------------------------------- 1 file changed, 18 insertions(+), 52 deletions(-) diff --git a/fs/adfs/map.c b/fs/adfs/map.c index 55bd7c20158c..9be0b47da19c 100644 --- a/fs/adfs/map.c +++ b/fs/adfs/map.c @@ -72,50 +72,32 @@ static int lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen, const u32 idmask = (1 << idlen) - 1; unsigned char *map = dm->dm_bh->b_data; unsigned int start = dm->dm_startbit; - unsigned int mapptr; + unsigned int fragend; u32 frag; do { frag = GET_FRAG_ID(map, start, idmask); - mapptr = start + idlen; - /* - * find end of fragment - */ - { - __le32 *_map = (__le32 *)map; - u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31); - while (v == 0) { - mapptr = (mapptr & ~31) + 32; - if (mapptr >= endbit) - goto error; - v = le32_to_cpu(_map[mapptr >> 5]); - } + fragend = find_next_bit_le(map, endbit, start + idlen); + if (fragend >= endbit) + goto error; - mapptr += 1 + ffz(~v); + if (frag == frag_id) { + unsigned int length = fragend + 1 - start; + + if (*offset < length) + return start + *offset; + *offset -= length; } - if (frag == frag_id) - goto found; -again: - start = mapptr; - } while (mapptr < endbit); + start = fragend + 1; + } while (start < endbit); return -1; error: printk(KERN_ERR "adfs: oversized fragment 0x%x at 0x%x-0x%x\n", - frag, start, mapptr); + frag, start, fragend); return -1; - -found: - { - int length = mapptr - start; - if (*offset >= length) { - *offset -= length; - goto again; - } - } - return start + *offset; } /* @@ -132,7 +114,7 @@ scan_free_map(struct adfs_sb_info *asb, struct adfs_discmap *dm) const unsigned int frag_idlen = idlen <= 15 ? idlen : 15; const u32 idmask = (1 << frag_idlen) - 1; unsigned char *map = dm->dm_bh->b_data; - unsigned int start = 8, mapptr; + unsigned int start = 8, fragend; u32 frag; unsigned long total = 0; @@ -151,29 +133,13 @@ scan_free_map(struct adfs_sb_info *asb, struct adfs_discmap *dm) do { start += frag; - /* - * get fragment id - */ frag = GET_FRAG_ID(map, start, idmask); - mapptr = start + idlen; - /* - * find end of fragment - */ - { - __le32 *_map = (__le32 *)map; - u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31); - while (v == 0) { - mapptr = (mapptr & ~31) + 32; - if (mapptr >= endbit) - goto error; - v = le32_to_cpu(_map[mapptr >> 5]); - } + fragend = find_next_bit_le(map, endbit, start + idlen); + if (fragend >= endbit) + goto error; - mapptr += 1 + ffz(~v); - } - - total += mapptr - start; + total += fragend + 1 - start; } while (frag >= idlen + 1); if (frag != 0) From f6f14a0d71b0773a1d4147d1a3c33d537cd213ab Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:08:59 +0000 Subject: [PATCH 09/42] fs/adfs: map: move map-specific sb initialisation to map.c Move map specific superblock initialisation to map.c, rather than having it spread into super.c. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/map.c | 13 +++++++++---- fs/adfs/super.c | 7 +------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/fs/adfs/map.c b/fs/adfs/map.c index 9be0b47da19c..82e1bf101fe6 100644 --- a/fs/adfs/map.c +++ b/fs/adfs/map.c @@ -355,14 +355,19 @@ struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecor unsigned int map_addr, zone_size, nzones; int ret; - nzones = asb->s_map_size; + nzones = dr->nzones | dr->nzones_high << 8; zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare); - map_addr = (nzones >> 1) * zone_size - - ((nzones > 1) ? ADFS_DR_SIZE_BITS : 0); - map_addr = signed_asl(map_addr, asb->s_map2blk); + asb->s_idlen = dr->idlen; + asb->s_map_size = nzones; + asb->s_map2blk = dr->log2bpmb - dr->log2secsize; + asb->s_log2sharesize = dr->log2sharesize; asb->s_ids_per_zone = zone_size / (asb->s_idlen + 1); + map_addr = (nzones >> 1) * zone_size - + ((nzones > 1) ? ADFS_DR_SIZE_BITS : 0); + map_addr = signed_asl(map_addr, asb->s_map2blk); + dm = kmalloc_array(nzones, sizeof(*dm), GFP_KERNEL); if (dm == NULL) { adfs_error(sb, "not enough memory"); diff --git a/fs/adfs/super.c b/fs/adfs/super.c index cef16028e9f2..b2455e9ab923 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -289,6 +289,7 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) return -ENOMEM; sb->s_fs_info = asb; + sb->s_magic = ADFS_SUPER_MAGIC; sb->s_time_gran = 10000000; /* set default options */ @@ -356,12 +357,6 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) * blocksize on this device should now be set to the ADFS log2secsize */ - sb->s_magic = ADFS_SUPER_MAGIC; - asb->s_idlen = dr->idlen; - asb->s_map_size = dr->nzones | (dr->nzones_high << 8); - asb->s_map2blk = dr->log2bpmb - dr->log2secsize; - asb->s_log2sharesize = dr->log2sharesize; - asb->s_map = adfs_read_map(sb, dr); if (IS_ERR(asb->s_map)) { ret = PTR_ERR(asb->s_map); From f93793fd73a629f4c86b0d91fd84fe175705aff9 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:09:04 +0000 Subject: [PATCH 10/42] fs/adfs: map: fix map scanning When scanning the map for a fragment id, we need to keep track of the free space links, so we don't inadvertently believe that the freespace link is a valid fragment id. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/map.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fs/adfs/map.c b/fs/adfs/map.c index 82e1bf101fe6..a81de80c45c1 100644 --- a/fs/adfs/map.c +++ b/fs/adfs/map.c @@ -72,9 +72,12 @@ static int lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen, const u32 idmask = (1 << idlen) - 1; unsigned char *map = dm->dm_bh->b_data; unsigned int start = dm->dm_startbit; - unsigned int fragend; + unsigned int freelink, fragend; u32 frag; + frag = GET_FRAG_ID(map, 8, idmask & 0x7fff); + freelink = frag ? 8 + frag : 0; + do { frag = GET_FRAG_ID(map, start, idmask); @@ -82,7 +85,9 @@ static int lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen, if (fragend >= endbit) goto error; - if (frag == frag_id) { + if (start == freelink) { + freelink += frag & 0x7fff; + } else if (frag == frag_id) { unsigned int length = fragend + 1 - start; if (*offset < length) From 71b2612776c1b9c34c460f79bcdaef46d0e77ed2 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:09:10 +0000 Subject: [PATCH 11/42] fs/adfs: dir: rename bh_fplus to bhs Rename bh_fplus to bhs in preparation to make some of the directory handling code sharable between implementations. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 4 +--- fs/adfs/dir_fplus.c | 54 ++++++++++++++++++++++----------------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 6497da8a2c8a..956ac0bd53e1 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -93,9 +93,7 @@ struct adfs_dir { int nr_buffers; struct buffer_head *bh[4]; - - /* big directories need allocated buffers */ - struct buffer_head **bh_fplus; + struct buffer_head **bhs; unsigned int pos; __u32 parent_id; diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index d56924c11b17..5f5420c9b943 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -20,7 +20,7 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct dir->nr_buffers = 0; /* start off using fixed bh set - only alloc for big dirs */ - dir->bh_fplus = &dir->bh[0]; + dir->bhs = &dir->bh[0]; block = __adfs_block_map(sb, id, 0); if (!block) { @@ -28,12 +28,12 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct goto out; } - dir->bh_fplus[0] = sb_bread(sb, block); - if (!dir->bh_fplus[0]) + dir->bhs[0] = sb_bread(sb, block); + if (!dir->bhs[0]) goto out; dir->nr_buffers += 1; - h = (struct adfs_bigdirheader *)dir->bh_fplus[0]->b_data; + h = (struct adfs_bigdirheader *)dir->bhs[0]->b_data; size = le32_to_cpu(h->bigdirsize); if (size != sz) { adfs_msg(sb, KERN_WARNING, @@ -51,19 +51,19 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct size >>= sb->s_blocksize_bits; if (size > ARRAY_SIZE(dir->bh)) { /* this directory is too big for fixed bh set, must allocate */ - struct buffer_head **bh_fplus = + struct buffer_head **bhs = kcalloc(size, sizeof(struct buffer_head *), GFP_KERNEL); - if (!bh_fplus) { + if (!bhs) { adfs_msg(sb, KERN_ERR, "not enough memory for dir object %X (%d blocks)", id, size); ret = -ENOMEM; goto out; } - dir->bh_fplus = bh_fplus; + dir->bhs = bhs; /* copy over the pointer to the block that we've already read */ - dir->bh_fplus[0] = dir->bh[0]; + dir->bhs[0] = dir->bh[0]; } for (blk = 1; blk < size; blk++) { @@ -73,8 +73,8 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct goto out; } - dir->bh_fplus[blk] = sb_bread(sb, block); - if (!dir->bh_fplus[blk]) { + dir->bhs[blk] = sb_bread(sb, block); + if (!dir->bhs[blk]) { adfs_error(sb, "dir object %x failed read for offset %d, mapped block %lX", id, blk, block); goto out; @@ -84,7 +84,7 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct } t = (struct adfs_bigdirtail *) - (dir->bh_fplus[size - 1]->b_data + (sb->s_blocksize - 8)); + (dir->bhs[size - 1]->b_data + (sb->s_blocksize - 8)); if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) || t->bigdirendmasseq != h->startmasseq || @@ -98,14 +98,14 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct return 0; out: - if (dir->bh_fplus) { + if (dir->bhs) { for (i = 0; i < dir->nr_buffers; i++) - brelse(dir->bh_fplus[i]); + brelse(dir->bhs[i]); - if (&dir->bh[0] != dir->bh_fplus) - kfree(dir->bh_fplus); + if (&dir->bh[0] != dir->bhs) + kfree(dir->bhs); - dir->bh_fplus = NULL; + dir->bhs = NULL; } dir->nr_buffers = 0; @@ -117,7 +117,7 @@ static int adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos) { struct adfs_bigdirheader *h = - (struct adfs_bigdirheader *) dir->bh_fplus[0]->b_data; + (struct adfs_bigdirheader *) dir->bhs[0]->b_data; int ret = -ENOENT; if (fpos <= le32_to_cpu(h->bigdirentries)) { @@ -140,18 +140,18 @@ dir_memcpy(struct adfs_dir *dir, unsigned int offset, void *to, int len) partial = sb->s_blocksize - offset; if (partial >= len) - memcpy(to, dir->bh_fplus[buffer]->b_data + offset, len); + memcpy(to, dir->bhs[buffer]->b_data + offset, len); else { char *c = (char *)to; remainder = len - partial; memcpy(c, - dir->bh_fplus[buffer]->b_data + offset, + dir->bhs[buffer]->b_data + offset, partial); memcpy(c + partial, - dir->bh_fplus[buffer + 1]->b_data, + dir->bhs[buffer + 1]->b_data, remainder); } } @@ -160,7 +160,7 @@ static int adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) { struct adfs_bigdirheader *h = - (struct adfs_bigdirheader *) dir->bh_fplus[0]->b_data; + (struct adfs_bigdirheader *) dir->bhs[0]->b_data; struct adfs_bigdirentry bde; unsigned int offset; int ret = -ENOENT; @@ -202,7 +202,7 @@ adfs_fplus_sync(struct adfs_dir *dir) int i; for (i = dir->nr_buffers - 1; i >= 0; i--) { - struct buffer_head *bh = dir->bh_fplus[i]; + struct buffer_head *bh = dir->bhs[i]; sync_dirty_buffer(bh); if (buffer_req(bh) && !buffer_uptodate(bh)) err = -EIO; @@ -216,14 +216,14 @@ adfs_fplus_free(struct adfs_dir *dir) { int i; - if (dir->bh_fplus) { + if (dir->bhs) { for (i = 0; i < dir->nr_buffers; i++) - brelse(dir->bh_fplus[i]); + brelse(dir->bhs[i]); - if (&dir->bh[0] != dir->bh_fplus) - kfree(dir->bh_fplus); + if (&dir->bh[0] != dir->bhs) + kfree(dir->bhs); - dir->bh_fplus = NULL; + dir->bhs = NULL; } dir->nr_buffers = 0; From 95fbadbb5566e383f0cfe40d895e698ab38bdbc7 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:09:15 +0000 Subject: [PATCH 12/42] fs/adfs: dir: add common dir object initialisation Initialise the dir object before we pass it down to the directory format specific read handler. This allows us to get rid of the initialisation inside those handlers. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir.c | 16 +++++++++++++--- fs/adfs/dir_f.c | 3 --- fs/adfs/dir_fplus.c | 6 ------ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index a54c53244992..c1b8b5bccbec 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -13,6 +13,16 @@ */ static DEFINE_RWLOCK(adfs_dir_lock); +static int adfs_dir_read(struct super_block *sb, u32 indaddr, + unsigned int size, struct adfs_dir *dir) +{ + dir->sb = sb; + dir->bhs = dir->bh; + dir->nr_buffers = 0; + + return ADFS_SB(sb)->s_dir->read(sb, indaddr, size, dir); +} + void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj) { unsigned int dots, i; @@ -64,7 +74,7 @@ adfs_readdir(struct file *file, struct dir_context *ctx) if (ctx->pos >> 32) return 0; - ret = ops->read(sb, inode->i_ino, inode->i_size, &dir); + ret = adfs_dir_read(sb, inode->i_ino, inode->i_size, &dir); if (ret) return ret; @@ -115,7 +125,7 @@ adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) goto out; } - ret = ops->read(sb, obj->parent_id, 0, &dir); + ret = adfs_dir_read(sb, obj->parent_id, 0, &dir); if (ret) goto out; @@ -167,7 +177,7 @@ static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr, u32 name_len; int ret; - ret = ops->read(sb, inode->i_ino, inode->i_size, &dir); + ret = adfs_dir_read(sb, inode->i_ino, inode->i_size, &dir); if (ret) goto out; diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index c1a950c7400a..e62f35eb7789 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -139,9 +139,6 @@ static int adfs_dir_read(struct super_block *sb, u32 indaddr, size >>= blocksize_bits; - dir->nr_buffers = 0; - dir->sb = sb; - for (blk = 0; blk < size; blk++) { int phys; diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index 5f5420c9b943..52c42a9986d9 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -17,11 +17,6 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct unsigned int blk, size; int i, ret = -EIO; - dir->nr_buffers = 0; - - /* start off using fixed bh set - only alloc for big dirs */ - dir->bhs = &dir->bh[0]; - block = __adfs_block_map(sb, id, 0); if (!block) { adfs_error(sb, "dir object %X has a hole at offset 0", id); @@ -94,7 +89,6 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct } dir->parent_id = le32_to_cpu(h->bigdirparent); - dir->sb = sb; return 0; out: From 1dd9f5babfd95fea5a77b27bab48c04c29db1f5f Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:09:20 +0000 Subject: [PATCH 13/42] fs/adfs: dir: add common directory buffer release method With the bhs pointer in place, we have no need for separate per-format free() methods, since a generic version will do. Provide a generic implementation, remove the format specific implementations and the method function pointer. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 2 +- fs/adfs/dir.c | 21 ++++++++++++++++++--- fs/adfs/dir_f.c | 28 ++++------------------------ fs/adfs/dir_fplus.c | 34 ++-------------------------------- 4 files changed, 25 insertions(+), 60 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 956ac0bd53e1..3bb6fd5b5eb0 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -126,7 +126,6 @@ struct adfs_dir_ops { int (*create)(struct adfs_dir *dir, struct object_info *obj); int (*remove)(struct adfs_dir *dir, struct object_info *obj); int (*sync)(struct adfs_dir *dir); - void (*free)(struct adfs_dir *dir); }; struct adfs_discmap { @@ -167,6 +166,7 @@ extern const struct dentry_operations adfs_dentry_operations; extern const struct adfs_dir_ops adfs_f_dir_ops; extern const struct adfs_dir_ops adfs_fplus_dir_ops; +void adfs_dir_relse(struct adfs_dir *dir); void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj); extern int adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait); diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index c1b8b5bccbec..f50302775504 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -6,6 +6,7 @@ * * Common directory handling for ADFS */ +#include #include "adfs.h" /* @@ -13,6 +14,20 @@ */ static DEFINE_RWLOCK(adfs_dir_lock); +void adfs_dir_relse(struct adfs_dir *dir) +{ + unsigned int i; + + for (i = 0; i < dir->nr_buffers; i++) + brelse(dir->bhs[i]); + dir->nr_buffers = 0; + + if (dir->bhs != dir->bh) + kfree(dir->bhs); + dir->bhs = NULL; + dir->sb = NULL; +} + static int adfs_dir_read(struct super_block *sb, u32 indaddr, unsigned int size, struct adfs_dir *dir) { @@ -105,7 +120,7 @@ unlock_out: read_unlock(&adfs_dir_lock); free_out: - ops->free(&dir); + adfs_dir_relse(&dir); return ret; } @@ -139,7 +154,7 @@ adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) ret = err; } - ops->free(&dir); + adfs_dir_relse(&dir); out: #endif return ret; @@ -211,7 +226,7 @@ unlock_out: read_unlock(&adfs_dir_lock); free_out: - ops->free(&dir); + adfs_dir_relse(&dir); out: return ret; } diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index e62f35eb7789..e249fdb915fa 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -9,8 +9,6 @@ #include "adfs.h" #include "dir_f.h" -static void adfs_f_free(struct adfs_dir *dir); - /* * Read an (unaligned) value of length 1..4 bytes */ @@ -128,7 +126,7 @@ 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; + int blk; /* * Directories which are not a multiple of 2048 bytes @@ -152,6 +150,8 @@ static int adfs_dir_read(struct super_block *sb, u32 indaddr, dir->bh[blk] = sb_bread(sb, phys); if (!dir->bh[blk]) goto release_buffers; + + dir->nr_buffers += 1; } memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead)); @@ -168,17 +168,12 @@ static int adfs_dir_read(struct super_block *sb, u32 indaddr, if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte) goto bad_dir; - dir->nr_buffers = blk; - return 0; bad_dir: adfs_error(sb, "dir %06x is corrupted", indaddr); release_buffers: - for (blk -= 1; blk >= 0; blk -= 1) - brelse(dir->bh[blk]); - - dir->sb = NULL; + adfs_dir_relse(dir); return -EIO; } @@ -435,25 +430,10 @@ adfs_f_sync(struct adfs_dir *dir) return err; } -static void -adfs_f_free(struct adfs_dir *dir) -{ - int i; - - for (i = dir->nr_buffers - 1; i >= 0; i--) { - brelse(dir->bh[i]); - dir->bh[i] = NULL; - } - - dir->nr_buffers = 0; - dir->sb = NULL; -} - const struct adfs_dir_ops adfs_f_dir_ops = { .read = adfs_f_read, .setpos = adfs_f_setpos, .getnext = adfs_f_getnext, .update = adfs_f_update, .sync = adfs_f_sync, - .free = adfs_f_free }; diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index 52c42a9986d9..25308b334dd3 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -15,7 +15,7 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct struct adfs_bigdirtail *t; unsigned long block; unsigned int blk, size; - int i, ret = -EIO; + int ret = -EIO; block = __adfs_block_map(sb, id, 0); if (!block) { @@ -92,18 +92,8 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct return 0; out: - if (dir->bhs) { - for (i = 0; i < dir->nr_buffers; i++) - brelse(dir->bhs[i]); + adfs_dir_relse(dir); - if (&dir->bh[0] != dir->bhs) - kfree(dir->bhs); - - dir->bhs = NULL; - } - - dir->nr_buffers = 0; - dir->sb = NULL; return ret; } @@ -205,29 +195,9 @@ adfs_fplus_sync(struct adfs_dir *dir) return err; } -static void -adfs_fplus_free(struct adfs_dir *dir) -{ - int i; - - if (dir->bhs) { - for (i = 0; i < dir->nr_buffers; i++) - brelse(dir->bhs[i]); - - if (&dir->bh[0] != dir->bhs) - kfree(dir->bhs); - - dir->bhs = NULL; - } - - dir->nr_buffers = 0; - dir->sb = NULL; -} - const struct adfs_dir_ops adfs_fplus_dir_ops = { .read = adfs_fplus_read, .setpos = adfs_fplus_setpos, .getnext = adfs_fplus_getnext, .sync = adfs_fplus_sync, - .free = adfs_fplus_free }; From acf5f0be8a520c02bfed74cfc6735bf5fdd4a9e5 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:09:25 +0000 Subject: [PATCH 14/42] fs/adfs: dir: add common directory sync method adfs_fplus_sync() can be used for both directory formats since we now have a common way to access the buffer heads, so move it into dir.c and appropriately rename it. Remove the directory-format specific implementations. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 1 - fs/adfs/dir.c | 23 ++++++++++++++++++----- fs/adfs/dir_f.c | 17 ----------------- fs/adfs/dir_fplus.c | 17 ----------------- 4 files changed, 18 insertions(+), 40 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 3bb6fd5b5eb0..5f1acee768f5 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -125,7 +125,6 @@ struct adfs_dir_ops { int (*update)(struct adfs_dir *dir, struct object_info *obj); int (*create)(struct adfs_dir *dir, struct object_info *obj); int (*remove)(struct adfs_dir *dir, struct object_info *obj); - int (*sync)(struct adfs_dir *dir); }; struct adfs_discmap { diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index f50302775504..16a2639d3ca5 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -38,6 +38,21 @@ static int adfs_dir_read(struct super_block *sb, u32 indaddr, return ADFS_SB(sb)->s_dir->read(sb, indaddr, size, dir); } +static int adfs_dir_sync(struct adfs_dir *dir) +{ + int err = 0; + int i; + + for (i = dir->nr_buffers - 1; i >= 0; i--) { + struct buffer_head *bh = dir->bhs[i]; + sync_dirty_buffer(bh); + if (buffer_req(bh) && !buffer_uptodate(bh)) + err = -EIO; + } + + return err; +} + void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj) { unsigned int dots, i; @@ -135,10 +150,8 @@ adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) printk(KERN_INFO "adfs_dir_update: object %06x in dir %06x\n", obj->indaddr, obj->parent_id); - if (!ops->update) { - ret = -EINVAL; - goto out; - } + if (!ops->update) + return -EINVAL; ret = adfs_dir_read(sb, obj->parent_id, 0, &dir); if (ret) @@ -149,7 +162,7 @@ adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) write_unlock(&adfs_dir_lock); if (wait) { - int err = ops->sync(&dir); + int err = adfs_dir_sync(&dir); if (!ret) ret = err; } diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index e249fdb915fa..80ac261b9ec4 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -414,26 +414,9 @@ bad_dir: #endif } -static int -adfs_f_sync(struct adfs_dir *dir) -{ - int err = 0; - int i; - - for (i = dir->nr_buffers - 1; i >= 0; i--) { - struct buffer_head *bh = dir->bh[i]; - sync_dirty_buffer(bh); - if (buffer_req(bh) && !buffer_uptodate(bh)) - err = -EIO; - } - - return err; -} - const struct adfs_dir_ops adfs_f_dir_ops = { .read = adfs_f_read, .setpos = adfs_f_setpos, .getnext = adfs_f_getnext, .update = adfs_f_update, - .sync = adfs_f_sync, }; diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index 25308b334dd3..1196c8962feb 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -179,25 +179,8 @@ out: return ret; } -static int -adfs_fplus_sync(struct adfs_dir *dir) -{ - int err = 0; - int i; - - for (i = dir->nr_buffers - 1; i >= 0; i--) { - struct buffer_head *bh = dir->bhs[i]; - sync_dirty_buffer(bh); - if (buffer_req(bh) && !buffer_uptodate(bh)) - err = -EIO; - } - - return err; -} - const struct adfs_dir_ops adfs_fplus_dir_ops = { .read = adfs_fplus_read, .setpos = adfs_fplus_setpos, .getnext = adfs_fplus_getnext, - .sync = adfs_fplus_sync, }; From a317120bf7f8306b594ee650ee14f08a0e599602 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:09:30 +0000 Subject: [PATCH 15/42] fs/adfs: dir: add generic copy functions Directories can span multiple buffers, and we currently open-code memcpy access to these buffers, including dealing with entries that are split across multiple buffers. Such code exists in both directory format implementations. Provide common functions to allow data to be copied from/to the directory buffers as if they were a contiguous set of buffers, and use them when accessing directories. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 4 ++++ fs/adfs/dir.c | 50 ++++++++++++++++++++++++++++++++++++++++ fs/adfs/dir_f.c | 56 ++++++++------------------------------------- fs/adfs/dir_fplus.c | 47 ++++++++++--------------------------- 4 files changed, 75 insertions(+), 82 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 5f1acee768f5..92cbc4b1d902 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -165,6 +165,10 @@ extern const struct dentry_operations adfs_dentry_operations; extern const struct adfs_dir_ops adfs_f_dir_ops; extern const struct adfs_dir_ops adfs_fplus_dir_ops; +int adfs_dir_copyfrom(void *dst, struct adfs_dir *dir, unsigned int offset, + size_t len); +int adfs_dir_copyto(struct adfs_dir *dir, unsigned int offset, const void *src, + size_t len); void adfs_dir_relse(struct adfs_dir *dir); void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj); extern int adfs_dir_update(struct super_block *sb, struct object_info *obj, diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index 16a2639d3ca5..3c303074aa5e 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -14,6 +14,56 @@ */ static DEFINE_RWLOCK(adfs_dir_lock); +int adfs_dir_copyfrom(void *dst, struct adfs_dir *dir, unsigned int offset, + size_t len) +{ + struct super_block *sb = dir->sb; + unsigned int index, remain; + + index = offset >> sb->s_blocksize_bits; + offset &= sb->s_blocksize - 1; + remain = sb->s_blocksize - offset; + if (index + (remain < len) >= dir->nr_buffers) + return -EINVAL; + + if (remain < len) { + memcpy(dst, dir->bhs[index]->b_data + offset, remain); + dst += remain; + len -= remain; + index += 1; + offset = 0; + } + + memcpy(dst, dir->bhs[index]->b_data + offset, len); + + return 0; +} + +int adfs_dir_copyto(struct adfs_dir *dir, unsigned int offset, const void *src, + size_t len) +{ + struct super_block *sb = dir->sb; + unsigned int index, remain; + + index = offset >> sb->s_blocksize_bits; + offset &= sb->s_blocksize - 1; + remain = sb->s_blocksize - offset; + if (index + (remain < len) >= dir->nr_buffers) + return -EINVAL; + + if (remain < len) { + memcpy(dir->bhs[index]->b_data + offset, src, remain); + src += remain; + len -= remain; + index += 1; + offset = 0; + } + + memcpy(dir->bhs[index]->b_data + offset, src, len); + + return 0; +} + void adfs_dir_relse(struct adfs_dir *dir) { unsigned int i; diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index 80ac261b9ec4..3c3b423577d2 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -224,24 +224,12 @@ adfs_obj2dir(struct adfs_direntry *de, struct object_info *obj) static int __adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj) { - struct super_block *sb = dir->sb; struct adfs_direntry de; - int thissize, buffer, offset; + int ret; - buffer = pos >> sb->s_blocksize_bits; - - if (buffer > dir->nr_buffers) - return -EINVAL; - - offset = pos & (sb->s_blocksize - 1); - thissize = sb->s_blocksize - offset; - if (thissize > 26) - thissize = 26; - - memcpy(&de, dir->bh[buffer]->b_data + offset, thissize); - if (thissize != 26) - memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data, - 26 - thissize); + ret = adfs_dir_copyfrom(&de, dir, pos, 26); + if (ret) + return ret; if (!de.dirobname[0]) return -ENOENT; @@ -254,42 +242,16 @@ __adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj) static int __adfs_dir_put(struct adfs_dir *dir, int pos, struct object_info *obj) { - struct super_block *sb = dir->sb; struct adfs_direntry de; - int thissize, buffer, offset; + int ret; - buffer = pos >> sb->s_blocksize_bits; + ret = adfs_dir_copyfrom(&de, dir, pos, 26); + if (ret) + return ret; - if (buffer > dir->nr_buffers) - return -EINVAL; - - offset = pos & (sb->s_blocksize - 1); - thissize = sb->s_blocksize - offset; - if (thissize > 26) - thissize = 26; - - /* - * Get the entry in total - */ - memcpy(&de, dir->bh[buffer]->b_data + offset, thissize); - if (thissize != 26) - memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data, - 26 - thissize); - - /* - * update it - */ adfs_obj2dir(&de, obj); - /* - * Put the new entry back - */ - memcpy(dir->bh[buffer]->b_data + offset, &de, thissize); - if (thissize != 26) - memcpy(dir->bh[buffer + 1]->b_data, ((char *)&de) + thissize, - 26 - thissize); - - return 0; + return adfs_dir_copyto(dir, pos, &de, 26); } /* diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index 1196c8962feb..6a07c0dfcc93 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -112,34 +112,6 @@ adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos) return ret; } -static void -dir_memcpy(struct adfs_dir *dir, unsigned int offset, void *to, int len) -{ - struct super_block *sb = dir->sb; - unsigned int buffer, partial, remainder; - - buffer = offset >> sb->s_blocksize_bits; - offset &= sb->s_blocksize - 1; - - partial = sb->s_blocksize - offset; - - if (partial >= len) - memcpy(to, dir->bhs[buffer]->b_data + offset, len); - else { - char *c = (char *)to; - - remainder = len - partial; - - memcpy(c, - dir->bhs[buffer]->b_data + offset, - partial); - - memcpy(c + partial, - dir->bhs[buffer + 1]->b_data, - remainder); - } -} - static int adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) { @@ -147,16 +119,19 @@ adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) (struct adfs_bigdirheader *) dir->bhs[0]->b_data; struct adfs_bigdirentry bde; unsigned int offset; - int ret = -ENOENT; + int ret; if (dir->pos >= le32_to_cpu(h->bigdirentries)) - goto out; + return -ENOENT; offset = offsetof(struct adfs_bigdirheader, bigdirname); offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3); offset += dir->pos * sizeof(struct adfs_bigdirentry); - dir_memcpy(dir, offset, &bde, sizeof(struct adfs_bigdirentry)); + ret = adfs_dir_copyfrom(&bde, dir, offset, + sizeof(struct adfs_bigdirentry)); + if (ret) + return ret; obj->loadaddr = le32_to_cpu(bde.bigdirload); obj->execaddr = le32_to_cpu(bde.bigdirexec); @@ -170,13 +145,15 @@ adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) offset += le32_to_cpu(h->bigdirentries) * sizeof(struct adfs_bigdirentry); offset += le32_to_cpu(bde.bigdirobnameptr); - dir_memcpy(dir, offset, obj->name, obj->name_len); + ret = adfs_dir_copyfrom(obj->name, dir, offset, obj->name_len); + if (ret) + return ret; + adfs_object_fixup(dir, obj); dir->pos += 1; - ret = 0; -out: - return ret; + + return 0; } const struct adfs_dir_ops adfs_fplus_dir_ops = { From 419a6e5e82ca0bdba0cc3624d969b65ae49d959b Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:09:35 +0000 Subject: [PATCH 16/42] fs/adfs: dir: add generic directory reading Both directory formats code the mechanics of fetching the directory buffers using their own implementations. Consolidate these into one implementation. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 2 ++ fs/adfs/dir.c | 49 +++++++++++++++++++++++++++++ fs/adfs/dir_f.c | 24 +++----------- fs/adfs/dir_fplus.c | 76 ++++++++++++--------------------------------- 4 files changed, 74 insertions(+), 77 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 92cbc4b1d902..01d065937c01 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -170,6 +170,8 @@ int adfs_dir_copyfrom(void *dst, struct adfs_dir *dir, unsigned int offset, int adfs_dir_copyto(struct adfs_dir *dir, unsigned int offset, const void *src, size_t len); void adfs_dir_relse(struct adfs_dir *dir); +int adfs_dir_read_buffers(struct super_block *sb, u32 indaddr, + unsigned int size, struct adfs_dir *dir); void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj); extern int adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait); diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index 3c303074aa5e..b8e2a909fa3f 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -78,6 +78,55 @@ void adfs_dir_relse(struct adfs_dir *dir) dir->sb = NULL; } +int adfs_dir_read_buffers(struct super_block *sb, u32 indaddr, + unsigned int size, struct adfs_dir *dir) +{ + struct buffer_head **bhs; + unsigned int i, num; + int block; + + num = ALIGN(size, sb->s_blocksize) >> sb->s_blocksize_bits; + if (num > ARRAY_SIZE(dir->bh)) { + /* We only allow one extension */ + if (dir->bhs != dir->bh) + return -EINVAL; + + bhs = kcalloc(num, sizeof(*bhs), GFP_KERNEL); + if (!bhs) + return -ENOMEM; + + if (dir->nr_buffers) + memcpy(bhs, dir->bhs, dir->nr_buffers * sizeof(*bhs)); + + dir->bhs = bhs; + } + + for (i = dir->nr_buffers; i < num; i++) { + block = __adfs_block_map(sb, indaddr, i); + if (!block) { + adfs_error(sb, "dir %06x has a hole at offset %u", + indaddr, i); + goto error; + } + + dir->bhs[i] = sb_bread(sb, block); + if (!dir->bhs[i]) { + adfs_error(sb, + "dir %06x failed read at offset %u, mapped block 0x%08x", + indaddr, i, block); + goto error; + } + + dir->nr_buffers++; + } + return 0; + +error: + adfs_dir_relse(dir); + + return -EIO; +} + static int adfs_dir_read(struct super_block *sb, u32 indaddr, unsigned int size, struct adfs_dir *dir) { diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index 3c3b423577d2..027ee714f42b 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -126,7 +126,7 @@ 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; + int ret; /* * Directories which are not a multiple of 2048 bytes @@ -135,24 +135,9 @@ static int adfs_dir_read(struct super_block *sb, u32 indaddr, if (size & 2047) goto bad_dir; - size >>= blocksize_bits; - - for (blk = 0; blk < size; blk++) { - int phys; - - phys = __adfs_block_map(sb, indaddr, blk); - if (!phys) { - adfs_error(sb, "dir %06x has a hole at offset %d", - indaddr, blk); - goto release_buffers; - } - - dir->bh[blk] = sb_bread(sb, phys); - if (!dir->bh[blk]) - goto release_buffers; - - dir->nr_buffers += 1; - } + ret = adfs_dir_read_buffers(sb, indaddr, size, dir); + if (ret) + return ret; memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead)); memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail)); @@ -172,7 +157,6 @@ static int adfs_dir_read(struct super_block *sb, u32 indaddr, bad_dir: adfs_error(sb, "dir %06x is corrupted", indaddr); -release_buffers: adfs_dir_relse(dir); return -EIO; diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index 6a07c0dfcc93..ae11236515d0 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -4,87 +4,49 @@ * * Copyright (C) 1997-1999 Russell King */ -#include #include "adfs.h" #include "dir_fplus.h" -static int -adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir) +static int adfs_fplus_read(struct super_block *sb, u32 indaddr, + unsigned int size, struct adfs_dir *dir) { struct adfs_bigdirheader *h; struct adfs_bigdirtail *t; - unsigned long block; - unsigned int blk, size; - int ret = -EIO; + unsigned int dirsize; + int ret; - block = __adfs_block_map(sb, id, 0); - if (!block) { - adfs_error(sb, "dir object %X has a hole at offset 0", id); - goto out; - } - - dir->bhs[0] = sb_bread(sb, block); - if (!dir->bhs[0]) - goto out; - dir->nr_buffers += 1; + /* Read first buffer */ + ret = adfs_dir_read_buffers(sb, indaddr, sb->s_blocksize, dir); + if (ret) + return ret; h = (struct adfs_bigdirheader *)dir->bhs[0]->b_data; - size = le32_to_cpu(h->bigdirsize); - if (size != sz) { + dirsize = le32_to_cpu(h->bigdirsize); + if (dirsize != size) { adfs_msg(sb, KERN_WARNING, - "directory header size %X does not match directory size %X", - size, sz); + "dir %06x header size %X does not match directory size %X", + indaddr, dirsize, size); } if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || h->bigdirversion[2] != 0 || size & 2047 || h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) { - adfs_error(sb, "dir %06x has malformed header", id); + adfs_error(sb, "dir %06x has malformed header", indaddr); goto out; } - size >>= sb->s_blocksize_bits; - if (size > ARRAY_SIZE(dir->bh)) { - /* this directory is too big for fixed bh set, must allocate */ - struct buffer_head **bhs = - kcalloc(size, sizeof(struct buffer_head *), - GFP_KERNEL); - if (!bhs) { - adfs_msg(sb, KERN_ERR, - "not enough memory for dir object %X (%d blocks)", - id, size); - ret = -ENOMEM; - goto out; - } - dir->bhs = bhs; - /* copy over the pointer to the block that we've already read */ - dir->bhs[0] = dir->bh[0]; - } - - for (blk = 1; blk < size; blk++) { - block = __adfs_block_map(sb, id, blk); - if (!block) { - adfs_error(sb, "dir object %X has a hole at offset %d", id, blk); - goto out; - } - - dir->bhs[blk] = sb_bread(sb, block); - if (!dir->bhs[blk]) { - adfs_error(sb, "dir object %x failed read for offset %d, mapped block %lX", - id, blk, block); - goto out; - } - - dir->nr_buffers += 1; - } + /* Read remaining buffers */ + ret = adfs_dir_read_buffers(sb, indaddr, dirsize, dir); + if (ret) + return ret; t = (struct adfs_bigdirtail *) - (dir->bhs[size - 1]->b_data + (sb->s_blocksize - 8)); + (dir->bhs[dir->nr_buffers - 1]->b_data + (sb->s_blocksize - 8)); if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) || t->bigdirendmasseq != h->startmasseq || t->reserved[0] != 0 || t->reserved[1] != 0) { - adfs_error(sb, "dir %06x has malformed tail", id); + adfs_error(sb, "dir %06x has malformed tail", indaddr); goto out; } From 90011c7ad999c9565a5c97704cd5bda151ebe447 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:09:40 +0000 Subject: [PATCH 17/42] fs/adfs: dir: add helper to read directory using inode Add a helper to read a directory using the inode, which we do in two places. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index b8e2a909fa3f..882377e86041 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -137,6 +137,26 @@ static int adfs_dir_read(struct super_block *sb, u32 indaddr, return ADFS_SB(sb)->s_dir->read(sb, indaddr, size, dir); } +static int adfs_dir_read_inode(struct super_block *sb, struct inode *inode, + struct adfs_dir *dir) +{ + int ret; + + ret = adfs_dir_read(sb, inode->i_ino, inode->i_size, dir); + if (ret) + return ret; + + if (ADFS_I(inode)->parent_id != dir->parent_id) { + adfs_error(sb, + "parent directory id changed under me! (%06x but got %06x)\n", + ADFS_I(inode)->parent_id, dir->parent_id); + adfs_dir_relse(dir); + ret = -EIO; + } + + return ret; +} + static int adfs_dir_sync(struct adfs_dir *dir) { int err = 0; @@ -203,7 +223,7 @@ adfs_readdir(struct file *file, struct dir_context *ctx) if (ctx->pos >> 32) return 0; - ret = adfs_dir_read(sb, inode->i_ino, inode->i_size, &dir); + ret = adfs_dir_read_inode(sb, inode, &dir); if (ret) return ret; @@ -304,18 +324,10 @@ static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr, u32 name_len; int ret; - ret = adfs_dir_read(sb, inode->i_ino, inode->i_size, &dir); + ret = adfs_dir_read_inode(sb, inode, &dir); if (ret) goto out; - if (ADFS_I(inode)->parent_id != dir.parent_id) { - 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; - } - obj->parent_id = inode->i_ino; read_lock(&adfs_dir_lock); From c3c8149b3552b6656ded9ac86d53072f74771ba7 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:09:45 +0000 Subject: [PATCH 18/42] fs/adfs: dir: add helper to mark directory buffers dirty Provide a helper for marking directory buffers dirty so they get written back to disk. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir.c | 12 ++++++++++++ fs/adfs/dir_f.c | 5 +---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index 882377e86041..e8aafc65d545 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -157,6 +157,15 @@ static int adfs_dir_read_inode(struct super_block *sb, struct inode *inode, return ret; } +static void adfs_dir_mark_dirty(struct adfs_dir *dir) +{ + unsigned int i; + + /* Mark the buffers dirty */ + for (i = 0; i < dir->nr_buffers; i++) + mark_buffer_dirty(dir->bhs[i]); +} + static int adfs_dir_sync(struct adfs_dir *dir) { int err = 0; @@ -280,6 +289,9 @@ adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) ret = ops->update(&dir, obj); write_unlock(&adfs_dir_lock); + if (ret == 0) + adfs_dir_mark_dirty(&dir); + if (wait) { int err = adfs_dir_sync(&dir); if (!ret) diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index 027ee714f42b..682df46d8d33 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -306,7 +306,7 @@ static int adfs_f_update(struct adfs_dir *dir, struct object_info *obj) { struct super_block *sb = dir->sb; - int ret, i; + int ret; ret = adfs_dir_find_entry(dir, obj->indaddr); if (ret < 0) { @@ -347,9 +347,6 @@ adfs_f_update(struct adfs_dir *dir, struct object_info *obj) goto bad_dir; } #endif - for (i = dir->nr_buffers - 1; i >= 0; i--) - mark_buffer_dirty(dir->bh[i]); - ret = 0; out: return ret; From deed1bfd150c7c71bcdc16419c90933096c1c75e Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:09:51 +0000 Subject: [PATCH 19/42] fs/adfs: dir: update directory locking Update directory locking such that it covers the validation of the directory, which could fail if another thread is concurrently writing to the same directory. Since we may sleep, we need to use a rwsem rather than a rw spinlock. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir.c | 55 +++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index e8aafc65d545..ff9c921be31c 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -12,7 +12,7 @@ /* * For future. This should probably be per-directory. */ -static DEFINE_RWLOCK(adfs_dir_lock); +static DECLARE_RWSEM(adfs_dir_rwsem); int adfs_dir_copyfrom(void *dst, struct adfs_dir *dir, unsigned int offset, size_t len) @@ -232,26 +232,25 @@ adfs_readdir(struct file *file, struct dir_context *ctx) if (ctx->pos >> 32) return 0; + down_read(&adfs_dir_rwsem); ret = adfs_dir_read_inode(sb, inode, &dir); if (ret) - return ret; + goto unlock; if (ctx->pos == 0) { if (!dir_emit_dot(file, ctx)) - goto free_out; + goto unlock_relse; ctx->pos = 1; } if (ctx->pos == 1) { if (!dir_emit(ctx, "..", 2, dir.parent_id, DT_DIR)) - goto free_out; + goto unlock_relse; ctx->pos = 2; } - read_lock(&adfs_dir_lock); - ret = ops->setpos(&dir, ctx->pos - 2); if (ret) - goto unlock_out; + goto unlock_relse; while (ops->getnext(&dir, &obj) == 0) { if (!dir_emit(ctx, obj.name, obj.name_len, obj.indaddr, DT_UNKNOWN)) @@ -259,12 +258,14 @@ adfs_readdir(struct file *file, struct dir_context *ctx) ctx->pos++; } -unlock_out: - read_unlock(&adfs_dir_lock); - -free_out: +unlock_relse: + up_read(&adfs_dir_rwsem); adfs_dir_relse(&dir); return ret; + +unlock: + up_read(&adfs_dir_rwsem); + return ret; } int @@ -281,13 +282,13 @@ adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) if (!ops->update) return -EINVAL; + down_write(&adfs_dir_rwsem); ret = adfs_dir_read(sb, obj->parent_id, 0, &dir); if (ret) - goto out; + goto unlock; - write_lock(&adfs_dir_lock); ret = ops->update(&dir, obj); - write_unlock(&adfs_dir_lock); + up_write(&adfs_dir_rwsem); if (ret == 0) adfs_dir_mark_dirty(&dir); @@ -299,7 +300,10 @@ adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) } adfs_dir_relse(&dir); -out: + return ret; + +unlock: + up_write(&adfs_dir_rwsem); #endif return ret; } @@ -336,17 +340,14 @@ static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr, u32 name_len; int ret; + down_read(&adfs_dir_rwsem); ret = adfs_dir_read_inode(sb, inode, &dir); if (ret) - goto out; - - obj->parent_id = inode->i_ino; - - read_lock(&adfs_dir_lock); + goto unlock; ret = ops->setpos(&dir, 0); if (ret) - goto unlock_out; + goto unlock_relse; ret = -ENOENT; name = qstr->name; @@ -357,13 +358,15 @@ static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr, break; } } + obj->parent_id = inode->i_ino; -unlock_out: - read_unlock(&adfs_dir_lock); - -free_out: +unlock_relse: + up_read(&adfs_dir_rwsem); adfs_dir_relse(&dir); -out: + return ret; + +unlock: + up_read(&adfs_dir_rwsem); return ret; } From ae5df41390eb1c40b9a5c220673d8c31a4cb57db Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:09:56 +0000 Subject: [PATCH 20/42] fs/adfs: dir: modernise on-disk directory structures Use __u8 and pack the structures for on-disk directories. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir_f.h | 52 ++++++++++++++++++++++++--------------------- fs/adfs/dir_fplus.h | 6 +++--- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/fs/adfs/dir_f.h b/fs/adfs/dir_f.h index 5aec332b90f5..a5393e6cf9f4 100644 --- a/fs/adfs/dir_f.h +++ b/fs/adfs/dir_f.h @@ -13,9 +13,9 @@ * Directory header */ struct adfs_dirheader { - unsigned char startmasseq; - unsigned char startname[4]; -}; + __u8 startmasseq; + __u8 startname[4]; +} __attribute__((packed)); #define ADFS_NEWDIR_SIZE 2048 #define ADFS_NUM_DIR_ENTRIES 77 @@ -31,32 +31,36 @@ struct adfs_direntry { __u8 dirlen[4]; __u8 dirinddiscadd[3]; __u8 newdiratts; -}; +} __attribute__((packed)); /* * Directory tail */ +struct adfs_olddirtail { + __u8 dirlastmask; + char dirname[10]; + __u8 dirparent[3]; + char dirtitle[19]; + __u8 reserved[14]; + __u8 endmasseq; + __u8 endname[4]; + __u8 dircheckbyte; +} __attribute__((packed)); + +struct adfs_newdirtail { + __u8 dirlastmask; + __u8 reserved[2]; + __u8 dirparent[3]; + char dirtitle[19]; + char dirname[10]; + __u8 endmasseq; + __u8 endname[4]; + __u8 dircheckbyte; +} __attribute__((packed)); + union adfs_dirtail { - struct { - unsigned char dirlastmask; - char dirname[10]; - unsigned char dirparent[3]; - char dirtitle[19]; - unsigned char reserved[14]; - unsigned char endmasseq; - unsigned char endname[4]; - unsigned char dircheckbyte; - } old; - struct { - unsigned char dirlastmask; - unsigned char reserved[2]; - unsigned char dirparent[3]; - char dirtitle[19]; - char dirname[10]; - unsigned char endmasseq; - unsigned char endname[4]; - unsigned char dircheckbyte; - } new; + struct adfs_olddirtail old; + struct adfs_newdirtail new; }; #endif diff --git a/fs/adfs/dir_fplus.h b/fs/adfs/dir_fplus.h index 4ec0931e36ad..d729b1591e5e 100644 --- a/fs/adfs/dir_fplus.h +++ b/fs/adfs/dir_fplus.h @@ -22,7 +22,7 @@ struct adfs_bigdirheader { __le32 bigdirnamesize; __le32 bigdirparent; char bigdirname[1]; -}; +} __attribute__((packed, aligned(4))); struct adfs_bigdirentry { __le32 bigdirload; @@ -32,11 +32,11 @@ struct adfs_bigdirentry { __le32 bigdirattr; __le32 bigdirobnamelen; __le32 bigdirobnameptr; -}; +} __attribute__((packed, aligned(4))); struct adfs_bigdirtail { __le32 bigdirendname; __u8 bigdirendmasseq; __u8 reserved[2]; __u8 bigdircheckbyte; -}; +} __attribute__((packed, aligned(4))); From f6075c79074378910e131bbebc9d1dab53fd9986 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:10:01 +0000 Subject: [PATCH 21/42] fs/adfs: dir: improve update failure handling When we update a directory, a number of errors may happen. If we failed to find the entry to update, we can just release the directory buffers as normal. However, if we have some other error, we may have partially updated the buffers, resulting in an invalid directory. In this case, we need to discard the buffers to avoid writing the contents back to the media, and later re-read the directory from the media. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir.c | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index ff9c921be31c..5e5d344bae7c 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -64,12 +64,8 @@ int adfs_dir_copyto(struct adfs_dir *dir, unsigned int offset, const void *src, return 0; } -void adfs_dir_relse(struct adfs_dir *dir) +static void __adfs_dir_cleanup(struct adfs_dir *dir) { - unsigned int i; - - for (i = 0; i < dir->nr_buffers; i++) - brelse(dir->bhs[i]); dir->nr_buffers = 0; if (dir->bhs != dir->bh) @@ -78,6 +74,26 @@ void adfs_dir_relse(struct adfs_dir *dir) dir->sb = NULL; } +void adfs_dir_relse(struct adfs_dir *dir) +{ + unsigned int i; + + for (i = 0; i < dir->nr_buffers; i++) + brelse(dir->bhs[i]); + + __adfs_dir_cleanup(dir); +} + +static void adfs_dir_forget(struct adfs_dir *dir) +{ + unsigned int i; + + for (i = 0; i < dir->nr_buffers; i++) + bforget(dir->bhs[i]); + + __adfs_dir_cleanup(dir); +} + int adfs_dir_read_buffers(struct super_block *sb, u32 indaddr, unsigned int size, struct adfs_dir *dir) { @@ -288,20 +304,28 @@ adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) goto unlock; ret = ops->update(&dir, obj); + if (ret) + goto forget; up_write(&adfs_dir_rwsem); - if (ret == 0) - adfs_dir_mark_dirty(&dir); + adfs_dir_mark_dirty(&dir); - if (wait) { - int err = adfs_dir_sync(&dir); - if (!ret) - ret = err; - } + if (wait) + ret = adfs_dir_sync(&dir); adfs_dir_relse(&dir); return ret; + /* + * If the updated failed because the entry wasn't found, we can + * just release the buffers. If it was any other error, forget + * the dirtied buffers so they aren't written back to the media. + */ +forget: + if (ret == -ENOENT) + adfs_dir_relse(&dir); + else + adfs_dir_forget(&dir); unlock: up_write(&adfs_dir_rwsem); #endif From 4a0a88b6660b48f773b6e7631e0be57b7f5048ed Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:10:06 +0000 Subject: [PATCH 22/42] fs/adfs: dir: improve compiler coverage in adfs_dir_update Get rid of the ifdef, using IS_ENABLED() instead to detect whether the code should be callable. This allows the compiler to always parse the following code, reducing the chances of errors being missed. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index 5e5d344bae7c..931eefb2375b 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -287,14 +287,16 @@ unlock: int adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) { - int ret = -EINVAL; -#ifdef CONFIG_ADFS_FS_RW const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; struct adfs_dir dir; + int ret; printk(KERN_INFO "adfs_dir_update: object %06x in dir %06x\n", obj->indaddr, obj->parent_id); + if (!IS_ENABLED(CONFIG_ADFS_FS_RW)) + return -EINVAL; + if (!ops->update) return -EINVAL; @@ -328,7 +330,7 @@ forget: adfs_dir_forget(&dir); unlock: up_write(&adfs_dir_rwsem); -#endif + return ret; } From cdc46e99e1c9f50802c4f543f10151887e4c4e0e Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:10:11 +0000 Subject: [PATCH 23/42] fs/adfs: dir: switch to iterate_shared method There is nothing in our readdir (aka iterate) method that relies on the directory inode being exclusively locked, so switch to using the iterate_shared() hook rather than iterate(). Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index 931eefb2375b..2a8f5f1fd3d0 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -235,8 +235,7 @@ void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj) } } -static int -adfs_readdir(struct file *file, struct dir_context *ctx) +static int adfs_iterate(struct file *file, struct dir_context *ctx) { struct inode *inode = file_inode(file); struct super_block *sb = inode->i_sb; @@ -399,7 +398,7 @@ unlock: const struct file_operations adfs_dir_operations = { .read = generic_read_dir, .llseek = generic_file_llseek, - .iterate = adfs_readdir, + .iterate_shared = adfs_iterate, .fsync = generic_file_fsync, }; From 4287e4deb1280633ffbda608f946d6d7c2d76d4a Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:10:16 +0000 Subject: [PATCH 24/42] fs/adfs: dir: add more efficient iterate() per-format method Rather than using setpos + getnext to iterate through the directory entries, pass iterate() down to the dir format code to populate the dirents. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 1 + fs/adfs/dir.c | 16 ++-------------- fs/adfs/dir_f.c | 18 ++++++++++++++++++ fs/adfs/dir_fplus.c | 21 +++++++++++++++++++++ 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 01d065937c01..cbf33f375e0b 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -120,6 +120,7 @@ struct object_info { struct adfs_dir_ops { int (*read)(struct super_block *sb, unsigned int indaddr, unsigned int size, struct adfs_dir *dir); + int (*iterate)(struct adfs_dir *dir, struct dir_context *ctx); 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); diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index 2a8f5f1fd3d0..7fda44464121 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -240,12 +240,8 @@ static int adfs_iterate(struct file *file, struct dir_context *ctx) struct inode *inode = file_inode(file); struct super_block *sb = inode->i_sb; const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; - struct object_info obj; struct adfs_dir dir; - int ret = 0; - - if (ctx->pos >> 32) - return 0; + int ret; down_read(&adfs_dir_rwsem); ret = adfs_dir_read_inode(sb, inode, &dir); @@ -263,15 +259,7 @@ static int adfs_iterate(struct file *file, struct dir_context *ctx) ctx->pos = 2; } - ret = ops->setpos(&dir, ctx->pos - 2); - if (ret) - goto unlock_relse; - while (ops->getnext(&dir, &obj) == 0) { - if (!dir_emit(ctx, obj.name, obj.name_len, - obj.indaddr, DT_UNKNOWN)) - break; - ctx->pos++; - } + ret = ops->iterate(&dir, ctx); unlock_relse: up_read(&adfs_dir_rwsem); diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index 682df46d8d33..2e342871d6df 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -302,6 +302,23 @@ adfs_f_getnext(struct adfs_dir *dir, struct object_info *obj) return ret; } +static int adfs_f_iterate(struct adfs_dir *dir, struct dir_context *ctx) +{ + struct object_info obj; + int pos = 5 + (ctx->pos - 2) * 26; + + while (ctx->pos < 2 + ADFS_NUM_DIR_ENTRIES) { + if (__adfs_dir_get(dir, pos, &obj)) + break; + if (!dir_emit(ctx, obj.name, obj.name_len, + obj.indaddr, DT_UNKNOWN)) + break; + pos += 26; + ctx->pos++; + } + return 0; +} + static int adfs_f_update(struct adfs_dir *dir, struct object_info *obj) { @@ -359,6 +376,7 @@ bad_dir: const struct adfs_dir_ops adfs_f_dir_ops = { .read = adfs_f_read, + .iterate = adfs_f_iterate, .setpos = adfs_f_setpos, .getnext = adfs_f_getnext, .update = adfs_f_update, diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index ae11236515d0..edcbaa94ecb9 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -118,8 +118,29 @@ adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) return 0; } +static int adfs_fplus_iterate(struct adfs_dir *dir, struct dir_context *ctx) +{ + struct object_info obj; + + if ((ctx->pos - 2) >> 32) + return 0; + + if (adfs_fplus_setpos(dir, ctx->pos - 2)) + return 0; + + while (!adfs_fplus_getnext(dir, &obj)) { + if (!dir_emit(ctx, obj.name, obj.name_len, + obj.indaddr, DT_UNKNOWN)) + break; + ctx->pos++; + } + + return 0; +} + const struct adfs_dir_ops adfs_fplus_dir_ops = { .read = adfs_fplus_read, + .iterate = adfs_fplus_iterate, .setpos = adfs_fplus_setpos, .getnext = adfs_fplus_getnext, }; From 016936b32131d0b33328d8c109f83fabb56618a3 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:10:21 +0000 Subject: [PATCH 25/42] fs/adfs: dir: use pointers to access directory head/tails Add and use pointers in the adfs_dir structure to access the directory head and tail structures, which will always be contiguous in a buffer. This allows us to avoid memcpy()ing the data in the new directory code, making it slightly more efficient. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 12 ++++++++---- fs/adfs/dir_f.c | 42 +++++++++++++++++------------------------- fs/adfs/dir_fplus.c | 11 ++++------- 3 files changed, 29 insertions(+), 36 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index cbf33f375e0b..1f431a42e14c 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -26,8 +26,6 @@ static inline u16 adfs_filetype(u32 loadaddr) #define ADFS_NDA_PUBLIC_READ (1 << 5) #define ADFS_NDA_PUBLIC_WRITE (1 << 6) -#include "dir_f.h" - /* * adfs file system inode data in memory */ @@ -98,8 +96,14 @@ struct adfs_dir { unsigned int pos; __u32 parent_id; - struct adfs_dirheader dirhead; - union adfs_dirtail dirtail; + union { + struct adfs_dirheader *dirhead; + struct adfs_bigdirheader *bighead; + }; + union { + struct adfs_newdirtail *newtail; + struct adfs_bigdirtail *bigtail; + }; }; /* diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index 2e342871d6df..7e56fcc21303 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -58,7 +58,7 @@ static inline void adfs_writeval(unsigned char *p, int len, unsigned int val) #define bufoff(_bh,_idx) \ ({ int _buf = _idx >> blocksize_bits; \ int _off = _idx - (_buf << blocksize_bits);\ - (u8 *)(_bh[_buf]->b_data + _off); \ + (void *)(_bh[_buf]->b_data + _off); \ }) /* @@ -139,18 +139,18 @@ static int adfs_dir_read(struct super_block *sb, u32 indaddr, if (ret) return ret; - memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead)); - memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail)); + dir->dirhead = bufoff(dir->bh, 0); + dir->newtail = bufoff(dir->bh, 2007); - if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq || - memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4)) + if (dir->dirhead->startmasseq != dir->newtail->endmasseq || + memcmp(&dir->dirhead->startname, &dir->newtail->endname, 4)) goto bad_dir; - if (memcmp(&dir->dirhead.startname, "Nick", 4) && - memcmp(&dir->dirhead.startname, "Hugo", 4)) + if (memcmp(&dir->dirhead->startname, "Nick", 4) && + memcmp(&dir->dirhead->startname, "Hugo", 4)) goto bad_dir; - if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte) + if (adfs_dir_checkbyte(dir) != dir->newtail->dircheckbyte) goto bad_dir; return 0; @@ -275,7 +275,7 @@ static int adfs_f_read(struct super_block *sb, u32 indaddr, unsigned int size, if (ret) adfs_error(sb, "unable to read directory"); else - dir->parent_id = adfs_readval(dir->dirtail.new.dirparent, 3); + dir->parent_id = adfs_readval(dir->newtail->dirparent, 3); return ret; } @@ -322,7 +322,6 @@ static int adfs_f_iterate(struct adfs_dir *dir, struct dir_context *ctx) static int adfs_f_update(struct adfs_dir *dir, struct object_info *obj) { - struct super_block *sb = dir->sb; int ret; ret = adfs_dir_find_entry(dir, obj->indaddr); @@ -336,33 +335,26 @@ adfs_f_update(struct adfs_dir *dir, struct object_info *obj) /* * Increment directory sequence number */ - dir->bh[0]->b_data[0] += 1; - dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 6] += 1; + dir->dirhead->startmasseq += 1; + dir->newtail->endmasseq += 1; ret = adfs_dir_checkbyte(dir); /* * Update directory check byte */ - dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 1] = ret; + dir->newtail->dircheckbyte = ret; #if 1 - { - const unsigned int blocksize_bits = sb->s_blocksize_bits; - - memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead)); - memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail)); - - if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq || - memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4)) + if (dir->dirhead->startmasseq != dir->newtail->endmasseq || + memcmp(&dir->dirhead->startname, &dir->newtail->endname, 4)) goto bad_dir; - if (memcmp(&dir->dirhead.startname, "Nick", 4) && - memcmp(&dir->dirhead.startname, "Hugo", 4)) + if (memcmp(&dir->dirhead->startname, "Nick", 4) && + memcmp(&dir->dirhead->startname, "Hugo", 4)) goto bad_dir; - if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte) + if (adfs_dir_checkbyte(dir) != dir->newtail->dircheckbyte) goto bad_dir; - } #endif ret = 0; out: diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index edcbaa94ecb9..6f2dbcf6819b 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -20,7 +20,7 @@ static int adfs_fplus_read(struct super_block *sb, u32 indaddr, if (ret) return ret; - h = (struct adfs_bigdirheader *)dir->bhs[0]->b_data; + dir->bighead = h = (void *)dir->bhs[0]->b_data; dirsize = le32_to_cpu(h->bigdirsize); if (dirsize != size) { adfs_msg(sb, KERN_WARNING, @@ -40,7 +40,7 @@ static int adfs_fplus_read(struct super_block *sb, u32 indaddr, if (ret) return ret; - t = (struct adfs_bigdirtail *) + dir->bigtail = t = (struct adfs_bigdirtail *) (dir->bhs[dir->nr_buffers - 1]->b_data + (sb->s_blocksize - 8)); if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) || @@ -62,11 +62,9 @@ out: static int adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos) { - struct adfs_bigdirheader *h = - (struct adfs_bigdirheader *) dir->bhs[0]->b_data; int ret = -ENOENT; - if (fpos <= le32_to_cpu(h->bigdirentries)) { + if (fpos <= le32_to_cpu(dir->bighead->bigdirentries)) { dir->pos = fpos; ret = 0; } @@ -77,8 +75,7 @@ adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos) static int adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) { - struct adfs_bigdirheader *h = - (struct adfs_bigdirheader *) dir->bhs[0]->b_data; + struct adfs_bigdirheader *h = dir->bighead; struct adfs_bigdirentry bde; unsigned int offset; int ret; From ffc8df347e4934c8bad776f7bdacb4842620b0c7 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:10:27 +0000 Subject: [PATCH 26/42] fs/adfs: newdir: factor out directory format validation We have two locations where we validate the new directory format, so factor this out to a helper. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir_f.c | 48 ++++++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index 7e56fcc21303..196706d581bf 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -121,6 +121,21 @@ adfs_dir_checkbyte(const struct adfs_dir *dir) return (dircheck ^ (dircheck >> 8) ^ (dircheck >> 16) ^ (dircheck >> 24)) & 0xff; } +static int adfs_f_validate(struct adfs_dir *dir) +{ + struct adfs_dirheader *head = dir->dirhead; + struct adfs_newdirtail *tail = dir->newtail; + + if (head->startmasseq != tail->endmasseq || + (memcmp(&head->startname, "Nick", 4) && + memcmp(&head->startname, "Hugo", 4)) || + memcmp(&head->startname, &tail->endname, 4) || + adfs_dir_checkbyte(dir) != tail->dircheckbyte) + return -EIO; + + return 0; +} + /* 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) @@ -142,15 +157,7 @@ static int adfs_dir_read(struct super_block *sb, u32 indaddr, dir->dirhead = bufoff(dir->bh, 0); dir->newtail = bufoff(dir->bh, 2007); - if (dir->dirhead->startmasseq != dir->newtail->endmasseq || - memcmp(&dir->dirhead->startname, &dir->newtail->endname, 4)) - goto bad_dir; - - if (memcmp(&dir->dirhead->startname, "Nick", 4) && - memcmp(&dir->dirhead->startname, "Hugo", 4)) - goto bad_dir; - - if (adfs_dir_checkbyte(dir) != dir->newtail->dircheckbyte) + if (adfs_f_validate(dir)) goto bad_dir; return 0; @@ -327,7 +334,7 @@ adfs_f_update(struct adfs_dir *dir, struct object_info *obj) ret = adfs_dir_find_entry(dir, obj->indaddr); if (ret < 0) { adfs_error(dir->sb, "unable to locate entry to update"); - goto out; + return ret; } __adfs_dir_put(dir, ret, obj); @@ -344,26 +351,11 @@ adfs_f_update(struct adfs_dir *dir, struct object_info *obj) */ dir->newtail->dircheckbyte = ret; -#if 1 - if (dir->dirhead->startmasseq != dir->newtail->endmasseq || - memcmp(&dir->dirhead->startname, &dir->newtail->endname, 4)) - goto bad_dir; + ret = adfs_f_validate(dir); + if (ret) + adfs_error(dir->sb, "whoops! I broke a directory!"); - if (memcmp(&dir->dirhead->startname, "Nick", 4) && - memcmp(&dir->dirhead->startname, "Hugo", 4)) - goto bad_dir; - - if (adfs_dir_checkbyte(dir) != dir->newtail->dircheckbyte) - goto bad_dir; -#endif - ret = 0; -out: return ret; -#if 1 -bad_dir: - adfs_error(dir->sb, "whoops! I broke a directory!"); - return -EIO; -#endif } const struct adfs_dir_ops adfs_f_dir_ops = { From 7a0e4048bfd16848ac115b17f49a3df7993a2fac Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:10:32 +0000 Subject: [PATCH 27/42] fs/adfs: newdir: improve directory validation Check that the lastmask and reserved fields are all zero, as per the documentation. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir_f.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index 196706d581bf..ebe8616ee533 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -127,6 +127,7 @@ static int adfs_f_validate(struct adfs_dir *dir) struct adfs_newdirtail *tail = dir->newtail; if (head->startmasseq != tail->endmasseq || + tail->dirlastmask || tail->reserved[0] || tail->reserved[1] || (memcmp(&head->startname, "Nick", 4) && memcmp(&head->startname, "Hugo", 4)) || memcmp(&head->startname, &tail->endname, 4) || From 9318731bec8d38bdbe701d395cf103157046831d Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:10:37 +0000 Subject: [PATCH 28/42] fs/adfs: newdir: merge adfs_dir_read() into adfs_f_read() adfs_dir_read() is only called from adfs_f_read(), so merge it into that function. As new directories are always 2048 bytes in size, (which we rely on elsewhere) we can consolidate some of the code. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir_f.c | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index ebe8616ee533..dbb4f1ef7bb7 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -138,20 +138,16 @@ static int adfs_f_validate(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) +static int adfs_f_read(struct super_block *sb, u32 indaddr, unsigned int size, + struct adfs_dir *dir) { const unsigned int blocksize_bits = sb->s_blocksize_bits; int ret; - /* - * Directories which are not a multiple of 2048 bytes - * are considered bad v2 [3.6] - */ - if (size & 2047) - goto bad_dir; + if (size && size != ADFS_NEWDIR_SIZE) + return -EIO; - ret = adfs_dir_read_buffers(sb, indaddr, size, dir); + ret = adfs_dir_read_buffers(sb, indaddr, ADFS_NEWDIR_SIZE, dir); if (ret) return ret; @@ -161,6 +157,8 @@ static int adfs_dir_read(struct super_block *sb, u32 indaddr, if (adfs_f_validate(dir)) goto bad_dir; + dir->parent_id = adfs_readval(dir->newtail->dirparent, 3); + return 0; bad_dir: @@ -271,23 +269,6 @@ static int adfs_dir_find_entry(struct adfs_dir *dir, u32 indaddr) return ret; } -static int adfs_f_read(struct super_block *sb, u32 indaddr, unsigned int size, - struct adfs_dir *dir) -{ - int ret; - - if (size != ADFS_NEWDIR_SIZE) - return -EIO; - - ret = adfs_dir_read(sb, indaddr, size, dir); - if (ret) - adfs_error(sb, "unable to read directory"); - else - dir->parent_id = adfs_readval(dir->newtail->dirparent, 3); - - return ret; -} - static int adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos) { From cc625ccd0e6c2804cd0935743e3b51121a712562 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:10:42 +0000 Subject: [PATCH 29/42] fs/adfs: newdir: clean up adfs_f_update() __adfs_dir_put() and adfs_dir_find_entry() are only called from adfs_f_update(), so move them into this function, removing some unnecessary entry copying by doing so. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir_f.c | 73 ++++++++++++++++--------------------------------- 1 file changed, 24 insertions(+), 49 deletions(-) diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index dbb4f1ef7bb7..36cfadb2b893 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -229,46 +229,6 @@ __adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj) return 0; } -static int -__adfs_dir_put(struct adfs_dir *dir, int pos, struct object_info *obj) -{ - struct adfs_direntry de; - int ret; - - ret = adfs_dir_copyfrom(&de, dir, pos, 26); - if (ret) - return ret; - - adfs_obj2dir(&de, obj); - - return adfs_dir_copyto(dir, pos, &de, 26); -} - -/* - * the caller is responsible for holding the necessary - * locks. - */ -static int adfs_dir_find_entry(struct adfs_dir *dir, u32 indaddr) -{ - int pos, ret; - - ret = -ENOENT; - - for (pos = 5; pos < ADFS_NUM_DIR_ENTRIES * 26 + 5; pos += 26) { - struct object_info obj; - - if (!__adfs_dir_get(dir, pos, &obj)) - break; - - if (obj.indaddr == indaddr) { - ret = pos; - break; - } - } - - return ret; -} - static int adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos) { @@ -308,18 +268,33 @@ static int adfs_f_iterate(struct adfs_dir *dir, struct dir_context *ctx) return 0; } -static int -adfs_f_update(struct adfs_dir *dir, struct object_info *obj) +static int adfs_f_update(struct adfs_dir *dir, struct object_info *obj) { - int ret; + struct adfs_direntry de; + int offset, ret; - ret = adfs_dir_find_entry(dir, obj->indaddr); - if (ret < 0) { - adfs_error(dir->sb, "unable to locate entry to update"); + offset = 5 - (int)sizeof(de); + + do { + offset += sizeof(de); + ret = adfs_dir_copyfrom(&de, dir, offset, sizeof(de)); + if (ret) { + adfs_error(dir->sb, "error reading directory entry"); + return -ENOENT; + } + if (!de.dirobname[0]) { + adfs_error(dir->sb, "unable to locate entry to update"); + return -ENOENT; + } + } while (adfs_readval(de.dirinddiscadd, 3) != obj->indaddr); + + /* Update the directory entry with the new object state */ + adfs_obj2dir(&de, obj); + + /* Write the directory entry back to the directory */ + ret = adfs_dir_copyto(dir, pos, &de, 26); + if (ret) return ret; - } - - __adfs_dir_put(dir, ret, obj); /* * Increment directory sequence number From aacc954c1be8910a994e09a8f8757a2e3e231c37 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:10:47 +0000 Subject: [PATCH 30/42] fs/adfs: newdir: split out directory commit from update After changing a directory, we need to update the sequence numbers and calculate the new check byte before the directory is scheduled to be written back to the media. Since this needs to happen for any change to the directory, move this into a separate method. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 1 + fs/adfs/dir.c | 4 ++++ fs/adfs/dir_f.c | 26 +++++++++++++------------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 1f431a42e14c..c05555252fec 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -130,6 +130,7 @@ struct adfs_dir_ops { int (*update)(struct adfs_dir *dir, struct object_info *obj); int (*create)(struct adfs_dir *dir, struct object_info *obj); int (*remove)(struct adfs_dir *dir, struct object_info *obj); + int (*commit)(struct adfs_dir *dir); }; struct adfs_discmap { diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index 7fda44464121..3d4bbe836fb5 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -293,6 +293,10 @@ adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) goto unlock; ret = ops->update(&dir, obj); + if (ret) + goto forget; + + ret = ops->commit(&dir); if (ret) goto forget; up_write(&adfs_dir_rwsem); diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c index 36cfadb2b893..30d526fecc3f 100644 --- a/fs/adfs/dir_f.c +++ b/fs/adfs/dir_f.c @@ -292,25 +292,24 @@ static int adfs_f_update(struct adfs_dir *dir, struct object_info *obj) adfs_obj2dir(&de, obj); /* Write the directory entry back to the directory */ - ret = adfs_dir_copyto(dir, pos, &de, 26); - if (ret) - return ret; - - /* - * Increment directory sequence number - */ + return adfs_dir_copyto(dir, offset, &de, 26); +} + +static int adfs_f_commit(struct adfs_dir *dir) +{ + int ret; + + /* Increment directory sequence number */ dir->dirhead->startmasseq += 1; dir->newtail->endmasseq += 1; - ret = adfs_dir_checkbyte(dir); - /* - * Update directory check byte - */ - dir->newtail->dircheckbyte = ret; + /* Update directory check byte */ + dir->newtail->dircheckbyte = adfs_dir_checkbyte(dir); + /* Make sure the directory still validates correctly */ ret = adfs_f_validate(dir); if (ret) - adfs_error(dir->sb, "whoops! I broke a directory!"); + adfs_msg(dir->sb, KERN_ERR, "error: update broke directory"); return ret; } @@ -321,4 +320,5 @@ const struct adfs_dir_ops adfs_f_dir_ops = { .setpos = adfs_f_setpos, .getnext = adfs_f_getnext, .update = adfs_f_update, + .commit = adfs_f_commit, }; From 0db35a02a1c3f3abdfac9d56c1cee2fe23b66987 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:10:52 +0000 Subject: [PATCH 31/42] fs/adfs: bigdir: factor out directory entry offset calculation Factor out the directory entry byte offset calculation. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir_fplus.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index 6f2dbcf6819b..393921f5121e 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -7,6 +7,15 @@ #include "adfs.h" #include "dir_fplus.h" +/* Return the byte offset to directory entry pos */ +static unsigned int adfs_fplus_offset(const struct adfs_bigdirheader *h, + unsigned int pos) +{ + return offsetof(struct adfs_bigdirheader, bigdirname) + + ALIGN(le32_to_cpu(h->bigdirnamelen), 4) + + pos * sizeof(struct adfs_bigdirentry); +} + static int adfs_fplus_read(struct super_block *sb, u32 indaddr, unsigned int size, struct adfs_dir *dir) { @@ -83,9 +92,7 @@ adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) if (dir->pos >= le32_to_cpu(h->bigdirentries)) return -ENOENT; - offset = offsetof(struct adfs_bigdirheader, bigdirname); - offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3); - offset += dir->pos * sizeof(struct adfs_bigdirentry); + offset = adfs_fplus_offset(h, dir->pos); ret = adfs_dir_copyfrom(&bde, dir, offset, sizeof(struct adfs_bigdirentry)); @@ -99,9 +106,7 @@ adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) obj->attr = le32_to_cpu(bde.bigdirattr); obj->name_len = le32_to_cpu(bde.bigdirobnamelen); - offset = offsetof(struct adfs_bigdirheader, bigdirname); - offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3); - offset += le32_to_cpu(h->bigdirentries) * sizeof(struct adfs_bigdirentry); + offset = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries)); offset += le32_to_cpu(bde.bigdirobnameptr); ret = adfs_dir_copyfrom(obj->name, dir, offset, obj->name_len); From 6674ecab9004dcc4d8a65744f581b9ccf1f17504 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:10:57 +0000 Subject: [PATCH 32/42] fs/adfs: bigdir: extract directory validation Extract the directory validation from the directory reading function as we will want to re-use this code. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir_fplus.c | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index 393921f5121e..b83a74e9ff6d 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -16,6 +16,30 @@ static unsigned int adfs_fplus_offset(const struct adfs_bigdirheader *h, pos * sizeof(struct adfs_bigdirentry); } +static int adfs_fplus_validate_header(const struct adfs_bigdirheader *h) +{ + unsigned int size = le32_to_cpu(h->bigdirsize); + + if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || + h->bigdirversion[2] != 0 || + h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME) || + size & 2047) + return -EIO; + + return 0; +} + +static int adfs_fplus_validate_tail(const struct adfs_bigdirheader *h, + const struct adfs_bigdirtail *t) +{ + if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) || + t->bigdirendmasseq != h->startmasseq || + t->reserved[0] != 0 || t->reserved[1] != 0) + return -EIO; + + return 0; +} + static int adfs_fplus_read(struct super_block *sb, u32 indaddr, unsigned int size, struct adfs_dir *dir) { @@ -30,6 +54,11 @@ static int adfs_fplus_read(struct super_block *sb, u32 indaddr, return ret; dir->bighead = h = (void *)dir->bhs[0]->b_data; + if (adfs_fplus_validate_header(h)) { + adfs_error(sb, "dir %06x has malformed header", indaddr); + goto out; + } + dirsize = le32_to_cpu(h->bigdirsize); if (dirsize != size) { adfs_msg(sb, KERN_WARNING, @@ -37,13 +66,6 @@ static int adfs_fplus_read(struct super_block *sb, u32 indaddr, indaddr, dirsize, size); } - if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || - h->bigdirversion[2] != 0 || size & 2047 || - h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) { - adfs_error(sb, "dir %06x has malformed header", indaddr); - goto out; - } - /* Read remaining buffers */ ret = adfs_dir_read_buffers(sb, indaddr, dirsize, dir); if (ret) @@ -52,9 +74,8 @@ static int adfs_fplus_read(struct super_block *sb, u32 indaddr, dir->bigtail = t = (struct adfs_bigdirtail *) (dir->bhs[dir->nr_buffers - 1]->b_data + (sb->s_blocksize - 8)); - if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) || - t->bigdirendmasseq != h->startmasseq || - t->reserved[0] != 0 || t->reserved[1] != 0) { + ret = adfs_fplus_validate_tail(h, t); + if (ret) { adfs_error(sb, "dir %06x has malformed tail", indaddr); goto out; } From aa3d4e015298fd523617c2bea392d02ea19eaa1a Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:11:02 +0000 Subject: [PATCH 33/42] fs/adfs: bigdir: directory validation strengthening Strengthen the directory validation by ensuring that the header fields contain sensible values that fit inside the directory, and limit the directory size to 4MB as per RISC OS requirements. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir_fplus.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index b83a74e9ff6d..a2fa416fbb6d 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -19,11 +19,38 @@ static unsigned int adfs_fplus_offset(const struct adfs_bigdirheader *h, static int adfs_fplus_validate_header(const struct adfs_bigdirheader *h) { unsigned int size = le32_to_cpu(h->bigdirsize); + unsigned int len; if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || h->bigdirversion[2] != 0 || h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME) || - size & 2047) + !size || size & 2047 || size > SZ_4M) + return -EIO; + + size -= sizeof(struct adfs_bigdirtail) + + offsetof(struct adfs_bigdirheader, bigdirname); + + /* Check that bigdirnamelen fits within the directory */ + len = ALIGN(le32_to_cpu(h->bigdirnamelen), 4); + if (len > size) + return -EIO; + + size -= len; + + /* Check that bigdirnamesize fits within the directory */ + len = le32_to_cpu(h->bigdirnamesize); + if (len > size) + return -EIO; + + size -= len; + + /* + * Avoid division, we know that absolute maximum number of entries + * can not be so large to cause overflow of the multiplication below. + */ + len = le32_to_cpu(h->bigdirentries); + if (len > SZ_4M / sizeof(struct adfs_bigdirentry) || + len * sizeof(struct adfs_bigdirentry) > size) return -EIO; return 0; From d79288b4f61b40976182786ba2cb05ed5f2b6945 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:11:08 +0000 Subject: [PATCH 34/42] fs/adfs: bigdir: calculate and validate directory checkbyte When reading a big directory, calculate the validate the directory checkbyte to ensure that the directory contents are valid. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir_fplus.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index a2fa416fbb6d..4ab8987962f0 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -67,6 +67,39 @@ static int adfs_fplus_validate_tail(const struct adfs_bigdirheader *h, return 0; } +static u8 adfs_fplus_checkbyte(struct adfs_dir *dir) +{ + struct adfs_bigdirheader *h = dir->bighead; + struct adfs_bigdirtail *t = dir->bigtail; + unsigned int end, bs, bi, i; + __le32 *bp; + u32 dircheck; + + end = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries)) + + le32_to_cpu(h->bigdirnamesize); + + /* Accumulate the contents of the header, entries and names */ + for (dircheck = 0, bi = 0; end; bi++) { + bp = (void *)dir->bhs[bi]->b_data; + bs = dir->bhs[bi]->b_size; + if (bs > end) + bs = end; + + for (i = 0; i < bs; i += sizeof(u32)) + dircheck = ror32(dircheck, 13) ^ le32_to_cpup(bp++); + + end -= bs; + } + + /* Accumulate the contents of the tail except for the check byte */ + dircheck = ror32(dircheck, 13) ^ le32_to_cpu(t->bigdirendname); + dircheck = ror32(dircheck, 13) ^ t->bigdirendmasseq; + dircheck = ror32(dircheck, 13) ^ t->reserved[0]; + dircheck = ror32(dircheck, 13) ^ t->reserved[1]; + + return dircheck ^ dircheck >> 8 ^ dircheck >> 16 ^ dircheck >> 24; +} + static int adfs_fplus_read(struct super_block *sb, u32 indaddr, unsigned int size, struct adfs_dir *dir) { @@ -107,6 +140,11 @@ static int adfs_fplus_read(struct super_block *sb, u32 indaddr, goto out; } + if (adfs_fplus_checkbyte(dir) != t->bigdircheckbyte) { + adfs_error(sb, "dir %06x checkbyte mismatch\n", indaddr); + goto out; + } + dir->parent_id = le32_to_cpu(h->bigdirparent); return 0; From a464152f2e6dfd6d8be45c5e591cb8be20a97bdb Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:11:13 +0000 Subject: [PATCH 35/42] fs/adfs: bigdir: implement directory update support Implement big directory entry update support in the same way that we do for new directories. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir_fplus.c | 54 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index 4ab8987962f0..48ea299b6ece 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -120,7 +120,7 @@ static int adfs_fplus_read(struct super_block *sb, u32 indaddr, } dirsize = le32_to_cpu(h->bigdirsize); - if (dirsize != size) { + if (size && dirsize != size) { adfs_msg(sb, KERN_WARNING, "dir %06x header size %X does not match directory size %X", indaddr, dirsize, size); @@ -226,9 +226,61 @@ static int adfs_fplus_iterate(struct adfs_dir *dir, struct dir_context *ctx) return 0; } +static int adfs_fplus_update(struct adfs_dir *dir, struct object_info *obj) +{ + struct adfs_bigdirheader *h = dir->bighead; + struct adfs_bigdirentry bde; + int offset, end, ret; + + offset = adfs_fplus_offset(h, 0) - sizeof(bde); + end = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries)); + + do { + offset += sizeof(bde); + if (offset >= end) { + adfs_error(dir->sb, "unable to locate entry to update"); + return -ENOENT; + } + ret = adfs_dir_copyfrom(&bde, dir, offset, sizeof(bde)); + if (ret) { + adfs_error(dir->sb, "error reading directory entry"); + return -ENOENT; + } + } while (le32_to_cpu(bde.bigdirindaddr) != obj->indaddr); + + bde.bigdirload = cpu_to_le32(obj->loadaddr); + bde.bigdirexec = cpu_to_le32(obj->execaddr); + bde.bigdirlen = cpu_to_le32(obj->size); + bde.bigdirindaddr = cpu_to_le32(obj->indaddr); + bde.bigdirattr = cpu_to_le32(obj->attr); + + return adfs_dir_copyto(dir, offset, &bde, sizeof(bde)); +} + +static int adfs_fplus_commit(struct adfs_dir *dir) +{ + int ret; + + /* Increment directory sequence number */ + dir->bighead->startmasseq += 1; + dir->bigtail->bigdirendmasseq += 1; + + /* Update directory check byte */ + dir->bigtail->bigdircheckbyte = adfs_fplus_checkbyte(dir); + + /* Make sure the directory still validates correctly */ + ret = adfs_fplus_validate_header(dir->bighead); + if (ret == 0) + ret = adfs_fplus_validate_tail(dir->bighead, dir->bigtail); + + return ret; +} + const struct adfs_dir_ops adfs_fplus_dir_ops = { .read = adfs_fplus_read, .iterate = adfs_fplus_iterate, .setpos = adfs_fplus_setpos, .getnext = adfs_fplus_getnext, + .update = adfs_fplus_update, + .commit = adfs_fplus_commit, }; From f352064275adfeb6f88cb0fb25cc623750adf89f Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:11:18 +0000 Subject: [PATCH 36/42] fs/adfs: super: fix inode dropping When we have write support enabled, we must not drop inodes before they have been written back, otherwise we lose updates to the filesystem on umount. Keep the inodes around unless we are built in read-only mode, or we are mounted read-only. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/super.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/adfs/super.c b/fs/adfs/super.c index b2455e9ab923..9c93122925cf 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -231,6 +231,12 @@ static void adfs_free_inode(struct inode *inode) kmem_cache_free(adfs_inode_cachep, ADFS_I(inode)); } +static int adfs_drop_inode(struct inode *inode) +{ + /* always drop inodes if we are read-only */ + return !IS_ENABLED(CONFIG_ADFS_FS_RW) || IS_RDONLY(inode); +} + static void init_once(void *foo) { struct adfs_inode_info *ei = (struct adfs_inode_info *) foo; @@ -263,7 +269,7 @@ static void destroy_inodecache(void) static const struct super_operations adfs_sops = { .alloc_inode = adfs_alloc_inode, .free_inode = adfs_free_inode, - .drop_inode = generic_delete_inode, + .drop_inode = adfs_drop_inode, .write_inode = adfs_write_inode, .put_super = adfs_put_super, .statfs = adfs_statfs, From ccbc80a89d1399b79e43544cfbe44df964a29810 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:11:23 +0000 Subject: [PATCH 37/42] fs/adfs: dir: remove debug in adfs_dir_update() Remove the noisy debug in adfs_dir_update(). Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/dir.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index 3d4bbe836fb5..dd940f17767d 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -278,9 +278,6 @@ adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) struct adfs_dir dir; int ret; - printk(KERN_INFO "adfs_dir_update: object %06x in dir %06x\n", - obj->indaddr, obj->parent_id); - if (!IS_ENABLED(CONFIG_ADFS_FS_RW)) return -EINVAL; From e3858e125bd57b827af52dfb38df6c8602559886 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:11:28 +0000 Subject: [PATCH 38/42] fs/adfs: super: extract filesystem block probe Separate the filesystem block probing from the superblock filling so we can support other ADFS filesystem formats, such as the single-zone E and E+ floppy image formats which do not have a boot block. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/super.c | 149 +++++++++++++++++++++++++----------------------- 1 file changed, 78 insertions(+), 71 deletions(-) diff --git a/fs/adfs/super.c b/fs/adfs/super.c index 9c93122925cf..4c06b2d5a861 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -277,13 +277,80 @@ static const struct super_operations adfs_sops = { .show_options = adfs_show_options, }; +static int adfs_probe(struct super_block *sb, unsigned int offset, int silent, + int (*validate)(struct super_block *sb, + struct buffer_head *bh, + struct adfs_discrecord **bhp)) +{ + struct adfs_sb_info *asb = ADFS_SB(sb); + struct adfs_discrecord *dr; + struct buffer_head *bh; + unsigned int blocksize = BLOCK_SIZE; + int ret, try; + + for (try = 0; try < 2; try++) { + /* try to set the requested block size */ + if (sb->s_blocksize != blocksize && + !sb_set_blocksize(sb, blocksize)) { + if (!silent) + adfs_msg(sb, KERN_ERR, + "error: unsupported blocksize"); + return -EINVAL; + } + + /* read the buffer */ + bh = sb_bread(sb, offset >> sb->s_blocksize_bits); + if (!bh) { + adfs_msg(sb, KERN_ERR, + "error: unable to read block %u, try %d", + offset >> sb->s_blocksize_bits, try); + return -EIO; + } + + /* validate it */ + ret = validate(sb, bh, &dr); + if (ret) { + brelse(bh); + return ret; + } + + /* does the block size match the filesystem block size? */ + blocksize = 1 << dr->log2secsize; + if (sb->s_blocksize == blocksize) { + asb->s_map = adfs_read_map(sb, dr); + brelse(bh); + return PTR_ERR_OR_ZERO(asb->s_map); + } + + brelse(bh); + } + + return -EIO; +} + +static int adfs_validate_bblk(struct super_block *sb, struct buffer_head *bh, + struct adfs_discrecord **drp) +{ + struct adfs_discrecord *dr; + unsigned char *b_data; + + b_data = bh->b_data + (ADFS_DISCRECORD % sb->s_blocksize); + if (adfs_checkbblk(b_data)) + return -EILSEQ; + + /* Do some sanity checks on the ADFS disc record */ + dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET); + if (adfs_checkdiscrecord(dr)) + return -EILSEQ; + + *drp = dr; + return 0; +} + static int adfs_fill_super(struct super_block *sb, void *data, int silent) { struct adfs_discrecord *dr; - 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; @@ -308,72 +375,19 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) if (parse_options(sb, asb, data)) goto error; - sb_set_blocksize(sb, BLOCK_SIZE); - if (!(bh = sb_bread(sb, ADFS_DISCRECORD / BLOCK_SIZE))) { - adfs_msg(sb, KERN_ERR, "error: unable to read superblock"); - ret = -EIO; - goto error; - } - - b_data = bh->b_data + (ADFS_DISCRECORD % BLOCK_SIZE); - - if (adfs_checkbblk(b_data)) { - ret = -EINVAL; - goto error_badfs; - } - - dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET); - - /* - * Do some sanity checks on the ADFS disc record - */ - if (adfs_checkdiscrecord(dr)) { - ret = -EINVAL; - goto error_badfs; - } - - blocksize = 1 << dr->log2secsize; - brelse(bh); - - if (sb_set_blocksize(sb, blocksize)) { - bh = sb_bread(sb, ADFS_DISCRECORD / sb->s_blocksize); - if (!bh) { - 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_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 { + /* Try to probe the filesystem boot block */ + ret = adfs_probe(sb, ADFS_DISCRECORD, silent, adfs_validate_bblk); + if (ret == -EILSEQ) { if (!silent) adfs_msg(sb, KERN_ERR, - "error: unsupported blocksize"); + "error: can't find an ADFS filesystem on dev %s.", + sb->s_id); ret = -EINVAL; + } + if (ret) goto error; - } - /* - * blocksize on this device should now be set to the ADFS log2secsize - */ - - asb->s_map = adfs_read_map(sb, dr); - if (IS_ERR(asb->s_map)) { - ret = PTR_ERR(asb->s_map); - goto error_free_bh; - } - - brelse(bh); - - /* - * set up enough so that we can read an inode - */ + /* set up enough so that we can read an inode */ sb->s_op = &adfs_sops; dr = adfs_map_discrecord(asb->s_map); @@ -417,13 +431,6 @@ 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: sb->s_fs_info = NULL; kfree(asb); From 08ead1b8b98d90795bf934d93a718328d11f6ce6 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:11:33 +0000 Subject: [PATCH 39/42] fs/adfs: super: add support for E and E+ floppy image formats Add support for ADFS E and E+ floppy image formats, which, unlike their hard disk variants, do not have a filesystem boot block - they have a single map zone, with the map fragment stored at sector 0. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/super.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/fs/adfs/super.c b/fs/adfs/super.c index 4c06b2d5a861..a3cc8ecb50da 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -347,6 +347,20 @@ static int adfs_validate_bblk(struct super_block *sb, struct buffer_head *bh, return 0; } +static int adfs_validate_dr0(struct super_block *sb, struct buffer_head *bh, + struct adfs_discrecord **drp) +{ + struct adfs_discrecord *dr; + + /* Do some sanity checks on the ADFS disc record */ + dr = (struct adfs_discrecord *)(bh->b_data + 4); + if (adfs_checkdiscrecord(dr) || dr->nzones_high || dr->nzones != 1) + return -EILSEQ; + + *drp = dr; + return 0; +} + static int adfs_fill_super(struct super_block *sb, void *data, int silent) { struct adfs_discrecord *dr; @@ -376,7 +390,9 @@ static int adfs_fill_super(struct super_block *sb, void *data, int silent) goto error; /* Try to probe the filesystem boot block */ - ret = adfs_probe(sb, ADFS_DISCRECORD, silent, adfs_validate_bblk); + ret = adfs_probe(sb, ADFS_DISCRECORD, 1, adfs_validate_bblk); + if (ret == -EILSEQ) + ret = adfs_probe(sb, 0, silent, adfs_validate_dr0); if (ret == -EILSEQ) { if (!silent) adfs_msg(sb, KERN_ERR, From 25e5d4df3b46a345dccc0a07f998ce443077b4ff Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:11:38 +0000 Subject: [PATCH 40/42] fs/adfs: mostly divorse inode number from indirect disc address Avoid using the inode number as the indirect disc address, even though these currently have the same value. Signed-off-by: Russell King Signed-off-by: Al Viro --- fs/adfs/adfs.h | 1 + fs/adfs/dir.c | 4 ++-- fs/adfs/inode.c | 6 ++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index c05555252fec..699c4fa8b78b 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -32,6 +32,7 @@ static inline u16 adfs_filetype(u32 loadaddr) struct adfs_inode_info { loff_t mmu_private; __u32 parent_id; /* parent indirect disc address */ + __u32 indaddr; /* object indirect disc address */ __u32 loadaddr; /* RISC OS load address */ __u32 execaddr; /* RISC OS exec address */ unsigned int attr; /* RISC OS permissions */ diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index dd940f17767d..77fbd196008f 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -158,7 +158,7 @@ static int adfs_dir_read_inode(struct super_block *sb, struct inode *inode, { int ret; - ret = adfs_dir_read(sb, inode->i_ino, inode->i_size, dir); + ret = adfs_dir_read(sb, ADFS_I(inode)->indaddr, inode->i_size, dir); if (ret) return ret; @@ -372,7 +372,7 @@ static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr, break; } } - obj->parent_id = inode->i_ino; + obj->parent_id = ADFS_I(inode)->indaddr; unlock_relse: up_read(&adfs_dir_rwsem); diff --git a/fs/adfs/inode.c b/fs/adfs/inode.c index 212a56fc7911..32620f4a7623 100644 --- a/fs/adfs/inode.c +++ b/fs/adfs/inode.c @@ -20,7 +20,8 @@ adfs_get_block(struct inode *inode, sector_t block, struct buffer_head *bh, if (block >= inode->i_blocks) goto abort_toobig; - block = __adfs_block_map(inode->i_sb, inode->i_ino, block); + block = __adfs_block_map(inode->i_sb, ADFS_I(inode)->indaddr, + block); if (block) map_bh(bh, inode->i_sb, block); return 0; @@ -259,6 +260,7 @@ adfs_iget(struct super_block *sb, struct object_info *obj) * for cross-directory renames. */ ADFS_I(inode)->parent_id = obj->parent_id; + ADFS_I(inode)->indaddr = obj->indaddr; ADFS_I(inode)->loadaddr = obj->loadaddr; ADFS_I(inode)->execaddr = obj->execaddr; ADFS_I(inode)->attr = obj->attr; @@ -353,7 +355,7 @@ int adfs_write_inode(struct inode *inode, struct writeback_control *wbc) struct object_info obj; int ret; - obj.indaddr = inode->i_ino; + obj.indaddr = ADFS_I(inode)->indaddr; obj.name_len = 0; obj.parent_id = ADFS_I(inode)->parent_id; obj.loadaddr = ADFS_I(inode)->loadaddr; From 76ed99d199f7b66b1f762392b19d115994d7e81b Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 9 Dec 2019 11:11:43 +0000 Subject: [PATCH 41/42] Documentation: update adfs filesystem documentation Add an introduction to adfs to its documentation detailing which formats are supported by the module. Signed-off-by: Russell King Signed-off-by: Al Viro --- Documentation/filesystems/adfs.txt | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Documentation/filesystems/adfs.txt b/Documentation/filesystems/adfs.txt index 5949766353f7..0baa8e8c1fc1 100644 --- a/Documentation/filesystems/adfs.txt +++ b/Documentation/filesystems/adfs.txt @@ -1,3 +1,27 @@ +Filesystems supported by ADFS +----------------------------- + +The ADFS module supports the following Filecore formats which have: + +- new maps +- new directories or big directories + +In terms of the named formats, this means we support: + +- E and E+, with or without boot block +- F and F+ + +We fully support reading files from these filesystems, and writing to +existing files within their existing allocation. Essentially, we do +not support changing any of the filesystem metadata. + +This is intended to support loopback mounted Linux native filesystems +on a RISC OS Filecore filesystem, but will allow the data within files +to be changed. + +If write support (ADFS_FS_RW) is configured, we allow rudimentary +directory updates, specifically updating the access mode and timestamp. + Mount options for ADFS ---------------------- From 587065dcac64e88132803cdb0a7f26bb4a79cf46 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 24 Jan 2020 13:15:37 +0300 Subject: [PATCH 42/42] fs/adfs: bigdir: Fix an error code in adfs_fplus_read() This code accidentally returns success, but it should return the -EIO error code from adfs_fplus_validate_header(). Acked-by: Russell King Fixes: d79288b4f61b ("fs/adfs: bigdir: calculate and validate directory checkbyte") Signed-off-by: Dan Carpenter Signed-off-by: Al Viro --- fs/adfs/dir_fplus.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index 48ea299b6ece..4a15924014da 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c @@ -114,7 +114,8 @@ static int adfs_fplus_read(struct super_block *sb, u32 indaddr, return ret; dir->bighead = h = (void *)dir->bhs[0]->b_data; - if (adfs_fplus_validate_header(h)) { + ret = adfs_fplus_validate_header(h); + if (ret) { adfs_error(sb, "dir %06x has malformed header", indaddr); goto out; }