Commit 53070406 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'work.adfs' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs

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
  ...
parents 6aee4bad 587065dc
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
----------------------
......
......@@ -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);
......
......@@ -6,12 +6,196 @@
*
* 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,
};
......
......@@ -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));
if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
goto bad_dir;
static int adfs_f_update(struct adfs_dir *dir, struct object_info *obj)
{
struct adfs_direntry de;
int offset, ret;
if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
memcmp(&dir->dirhead.startname, "Hugo", 4))
goto bad_dir;
offset = 5 - (int)sizeof(de);
if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
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;
}
#endif
for (i = dir->nr_buffers - 1; i >= 0; i--)
mark_buffer_dirty(dir->bh[i]);
} while (adfs_readval(de.dirinddiscadd, 3) != obj->indaddr);
ret = 0;
out:
return ret;
#if 1
bad_dir:
adfs_error(dir->sb, "whoops! I broke a directory!");
return -EIO;
#endif
/* Update the directory entry with the new object state */
adfs_obj2dir(&de, obj);
/* 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,
};
......@@ -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
*/
union adfs_dirtail {
struct {
unsigned char dirlastmask;
struct adfs_olddirtail {
__u8 dirlastmask;
char dirname[10];
unsigned char dirparent[3];
__u8 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];
__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];
unsigned char endmasseq;
unsigned char endname[4];
unsigned char dircheckbyte;
} new;
__u8 endmasseq;
__u8 endname[4];
__u8 dircheckbyte;
} __attribute__((packed));
union adfs_dirtail {
struct adfs_olddirtail old;
struct adfs_newdirtail new;
};
#endif
......@@ -4,123 +4,163 @@
*
* 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;
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);
goto out;
}
/* Check that bigdirnamesize fits within the directory */
len = le32_to_cpu(h->bigdirnamesize);
if (len > size)
return -EIO;
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];
}
size -= len;
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;
/*
* 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;
}
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);
/* 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;
}
dir->nr_buffers += 1;
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);
}
t = (struct adfs_bigdirtail *)
(dir->bh_fplus[size - 1]->b_data + (sb->s_blocksize - 8));
/* Read remaining buffers */
ret = adfs_dir_read_buffers(sb, indaddr, dirsize, dir);
if (ret)
return ret;
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);
dir->bigtail = t = (struct adfs_bigdirtail *)
(dir->bhs[dir->nr_buffers - 1]->b_data + (sb->s_blocksize - 8));
ret = adfs_fplus_validate_tail(h, t);
if (ret) {
adfs_error(sb, "dir %06x has malformed tail", 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);
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);
dir->bh_fplus = NULL;
}
adfs_dir_relse(dir);
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;
if (dir->bh_fplus) {
for (i = 0; i < dir->nr_buffers; i++)
brelse(dir->bh_fplus[i]);
struct adfs_bigdirheader *h = dir->bighead;
struct adfs_bigdirentry bde;
int offset, end, ret;
if (&dir->bh[0] != dir->bh_fplus)
kfree(dir->bh_fplus);
offset = adfs_fplus_offset(h, 0) - sizeof(bde);
end = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries));
dir->bh_fplus = NULL;
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);
dir->nr_buffers = 0;
dir->sb = NULL;
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,
.sync = adfs_fplus_sync,
.free = adfs_fplus_free
.update = adfs_fplus_update,
.commit = adfs_fplus_commit,
};
......@@ -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)));
......@@ -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;
......
......@@ -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)
fragend = find_next_bit_le(map, endbit, start + idlen);
if (fragend >= endbit)
goto error;
v = le32_to_cpu(_map[mapptr >> 5]);
}
mapptr += 1 + ffz(~v);
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 @@ static int lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen,
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)
fragend = find_next_bit_le(map, endbit, start + idlen);
if (fragend >= endbit)
goto error;
v = le32_to_cpu(_map[mapptr >> 5]);
}
mapptr += 1 + ffz(~v);
}
total += mapptr - start;
total += fragend + 1 - start;
} while (frag >= idlen + 1);
if (frag != 0)
......@@ -220,10 +193,10 @@ static int scan_map(struct adfs_sb_info *asb, unsigned int zone,
* 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 @@ int adfs_map_lookup(struct super_block *sb, u32 frag_id, unsigned int offset)
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);
}
......@@ -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;
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);
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;
}
dm = kmalloc_array(nzones, sizeof(*dm), GFP_KERNEL);
if (dm == NULL) {
adfs_error(sb, "not enough memory");
return ERR_PTR(-ENOMEM);
/* 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;
}
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);
/* validate it */
ret = validate(sb, bh, &dr);
if (ret) {
brelse(bh);
return ret;
}
if (!dm[zone].dm_bh) {
adfs_error(sb, "unable to read map");
goto error_free;
/* 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);
}
/* 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);
return -EIO;
}
if (adfs_checkmap(sb, dm))
return dm;
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;
adfs_error(sb, "map corrupted");
b_data = bh->b_data + (ADFS_DISCRECORD % sb->s_blocksize);
if (adfs_checkbblk(b_data))
return -EILSEQ;
error_free:
while (--zone >= 0)
brelse(dm[zone].dm_bh);
/* Do some sanity checks on the ADFS disc record */
dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET);
if (adfs_checkdiscrecord(dr))
return -EILSEQ;
kfree(dm);
return ERR_PTR(-EIO);
*drp = dr;
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;
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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment