Merge branch 'work.adfs' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 29 Jan 2020 19:45:09 +0000 (11:45 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 29 Jan 2020 19:45:09 +0000 (11:45 -0800)
Pull adfs updates from Al Viro:
 "adfs stuff for this cycle"

* 'work.adfs' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs: (42 commits)
  fs/adfs: bigdir: Fix an error code in adfs_fplus_read()
  Documentation: update adfs filesystem documentation
  fs/adfs: mostly divorse inode number from indirect disc address
  fs/adfs: super: add support for E and E+ floppy image formats
  fs/adfs: super: extract filesystem block probe
  fs/adfs: dir: remove debug in adfs_dir_update()
  fs/adfs: super: fix inode dropping
  fs/adfs: bigdir: implement directory update support
  fs/adfs: bigdir: calculate and validate directory checkbyte
  fs/adfs: bigdir: directory validation strengthening
  fs/adfs: bigdir: extract directory validation
  fs/adfs: bigdir: factor out directory entry offset calculation
  fs/adfs: newdir: split out directory commit from update
  fs/adfs: newdir: clean up adfs_f_update()
  fs/adfs: newdir: merge adfs_dir_read() into adfs_f_read()
  fs/adfs: newdir: improve directory validation
  fs/adfs: newdir: factor out directory format validation
  fs/adfs: dir: use pointers to access directory head/tails
  fs/adfs: dir: add more efficient iterate() per-format method
  fs/adfs: dir: switch to iterate_shared method
  ...

Documentation/filesystems/adfs.txt
fs/adfs/adfs.h
fs/adfs/dir.c
fs/adfs/dir_f.c
fs/adfs/dir_f.h
fs/adfs/dir_fplus.c
fs/adfs/dir_fplus.h
fs/adfs/inode.c
fs/adfs/map.c
fs/adfs/super.c

index 5949766..0baa8e8 100644 (file)
@@ -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
 ----------------------
 
index b7e844d..699c4fa 100644 (file)
@@ -26,14 +26,13 @@ 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
  */
 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          */
@@ -93,15 +92,19 @@ 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;
 
-       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;
+       };
 };
 
 /*
@@ -122,13 +125,13 @@ 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);
        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);
+       int     (*commit)(struct adfs_dir *dir);
 };
 
 struct adfs_discmap {
@@ -145,7 +148,9 @@ 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);
+void adfs_free_map(struct super_block *sb);
 
 /* Misc */
 __printf(3, 4)
@@ -167,6 +172,13 @@ 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);
+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);
index a54c532..77fbd19 100644 (file)
  *
  *  Common directory handling for ADFS
  */
+#include <linux/slab.h>
 #include "adfs.h"
 
 /*
  * 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)
+{
+       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;
+}
+
+static void __adfs_dir_cleanup(struct adfs_dir *dir)
+{
+       dir->nr_buffers = 0;
+
+       if (dir->bhs != dir->bh)
+               kfree(dir->bhs);
+       dir->bhs = NULL;
+       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)
+{
+       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)
+{
+       dir->sb = sb;
+       dir->bhs = dir->bh;
+       dir->nr_buffers = 0;
+
+       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, ADFS_I(inode)->indaddr, 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 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;
+       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)
 {
@@ -51,87 +235,90 @@ 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;
        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;
 
-       ret = ops->read(sb, inode->i_ino, inode->i_size, &dir);
+       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->iterate(&dir, ctx);
 
-       ret = ops->setpos(&dir, ctx->pos - 2);
-       if (ret)
-               goto unlock_out;
-       while (ops->getnext(&dir, &obj) == 0) {
-               if (!dir_emit(ctx, obj.name, obj.name_len,
-                             obj.indaddr, DT_UNKNOWN))
-                       break;
-               ctx->pos++;
-       }
-
-unlock_out:
-       read_unlock(&adfs_dir_lock);
+unlock_relse:
+       up_read(&adfs_dir_rwsem);
+       adfs_dir_relse(&dir);
+       return ret;
 
-free_out:
-       ops->free(&dir);
+unlock:
+       up_read(&adfs_dir_rwsem);
        return ret;
 }
 
 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) {
-               ret = -EINVAL;
-               goto out;
-       }
+       if (!ops->update)
+               return -EINVAL;
 
-       ret = ops->read(sb, obj->parent_id, 0, &dir);
+       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);
+       if (ret)
+               goto forget;
 
-       if (wait) {
-               int err = ops->sync(&dir);
-               if (!ret)
-                       ret = err;
-       }
+       ret = ops->commit(&dir);
+       if (ret)
+               goto forget;
+       up_write(&adfs_dir_rwsem);
+
+       adfs_dir_mark_dirty(&dir);
+
+       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);
 
-       ops->free(&dir);
-out:
-#endif
        return ret;
 }
 
@@ -167,25 +354,14 @@ 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);
+       down_read(&adfs_dir_rwsem);
+       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);
+               goto unlock;
 
        ret = ops->setpos(&dir, 0);
        if (ret)
-               goto unlock_out;
+               goto unlock_relse;
 
        ret = -ENOENT;
        name = qstr->name;
@@ -196,20 +372,22 @@ static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr,
                        break;
                }
        }
+       obj->parent_id = ADFS_I(inode)->indaddr;
 
-unlock_out:
-       read_unlock(&adfs_dir_lock);
+unlock_relse:
+       up_read(&adfs_dir_rwsem);
+       adfs_dir_relse(&dir);
+       return ret;
 
-free_out:
-       ops->free(&dir);
-out:
+unlock:
+       up_read(&adfs_dir_rwsem);
        return ret;
 }
 
 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,
 };
 
index c1a950c..30d526f 100644 (file)
@@ -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
  */
@@ -60,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);   \
        })
 
 /*
@@ -123,65 +121,49 @@ adfs_dir_checkbyte(const struct adfs_dir *dir)
        return (dircheck ^ (dircheck >> 8) ^ (dircheck >> 16) ^ (dircheck >> 24)) & 0xff;
 }
 
-/* Read and check that a directory is valid */
-static int adfs_dir_read(struct super_block *sb, u32 indaddr,
-                        unsigned int size, struct adfs_dir *dir)
+static int adfs_f_validate(struct adfs_dir *dir)
 {
-       const unsigned int blocksize_bits = sb->s_blocksize_bits;
-       int blk = 0;
-
-       /*
-        * Directories which are not a multiple of 2048 bytes
-        * are considered bad v2 [3.6]
-        */
-       if (size & 2047)
-               goto bad_dir;
-
-       size >>= blocksize_bits;
-
-       dir->nr_buffers = 0;
-       dir->sb = sb;
-
-       for (blk = 0; blk < size; blk++) {
-               int phys;
+       struct adfs_dirheader *head = dir->dirhead;
+       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) ||
+           adfs_dir_checkbyte(dir) != tail->dircheckbyte)
+               return -EIO;
 
-               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;
-               }
+       return 0;
+}
 
-               dir->bh[blk] = sb_bread(sb, phys);
-               if (!dir->bh[blk])
-                       goto release_buffers;
-       }
+/* Read and check that a directory is valid */
+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;
 
-       memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead));
-       memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail));
+       if (size && size != ADFS_NEWDIR_SIZE)
+               return -EIO;
 
-       if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
-           memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
-               goto bad_dir;
+       ret = adfs_dir_read_buffers(sb, indaddr, ADFS_NEWDIR_SIZE, dir);
+       if (ret)
+               return ret;
 
-       if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
-           memcmp(&dir->dirhead.startname, "Hugo", 4))
-               goto bad_dir;
+       dir->dirhead = bufoff(dir->bh, 0);
+       dir->newtail = bufoff(dir->bh, 2007);
 
-       if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
+       if (adfs_f_validate(dir))
                goto bad_dir;
 
-       dir->nr_buffers = blk;
+       dir->parent_id = adfs_readval(dir->newtail->dirparent, 3);
 
        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;
 }
@@ -232,24 +214,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;
-
-       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;
+       int ret;
 
-       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;
@@ -259,89 +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 super_block *sb = dir->sb;
-       struct adfs_direntry de;
-       int thissize, buffer, offset;
-
-       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;
-
-       /*
-        * 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;
-}
-
-/*
- * 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_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->dirtail.new.dirparent, 3);
-
-       return ret;
-}
-
 static int
 adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos)
 {
@@ -364,99 +251,74 @@ adfs_f_getnext(struct adfs_dir *dir, struct object_info *obj)
        return ret;
 }
 
-static int
-adfs_f_update(struct adfs_dir *dir, struct object_info *obj)
+static int adfs_f_iterate(struct adfs_dir *dir, struct dir_context *ctx)
 {
-       struct super_block *sb = dir->sb;
-       int ret, i;
+       struct object_info obj;
+       int pos = 5 + (ctx->pos - 2) * 26;
 
-       ret = adfs_dir_find_entry(dir, obj->indaddr);
-       if (ret < 0) {
-               adfs_error(dir->sb, "unable to locate entry to update");
-               goto out;
+       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;
+}
 
-       __adfs_dir_put(dir, ret, 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;
-
-       ret = adfs_dir_checkbyte(dir);
-       /*
-        * Update directory check byte
-        */
-       dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 1] = 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));
+static int adfs_f_update(struct adfs_dir *dir, struct object_info *obj)
+{
+       struct adfs_direntry de;
+       int offset, ret;
 
-       if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
-           memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
-               goto bad_dir;
+       offset = 5 - (int)sizeof(de);
 
-       if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
-           memcmp(&dir->dirhead.startname, "Hugo", 4))
-               goto bad_dir;
+       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);
 
-       if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
-               goto bad_dir;
-       }
-#endif
-       for (i = dir->nr_buffers - 1; i >= 0; i--)
-               mark_buffer_dirty(dir->bh[i]);
+       /* Update the directory entry with the new object state */
+       adfs_obj2dir(&de, obj);
 
-       ret = 0;
-out:
-       return ret;
-#if 1
-bad_dir:
-       adfs_error(dir->sb, "whoops!  I broke a directory!");
-       return -EIO;
-#endif
+       /* Write the directory entry back to the directory */
+       return adfs_dir_copyto(dir, offset, &de, 26);
 }
 
-static int
-adfs_f_sync(struct adfs_dir *dir)
+static int adfs_f_commit(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;
-       }
+       int ret;
 
-       return err;
-}
+       /* Increment directory sequence number */
+       dir->dirhead->startmasseq += 1;
+       dir->newtail->endmasseq += 1;
 
-static void
-adfs_f_free(struct adfs_dir *dir)
-{
-       int i;
+       /* Update directory check byte */
+       dir->newtail->dircheckbyte = adfs_dir_checkbyte(dir);
 
-       for (i = dir->nr_buffers - 1; i >= 0; i--) {
-               brelse(dir->bh[i]);
-               dir->bh[i] = NULL;
-       }
+       /* Make sure the directory still validates correctly */
+       ret = adfs_f_validate(dir);
+       if (ret)
+               adfs_msg(dir->sb, KERN_ERR, "error: update broke directory");
 
-       dir->nr_buffers = 0;
-       dir->sb = NULL;
+       return ret;
 }
 
 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,
-       .sync           = adfs_f_sync,
-       .free           = adfs_f_free
+       .commit         = adfs_f_commit,
 };
index 5aec332..a5393e6 100644 (file)
@@ -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
index d56924c..4a15924 100644 (file)
  *
  *  Copyright (C) 1997-1999 Russell King
  */
-#include <linux/slab.h>
 #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)
+/* Return the byte offset to directory entry pos */
+static unsigned int adfs_fplus_offset(const struct adfs_bigdirheader *h,
+                                     unsigned int pos)
 {
-       struct adfs_bigdirheader *h;
-       struct adfs_bigdirtail *t;
-       unsigned long block;
-       unsigned int blk, size;
-       int i, ret = -EIO;
+       return offsetof(struct adfs_bigdirheader, bigdirname) +
+              ALIGN(le32_to_cpu(h->bigdirnamelen), 4) +
+              pos * sizeof(struct adfs_bigdirentry);
+}
 
-       dir->nr_buffers = 0;
+static int adfs_fplus_validate_header(const struct adfs_bigdirheader *h)
+{
+       unsigned int size = le32_to_cpu(h->bigdirsize);
+       unsigned int len;
 
-       /* start off using fixed bh set - only alloc for big dirs */
-       dir->bh_fplus = &dir->bh[0];
+       if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 ||
+           h->bigdirversion[2] != 0 ||
+           h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME) ||
+           !size || size & 2047 || size > SZ_4M)
+               return -EIO;
 
-       block = __adfs_block_map(sb, id, 0);
-       if (!block) {
-               adfs_error(sb, "dir object %X has a hole at offset 0", id);
-               goto out;
-       }
+       size -= sizeof(struct adfs_bigdirtail) +
+               offsetof(struct adfs_bigdirheader, bigdirname);
 
-       dir->bh_fplus[0] = sb_bread(sb, block);
-       if (!dir->bh_fplus[0])
-               goto out;
-       dir->nr_buffers += 1;
+       /* Check that bigdirnamelen fits within the directory */
+       len = ALIGN(le32_to_cpu(h->bigdirnamelen), 4);
+       if (len > size)
+               return -EIO;
 
-       h = (struct adfs_bigdirheader *)dir->bh_fplus[0]->b_data;
-       size = le32_to_cpu(h->bigdirsize);
-       if (size != sz) {
-               adfs_msg(sb, KERN_WARNING,
-                        "directory header size %X does not match directory size %X",
-                        size, sz);
+       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;
+}
+
+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 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;
        }
 
-       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);
+       /* 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)
+{
+       struct adfs_bigdirheader *h;
+       struct adfs_bigdirtail *t;
+       unsigned int dirsize;
+       int ret;
+
+       /* Read first buffer */
+       ret = adfs_dir_read_buffers(sb, indaddr, sb->s_blocksize, dir);
+       if (ret)
+               return ret;
+
+       dir->bighead = h = (void *)dir->bhs[0]->b_data;
+       ret = adfs_fplus_validate_header(h);
+       if (ret) {
+               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 **bh_fplus =
-                       kcalloc(size, sizeof(struct buffer_head *),
-                               GFP_KERNEL);
-               if (!bh_fplus) {
-                       adfs_msg(sb, KERN_ERR,
-                                "not enough memory for dir object %X (%d blocks)",
-                                id, size);
-                       ret = -ENOMEM;
-                       goto out;
-               }
-               dir->bh_fplus = bh_fplus;
-               /* copy over the pointer to the block that we've already read */
-               dir->bh_fplus[0] = dir->bh[0];
+       dirsize = le32_to_cpu(h->bigdirsize);
+       if (size && dirsize != size) {
+               adfs_msg(sb, KERN_WARNING,
+                        "dir %06x header size %X does not match directory size %X",
+                        indaddr, dirsize, size);
        }
 
-       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;
-               }
+       /* Read remaining buffers */
+       ret = adfs_dir_read_buffers(sb, indaddr, dirsize, dir);
+       if (ret)
+               return ret;
 
-               dir->bh_fplus[blk] = sb_bread(sb, block);
-               if (!dir->bh_fplus[blk]) {
-                       adfs_error(sb,  "dir object %x failed read for offset %d, mapped block %lX",
-                                  id, blk, block);
-                       goto out;
-               }
+       dir->bigtail = t = (struct adfs_bigdirtail *)
+               (dir->bhs[dir->nr_buffers - 1]->b_data + (sb->s_blocksize - 8));
 
-               dir->nr_buffers += 1;
+       ret = adfs_fplus_validate_tail(h, t);
+       if (ret) {
+               adfs_error(sb, "dir %06x has malformed tail", indaddr);
+               goto out;
        }
 
-       t = (struct adfs_bigdirtail *)
-               (dir->bh_fplus[size - 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);
+       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);
-       dir->sb = sb;
        return 0;
 
 out:
