Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
# SPDX-License-Identifier: GPL-2.0-only
config ADFS_FS
tristate "ADFS file system support"
depends on BLOCK
select BUFFER_HEAD
help
The Acorn Disc Filing System is the standard file system of the
RiscOS operating system which runs on Acorn's ARM-based Risc PC
systems and the Acorn Archimedes range of machines. If you say Y
here, Linux will be able to read from ADFS partitions on hard drives
and from ADFS-formatted floppy discs. If you also want to be able to
write to those devices, say Y to "ADFS write support" below.
The ADFS partition should be the first partition (i.e.,
/dev/[hs]d?1) on each of your drives. Please read the file
<file:Documentation/filesystems/adfs.rst> for further details.
To compile this code as a module, choose M here: the module will be
called adfs.
If unsure, say N.
config ADFS_FS_RW
bool "ADFS write support (DANGEROUS)"
depends on ADFS_FS
help
If you say Y here, you will be able to write to ADFS partitions on
hard drives and ADFS-formatted floppy disks. This is experimental
codes, so if you're unsure, say N.
+8
View File
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for the linux adfs filesystem routines.
#
obj-$(CONFIG_ADFS_FS) += adfs.o
adfs-objs := dir.o dir_f.o dir_fplus.o file.o inode.o map.o super.o
+230
View File
@@ -0,0 +1,230 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/buffer_head.h>
#include <linux/fs.h>
#include <linux/adfs_fs.h>
/* Internal data structures for ADFS */
#define ADFS_FREE_FRAG 0
#define ADFS_BAD_FRAG 1
#define ADFS_ROOT_FRAG 2
#define ADFS_FILETYPE_NONE ((u16)~0)
/* RISC OS 12-bit filetype is stored in load_address[19:8] */
static inline u16 adfs_filetype(u32 loadaddr)
{
return (loadaddr & 0xfff00000) == 0xfff00000 ?
(loadaddr >> 8) & 0xfff : ADFS_FILETYPE_NONE;
}
#define ADFS_NDA_OWNER_READ (1 << 0)
#define ADFS_NDA_OWNER_WRITE (1 << 1)
#define ADFS_NDA_LOCKED (1 << 2)
#define ADFS_NDA_DIRECTORY (1 << 3)
#define ADFS_NDA_EXECUTE (1 << 4)
#define ADFS_NDA_PUBLIC_READ (1 << 5)
#define ADFS_NDA_PUBLIC_WRITE (1 << 6)
/*
* 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 */
struct inode vfs_inode;
};
static inline struct adfs_inode_info *ADFS_I(struct inode *inode)
{
return container_of(inode, struct adfs_inode_info, vfs_inode);
}
static inline bool adfs_inode_is_stamped(struct inode *inode)
{
return (ADFS_I(inode)->loadaddr & 0xfff00000) == 0xfff00000;
}
/*
* Forward-declare this
*/
struct adfs_discmap;
struct adfs_dir_ops;
/*
* ADFS file system superblock data in memory
*/
struct adfs_sb_info {
union { struct {
struct adfs_discmap *s_map; /* bh list containing map */
const struct adfs_dir_ops *s_dir; /* directory operations */
};
struct rcu_head rcu; /* used only at shutdown time */
};
kuid_t s_uid; /* owner uid */
kgid_t s_gid; /* owner gid */
umode_t s_owner_mask; /* ADFS owner perm -> unix perm */
umode_t s_other_mask; /* ADFS other perm -> unix perm */
int s_ftsuffix; /* ,xyz hex filetype suffix option */
__u32 s_ids_per_zone; /* max. no ids in one zone */
__u32 s_idlen; /* length of ID in map */
__u32 s_map_size; /* sector size of a map */
signed int s_map2blk; /* shift left by this for map->sector*/
unsigned int s_log2sharesize;/* log2 share size */
unsigned int s_namelen; /* maximum number of characters in name */
};
static inline struct adfs_sb_info *ADFS_SB(struct super_block *sb)
{
return sb->s_fs_info;
}
/*
* Directory handling
*/
struct adfs_dir {
struct super_block *sb;
int nr_buffers;
struct buffer_head *bh[4];
struct buffer_head **bhs;
unsigned int pos;
__u32 parent_id;
union {
struct adfs_dirheader *dirhead;
struct adfs_bigdirheader *bighead;
};
union {
struct adfs_newdirtail *newtail;
struct adfs_bigdirtail *bigtail;
};
};
/*
* This is the overall maximum name length
*/
#define ADFS_MAX_NAME_LEN (256 + 4) /* +4 for ,xyz hex filetype suffix */
struct object_info {
__u32 parent_id; /* parent object id */
__u32 indaddr; /* indirect disc addr */
__u32 loadaddr; /* load address */
__u32 execaddr; /* execution address */
__u32 size; /* size */
__u8 attr; /* RISC OS attributes */
unsigned int name_len; /* name length */
char name[ADFS_MAX_NAME_LEN];/* file name */
};
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 (*commit)(struct adfs_dir *dir);
};
struct adfs_discmap {
struct buffer_head *dm_bh;
__u32 dm_startblk;
unsigned int dm_startbit;
unsigned int dm_endbit;
};
/* Inode stuff */
struct inode *adfs_iget(struct super_block *sb, struct object_info *obj);
int adfs_write_inode(struct inode *inode, struct writeback_control *wbc);
int adfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr);
/* map.c */
int adfs_map_lookup(struct super_block *sb, u32 frag_id, unsigned int offset);
void adfs_map_statfs(struct super_block *sb, struct kstatfs *buf);
struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecord *dr);
void adfs_free_map(struct super_block *sb);
/* Misc */
__printf(3, 4)
void __adfs_error(struct super_block *sb, const char *function,
const char *fmt, ...);
#define adfs_error(sb, fmt...) __adfs_error(sb, __func__, fmt)
void adfs_msg(struct super_block *sb, const char *pfx, const char *fmt, ...);
/* super.c */
/*
* Inodes and file operations
*/
/* dir_*.c */
extern const struct inode_operations adfs_dir_inode_operations;
extern const struct file_operations adfs_dir_operations;
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);
/* file.c */
extern const struct inode_operations adfs_file_inode_operations;
extern const struct file_operations adfs_file_operations;
static inline __u32 signed_asl(__u32 val, signed int shift)
{
if (shift >= 0)
val <<= shift;
else
val >>= -shift;
return val;
}
/*
* Calculate the address of a block in an object given the block offset
* and the object identity.
*
* The root directory ID should always be looked up in the map [3.4]
*/
static inline int __adfs_block_map(struct super_block *sb, u32 indaddr,
unsigned int block)
{
if (indaddr & 255) {
unsigned int off;
off = (indaddr & 255) - 1;
block += off << ADFS_SB(sb)->s_log2sharesize;
}
return adfs_map_lookup(sb, indaddr >> 8, block);
}
/* Return the disc record from the map */
static inline
struct adfs_discrecord *adfs_map_discrecord(struct adfs_discmap *dm)
{
return (void *)(dm[0].dm_bh->b_data + 4);
}
static inline u64 adfs_disc_size(const struct adfs_discrecord *dr)
{
return (u64)le32_to_cpu(dr->disc_size_high) << 32 |
le32_to_cpu(dr->disc_size);
}
+458
View File
@@ -0,0 +1,458 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/fs/adfs/dir.c
*
* Copyright (C) 1999-2000 Russell King
*
* Common directory handling for ADFS
*/
#include <linux/hex.h>
#include <linux/slab.h>
#include "adfs.h"
/*
* For future. This should probably be per-directory.
*/
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 = kzalloc_objs(*bhs, num);
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)
{
unsigned int dots, i;
/*
* RISC OS allows the use of '/' in directory entry names, so we need
* to fix these up. '/' is typically used for FAT compatibility to
* represent '.', so do the same conversion here. In any case, '.'
* will never be in a RISC OS name since it is used as the pathname
* separator. Handle the case where we may generate a '.' or '..'
* name, replacing the first character with '^' (the RISC OS "parent
* directory" character.)
*/
for (i = dots = 0; i < obj->name_len; i++)
if (obj->name[i] == '/') {
obj->name[i] = '.';
dots++;
}
if (obj->name_len <= 2 && dots == obj->name_len)
obj->name[0] = '^';
/*
* If the object is a file, and the user requested the ,xyz hex
* filetype suffix to the name, check the filetype and append.
*/
if (!(obj->attr & ADFS_NDA_DIRECTORY) && ADFS_SB(dir->sb)->s_ftsuffix) {
u16 filetype = adfs_filetype(obj->loadaddr);
if (filetype != ADFS_FILETYPE_NONE) {
obj->name[obj->name_len++] = ',';
obj->name[obj->name_len++] = hex_asc_lo(filetype >> 8);
obj->name[obj->name_len++] = hex_asc_lo(filetype >> 4);
obj->name[obj->name_len++] = hex_asc_lo(filetype >> 0);
}
}
}
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 adfs_dir dir;
int ret;
down_read(&adfs_dir_rwsem);
ret = adfs_dir_read_inode(sb, inode, &dir);
if (ret)
goto unlock;
if (ctx->pos == 0) {
if (!dir_emit_dot(file, ctx))
goto unlock_relse;
ctx->pos = 1;
}
if (ctx->pos == 1) {
if (!dir_emit(ctx, "..", 2, dir.parent_id, DT_DIR))
goto unlock_relse;
ctx->pos = 2;
}
ret = ops->iterate(&dir, ctx);
unlock_relse:
up_read(&adfs_dir_rwsem);
adfs_dir_relse(&dir);
return ret;
unlock:
up_read(&adfs_dir_rwsem);
return ret;
}
int
adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait)
{
const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
struct adfs_dir dir;
int ret;
if (!IS_ENABLED(CONFIG_ADFS_FS_RW))
return -EINVAL;
if (!ops->update)
return -EINVAL;
down_write(&adfs_dir_rwsem);
ret = adfs_dir_read(sb, obj->parent_id, 0, &dir);
if (ret)
goto unlock;
ret = ops->update(&dir, obj);
if (ret)
goto forget;
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);
return ret;
}
static unsigned char adfs_tolower(unsigned char c)
{
if (c >= 'A' && c <= 'Z')
c += 'a' - 'A';
return c;
}
static int __adfs_compare(const unsigned char *qstr, u32 qlen,
const char *str, u32 len)
{
u32 i;
if (qlen != len)
return 1;
for (i = 0; i < qlen; i++)
if (adfs_tolower(qstr[i]) != adfs_tolower(str[i]))
return 1;
return 0;
}
static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr,
struct object_info *obj)
{
struct super_block *sb = inode->i_sb;
const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
const unsigned char *name;
struct adfs_dir dir;
u32 name_len;
int ret;
down_read(&adfs_dir_rwsem);
ret = adfs_dir_read_inode(sb, inode, &dir);
if (ret)
goto unlock;
ret = ops->setpos(&dir, 0);
if (ret)
goto unlock_relse;
ret = -ENOENT;
name = qstr->name;
name_len = qstr->len;
while (ops->getnext(&dir, obj) == 0) {
if (!__adfs_compare(name, name_len, obj->name, obj->name_len)) {
ret = 0;
break;
}
}
obj->parent_id = ADFS_I(inode)->indaddr;
unlock_relse:
up_read(&adfs_dir_rwsem);
adfs_dir_relse(&dir);
return ret;
unlock:
up_read(&adfs_dir_rwsem);
return ret;
}
const struct file_operations adfs_dir_operations = {
.read = generic_read_dir,
.llseek = generic_file_llseek,
.iterate_shared = adfs_iterate,
.fsync = simple_fsync,
};
static int
adfs_hash(const struct dentry *parent, struct qstr *qstr)
{
const unsigned char *name;
unsigned long hash;
u32 len;
if (qstr->len > ADFS_SB(parent->d_sb)->s_namelen)
return -ENAMETOOLONG;
len = qstr->len;
name = qstr->name;
hash = init_name_hash(parent);
while (len--)
hash = partial_name_hash(adfs_tolower(*name++), hash);
qstr->hash = end_name_hash(hash);
return 0;
}
/*
* Compare two names, taking note of the name length
* requirements of the underlying filesystem.
*/
static int adfs_compare(const struct dentry *dentry, unsigned int len,
const char *str, const struct qstr *qstr)
{
return __adfs_compare(qstr->name, qstr->len, str, len);
}
const struct dentry_operations adfs_dentry_operations = {
.d_hash = adfs_hash,
.d_compare = adfs_compare,
};
static struct dentry *
adfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
{
struct inode *inode = NULL;
struct object_info obj;
int error;
error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj);
if (error == 0) {
/*
* This only returns NULL if get_empty_inode
* fails.
*/
inode = adfs_iget(dir->i_sb, &obj);
if (!inode)
inode = ERR_PTR(-EACCES);
} else if (error != -ENOENT) {
inode = ERR_PTR(error);
}
return d_splice_alias(inode, dentry);
}
/*
* directories can handle most operations...
*/
const struct inode_operations adfs_dir_inode_operations = {
.lookup = adfs_lookup,
.setattr = adfs_setattr,
};
+324
View File
@@ -0,0 +1,324 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/fs/adfs/dir_f.c
*
* Copyright (C) 1997-1999 Russell King
*
* E and F format directory handling
*/
#include "adfs.h"
#include "dir_f.h"
/*
* Read an (unaligned) value of length 1..4 bytes
*/
static inline unsigned int adfs_readval(unsigned char *p, int len)
{
unsigned int val = 0;
switch (len) {
case 4: val |= p[3] << 24;
fallthrough;
case 3: val |= p[2] << 16;
fallthrough;
case 2: val |= p[1] << 8;
fallthrough;
default: val |= p[0];
}
return val;
}
static inline void adfs_writeval(unsigned char *p, int len, unsigned int val)
{
switch (len) {
case 4: p[3] = val >> 24;
fallthrough;
case 3: p[2] = val >> 16;
fallthrough;
case 2: p[1] = val >> 8;
fallthrough;
default: p[0] = val;
}
}
#define ror13(v) ((v >> 13) | (v << 19))
#define dir_u8(idx) \
({ int _buf = idx >> blocksize_bits; \
int _off = idx - (_buf << blocksize_bits);\
*(u8 *)(bh[_buf]->b_data + _off); \
})
#define dir_u32(idx) \
({ int _buf = idx >> blocksize_bits; \
int _off = idx - (_buf << blocksize_bits);\
*(__le32 *)(bh[_buf]->b_data + _off); \
})
#define bufoff(_bh,_idx) \
({ int _buf = _idx >> blocksize_bits; \
int _off = _idx - (_buf << blocksize_bits);\
(void *)(_bh[_buf]->b_data + _off); \
})
/*
* There are some algorithms that are nice in
* assembler, but a bitch in C... This is one
* of them.
*/
static u8
adfs_dir_checkbyte(const struct adfs_dir *dir)
{
struct buffer_head * const *bh = dir->bh;
const int blocksize_bits = dir->sb->s_blocksize_bits;
union { __le32 *ptr32; u8 *ptr8; } ptr, end;
u32 dircheck = 0;
int last = 5 - 26;
int i = 0;
/*
* Accumulate each word up to the last whole
* word of the last directory entry. This
* can spread across several buffer heads.
*/
do {
last += 26;
do {
dircheck = le32_to_cpu(dir_u32(i)) ^ ror13(dircheck);
i += sizeof(u32);
} while (i < (last & ~3));
} while (dir_u8(last) != 0);
/*
* Accumulate the last few bytes. These
* bytes will be within the same bh.
*/
if (i != last) {
ptr.ptr8 = bufoff(bh, i);
end.ptr8 = ptr.ptr8 + last - i;
do {
dircheck = *ptr.ptr8++ ^ ror13(dircheck);
} while (ptr.ptr8 < end.ptr8);
}
/*
* The directory tail is in the final bh
* Note that contary to the RISC OS PRMs,
* the first few bytes are NOT included
* in the check. All bytes are in the
* same bh.
*/
ptr.ptr8 = bufoff(bh, 2008);
end.ptr8 = ptr.ptr8 + 36;
do {
__le32 v = *ptr.ptr32++;
dircheck = le32_to_cpu(v) ^ ror13(dircheck);
} while (ptr.ptr32 < end.ptr32);
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_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;
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;
dir->parent_id = adfs_readval(dir->newtail->dirparent, 3);
return 0;
bad_dir:
adfs_error(sb, "dir %06x is corrupted", indaddr);
adfs_dir_relse(dir);
return -EIO;
}
/*
* convert a disk-based directory entry to a Linux ADFS directory entry
*/
static inline void
adfs_dir2obj(struct adfs_dir *dir, struct object_info *obj,
struct adfs_direntry *de)
{
unsigned int name_len;
for (name_len = 0; name_len < ADFS_F_NAME_LEN; name_len++) {
if (de->dirobname[name_len] < ' ')
break;
obj->name[name_len] = de->dirobname[name_len];
}
obj->name_len = name_len;
obj->indaddr = adfs_readval(de->dirinddiscadd, 3);
obj->loadaddr = adfs_readval(de->dirload, 4);
obj->execaddr = adfs_readval(de->direxec, 4);
obj->size = adfs_readval(de->dirlen, 4);
obj->attr = de->newdiratts;
adfs_object_fixup(dir, obj);
}
/*
* convert a Linux ADFS directory entry to a disk-based directory entry
*/
static inline void
adfs_obj2dir(struct adfs_direntry *de, struct object_info *obj)
{
adfs_writeval(de->dirinddiscadd, 3, obj->indaddr);
adfs_writeval(de->dirload, 4, obj->loadaddr);
adfs_writeval(de->direxec, 4, obj->execaddr);
adfs_writeval(de->dirlen, 4, obj->size);
de->newdiratts = obj->attr;
}
/*
* get a directory entry. Note that the caller is responsible
* for holding the relevant locks.
*/
static int
__adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj)
{
struct adfs_direntry de;
int ret;
ret = adfs_dir_copyfrom(&de, dir, pos, 26);
if (ret)
return ret;
if (!de.dirobname[0])
return -ENOENT;
adfs_dir2obj(dir, obj, &de);
return 0;
}
static int
adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos)
{
if (fpos >= ADFS_NUM_DIR_ENTRIES)
return -ENOENT;
dir->pos = 5 + fpos * 26;
return 0;
}
static int
adfs_f_getnext(struct adfs_dir *dir, struct object_info *obj)
{
unsigned int ret;
ret = __adfs_dir_get(dir, dir->pos, obj);
if (ret == 0)
dir->pos += 26;
return ret;
}
static int adfs_f_iterate(struct adfs_dir *dir, struct dir_context *ctx)
{
struct object_info obj;
int pos = 5 + (ctx->pos - 2) * 26;
while (ctx->pos < 2 + ADFS_NUM_DIR_ENTRIES) {
if (__adfs_dir_get(dir, pos, &obj))
break;
if (!dir_emit(ctx, obj.name, obj.name_len,
obj.indaddr, DT_UNKNOWN))
break;
pos += 26;
ctx->pos++;
}
return 0;
}
static int adfs_f_update(struct adfs_dir *dir, struct object_info *obj)
{
struct adfs_direntry de;
int offset, ret;
offset = 5 - (int)sizeof(de);
do {
offset += sizeof(de);
ret = adfs_dir_copyfrom(&de, dir, offset, sizeof(de));
if (ret) {
adfs_error(dir->sb, "error reading directory entry");
return -ENOENT;
}
if (!de.dirobname[0]) {
adfs_error(dir->sb, "unable to locate entry to update");
return -ENOENT;
}
} while (adfs_readval(de.dirinddiscadd, 3) != obj->indaddr);
/* Update the directory entry with the new object state */
adfs_obj2dir(&de, obj);
/* Write the directory entry back to the directory */
return adfs_dir_copyto(dir, offset, &de, 26);
}
static int adfs_f_commit(struct adfs_dir *dir)
{
int ret;
/* Increment directory sequence number */
dir->dirhead->startmasseq += 1;
dir->newtail->endmasseq += 1;
/* 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");
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,
.commit = adfs_f_commit,
};
+61
View File
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* linux/fs/adfs/dir_f.h
*
* Copyright (C) 1999 Russell King
*
* Structures of directories on the F format disk
*/
#ifndef ADFS_DIR_F_H
#define ADFS_DIR_F_H
/*
* Directory header
*/
struct adfs_dirheader {
__u8 startmasseq;
__u8 startname[4];
} __attribute__((packed));
#define ADFS_NEWDIR_SIZE 2048
#define ADFS_NUM_DIR_ENTRIES 77
/*
* Directory entries
*/
struct adfs_direntry {
#define ADFS_F_NAME_LEN 10
char dirobname[ADFS_F_NAME_LEN];
__u8 dirload[4];
__u8 direxec[4];
__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));
#endif
+287
View File
@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/fs/adfs/dir_fplus.c
*
* Copyright (C) 1997-1999 Russell King
*/
#include "adfs.h"
#include "dir_fplus.h"
/* Return the byte offset to directory entry pos */
static unsigned int adfs_fplus_offset(const struct adfs_bigdirheader *h,
unsigned int pos)
{
return offsetof(struct adfs_bigdirheader, bigdirname) +
ALIGN(le32_to_cpu(h->bigdirnamelen), 4) +
pos * sizeof(struct adfs_bigdirentry);
}
static int adfs_fplus_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 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;
}
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);
}
/* 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;
}
if (adfs_fplus_checkbyte(dir) != t->bigdircheckbyte) {
adfs_error(sb, "dir %06x checkbyte mismatch\n", indaddr);
goto out;
}
dir->parent_id = le32_to_cpu(h->bigdirparent);
return 0;
out:
adfs_dir_relse(dir);
return ret;
}
static int
adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos)
{
int ret = -ENOENT;
if (fpos <= le32_to_cpu(dir->bighead->bigdirentries)) {
dir->pos = fpos;
ret = 0;
}
return ret;
}
static int
adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj)
{
struct adfs_bigdirheader *h = dir->bighead;
struct adfs_bigdirentry bde;
unsigned int offset;
int ret;
if (dir->pos >= le32_to_cpu(h->bigdirentries))
return -ENOENT;
offset = adfs_fplus_offset(h, dir->pos);
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);
obj->size = le32_to_cpu(bde.bigdirlen);
obj->indaddr = le32_to_cpu(bde.bigdirindaddr);
obj->attr = le32_to_cpu(bde.bigdirattr);
obj->name_len = le32_to_cpu(bde.bigdirobnamelen);
offset = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries));
offset += le32_to_cpu(bde.bigdirobnameptr);
ret = adfs_dir_copyfrom(obj->name, dir, offset, obj->name_len);
if (ret)
return ret;
adfs_object_fixup(dir, obj);
dir->pos += 1;
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;
}
const struct adfs_dir_ops adfs_fplus_dir_ops = {
.read = adfs_fplus_read,
.iterate = adfs_fplus_iterate,
.setpos = adfs_fplus_setpos,
.getnext = adfs_fplus_getnext,
.update = adfs_fplus_update,
.commit = adfs_fplus_commit,
};
+42
View File
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* linux/fs/adfs/dir_fplus.h
*
* Copyright (C) 1999 Russell King
*
* Structures of directories on the F+ format disk
*/
#define ADFS_FPLUS_NAME_LEN 255
#define BIGDIRSTARTNAME ('S' | 'B' << 8 | 'P' << 16 | 'r' << 24)
#define BIGDIRENDNAME ('o' | 'v' << 8 | 'e' << 16 | 'n' << 24)
struct adfs_bigdirheader {
__u8 startmasseq;
__u8 bigdirversion[3];
__le32 bigdirstartname;
__le32 bigdirnamelen;
__le32 bigdirsize;
__le32 bigdirentries;
__le32 bigdirnamesize;
__le32 bigdirparent;
char bigdirname[1];
} __attribute__((packed, aligned(4)));
struct adfs_bigdirentry {
__le32 bigdirload;
__le32 bigdirexec;
__le32 bigdirlen;
__le32 bigdirindaddr;
__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)));
+36
View File
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/adfs/file.c
*
* Copyright (C) 1997-1999 Russell King
* from:
*
* linux/fs/ext2/file.c
*
* Copyright (C) 1992, 1993, 1994, 1995
* Remy Card (card@masi.ibp.fr)
* Laboratoire MASI - Institut Blaise Pascal
* Universite Pierre et Marie Curie (Paris VI)
*
* from
*
* linux/fs/minix/file.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*
* adfs regular file handling primitives
*/
#include "adfs.h"
const struct file_operations adfs_file_operations = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
.mmap_prepare = generic_file_mmap_prepare,
.fsync = simple_fsync,
.write_iter = generic_file_write_iter,
.splice_read = filemap_splice_read,
};
const struct inode_operations adfs_file_inode_operations = {
.setattr = adfs_setattr,
};
+373
View File
@@ -0,0 +1,373 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/fs/adfs/inode.c
*
* Copyright (C) 1997-1999 Russell King
*/
#include <linux/buffer_head.h>
#include <linux/mpage.h>
#include <linux/writeback.h>
#include "adfs.h"
/*
* Lookup/Create a block at offset 'block' into 'inode'. We currently do
* not support creation of new blocks, so we return -EIO for this case.
*/
static int
adfs_get_block(struct inode *inode, sector_t block, struct buffer_head *bh,
int create)
{
if (!create) {
if (block >= inode->i_blocks)
goto abort_toobig;
block = __adfs_block_map(inode->i_sb, ADFS_I(inode)->indaddr,
block);
if (block)
map_bh(bh, inode->i_sb, block);
return 0;
}
/* don't support allocation of blocks yet */
return -EIO;
abort_toobig:
return 0;
}
static int adfs_writepages(struct address_space *mapping,
struct writeback_control *wbc)
{
return mpage_writepages(mapping, wbc, adfs_get_block);
}
static int adfs_read_folio(struct file *file, struct folio *folio)
{
return block_read_full_folio(folio, adfs_get_block);
}
static void adfs_write_failed(struct address_space *mapping, loff_t to)
{
struct inode *inode = mapping->host;
if (to > inode->i_size)
truncate_pagecache(inode, inode->i_size);
}
static int adfs_write_begin(const struct kiocb *iocb,
struct address_space *mapping,
loff_t pos, unsigned len,
struct folio **foliop, void **fsdata)
{
int ret;
ret = cont_write_begin(iocb, mapping, pos, len, foliop, fsdata,
adfs_get_block,
&ADFS_I(mapping->host)->mmu_private);
if (unlikely(ret))
adfs_write_failed(mapping, pos + len);
return ret;
}
static sector_t _adfs_bmap(struct address_space *mapping, sector_t block)
{
return generic_block_bmap(mapping, block, adfs_get_block);
}
static const struct address_space_operations adfs_aops = {
.dirty_folio = block_dirty_folio,
.invalidate_folio = block_invalidate_folio,
.read_folio = adfs_read_folio,
.writepages = adfs_writepages,
.write_begin = adfs_write_begin,
.write_end = generic_write_end,
.migrate_folio = buffer_migrate_folio,
.bmap = _adfs_bmap,
};
/*
* Convert ADFS attributes and filetype to Linux permission.
*/
static umode_t
adfs_atts2mode(struct super_block *sb, struct inode *inode)
{
unsigned int attr = ADFS_I(inode)->attr;
umode_t mode, rmask;
struct adfs_sb_info *asb = ADFS_SB(sb);
if (attr & ADFS_NDA_DIRECTORY) {
mode = S_IRUGO & asb->s_owner_mask;
return S_IFDIR | S_IXUGO | mode;
}
switch (adfs_filetype(ADFS_I(inode)->loadaddr)) {
case 0xfc0: /* LinkFS */
return S_IFLNK|S_IRWXUGO;
case 0xfe6: /* UnixExec */
rmask = S_IRUGO | S_IXUGO;
break;
default:
rmask = S_IRUGO;
}
mode = S_IFREG;
if (attr & ADFS_NDA_OWNER_READ)
mode |= rmask & asb->s_owner_mask;
if (attr & ADFS_NDA_OWNER_WRITE)
mode |= S_IWUGO & asb->s_owner_mask;
if (attr & ADFS_NDA_PUBLIC_READ)
mode |= rmask & asb->s_other_mask;
if (attr & ADFS_NDA_PUBLIC_WRITE)
mode |= S_IWUGO & asb->s_other_mask;
return mode;
}
/*
* 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,
umode_t ia_mode)
{
struct adfs_sb_info *asb = ADFS_SB(sb);
umode_t mode;
int attr;
/* 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))
return ADFS_NDA_DIRECTORY;
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 = ia_mode & asb->s_other_mask;
mode &= ~asb->s_owner_mask;
if (mode & S_IRUGO)
attr |= ADFS_NDA_PUBLIC_READ;
if (mode & S_IWUGO)
attr |= ADFS_NDA_PUBLIC_WRITE;
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
* of time to convert from RISC OS epoch to Unix epoch.
*/
static void
adfs_adfs2unix_time(struct timespec64 *tv, struct inode *inode)
{
unsigned int high, low;
/* 01 Jan 1970 00:00:00 (Unix epoch) as nanoseconds since
* 01 Jan 1900 00:00:00 (RISC OS epoch)
*/
s64 nsec;
if (!adfs_inode_is_stamped(inode))
goto cur_time;
high = ADFS_I(inode)->loadaddr & 0xFF; /* top 8 bits of timestamp */
low = ADFS_I(inode)->execaddr; /* bottom 32 bits of timestamp */
/* convert 40-bit centi-seconds to 32-bit seconds
* going via nanoseconds to retain precision
*/
nsec = (((s64) high << 32) | (s64) low) * 10000000; /* cs to ns */
/* Files dated pre 01 Jan 1970 00:00:00. */
if (nsec < nsec_unix_epoch_diff_risc_os_epoch)
goto too_early;
/* convert from RISC OS to Unix epoch */
nsec -= nsec_unix_epoch_diff_risc_os_epoch;
*tv = ns_to_timespec64(nsec);
return;
cur_time:
*tv = current_time(inode);
return;
too_early:
tv->tv_sec = tv->tv_nsec = 0;
return;
}
/* 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)
{
s64 cs, nsec = timespec64_to_ns(ts);
/* convert from Unix to RISC OS epoch */
nsec += nsec_unix_epoch_diff_risc_os_epoch;
/* 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;
}
/*
* Fill in the inode information from the object information.
*
* Note that this is an inode-less filesystem, so we can't use the inode
* number to reference the metadata on the media. Instead, we use the
* inode number to hold the object ID, which in turn will tell us where
* the data is held. We also save the parent object ID, and with these
* two, we can locate the metadata.
*
* This does mean that we rely on an objects parent remaining the same at
* all times - we cannot cope with a cross-directory rename (yet).
*/
struct inode *
adfs_iget(struct super_block *sb, struct object_info *obj)
{
struct inode *inode;
struct timespec64 ts;
inode = new_inode(sb);
if (!inode)
goto out;
inode->i_uid = ADFS_SB(sb)->s_uid;
inode->i_gid = ADFS_SB(sb)->s_gid;
inode->i_ino = obj->indaddr;
inode->i_size = obj->size;
set_nlink(inode, 2);
inode->i_blocks = (inode->i_size + sb->s_blocksize - 1) >>
sb->s_blocksize_bits;
/*
* we need to save the parent directory ID so that
* write_inode can update the directory information
* for this file. This will need special handling
* 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;
inode->i_mode = adfs_atts2mode(sb, inode);
adfs_adfs2unix_time(&ts, inode);
inode_set_atime_to_ts(inode, ts);
inode_set_mtime_to_ts(inode, ts);
inode_set_ctime_to_ts(inode, ts);
if (S_ISDIR(inode->i_mode)) {
inode->i_op = &adfs_dir_inode_operations;
inode->i_fop = &adfs_dir_operations;
} else if (S_ISREG(inode->i_mode)) {
inode->i_op = &adfs_file_inode_operations;
inode->i_fop = &adfs_file_operations;
inode->i_mapping->a_ops = &adfs_aops;
ADFS_I(inode)->mmu_private = inode->i_size;
}
inode_fake_hash(inode);
out:
return inode;
}
/*
* Validate and convert a changed access mode/time to their ADFS equivalents.
* adfs_write_inode will actually write the information back to the directory
* later.
*/
int
adfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr)
{
struct inode *inode = d_inode(dentry);
struct super_block *sb = inode->i_sb;
unsigned int ia_valid = attr->ia_valid;
int error;
error = setattr_prepare(&nop_mnt_idmap, dentry, attr);
/*
* we can't change the UID or GID of any file -
* we have a global UID/GID in the superblock
*/
if ((ia_valid & ATTR_UID && !uid_eq(attr->ia_uid, ADFS_SB(sb)->s_uid)) ||
(ia_valid & ATTR_GID && !gid_eq(attr->ia_gid, ADFS_SB(sb)->s_gid)))
error = -EPERM;
if (error)
goto out;
/* XXX: this is missing some actual on-disk truncation.. */
if (ia_valid & ATTR_SIZE)
truncate_setsize(inode, attr->ia_size);
if (ia_valid & ATTR_MTIME && adfs_inode_is_stamped(inode)) {
adfs_unix2adfs_time(inode, &attr->ia_mtime);
adfs_adfs2unix_time(&attr->ia_mtime, inode);
inode_set_mtime_to_ts(inode, attr->ia_mtime);
}
/*
* FIXME: should we make these == to i_mtime since we don't
* have the ability to represent them in our filesystem?
*/
if (ia_valid & ATTR_ATIME)
inode_set_atime_to_ts(inode, attr->ia_atime);
if (ia_valid & ATTR_CTIME)
inode_set_ctime_to_ts(inode, attr->ia_ctime);
if (ia_valid & ATTR_MODE) {
ADFS_I(inode)->attr = adfs_mode2atts(sb, inode, attr->ia_mode);
inode->i_mode = adfs_atts2mode(sb, inode);
}
/*
* FIXME: should we be marking this inode dirty even if
* we don't have any metadata to write back?
*/
if (ia_valid & (ATTR_SIZE | ATTR_MTIME | ATTR_MODE))
mark_inode_dirty(inode);
out:
return error;
}
/*
* write an existing inode back to the directory, and therefore the disk.
* The adfs-specific inode data has already been updated by * adfs_setattr().
*/
int adfs_write_inode(struct inode *inode, struct writeback_control *wbc)
{
struct super_block *sb = inode->i_sb;
struct object_info obj;
obj.indaddr = ADFS_I(inode)->indaddr;
obj.name_len = 0;
obj.parent_id = ADFS_I(inode)->parent_id;
obj.loadaddr = ADFS_I(inode)->loadaddr;
obj.execaddr = ADFS_I(inode)->execaddr;
obj.attr = ADFS_I(inode)->attr;
obj.size = inode->i_size;
return adfs_dir_update(sb, &obj, wbc->sync_mode == WB_SYNC_ALL);
}
+407
View File
@@ -0,0 +1,407 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/fs/adfs/map.c
*
* Copyright (C) 1997-2002 Russell King
*/
#include <linux/slab.h>
#include <linux/statfs.h>
#include <linux/unaligned.h>
#include "adfs.h"
/*
* The ADFS map is basically a set of sectors. Each sector is called a
* zone which contains a bitstream made up of variable sized fragments.
* Each bit refers to a set of bytes in the filesystem, defined by
* log2bpmb. This may be larger or smaller than the sector size, but
* the overall size it describes will always be a round number of
* sectors. A fragment id is always idlen bits long.
*
* < idlen > < n > <1>
* +---------+-------//---------+---+
* | frag id | 0000....000000 | 1 |
* +---------+-------//---------+---+
*
* The physical disk space used by a fragment is taken from the start of
* the fragment id up to and including the '1' bit - ie, idlen + n + 1
* bits.
*
* A fragment id can be repeated multiple times in the whole map for
* large or fragmented files. The first map zone a fragment starts in
* is given by fragment id / ids_per_zone - this allows objects to start
* from any zone on the disk.
*
* Free space is described by a linked list of fragments. Each free
* fragment describes free space in the same way as the other fragments,
* however, the frag id specifies an offset (in map bits) from the end
* of this fragment to the start of the next free fragment.
*
* Objects stored on the disk are allocated object ids (we use these as
* our inode numbers.) Object ids contain a fragment id and an optional
* offset. This allows a directory fragment to contain small files
* associated with that directory.
*/
/*
* For the future...
*/
static DEFINE_RWLOCK(adfs_map_lock);
/*
* This is fun. We need to load up to 19 bits from the map at an
* arbitrary bit alignment. (We're limited to 19 bits by F+ version 2).
*/
#define GET_FRAG_ID(_map,_start,_idmask) \
({ \
unsigned char *_m = _map + (_start >> 3); \
u32 _frag = get_unaligned_le32(_m); \
_frag >>= (_start & 7); \
_frag & _idmask; \
})
/*
* return the map bit offset of the fragment frag_id in the zone dm.
* Note that the loop is optimised for best asm code - look at the
* output of:
* gcc -D__KERNEL__ -O2 -I../../include -o - -S map.c
*/
static int lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen,
const u32 frag_id, unsigned int *offset)
{
const unsigned int endbit = dm->dm_endbit;
const u32 idmask = (1 << idlen) - 1;
unsigned char *map = dm->dm_bh->b_data;
unsigned int start = dm->dm_startbit;
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);
fragend = find_next_bit_le(map, endbit, start + idlen);
if (fragend >= endbit)
goto error;
if (start == freelink) {
freelink += frag & 0x7fff;
} else if (frag == frag_id) {
unsigned int length = fragend + 1 - start;
if (*offset < length)
return start + *offset;
*offset -= length;
}
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, fragend);
return -1;
}
/*
* Scan the free space map, for this zone, calculating the total
* number of map bits in each free space fragment.
*
* Note: idmask is limited to 15 bits [3.2]
*/
static unsigned int
scan_free_map(struct adfs_sb_info *asb, struct adfs_discmap *dm)
{
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, fragend;
u32 frag;
unsigned long total = 0;
/*
* get fragment id
*/
frag = GET_FRAG_ID(map, start, idmask);
/*
* If the freelink is null, then no free fragments
* exist in this zone.
*/
if (frag == 0)
return 0;
do {
start += frag;
frag = GET_FRAG_ID(map, start, idmask);
fragend = find_next_bit_le(map, endbit, start + idlen);
if (fragend >= endbit)
goto error;
total += fragend + 1 - start;
} while (frag >= idlen + 1);
if (frag != 0)
printk(KERN_ERR "adfs: undersized free fragment\n");
return total;
error:
printk(KERN_ERR "adfs: oversized free fragment\n");
return 0;
}
static int scan_map(struct adfs_sb_info *asb, unsigned int zone,
const u32 frag_id, unsigned int mapoff)
{
const unsigned int idlen = asb->s_idlen;
struct adfs_discmap *dm, *dm_end;
int result;
dm = asb->s_map + zone;
zone = asb->s_map_size;
dm_end = asb->s_map + zone;
do {
result = lookup_zone(dm, idlen, frag_id, &mapoff);
if (result != -1)
goto found;
dm ++;
if (dm == dm_end)
dm = asb->s_map;
} while (--zone > 0);
return -1;
found:
result -= dm->dm_startbit;
result += dm->dm_startblk;
return result;
}
/*
* calculate the amount of free blocks in the map.
*
* n=1
* total_free = E(free_in_zone_n)
* nzones
*/
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;
dm = asb->s_map;
zone = asb->s_map_size;
do {
total += scan_free_map(asb, dm++);
} while (--zone > 0);
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)
{
struct adfs_sb_info *asb = ADFS_SB(sb);
unsigned int zone, mapoff;
int result;
/*
* map & root fragment is special - it starts in the center of the
* disk. The other fragments start at zone (frag / ids_per_zone)
*/
if (frag_id == ADFS_ROOT_FRAG)
zone = asb->s_map_size >> 1;
else
zone = frag_id / asb->s_ids_per_zone;
if (zone >= asb->s_map_size)
goto bad_fragment;
/* Convert sector offset to map offset */
mapoff = signed_asl(offset, -asb->s_map2blk);
read_lock(&adfs_map_lock);
result = scan_map(asb, zone, frag_id, mapoff);
read_unlock(&adfs_map_lock);
if (result > 0) {
unsigned int secoff;
/* Calculate sector offset into map block */
secoff = offset - signed_asl(mapoff, asb->s_map2blk);
return secoff + signed_asl(result, asb->s_map2blk);
}
adfs_error(sb, "fragment 0x%04x at offset %d not found in map",
frag_id, offset);
return 0;
bad_fragment:
adfs_error(sb, "invalid fragment 0x%04x (zone = %d, max = %d)",
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_objs(*dm, nzones);
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);
}
+502
View File
@@ -0,0 +1,502 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/fs/adfs/super.c
*
* Copyright (C) 1997-1999 Russell King
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs_parser.h>
#include <linux/fs_context.h>
#include <linux/mount.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/statfs.h>
#include <linux/user_namespace.h>
#include <linux/blkdev.h>
#include "adfs.h"
#include "dir_f.h"
#include "dir_fplus.h"
#define ADFS_SB_FLAGS SB_NOATIME
#define ADFS_DEFAULT_OWNER_MASK S_IRWXU
#define ADFS_DEFAULT_OTHER_MASK (S_IRWXG | S_IRWXO)
void __adfs_error(struct super_block *sb, const char *function, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
printk(KERN_CRIT "ADFS-fs error (device %s)%s%s: %pV\n",
sb->s_id, function ? ": " : "",
function ? function : "", &vaf);
va_end(args);
}
void adfs_msg(struct super_block *sb, const char *pfx, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
printk("%sADFS-fs (%s): %pV\n", pfx, sb->s_id, &vaf);
va_end(args);
}
static int adfs_checkdiscrecord(struct adfs_discrecord *dr)
{
unsigned int max_idlen;
int i;
/* sector size must be 256, 512 or 1024 bytes */
if (dr->log2secsize != 8 &&
dr->log2secsize != 9 &&
dr->log2secsize != 10)
return 1;
/* idlen must be at least log2secsize + 3 */
if (dr->idlen < dr->log2secsize + 3)
return 1;
/* we cannot have such a large disc that we
* are unable to represent sector offsets in
* 32 bits. This works out at 2.0 TB.
*/
if (le32_to_cpu(dr->disc_size_high) >> dr->log2secsize)
return 1;
/*
* Maximum idlen is limited to 16 bits for new directories by
* the three-byte storage of an indirect disc address. For
* big directories, idlen must be no greater than 19 v2 [1.0]
*/
max_idlen = dr->format_version ? 19 : 16;
if (dr->idlen > max_idlen)
return 1;
/* reserved bytes should be zero */
for (i = 0; i < sizeof(dr->unused52); i++)
if (dr->unused52[i] != 0)
return 1;
return 0;
}
static void adfs_put_super(struct super_block *sb)
{
struct adfs_sb_info *asb = ADFS_SB(sb);
adfs_free_map(sb);
kfree_rcu(asb, rcu);
}
static int adfs_show_options(struct seq_file *seq, struct dentry *root)
{
struct adfs_sb_info *asb = ADFS_SB(root->d_sb);
if (!uid_eq(asb->s_uid, GLOBAL_ROOT_UID))
seq_printf(seq, ",uid=%u", from_kuid_munged(&init_user_ns, asb->s_uid));
if (!gid_eq(asb->s_gid, GLOBAL_ROOT_GID))
seq_printf(seq, ",gid=%u", from_kgid_munged(&init_user_ns, asb->s_gid));
if (asb->s_owner_mask != ADFS_DEFAULT_OWNER_MASK)
seq_printf(seq, ",ownmask=%o", asb->s_owner_mask);
if (asb->s_other_mask != ADFS_DEFAULT_OTHER_MASK)
seq_printf(seq, ",othmask=%o", asb->s_other_mask);
if (asb->s_ftsuffix != 0)
seq_printf(seq, ",ftsuffix=%u", asb->s_ftsuffix);
return 0;
}
enum {Opt_uid, Opt_gid, Opt_ownmask, Opt_othmask, Opt_ftsuffix};
static const struct fs_parameter_spec adfs_param_spec[] = {
fsparam_uid ("uid", Opt_uid),
fsparam_gid ("gid", Opt_gid),
fsparam_u32oct ("ownmask", Opt_ownmask),
fsparam_u32oct ("othmask", Opt_othmask),
fsparam_u32 ("ftsuffix", Opt_ftsuffix),
{}
};
static int adfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
struct adfs_sb_info *asb = fc->s_fs_info;
struct fs_parse_result result;
int opt;
opt = fs_parse(fc, adfs_param_spec, param, &result);
if (opt < 0)
return opt;
switch (opt) {
case Opt_uid:
asb->s_uid = result.uid;
break;
case Opt_gid:
asb->s_gid = result.gid;
break;
case Opt_ownmask:
asb->s_owner_mask = result.uint_32;
break;
case Opt_othmask:
asb->s_other_mask = result.uint_32;
break;
case Opt_ftsuffix:
asb->s_ftsuffix = result.uint_32;
break;
default:
return -EINVAL;
}
return 0;
}
static int adfs_reconfigure(struct fs_context *fc)
{
struct adfs_sb_info *new_asb = fc->s_fs_info;
struct adfs_sb_info *asb = ADFS_SB(fc->root->d_sb);
sync_filesystem(fc->root->d_sb);
fc->sb_flags |= ADFS_SB_FLAGS;
/* Structure copy newly parsed options */
*asb = *new_asb;
return 0;
}
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);
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_ffree = (long)(buf->f_bfree * buf->f_files) / (long)buf->f_blocks;
buf->f_fsid = u64_to_fsid(id);
return 0;
}
static struct kmem_cache *adfs_inode_cachep;
static struct inode *adfs_alloc_inode(struct super_block *sb)
{
struct adfs_inode_info *ei;
ei = alloc_inode_sb(sb, adfs_inode_cachep, GFP_KERNEL);
if (!ei)
return NULL;
return &ei->vfs_inode;
}
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;
inode_init_once(&ei->vfs_inode);
}
static int __init init_inodecache(void)
{
adfs_inode_cachep = kmem_cache_create("adfs_inode_cache",
sizeof(struct adfs_inode_info),
0, (SLAB_RECLAIM_ACCOUNT|
SLAB_ACCOUNT),
init_once);
if (adfs_inode_cachep == NULL)
return -ENOMEM;
return 0;
}
static void destroy_inodecache(void)
{
/*
* Make sure all delayed rcu free inodes are flushed before we
* destroy cache.
*/
rcu_barrier();
kmem_cache_destroy(adfs_inode_cachep);
}
static const struct super_operations adfs_sops = {
.alloc_inode = adfs_alloc_inode,
.free_inode = adfs_free_inode,
.drop_inode = adfs_drop_inode,
.write_inode = adfs_write_inode,
.put_super = adfs_put_super,
.statfs = adfs_statfs,
.show_options = adfs_show_options,
};
static int adfs_probe(struct super_block *sb, unsigned int offset, int silent,
int (*validate)(struct super_block *sb,
struct buffer_head *bh,
struct adfs_discrecord **bhp))
{
struct adfs_sb_info *asb = ADFS_SB(sb);
struct adfs_discrecord *dr;
struct buffer_head *bh;
unsigned int blocksize = BLOCK_SIZE;
int ret, try;
for (try = 0; try < 2; try++) {
/* try to set the requested block size */
if (sb->s_blocksize != blocksize &&
!sb_set_blocksize(sb, blocksize)) {
if (!silent)
adfs_msg(sb, KERN_ERR,
"error: unsupported blocksize");
return -EINVAL;
}
/* read the buffer */
bh = sb_bread(sb, offset >> sb->s_blocksize_bits);
if (!bh) {
adfs_msg(sb, KERN_ERR,
"error: unable to read block %u, try %d",
offset >> sb->s_blocksize_bits, try);
return -EIO;
}
/* validate it */
ret = validate(sb, bh, &dr);
if (ret) {
brelse(bh);
return ret;
}
/* does the block size match the filesystem block size? */
blocksize = 1 << dr->log2secsize;
if (sb->s_blocksize == blocksize) {
asb->s_map = adfs_read_map(sb, dr);
brelse(bh);
return PTR_ERR_OR_ZERO(asb->s_map);
}
brelse(bh);
}
return -EIO;
}
static int adfs_validate_bblk(struct super_block *sb, struct buffer_head *bh,
struct adfs_discrecord **drp)
{
struct adfs_discrecord *dr;
unsigned char *b_data;
b_data = bh->b_data + (ADFS_DISCRECORD % sb->s_blocksize);
if (adfs_checkbblk(b_data))
return -EILSEQ;
/* Do some sanity checks on the ADFS disc record */
dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET);
if (adfs_checkdiscrecord(dr))
return -EILSEQ;
if ((dr->nzones | dr->nzones_high << 8) == 0)
return -EILSEQ;
*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, struct fs_context *fc)
{
struct adfs_discrecord *dr;
struct object_info root_obj;
struct adfs_sb_info *asb = sb->s_fs_info;
struct inode *root;
int ret = -EINVAL;
int silent = fc->sb_flags & SB_SILENT;
sb->s_flags |= ADFS_SB_FLAGS;
sb->s_fs_info = asb;
sb->s_magic = ADFS_SUPER_MAGIC;
sb->s_time_gran = 10000000;
/* 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: can't find an ADFS filesystem on dev %s.",
sb->s_id);
ret = -EINVAL;
}
if (ret)
goto error;
/* set up enough so that we can read an inode */
sb->s_op = &adfs_sops;
dr = adfs_map_discrecord(asb->s_map);
root_obj.parent_id = root_obj.indaddr = le32_to_cpu(dr->root);
root_obj.name_len = 0;
/* Set root object date as 01 Jan 1987 00:00:00 */
root_obj.loadaddr = 0xfff0003f;
root_obj.execaddr = 0xec22c000;
root_obj.size = ADFS_NEWDIR_SIZE;
root_obj.attr = ADFS_NDA_DIRECTORY | ADFS_NDA_OWNER_READ |
ADFS_NDA_OWNER_WRITE | ADFS_NDA_PUBLIC_READ;
/*
* If this is a F+ disk with variable length directories,
* get the root_size from the disc record.
*/
if (dr->format_version) {
root_obj.size = le32_to_cpu(dr->root_size);
asb->s_dir = &adfs_fplus_dir_ops;
asb->s_namelen = ADFS_FPLUS_NAME_LEN;
} else {
asb->s_dir = &adfs_f_dir_ops;
asb->s_namelen = ADFS_F_NAME_LEN;
}
/*
* ,xyz hex filetype suffix may be added by driver
* to files that have valid RISC OS filetype
*/
if (asb->s_ftsuffix)
asb->s_namelen += 4;
set_default_d_op(sb, &adfs_dentry_operations);
root = adfs_iget(sb, &root_obj);
sb->s_root = d_make_root(root);
if (!sb->s_root) {
adfs_free_map(sb);
adfs_error(sb, "get root inode failed\n");
ret = -EIO;
goto error;
}
return 0;
error:
sb->s_fs_info = NULL;
kfree(asb);
return ret;
}
static int adfs_get_tree(struct fs_context *fc)
{
return get_tree_bdev(fc, adfs_fill_super);
}
static void adfs_free_fc(struct fs_context *fc)
{
struct adfs_context *asb = fc->s_fs_info;
kfree(asb);
}
static const struct fs_context_operations adfs_context_ops = {
.parse_param = adfs_parse_param,
.get_tree = adfs_get_tree,
.reconfigure = adfs_reconfigure,
.free = adfs_free_fc,
};
static int adfs_init_fs_context(struct fs_context *fc)
{
struct adfs_sb_info *asb;
asb = kzalloc_obj(struct adfs_sb_info);
if (!asb)
return -ENOMEM;
if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) {
struct super_block *sb = fc->root->d_sb;
struct adfs_sb_info *old_asb = ADFS_SB(sb);
/* structure copy existing options before parsing */
*asb = *old_asb;
} else {
/* set default options */
asb->s_uid = GLOBAL_ROOT_UID;
asb->s_gid = GLOBAL_ROOT_GID;
asb->s_owner_mask = ADFS_DEFAULT_OWNER_MASK;
asb->s_other_mask = ADFS_DEFAULT_OTHER_MASK;
asb->s_ftsuffix = 0;
}
fc->ops = &adfs_context_ops;
fc->s_fs_info = asb;
return 0;
}
static struct file_system_type adfs_fs_type = {
.owner = THIS_MODULE,
.name = "adfs",
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
.init_fs_context = adfs_init_fs_context,
.parameters = adfs_param_spec,
};
MODULE_ALIAS_FS("adfs");
static int __init init_adfs_fs(void)
{
int err = init_inodecache();
if (err)
goto out1;
err = register_filesystem(&adfs_fs_type);
if (err)
goto out;
return 0;
out:
destroy_inodecache();
out1:
return err;
}
static void __exit exit_adfs_fs(void)
{
unregister_filesystem(&adfs_fs_type);
destroy_inodecache();
}
module_init(init_adfs_fs)
module_exit(exit_adfs_fs)
MODULE_DESCRIPTION("Acorn Disc Filing System");
MODULE_LICENSE("GPL");