mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-18 10:13:57 +08:00
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 ...
This commit is contained in:
commit
5307040655
@ -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
|
||||
----------------------
|
||||
|
||||
|
@ -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);
|
||||
|
314
fs/adfs/dir.c
314
fs/adfs/dir.c
@ -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;
|
||||
int ret;
|
||||
|
||||
if (ctx->pos >> 32)
|
||||
return 0;
|
||||
|
||||
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_relse:
|
||||
up_read(&adfs_dir_rwsem);
|
||||
adfs_dir_relse(&dir);
|
||||
return ret;
|
||||
|
||||
unlock_out:
|
||||
read_unlock(&adfs_dir_lock);
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
|
314
fs/adfs/dir_f.c
314
fs/adfs/dir_f.c
@ -9,8 +9,6 @@
|
||||
#include "adfs.h"
|
||||
#include "dir_f.h"
|
||||
|
||||
static void adfs_f_free(struct adfs_dir *dir);
|
||||
|
||||
/*
|
||||
* Read an (unaligned) value of length 1..4 bytes
|
||||
*/
|
||||
@ -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;
|
||||
}
|
||||
|
||||
static int adfs_f_validate(struct adfs_dir *dir)
|
||||
{
|
||||
struct adfs_dirheader *head = dir->dirhead;
|
||||
struct adfs_newdirtail *tail = dir->newtail;
|
||||
|
||||
if (head->startmasseq != tail->endmasseq ||
|
||||
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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read and check that a directory is valid */
|
||||
static int adfs_dir_read(struct super_block *sb, u32 indaddr,
|
||||
unsigned int size, struct adfs_dir *dir)
|
||||
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 blk = 0;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Directories which are not a multiple of 2048 bytes
|
||||
* are considered bad v2 [3.6]
|
||||
*/
|
||||
if (size & 2047)
|
||||
if (size && size != ADFS_NEWDIR_SIZE)
|
||||
return -EIO;
|
||||
|
||||
ret = adfs_dir_read_buffers(sb, indaddr, ADFS_NEWDIR_SIZE, dir);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dir->dirhead = bufoff(dir->bh, 0);
|
||||
dir->newtail = bufoff(dir->bh, 2007);
|
||||
|
||||
if (adfs_f_validate(dir))
|
||||
goto bad_dir;
|
||||
|
||||
size >>= blocksize_bits;
|
||||
|
||||
dir->nr_buffers = 0;
|
||||
dir->sb = sb;
|
||||
|
||||
for (blk = 0; blk < size; blk++) {
|
||||
int phys;
|
||||
|
||||
phys = __adfs_block_map(sb, indaddr, blk);
|
||||
if (!phys) {
|
||||
adfs_error(sb, "dir %06x has a hole at offset %d",
|
||||
indaddr, blk);
|
||||
goto release_buffers;
|
||||
}
|
||||
|
||||
dir->bh[blk] = sb_bread(sb, phys);
|
||||
if (!dir->bh[blk])
|
||||
goto release_buffers;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
|
||||
memcmp(&dir->dirhead.startname, "Hugo", 4))
|
||||
goto bad_dir;
|
||||
|
||||
if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
|
||||
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;
|
||||
int ret;
|
||||
|
||||
buffer = pos >> sb->s_blocksize_bits;
|
||||
|
||||
if (buffer > dir->nr_buffers)
|
||||
return -EINVAL;
|
||||
|
||||
offset = pos & (sb->s_blocksize - 1);
|
||||
thissize = sb->s_blocksize - offset;
|
||||
if (thissize > 26)
|
||||
thissize = 26;
|
||||
|
||||
memcpy(&de, dir->bh[buffer]->b_data + offset, thissize);
|
||||
if (thissize != 26)
|
||||
memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data,
|
||||
26 - thissize);
|
||||
ret = adfs_dir_copyfrom(&de, dir, pos, 26);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!de.dirobname[0])
|
||||
return -ENOENT;
|
||||
@ -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;
|
||||
static int adfs_f_update(struct adfs_dir *dir, struct object_info *obj)
|
||||
{
|
||||
struct adfs_direntry de;
|
||||
int offset, ret;
|
||||
|
||||
ret = adfs_dir_checkbyte(dir);
|
||||
/*
|
||||
* Update directory check byte
|
||||
*/
|
||||
dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 1] = ret;
|
||||
offset = 5 - (int)sizeof(de);
|
||||
|
||||
#if 1
|
||||
{
|
||||
const unsigned int blocksize_bits = sb->s_blocksize_bits;
|
||||
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);
|
||||
|
||||
memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead));
|
||||
memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail));
|
||||
/* Update the directory entry with the new object state */
|
||||
adfs_obj2dir(&de, obj);
|
||||
|
||||
if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
|
||||
memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
|
||||
goto bad_dir;
|
||||
/* Write the directory entry back to the directory */
|
||||
return adfs_dir_copyto(dir, offset, &de, 26);
|
||||
}
|
||||
|
||||
if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
|
||||
memcmp(&dir->dirhead.startname, "Hugo", 4))
|
||||
goto bad_dir;
|
||||
static int adfs_f_commit(struct adfs_dir *dir)
|
||||
{
|
||||
int ret;
|
||||
|
||||
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]);
|
||||
/* Increment directory sequence number */
|
||||
dir->dirhead->startmasseq += 1;
|
||||
dir->newtail->endmasseq += 1;
|
||||
|
||||
/* Update directory check byte */
|
||||
dir->newtail->dircheckbyte = adfs_dir_checkbyte(dir);
|
||||
|
||||
/* Make sure the directory still validates correctly */
|
||||
ret = adfs_f_validate(dir);
|
||||
if (ret)
|
||||
adfs_msg(dir->sb, KERN_ERR, "error: update broke directory");
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
return ret;
|
||||
#if 1
|
||||
bad_dir:
|
||||
adfs_error(dir->sb, "whoops! I broke a directory!");
|
||||
return -EIO;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
adfs_f_sync(struct adfs_dir *dir)
|
||||
{
|
||||
int err = 0;
|
||||
int i;
|
||||
|
||||
for (i = dir->nr_buffers - 1; i >= 0; i--) {
|
||||
struct buffer_head *bh = dir->bh[i];
|
||||
sync_dirty_buffer(bh);
|
||||
if (buffer_req(bh) && !buffer_uptodate(bh))
|
||||
err = -EIO;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void
|
||||
adfs_f_free(struct adfs_dir *dir)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = dir->nr_buffers - 1; i >= 0; i--) {
|
||||
brelse(dir->bh[i]);
|
||||
dir->bh[i] = NULL;
|
||||
}
|
||||
|
||||
dir->nr_buffers = 0;
|
||||
dir->sb = NULL;
|
||||
}
|
||||
|
||||
const struct adfs_dir_ops adfs_f_dir_ops = {
|
||||
.read = adfs_f_read,
|
||||
.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
|
||||
*/
|
||||
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
|
||||
|
@ -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)
|
||||
{
|
||||
return offsetof(struct adfs_bigdirheader, bigdirname) +
|
||||
ALIGN(le32_to_cpu(h->bigdirnamelen), 4) +
|
||||
pos * sizeof(struct adfs_bigdirentry);
|
||||
}
|
||||
|
||||
static int adfs_fplus_validate_header(const struct adfs_bigdirheader *h)
|
||||
{
|
||||
unsigned int size = le32_to_cpu(h->bigdirsize);
|
||||
unsigned int len;
|
||||
|
||||
if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 ||
|
||||
h->bigdirversion[2] != 0 ||
|
||||
h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME) ||
|
||||
!size || size & 2047 || size > SZ_4M)
|
||||
return -EIO;
|
||||
|
||||
size -= sizeof(struct adfs_bigdirtail) +
|
||||
offsetof(struct adfs_bigdirheader, bigdirname);
|
||||
|
||||
/* Check that bigdirnamelen fits within the directory */
|
||||
len = ALIGN(le32_to_cpu(h->bigdirnamelen), 4);
|
||||
if (len > size)
|
||||
return -EIO;
|
||||
|
||||
size -= len;
|
||||
|
||||
/* Check that bigdirnamesize fits within the directory */
|
||||
len = le32_to_cpu(h->bigdirnamesize);
|
||||
if (len > size)
|
||||
return -EIO;
|
||||
|
||||
size -= len;
|
||||
|
||||
/*
|
||||
* Avoid division, we know that absolute maximum number of entries
|
||||
* can not be so large to cause overflow of the multiplication below.
|
||||
*/
|
||||
len = le32_to_cpu(h->bigdirentries);
|
||||
if (len > SZ_4M / sizeof(struct adfs_bigdirentry) ||
|
||||
len * sizeof(struct adfs_bigdirentry) > size)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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 long block;
|
||||
unsigned int blk, size;
|
||||
int i, ret = -EIO;
|
||||
unsigned int dirsize;
|
||||
int ret;
|
||||
|
||||
dir->nr_buffers = 0;
|
||||
/* Read first buffer */
|
||||
ret = adfs_dir_read_buffers(sb, indaddr, sb->s_blocksize, dir);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* start off using fixed bh set - only alloc for big dirs */
|
||||
dir->bh_fplus = &dir->bh[0];
|
||||
|
||||
block = __adfs_block_map(sb, id, 0);
|
||||
if (!block) {
|
||||
adfs_error(sb, "dir object %X has a hole at offset 0", id);
|
||||
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->bh_fplus[0] = sb_bread(sb, block);
|
||||
if (!dir->bh_fplus[0])
|
||||
goto out;
|
||||
dir->nr_buffers += 1;
|
||||
|
||||
h = (struct adfs_bigdirheader *)dir->bh_fplus[0]->b_data;
|
||||
size = le32_to_cpu(h->bigdirsize);
|
||||
if (size != sz) {
|
||||
dirsize = le32_to_cpu(h->bigdirsize);
|
||||
if (size && dirsize != size) {
|
||||
adfs_msg(sb, KERN_WARNING,
|
||||
"directory header size %X does not match directory size %X",
|
||||
size, sz);
|
||||
"dir %06x header size %X does not match directory size %X",
|
||||
indaddr, dirsize, size);
|
||||
}
|
||||
|
||||
if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 ||
|
||||
h->bigdirversion[2] != 0 || size & 2047 ||
|
||||
h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) {
|
||||
adfs_error(sb, "dir %06x has malformed header", id);
|
||||
/* Read remaining buffers */
|
||||
ret = adfs_dir_read_buffers(sb, indaddr, dirsize, dir);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
for (blk = 1; blk < size; blk++) {
|
||||
block = __adfs_block_map(sb, id, blk);
|
||||
if (!block) {
|
||||
adfs_error(sb, "dir object %X has a hole at offset %d", id, blk);
|
||||
goto out;
|
||||
}
|
||||
|
||||
dir->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->nr_buffers += 1;
|
||||
}
|
||||
|
||||
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]);
|
||||
adfs_dir_relse(dir);
|
||||
|
||||
if (&dir->bh[0] != dir->bh_fplus)
|
||||
kfree(dir->bh_fplus);
|
||||
|
||||
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 0;
|
||||
}
|
||||
|
||||
static int adfs_fplus_iterate(struct adfs_dir *dir, struct dir_context *ctx)
|
||||
{
|
||||
struct object_info obj;
|
||||
|
||||
if ((ctx->pos - 2) >> 32)
|
||||
return 0;
|
||||
|
||||
if (adfs_fplus_setpos(dir, ctx->pos - 2))
|
||||
return 0;
|
||||
|
||||
while (!adfs_fplus_getnext(dir, &obj)) {
|
||||
if (!dir_emit(ctx, obj.name, obj.name_len,
|
||||
obj.indaddr, DT_UNKNOWN))
|
||||
break;
|
||||
ctx->pos++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adfs_fplus_update(struct adfs_dir *dir, struct object_info *obj)
|
||||
{
|
||||
struct adfs_bigdirheader *h = dir->bighead;
|
||||
struct adfs_bigdirentry bde;
|
||||
int offset, end, ret;
|
||||
|
||||
offset = adfs_fplus_offset(h, 0) - sizeof(bde);
|
||||
end = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries));
|
||||
|
||||
do {
|
||||
offset += sizeof(bde);
|
||||
if (offset >= end) {
|
||||
adfs_error(dir->sb, "unable to locate entry to update");
|
||||
return -ENOENT;
|
||||
}
|
||||
ret = adfs_dir_copyfrom(&bde, dir, offset, sizeof(bde));
|
||||
if (ret) {
|
||||
adfs_error(dir->sb, "error reading directory entry");
|
||||
return -ENOENT;
|
||||
}
|
||||
} while (le32_to_cpu(bde.bigdirindaddr) != obj->indaddr);
|
||||
|
||||
bde.bigdirload = cpu_to_le32(obj->loadaddr);
|
||||
bde.bigdirexec = cpu_to_le32(obj->execaddr);
|
||||
bde.bigdirlen = cpu_to_le32(obj->size);
|
||||
bde.bigdirindaddr = cpu_to_le32(obj->indaddr);
|
||||
bde.bigdirattr = cpu_to_le32(obj->attr);
|
||||
|
||||
return adfs_dir_copyto(dir, offset, &bde, sizeof(bde));
|
||||
}
|
||||
|
||||
static int adfs_fplus_commit(struct adfs_dir *dir)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Increment directory sequence number */
|
||||
dir->bighead->startmasseq += 1;
|
||||
dir->bigtail->bigdirendmasseq += 1;
|
||||
|
||||
/* Update directory check byte */
|
||||
dir->bigtail->bigdircheckbyte = adfs_fplus_checkbyte(dir);
|
||||
|
||||
/* Make sure the directory still validates correctly */
|
||||
ret = adfs_fplus_validate_header(dir->bighead);
|
||||
if (ret == 0)
|
||||
ret = adfs_fplus_validate_tail(dir->bighead, dir->bigtail);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
adfs_fplus_sync(struct adfs_dir *dir)
|
||||
{
|
||||
int err = 0;
|
||||
int i;
|
||||
|
||||
for (i = dir->nr_buffers - 1; i >= 0; i--) {
|
||||
struct buffer_head *bh = dir->bh_fplus[i];
|
||||
sync_dirty_buffer(bh);
|
||||
if (buffer_req(bh) && !buffer_uptodate(bh))
|
||||
err = -EIO;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void
|
||||
adfs_fplus_free(struct adfs_dir *dir)
|
||||
{
|
||||
int i;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
dir->nr_buffers = 0;
|
||||
dir->sb = NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
|
241
fs/adfs/map.c
241
fs/adfs/map.c
@ -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]);
|
||||
}
|
||||
fragend = find_next_bit_le(map, endbit, start + idlen);
|
||||
if (fragend >= endbit)
|
||||
goto error;
|
||||
|
||||
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 @@ 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]);
|
||||
}
|
||||
fragend = find_next_bit_le(map, endbit, start + idlen);
|
||||
if (fragend >= endbit)
|
||||
goto error;
|
||||
|
||||
mapptr += 1 + ffz(~v);
|
||||
}
|
||||
|
||||
total += mapptr - start;
|
||||
total += fragend + 1 - start;
|
||||
} while (frag >= idlen + 1);
|
||||
|
||||
if (frag != 0)
|
||||
@ -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);
|
||||
}
|
||||
|
273
fs/adfs/super.c
273
fs/adfs/super.c
@ -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);
|
||||
|
||||
dm = kmalloc_array(nzones, sizeof(*dm), GFP_KERNEL);
|
||||
if (dm == NULL) {
|
||||
adfs_error(sb, "not enough memory");
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
for (zone = 0; zone < nzones; zone++, map_addr++) {
|
||||
dm[zone].dm_startbit = 0;
|
||||
dm[zone].dm_endbit = zone_size;
|
||||
dm[zone].dm_startblk = zone * zone_size - ADFS_DR_SIZE_BITS;
|
||||
dm[zone].dm_bh = sb_bread(sb, map_addr);
|
||||
|
||||
if (!dm[zone].dm_bh) {
|
||||
adfs_error(sb, "unable to read map");
|
||||
goto error_free;
|
||||
for (try = 0; try < 2; try++) {
|
||||
/* try to set the requested block size */
|
||||
if (sb->s_blocksize != blocksize &&
|
||||
!sb_set_blocksize(sb, blocksize)) {
|
||||
if (!silent)
|
||||
adfs_msg(sb, KERN_ERR,
|
||||
"error: unsupported blocksize");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* read the buffer */
|
||||
bh = sb_bread(sb, offset >> sb->s_blocksize_bits);
|
||||
if (!bh) {
|
||||
adfs_msg(sb, KERN_ERR,
|
||||
"error: unable to read block %u, try %d",
|
||||
offset >> sb->s_blocksize_bits, try);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* validate it */
|
||||
ret = validate(sb, bh, &dr);
|
||||
if (ret) {
|
||||
brelse(bh);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* does the block size match the filesystem block size? */
|
||||
blocksize = 1 << dr->log2secsize;
|
||||
if (sb->s_blocksize == blocksize) {
|
||||
asb->s_map = adfs_read_map(sb, dr);
|
||||
brelse(bh);
|
||||
return PTR_ERR_OR_ZERO(asb->s_map);
|
||||
}
|
||||
|
||||
brelse(bh);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user