-       if (dir->bh_fplus) {
-               for (i = 0; i < dir->nr_buffers; i++)
-                       brelse(dir->bh_fplus[i]);
-
-               if (&dir->bh[0] != dir->bh_fplus)
-                       kfree(dir->bh_fplus);
+       adfs_dir_relse(dir);
 
-               dir->bh_fplus = NULL;
-       }
-
-       dir->nr_buffers = 0;
-       dir->sb = NULL;
        return ret;
 }
 
 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;
        int ret = -ENOENT;
 
-       if (fpos <= le32_to_cpu(h->bigdirentries)) {
+       if (fpos <= le32_to_cpu(dir->bighead->bigdirentries)) {
                dir->pos = fpos;
                ret = 0;
        }
@@ -128,51 +168,23 @@ 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->bh_fplus[buffer]->b_data + offset, len);
-       else {
-               char *c = (char *)to;
-
-               remainder = len - partial;
-
-               memcpy(c,
-                       dir->bh_fplus[buffer]->b_data + offset,
-                       partial);
-
-               memcpy(c + partial,
-                       dir->bh_fplus[buffer + 1]->b_data,
-                       remainder);
-       }
-}
-
 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 *h = dir->bighead;
        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);
+       offset = adfs_fplus_offset(h, dir->pos);
 
-       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);
@@ -181,59 +193,95 @@ 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);
 
-       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;
 }
 
-static int
-adfs_fplus_sync(struct adfs_dir *dir)
+static int adfs_fplus_iterate(struct adfs_dir *dir, struct dir_context *ctx)
 {
-       int err = 0;
-       int i;
-
-       for (i = dir->nr_buffers - 1; i >= 0; i--) {
-               struct buffer_head *bh = dir->bh_fplus[i];
-               sync_dirty_buffer(bh);
-               if (buffer_req(bh) && !buffer_uptodate(bh))
-                       err = -EIO;
+       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 err;
+       return 0;
 }
 
-static void
-adfs_fplus_free(struct adfs_dir *dir)
+static int adfs_fplus_update(struct adfs_dir *dir, struct object_info *obj)
 {
-       int i;
+       struct adfs_bigdirheader *h = dir->bighead;
+       struct adfs_bigdirentry bde;
+       int offset, end, ret;
 
-       if (dir->bh_fplus) {
-               for (i = 0; i < dir->nr_buffers; i++)
-                       brelse(dir->bh_fplus[i]);
+       offset = adfs_fplus_offset(h, 0) - sizeof(bde);
+       end = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries));
 
-               if (&dir->bh[0] != dir->bh_fplus)
-                       kfree(dir->bh_fplus);
+       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);
 
-               dir->bh_fplus = NULL;
-       }
+       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;
 
-       dir->nr_buffers = 0;
-       dir->sb = NULL;
+       /* 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,
-       .sync           = adfs_fplus_sync,
-       .free           = adfs_fplus_free
+       .update         = adfs_fplus_update,
+       .commit         = adfs_fplus_commit,
 };
index 4ec0931..d729b15 100644 (file)
@@ -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)));
index 124de75..32620f4 100644 (file)
@@ -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;
@@ -126,29 +127,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;
@@ -158,6 +159,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 +173,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 +205,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;
 }
 
 /*
@@ -260,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;
@@ -315,10 +316,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?
@@ -328,7 +330,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);
        }
 
@@ -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;
index f44d12c..a81de80 100644 (file)
@@ -4,6 +4,8 @@
  *
  *  Copyright (C) 1997-2002 Russell King
  */
+#include <linux/slab.h>
+#include <linux/statfs.h>
 #include <asm/unaligned.h>
 #include "adfs.h"
 
@@ -66,54 +68,41 @@ 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;
+       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);
-               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 >= mapsize)
-                                       goto error;
-                               v = le32_to_cpu(_map[mapptr >> 5]);
-                       }
-
-                       mapptr += 1 + ffz(~v);
+
+               fragend = find_next_bit_le(map, endbit, start + idlen);
+               if (fragend >= endbit)
+                       goto error;
+
+               if (start == freelink) {
+                       freelink += frag & 0x7fff;
+               } else 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 < mapsize);
+               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;
 }
 
 /*
@@ -125,12 +114,12 @@ 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;
        unsigned char *map = dm->dm_bh->b_data;
-       unsigned int start = 8, mapptr;
+       unsigned int start = 8, fragend;
        u32 frag;
        unsigned long total = 0;
 
@@ -149,29 +138,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 >= mapsize)
-                                       goto error;
-                               v = le32_to_cpu(_map[mapptr >> 5]);
-                       }
-
-                       mapptr += 1 + ffz(~v);
-               }
 
-               total += mapptr - start;
+               fragend = find_next_bit_le(map, endbit, start + idlen);
+               if (fragend >= endbit)
+                       goto error;
+
+               total += fragend + 1 - start;
        } while (frag >= idlen + 1);
 
        if (frag != 0)
@@ -220,10 +193,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;
@@ -235,7 +208,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)
@@ -280,3 +256,152 @@ 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;
+}
+
+/*
+ * 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 = 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 = 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 = 32 + 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 ret;
+
+       nzones    = dr->nzones | dr->nzones_high << 8;
+       zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare);
+
+       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");
+               return ERR_PTR(-ENOMEM);
+       }
+
+       adfs_map_layout(dm, nzones, dr);
+
+       ret = adfs_map_read(dm, sb, map_addr, nzones);
+       if (ret) {
+               adfs_error(sb, "unable to read map");
+               goto error_free;
+       }
+
+       if (adfs_checkmap(sb, dm))
+               return dm;
+
+       adfs_error(sb, "map corrupted");
+
+error_free:
+       adfs_map_relse(dm, nzones);
+       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);
+}
index 65b04eb..a3cc8ec 100644 (file)
@@ -88,59 +88,11 @@ 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;
        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);
 }
 
@@ -249,16 +201,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);
@@ -282,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;
@@ -314,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,
@@ -322,66 +277,94 @@ 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)
+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_discmap *dm;
-       unsigned int map_addr, zone_size, nzones;
-       int i, zone;
        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;
+               }
 
-       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);
+               /* 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;
+               }
 
-       asb->s_ids_per_zone = zone_size / (asb->s_idlen + 1);
+               /* 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);
+               }
 
-       dm = kmalloc_array(nzones, sizeof(*dm), GFP_KERNEL);
-       if (dm == NULL) {
-               adfs_error(sb, "not enough memory");
-               return ERR_PTR(-ENOMEM);
+               brelse(bh);
        }
 
-       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);
+       return -EIO;
+}
 
-               if (!dm[zone].dm_bh) {
-                       adfs_error(sb, "unable to read map");
-                       goto error_free;
-               }
-       }
+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;
 
-       /* 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);
+       b_data = bh->b_data + (ADFS_DISCRECORD % sb->s_blocksize);
+       if (adfs_checkbblk(b_data))
+               return -EILSEQ;
 
-       if (adfs_checkmap(sb, dm))
-               return dm;
+       /* 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;
+}
 
-       adfs_error(sb, "map corrupted");
+static int adfs_validate_dr0(struct super_block *sb, struct buffer_head *bh,
+                             struct adfs_discrecord **drp)
+{
+       struct adfs_discrecord *dr;
 
-error_free:
-       while (--zone >= 0)
-               brelse(dm[zone].dm_bh);
+       /* 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;
 
-       kfree(dm);
-       return ERR_PTR(-EIO);
+       *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;
@@ -391,7 +374,10 @@ 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_magic = ADFS_SUPER_MAGIC;
+       sb->s_time_gran = 10000000;
 
        /* set default options */
        asb->s_uid = GLOBAL_ROOT_UID;
@@ -403,78 +389,21 @@ 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, 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,
-                                "error: unsupported blocksize");
+                                "error: can't find an ADFS filesystem on dev %s.",
+                                sb->s_id);
                ret = -EINVAL;
-               goto error;
        }
+       if (ret)
+               goto error;
 
-       /*
-        * 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);
-               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);
@@ -511,23 +440,13 @@ 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;
        }
        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);