mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-07 00:38:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
27
fs/adfs/Kconfig
Normal file
27
fs/adfs/Kconfig
Normal file
|
@ -0,0 +1,27 @@
|
|||
config ADFS_FS
|
||||
tristate "ADFS file system support"
|
||||
depends on BLOCK
|
||||
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.txt> 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.
|
7
fs/adfs/Makefile
Normal file
7
fs/adfs/Makefile
Normal file
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# 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
|
208
fs/adfs/adfs.h
Normal file
208
fs/adfs/adfs.h
Normal file
|
@ -0,0 +1,208 @@
|
|||
#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_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)
|
||||
|
||||
#include "dir_f.h"
|
||||
|
||||
struct buffer_head;
|
||||
|
||||
/*
|
||||
* adfs file system inode data in memory
|
||||
*/
|
||||
struct adfs_inode_info {
|
||||
loff_t mmu_private;
|
||||
unsigned long parent_id; /* object id of parent */
|
||||
__u32 loadaddr; /* RISC OS load address */
|
||||
__u32 execaddr; /* RISC OS exec address */
|
||||
unsigned int filetype; /* RISC OS file type */
|
||||
unsigned int attr; /* RISC OS permissions */
|
||||
unsigned int stamped:1; /* RISC OS file has date/time */
|
||||
struct inode vfs_inode;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
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 */
|
||||
unsigned long s_size; /* total size (in blocks) of this fs */
|
||||
signed int s_map2blk; /* shift left by this for map->sector */
|
||||
unsigned int s_log2sharesize;/* log2 share size */
|
||||
__le32 s_version; /* disc format version */
|
||||
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;
|
||||
}
|
||||
|
||||
static inline struct adfs_inode_info *ADFS_I(struct inode *inode)
|
||||
{
|
||||
return container_of(inode, struct adfs_inode_info, vfs_inode);
|
||||
}
|
||||
|
||||
/*
|
||||
* Directory handling
|
||||
*/
|
||||
struct adfs_dir {
|
||||
struct super_block *sb;
|
||||
|
||||
int nr_buffers;
|
||||
struct buffer_head *bh[4];
|
||||
|
||||
/* big directories need allocated buffers */
|
||||
struct buffer_head **bh_fplus;
|
||||
|
||||
unsigned int pos;
|
||||
unsigned int parent_id;
|
||||
|
||||
struct adfs_dirheader dirhead;
|
||||
union adfs_dirtail dirtail;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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 file_id; /* object id */
|
||||
__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 */
|
||||
|
||||
/* RISC OS file type (12-bit: derived from loadaddr) */
|
||||
__u16 filetype;
|
||||
};
|
||||
|
||||
/* RISC OS 12-bit filetype converts to ,xyz hex filename suffix */
|
||||
static inline int append_filetype_suffix(char *buf, __u16 filetype)
|
||||
{
|
||||
if (filetype == 0xffff) /* no explicit 12-bit file type was set */
|
||||
return 0;
|
||||
|
||||
*buf++ = ',';
|
||||
*buf++ = hex_asc_lo(filetype >> 8);
|
||||
*buf++ = hex_asc_lo(filetype >> 4);
|
||||
*buf++ = hex_asc_lo(filetype >> 0);
|
||||
return 4;
|
||||
}
|
||||
|
||||
struct adfs_dir_ops {
|
||||
int (*read)(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir);
|
||||
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);
|
||||
};
|
||||
|
||||
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_notify_change(struct dentry *dentry, struct iattr *attr);
|
||||
|
||||
/* map.c */
|
||||
extern int adfs_map_lookup(struct super_block *sb, unsigned int frag_id, unsigned int offset);
|
||||
extern unsigned int adfs_map_free(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)
|
||||
|
||||
/* 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 struct adfs_dir_ops adfs_f_dir_ops;
|
||||
extern struct adfs_dir_ops adfs_fplus_dir_ops;
|
||||
|
||||
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, unsigned int object_id,
|
||||
unsigned int block)
|
||||
{
|
||||
if (object_id & 255) {
|
||||
unsigned int off;
|
||||
|
||||
off = (object_id & 255) - 1;
|
||||
block += off << ADFS_SB(sb)->s_log2sharesize;
|
||||
}
|
||||
|
||||
return adfs_map_lookup(sb, object_id >> 8, block);
|
||||
}
|
288
fs/adfs/dir.c
Normal file
288
fs/adfs/dir.c
Normal file
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* linux/fs/adfs/dir.c
|
||||
*
|
||||
* Copyright (C) 1999-2000 Russell King
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Common directory handling for ADFS
|
||||
*/
|
||||
#include "adfs.h"
|
||||
|
||||
/*
|
||||
* For future. This should probably be per-directory.
|
||||
*/
|
||||
static DEFINE_RWLOCK(adfs_dir_lock);
|
||||
|
||||
static int
|
||||
adfs_readdir(struct file *file, struct dir_context *ctx)
|
||||
{
|
||||
struct inode *inode = file_inode(file);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
|
||||
struct object_info obj;
|
||||
struct adfs_dir dir;
|
||||
int ret = 0;
|
||||
|
||||
if (ctx->pos >> 32)
|
||||
return 0;
|
||||
|
||||
ret = ops->read(sb, inode->i_ino, inode->i_size, &dir);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (ctx->pos == 0) {
|
||||
if (!dir_emit_dot(file, ctx))
|
||||
goto free_out;
|
||||
ctx->pos = 1;
|
||||
}
|
||||
if (ctx->pos == 1) {
|
||||
if (!dir_emit(ctx, "..", 2, dir.parent_id, DT_DIR))
|
||||
goto free_out;
|
||||
ctx->pos = 2;
|
||||
}
|
||||
|
||||
read_lock(&adfs_dir_lock);
|
||||
|
||||
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.file_id, DT_UNKNOWN))
|
||||
break;
|
||||
ctx->pos++;
|
||||
}
|
||||
|
||||
unlock_out:
|
||||
read_unlock(&adfs_dir_lock);
|
||||
|
||||
free_out:
|
||||
ops->free(&dir);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
#ifdef CONFIG_ADFS_FS_RW
|
||||
struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
|
||||
struct adfs_dir dir;
|
||||
|
||||
printk(KERN_INFO "adfs_dir_update: object %06X in dir %06X\n",
|
||||
obj->file_id, obj->parent_id);
|
||||
|
||||
if (!ops->update) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = ops->read(sb, obj->parent_id, 0, &dir);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
write_lock(&adfs_dir_lock);
|
||||
ret = ops->update(&dir, obj);
|
||||
write_unlock(&adfs_dir_lock);
|
||||
|
||||
if (wait) {
|
||||
int err = ops->sync(&dir);
|
||||
if (!ret)
|
||||
ret = err;
|
||||
}
|
||||
|
||||
ops->free(&dir);
|
||||
out:
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
adfs_match(struct qstr *name, struct object_info *obj)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (name->len != obj->name_len)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < name->len; i++) {
|
||||
char c1, c2;
|
||||
|
||||
c1 = name->name[i];
|
||||
c2 = obj->name[i];
|
||||
|
||||
if (c1 >= 'A' && c1 <= 'Z')
|
||||
c1 += 'a' - 'A';
|
||||
if (c2 >= 'A' && c2 <= 'Z')
|
||||
c2 += 'a' - 'A';
|
||||
|
||||
if (c1 != c2)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
adfs_dir_lookup_byname(struct inode *inode, struct qstr *name, struct object_info *obj)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
|
||||
struct adfs_dir dir;
|
||||
int ret;
|
||||
|
||||
ret = ops->read(sb, inode->i_ino, inode->i_size, &dir);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (ADFS_I(inode)->parent_id != dir.parent_id) {
|
||||
adfs_error(sb, "parent directory changed under me! (%lx but got %x)\n",
|
||||
ADFS_I(inode)->parent_id, dir.parent_id);
|
||||
ret = -EIO;
|
||||
goto free_out;
|
||||
}
|
||||
|
||||
obj->parent_id = inode->i_ino;
|
||||
|
||||
/*
|
||||
* '.' is handled by reserved_lookup() in fs/namei.c
|
||||
*/
|
||||
if (name->len == 2 && name->name[0] == '.' && name->name[1] == '.') {
|
||||
/*
|
||||
* Currently unable to fill in the rest of 'obj',
|
||||
* but this is better than nothing. We need to
|
||||
* ascend one level to find it's parent.
|
||||
*/
|
||||
obj->name_len = 0;
|
||||
obj->file_id = obj->parent_id;
|
||||
goto free_out;
|
||||
}
|
||||
|
||||
read_lock(&adfs_dir_lock);
|
||||
|
||||
ret = ops->setpos(&dir, 0);
|
||||
if (ret)
|
||||
goto unlock_out;
|
||||
|
||||
ret = -ENOENT;
|
||||
while (ops->getnext(&dir, obj) == 0) {
|
||||
if (adfs_match(name, obj)) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unlock_out:
|
||||
read_unlock(&adfs_dir_lock);
|
||||
|
||||
free_out:
|
||||
ops->free(&dir);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
const struct file_operations adfs_dir_operations = {
|
||||
.read = generic_read_dir,
|
||||
.llseek = generic_file_llseek,
|
||||
.iterate = adfs_readdir,
|
||||
.fsync = generic_file_fsync,
|
||||
};
|
||||
|
||||
static int
|
||||
adfs_hash(const struct dentry *parent, struct qstr *qstr)
|
||||
{
|
||||
const unsigned int name_len = ADFS_SB(parent->d_sb)->s_namelen;
|
||||
const unsigned char *name;
|
||||
unsigned long hash;
|
||||
int i;
|
||||
|
||||
if (qstr->len < name_len)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Truncate the name in place, avoids
|
||||
* having to define a compare function.
|
||||
*/
|
||||
qstr->len = i = name_len;
|
||||
name = qstr->name;
|
||||
hash = init_name_hash();
|
||||
while (i--) {
|
||||
char c;
|
||||
|
||||
c = *name++;
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
c += 'a' - 'A';
|
||||
|
||||
hash = partial_name_hash(c, 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 *parent, const struct dentry *dentry,
|
||||
unsigned int len, const char *str, const struct qstr *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (len != name->len)
|
||||
return 1;
|
||||
|
||||
for (i = 0; i < name->len; i++) {
|
||||
char a, b;
|
||||
|
||||
a = str[i];
|
||||
b = name->name[i];
|
||||
|
||||
if (a >= 'A' && a <= 'Z')
|
||||
a += 'a' - 'A';
|
||||
if (b >= 'A' && b <= 'Z')
|
||||
b += 'a' - 'A';
|
||||
|
||||
if (a != b)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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) {
|
||||
error = -EACCES;
|
||||
/*
|
||||
* This only returns NULL if get_empty_inode
|
||||
* fails.
|
||||
*/
|
||||
inode = adfs_iget(dir->i_sb, &obj);
|
||||
if (inode)
|
||||
error = 0;
|
||||
}
|
||||
d_add(dentry, inode);
|
||||
return ERR_PTR(error);
|
||||
}
|
||||
|
||||
/*
|
||||
* directories can handle most operations...
|
||||
*/
|
||||
const struct inode_operations adfs_dir_inode_operations = {
|
||||
.lookup = adfs_lookup,
|
||||
.setattr = adfs_notify_change,
|
||||
};
|
486
fs/adfs/dir_f.c
Normal file
486
fs/adfs/dir_f.c
Normal file
|
@ -0,0 +1,486 @@
|
|||
/*
|
||||
* linux/fs/adfs/dir_f.c
|
||||
*
|
||||
* Copyright (C) 1997-1999 Russell King
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* E and F format directory handling
|
||||
*/
|
||||
#include <linux/buffer_head.h>
|
||||
#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
|
||||
*/
|
||||
static inline unsigned int adfs_readval(unsigned char *p, int len)
|
||||
{
|
||||
unsigned int val = 0;
|
||||
|
||||
switch (len) {
|
||||
case 4: val |= p[3] << 24;
|
||||
case 3: val |= p[2] << 16;
|
||||
case 2: val |= p[1] << 8;
|
||||
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;
|
||||
case 3: p[2] = val >> 16;
|
||||
case 2: p[1] = val >> 8;
|
||||
default: p[0] = val;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int adfs_readname(char *buf, char *ptr, int maxlen)
|
||||
{
|
||||
char *old_buf = buf;
|
||||
|
||||
while ((unsigned char)*ptr >= ' ' && maxlen--) {
|
||||
if (*ptr == '/')
|
||||
*buf++ = '.';
|
||||
else
|
||||
*buf++ = *ptr;
|
||||
ptr++;
|
||||
}
|
||||
|
||||
return buf - old_buf;
|
||||
}
|
||||
|
||||
#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);\
|
||||
(u8 *)(_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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read and check that a directory is valid
|
||||
*/
|
||||
static int
|
||||
adfs_dir_read(struct super_block *sb, unsigned long object_id,
|
||||
unsigned int size, struct adfs_dir *dir)
|
||||
{
|
||||
const unsigned int blocksize_bits = sb->s_blocksize_bits;
|
||||
int blk = 0;
|
||||
|
||||
/*
|
||||
* Directories which are not a multiple of 2048 bytes
|
||||
* are considered bad v2 [3.6]
|
||||
*/
|
||||
if (size & 2047)
|
||||
goto bad_dir;
|
||||
|
||||
size >>= blocksize_bits;
|
||||
|
||||
dir->nr_buffers = 0;
|
||||
dir->sb = sb;
|
||||
|
||||
for (blk = 0; blk < size; blk++) {
|
||||
int phys;
|
||||
|
||||
phys = __adfs_block_map(sb, object_id, blk);
|
||||
if (!phys) {
|
||||
adfs_error(sb, "dir object %lX has a hole at offset %d",
|
||||
object_id, 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;
|
||||
|
||||
return 0;
|
||||
|
||||
bad_dir:
|
||||
adfs_error(sb, "corrupted directory fragment %lX",
|
||||
object_id);
|
||||
release_buffers:
|
||||
for (blk -= 1; blk >= 0; blk -= 1)
|
||||
brelse(dir->bh[blk]);
|
||||
|
||||
dir->sb = NULL;
|
||||
|
||||
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)
|
||||
{
|
||||
obj->name_len = adfs_readname(obj->name, de->dirobname, ADFS_F_NAME_LEN);
|
||||
obj->file_id = 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;
|
||||
obj->filetype = -1;
|
||||
|
||||
/*
|
||||
* object is a file and is filetyped and timestamped?
|
||||
* RISC OS 12-bit filetype is stored in load_address[19:8]
|
||||
*/
|
||||
if ((0 == (obj->attr & ADFS_NDA_DIRECTORY)) &&
|
||||
(0xfff00000 == (0xfff00000 & obj->loadaddr))) {
|
||||
obj->filetype = (__u16) ((0x000fff00 & obj->loadaddr) >> 8);
|
||||
|
||||
/* optionally append the ,xyz hex filetype suffix */
|
||||
if (ADFS_SB(dir->sb)->s_ftsuffix)
|
||||
obj->name_len +=
|
||||
append_filetype_suffix(
|
||||
&obj->name[obj->name_len],
|
||||
obj->filetype);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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->file_id);
|
||||
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 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;
|
||||
|
||||
memcpy(&de, dir->bh[buffer]->b_data + offset, thissize);
|
||||
if (thissize != 26)
|
||||
memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data,
|
||||
26 - thissize);
|
||||
|
||||
if (!de.dirobname[0])
|
||||
return -ENOENT;
|
||||
|
||||
adfs_dir2obj(dir, obj, &de);
|
||||
|
||||
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, unsigned long object_id)
|
||||
{
|
||||
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.file_id == object_id) {
|
||||
ret = pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
adfs_f_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (sz != ADFS_NEWDIR_SIZE)
|
||||
return -EIO;
|
||||
|
||||
ret = adfs_dir_read(sb, id, sz, 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)
|
||||
{
|
||||
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_update(struct adfs_dir *dir, struct object_info *obj)
|
||||
{
|
||||
struct super_block *sb = dir->sb;
|
||||
int ret, i;
|
||||
|
||||
ret = adfs_dir_find_entry(dir, obj->file_id);
|
||||
if (ret < 0) {
|
||||
adfs_error(dir->sb, "unable to locate entry to update");
|
||||
goto out;
|
||||
}
|
||||
|
||||
__adfs_dir_put(dir, ret, obj);
|
||||
|
||||
/*
|
||||
* Increment directory sequence number
|
||||
*/
|
||||
dir->bh[0]->b_data[0] += 1;
|
||||
dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 6] += 1;
|
||||
|
||||
ret = adfs_dir_checkbyte(dir);
|
||||
/*
|
||||
* Update directory check byte
|
||||
*/
|
||||
dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 1] = ret;
|
||||
|
||||
#if 1
|
||||
{
|
||||
const unsigned int blocksize_bits = sb->s_blocksize_bits;
|
||||
|
||||
memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead));
|
||||
memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail));
|
||||
|
||||
if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
|
||||
memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
|
||||
goto bad_dir;
|
||||
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
for (i = dir->nr_buffers - 1; i >= 0; i--)
|
||||
mark_buffer_dirty(dir->bh[i]);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
struct adfs_dir_ops adfs_f_dir_ops = {
|
||||
.read = adfs_f_read,
|
||||
.setpos = adfs_f_setpos,
|
||||
.getnext = adfs_f_getnext,
|
||||
.update = adfs_f_update,
|
||||
.sync = adfs_f_sync,
|
||||
.free = adfs_f_free
|
||||
};
|
65
fs/adfs/dir_f.h
Normal file
65
fs/adfs/dir_f.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* linux/fs/adfs/dir_f.h
|
||||
*
|
||||
* Copyright (C) 1999 Russell King
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Structures of directories on the F format disk
|
||||
*/
|
||||
#ifndef ADFS_DIR_F_H
|
||||
#define ADFS_DIR_F_H
|
||||
|
||||
/*
|
||||
* Directory header
|
||||
*/
|
||||
struct adfs_dirheader {
|
||||
unsigned char startmasseq;
|
||||
unsigned char startname[4];
|
||||
};
|
||||
|
||||
#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;
|
||||
};
|
||||
|
||||
/*
|
||||
* Directory tail
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
#endif
|
264
fs/adfs/dir_fplus.c
Normal file
264
fs/adfs/dir_fplus.c
Normal file
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* linux/fs/adfs/dir_fplus.c
|
||||
*
|
||||
* Copyright (C) 1997-1999 Russell King
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/buffer_head.h>
|
||||
#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)
|
||||
{
|
||||
struct adfs_bigdirheader *h;
|
||||
struct adfs_bigdirtail *t;
|
||||
unsigned long block;
|
||||
unsigned int blk, size;
|
||||
int i, ret = -EIO;
|
||||
|
||||
dir->nr_buffers = 0;
|
||||
|
||||
/* 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);
|
||||
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) {
|
||||
printk(KERN_WARNING "adfs: adfs_fplus_read:"
|
||||
" directory header size %X\n"
|
||||
" does not match directory size %X\n",
|
||||
size, sz);
|
||||
}
|
||||
|
||||
if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 ||
|
||||
h->bigdirversion[2] != 0 || size & 2047 ||
|
||||
h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) {
|
||||
printk(KERN_WARNING "adfs: dir object %X has"
|
||||
" malformed dir header\n", id);
|
||||
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_error(sb, "not enough memory for"
|
||||
" dir object %X (%d blocks)", id, size);
|
||||
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) {
|
||||
printk(KERN_WARNING "adfs: dir object %X has "
|
||||
"malformed dir end\n", id);
|
||||
goto out;
|
||||
}
|
||||
|
||||
dir->parent_id = le32_to_cpu(h->bigdirparent);
|
||||
dir->sb = sb;
|
||||
return 0;
|
||||
|
||||
out:
|
||||
if (dir->bh_fplus) {
|
||||
for (i = 0; i < dir->nr_buffers; i++)
|
||||
brelse(dir->bh_fplus[i]);
|
||||
|
||||
if (&dir->bh[0] != dir->bh_fplus)
|
||||
kfree(dir->bh_fplus);
|
||||
|
||||
dir->bh_fplus = NULL;
|
||||
}
|
||||
|
||||
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)) {
|
||||
dir->pos = fpos;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
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_bigdirentry bde;
|
||||
unsigned int offset;
|
||||
int i, ret = -ENOENT;
|
||||
|
||||
if (dir->pos >= le32_to_cpu(h->bigdirentries))
|
||||
goto out;
|
||||
|
||||
offset = offsetof(struct adfs_bigdirheader, bigdirname);
|
||||
offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3);
|
||||
offset += dir->pos * sizeof(struct adfs_bigdirentry);
|
||||
|
||||
dir_memcpy(dir, offset, &bde, sizeof(struct adfs_bigdirentry));
|
||||
|
||||
obj->loadaddr = le32_to_cpu(bde.bigdirload);
|
||||
obj->execaddr = le32_to_cpu(bde.bigdirexec);
|
||||
obj->size = le32_to_cpu(bde.bigdirlen);
|
||||
obj->file_id = le32_to_cpu(bde.bigdirindaddr);
|
||||
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 += le32_to_cpu(bde.bigdirobnameptr);
|
||||
|
||||
dir_memcpy(dir, offset, obj->name, obj->name_len);
|
||||
for (i = 0; i < obj->name_len; i++)
|
||||
if (obj->name[i] == '/')
|
||||
obj->name[i] = '.';
|
||||
|
||||
obj->filetype = -1;
|
||||
|
||||
/*
|
||||
* object is a file and is filetyped and timestamped?
|
||||
* RISC OS 12-bit filetype is stored in load_address[19:8]
|
||||
*/
|
||||
if ((0 == (obj->attr & ADFS_NDA_DIRECTORY)) &&
|
||||
(0xfff00000 == (0xfff00000 & obj->loadaddr))) {
|
||||
obj->filetype = (__u16) ((0x000fff00 & obj->loadaddr) >> 8);
|
||||
|
||||
/* optionally append the ,xyz hex filetype suffix */
|
||||
if (ADFS_SB(dir->sb)->s_ftsuffix)
|
||||
obj->name_len +=
|
||||
append_filetype_suffix(
|
||||
&obj->name[obj->name_len],
|
||||
obj->filetype);
|
||||
}
|
||||
|
||||
dir->pos += 1;
|
||||
ret = 0;
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
adfs_fplus_sync(struct adfs_dir *dir)
|
||||
{
|
||||
int err = 0;
|
||||
int i;
|
||||
|
||||
for (i = dir->nr_buffers - 1; i >= 0; i--) {
|
||||
struct buffer_head *bh = dir->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;
|
||||
}
|
||||
|
||||
struct adfs_dir_ops adfs_fplus_dir_ops = {
|
||||
.read = adfs_fplus_read,
|
||||
.setpos = adfs_fplus_setpos,
|
||||
.getnext = adfs_fplus_getnext,
|
||||
.sync = adfs_fplus_sync,
|
||||
.free = adfs_fplus_free
|
||||
};
|
45
fs/adfs/dir_fplus.h
Normal file
45
fs/adfs/dir_fplus.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* linux/fs/adfs/dir_fplus.h
|
||||
*
|
||||
* Copyright (C) 1999 Russell King
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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];
|
||||
};
|
||||
|
||||
struct adfs_bigdirentry {
|
||||
__le32 bigdirload;
|
||||
__le32 bigdirexec;
|
||||
__le32 bigdirlen;
|
||||
__le32 bigdirindaddr;
|
||||
__le32 bigdirattr;
|
||||
__le32 bigdirobnamelen;
|
||||
__le32 bigdirobnameptr;
|
||||
};
|
||||
|
||||
struct adfs_bigdirtail {
|
||||
__le32 bigdirendname;
|
||||
__u8 bigdirendmasseq;
|
||||
__u8 reserved[2];
|
||||
__u8 bigdircheckbyte;
|
||||
};
|
37
fs/adfs/file.c
Normal file
37
fs/adfs/file.c
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 = new_sync_read,
|
||||
.read_iter = generic_file_read_iter,
|
||||
.mmap = generic_file_mmap,
|
||||
.fsync = generic_file_fsync,
|
||||
.write = new_sync_write,
|
||||
.write_iter = generic_file_write_iter,
|
||||
.splice_read = generic_file_splice_read,
|
||||
};
|
||||
|
||||
const struct inode_operations adfs_file_inode_operations = {
|
||||
.setattr = adfs_notify_change,
|
||||
};
|
371
fs/adfs/inode.c
Normal file
371
fs/adfs/inode.c
Normal file
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
* linux/fs/adfs/inode.c
|
||||
*
|
||||
* Copyright (C) 1997-1999 Russell King
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/buffer_head.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, inode->i_ino, 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_writepage(struct page *page, struct writeback_control *wbc)
|
||||
{
|
||||
return block_write_full_page(page, adfs_get_block, wbc);
|
||||
}
|
||||
|
||||
static int adfs_readpage(struct file *file, struct page *page)
|
||||
{
|
||||
return block_read_full_page(page, 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(struct file *file, struct address_space *mapping,
|
||||
loff_t pos, unsigned len, unsigned flags,
|
||||
struct page **pagep, void **fsdata)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*pagep = NULL;
|
||||
ret = cont_write_begin(file, mapping, pos, len, flags, pagep, 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 = {
|
||||
.readpage = adfs_readpage,
|
||||
.writepage = adfs_writepage,
|
||||
.write_begin = adfs_write_begin,
|
||||
.write_end = generic_write_end,
|
||||
.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_I(inode)->filetype) {
|
||||
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 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;
|
||||
|
||||
if (S_ISDIR(inode->i_mode))
|
||||
attr = ADFS_NDA_DIRECTORY;
|
||||
else
|
||||
attr = 0;
|
||||
|
||||
mode = inode->i_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 &= ~asb->s_owner_mask;
|
||||
if (mode & S_IRUGO)
|
||||
attr |= ADFS_NDA_PUBLIC_READ;
|
||||
if (mode & S_IWUGO)
|
||||
attr |= ADFS_NDA_PUBLIC_WRITE;
|
||||
|
||||
return attr;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 timespec *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)
|
||||
*/
|
||||
static const s64 nsec_unix_epoch_diff_risc_os_epoch =
|
||||
2208988800000000000LL;
|
||||
s64 nsec;
|
||||
|
||||
if (ADFS_I(inode)->stamped == 0)
|
||||
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_timespec(nsec);
|
||||
return;
|
||||
|
||||
cur_time:
|
||||
*tv = CURRENT_TIME;
|
||||
return;
|
||||
|
||||
too_early:
|
||||
tv->tv_sec = tv->tv_nsec = 0;
|
||||
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)
|
||||
{
|
||||
unsigned int high, low;
|
||||
|
||||
if (ADFS_I(inode)->stamped) {
|
||||
/* convert 32-bit seconds to 40-bit centi-seconds */
|
||||
low = (secs & 255) * 100;
|
||||
high = (secs / 256) * 100 + (low >> 8) + 0x336e996a;
|
||||
|
||||
ADFS_I(inode)->loadaddr = (high >> 24) |
|
||||
(ADFS_I(inode)->loadaddr & ~0xff);
|
||||
ADFS_I(inode)->execaddr = (low & 255) | (high << 8);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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->file_id;
|
||||
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)->loadaddr = obj->loadaddr;
|
||||
ADFS_I(inode)->execaddr = obj->execaddr;
|
||||
ADFS_I(inode)->attr = obj->attr;
|
||||
ADFS_I(inode)->filetype = obj->filetype;
|
||||
ADFS_I(inode)->stamped = ((obj->loadaddr & 0xfff00000) == 0xfff00000);
|
||||
|
||||
inode->i_mode = adfs_atts2mode(sb, inode);
|
||||
adfs_adfs2unix_time(&inode->i_mtime, inode);
|
||||
inode->i_atime = inode->i_mtime;
|
||||
inode->i_ctime = inode->i_mtime;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
insert_inode_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_notify_change(struct dentry *dentry, struct iattr *attr)
|
||||
{
|
||||
struct inode *inode = dentry->d_inode;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
unsigned int ia_valid = attr->ia_valid;
|
||||
int error;
|
||||
|
||||
error = inode_change_ok(inode, 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) {
|
||||
inode->i_mtime = attr->ia_mtime;
|
||||
adfs_unix2adfs_time(inode, attr->ia_mtime.tv_sec);
|
||||
}
|
||||
/*
|
||||
* 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->i_atime = attr->ia_atime;
|
||||
if (ia_valid & ATTR_CTIME)
|
||||
inode->i_ctime = attr->ia_ctime;
|
||||
if (ia_valid & ATTR_MODE) {
|
||||
ADFS_I(inode)->attr = adfs_mode2atts(sb, inode);
|
||||
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_notify_change()
|
||||
*/
|
||||
int adfs_write_inode(struct inode *inode, struct writeback_control *wbc)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct object_info obj;
|
||||
int ret;
|
||||
|
||||
obj.file_id = inode->i_ino;
|
||||
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;
|
||||
|
||||
ret = adfs_dir_update(sb, &obj, wbc->sync_mode == WB_SYNC_ALL);
|
||||
return ret;
|
||||
}
|
290
fs/adfs/map.c
Normal file
290
fs/adfs/map.c
Normal file
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
* linux/fs/adfs/map.c
|
||||
*
|
||||
* Copyright (C) 1997-2002 Russell King
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/buffer_head.h>
|
||||
#include <asm/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 unsigned int frag_id, unsigned int *offset)
|
||||
{
|
||||
const unsigned int mapsize = dm->dm_endbit;
|
||||
const u32 idmask = (1 << idlen) - 1;
|
||||
unsigned char *map = dm->dm_bh->b_data + 4;
|
||||
unsigned int start = dm->dm_startbit;
|
||||
unsigned int mapptr;
|
||||
u32 frag;
|
||||
|
||||
do {
|
||||
frag = GET_FRAG_ID(map, start, idmask);
|
||||
mapptr = start + idlen;
|
||||
|
||||
/*
|
||||
* find end of fragment
|
||||
*/
|
||||
{
|
||||
__le32 *_map = (__le32 *)map;
|
||||
u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31);
|
||||
while (v == 0) {
|
||||
mapptr = (mapptr & ~31) + 32;
|
||||
if (mapptr >= mapsize)
|
||||
goto error;
|
||||
v = le32_to_cpu(_map[mapptr >> 5]);
|
||||
}
|
||||
|
||||
mapptr += 1 + ffz(~v);
|
||||
}
|
||||
|
||||
if (frag == frag_id)
|
||||
goto found;
|
||||
again:
|
||||
start = mapptr;
|
||||
} while (mapptr < mapsize);
|
||||
return -1;
|
||||
|
||||
error:
|
||||
printk(KERN_ERR "adfs: oversized fragment 0x%x at 0x%x-0x%x\n",
|
||||
frag, start, mapptr);
|
||||
return -1;
|
||||
|
||||
found:
|
||||
{
|
||||
int length = mapptr - start;
|
||||
if (*offset >= length) {
|
||||
*offset -= length;
|
||||
goto again;
|
||||
}
|
||||
}
|
||||
return start + *offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 mapsize = dm->dm_endbit + 32;
|
||||
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;
|
||||
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;
|
||||
|
||||
/*
|
||||
* get fragment id
|
||||
*/
|
||||
frag = GET_FRAG_ID(map, start, idmask);
|
||||
mapptr = start + idlen;
|
||||
|
||||
/*
|
||||
* find end of fragment
|
||||
*/
|
||||
{
|
||||
__le32 *_map = (__le32 *)map;
|
||||
u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31);
|
||||
while (v == 0) {
|
||||
mapptr = (mapptr & ~31) + 32;
|
||||
if (mapptr >= mapsize)
|
||||
goto error;
|
||||
v = le32_to_cpu(_map[mapptr >> 5]);
|
||||
}
|
||||
|
||||
mapptr += 1 + ffz(~v);
|
||||
}
|
||||
|
||||
total += mapptr - start;
|
||||
} 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 unsigned int 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
|
||||
*/
|
||||
unsigned int
|
||||
adfs_map_free(struct super_block *sb)
|
||||
{
|
||||
struct adfs_sb_info *asb = ADFS_SB(sb);
|
||||
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);
|
||||
|
||||
return signed_asl(total, asb->s_map2blk);
|
||||
}
|
||||
|
||||
int
|
||||
adfs_map_lookup(struct super_block *sb, unsigned int 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;
|
||||
}
|
552
fs/adfs/super.c
Normal file
552
fs/adfs/super.c
Normal file
|
@ -0,0 +1,552 @@
|
|||
/*
|
||||
* linux/fs/adfs/super.c
|
||||
*
|
||||
* Copyright (C) 1997-1999 Russell King
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/buffer_head.h>
|
||||
#include <linux/parser.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/statfs.h>
|
||||
#include <linux/user_namespace.h>
|
||||
#include "adfs.h"
|
||||
#include "dir_f.h"
|
||||
#include "dir_fplus.h"
|
||||
|
||||
#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, ...)
|
||||
{
|
||||
char error_buf[128];
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
vsnprintf(error_buf, sizeof(error_buf), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
printk(KERN_CRIT "ADFS-fs error (device %s)%s%s: %s\n",
|
||||
sb->s_id, function ? ": " : "",
|
||||
function ? function : "", error_buf);
|
||||
}
|
||||
|
||||
static int adfs_checkdiscrecord(struct adfs_discrecord *dr)
|
||||
{
|
||||
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;
|
||||
|
||||
/* idlen must be no greater than 19 v2 [1.0] */
|
||||
if (dr->idlen > 19)
|
||||
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 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);
|
||||
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, Opt_err};
|
||||
|
||||
static const match_table_t tokens = {
|
||||
{Opt_uid, "uid=%u"},
|
||||
{Opt_gid, "gid=%u"},
|
||||
{Opt_ownmask, "ownmask=%o"},
|
||||
{Opt_othmask, "othmask=%o"},
|
||||
{Opt_ftsuffix, "ftsuffix=%u"},
|
||||
{Opt_err, NULL}
|
||||
};
|
||||
|
||||
static int parse_options(struct super_block *sb, char *options)
|
||||
{
|
||||
char *p;
|
||||
struct adfs_sb_info *asb = ADFS_SB(sb);
|
||||
int option;
|
||||
|
||||
if (!options)
|
||||
return 0;
|
||||
|
||||
while ((p = strsep(&options, ",")) != NULL) {
|
||||
substring_t args[MAX_OPT_ARGS];
|
||||
int token;
|
||||
if (!*p)
|
||||
continue;
|
||||
|
||||
token = match_token(p, tokens, args);
|
||||
switch (token) {
|
||||
case Opt_uid:
|
||||
if (match_int(args, &option))
|
||||
return -EINVAL;
|
||||
asb->s_uid = make_kuid(current_user_ns(), option);
|
||||
if (!uid_valid(asb->s_uid))
|
||||
return -EINVAL;
|
||||
break;
|
||||
case Opt_gid:
|
||||
if (match_int(args, &option))
|
||||
return -EINVAL;
|
||||
asb->s_gid = make_kgid(current_user_ns(), option);
|
||||
if (!gid_valid(asb->s_gid))
|
||||
return -EINVAL;
|
||||
break;
|
||||
case Opt_ownmask:
|
||||
if (match_octal(args, &option))
|
||||
return -EINVAL;
|
||||
asb->s_owner_mask = option;
|
||||
break;
|
||||
case Opt_othmask:
|
||||
if (match_octal(args, &option))
|
||||
return -EINVAL;
|
||||
asb->s_other_mask = option;
|
||||
break;
|
||||
case Opt_ftsuffix:
|
||||
if (match_int(args, &option))
|
||||
return -EINVAL;
|
||||
asb->s_ftsuffix = option;
|
||||
break;
|
||||
default:
|
||||
printk("ADFS-fs: unrecognised mount option \"%s\" "
|
||||
"or missing value\n", p);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adfs_remount(struct super_block *sb, int *flags, char *data)
|
||||
{
|
||||
sync_filesystem(sb);
|
||||
*flags |= MS_NODIRATIME;
|
||||
return parse_options(sb, data);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
buf->f_type = ADFS_SUPER_MAGIC;
|
||||
buf->f_namelen = sbi->s_namelen;
|
||||
buf->f_bsize = sb->s_blocksize;
|
||||
buf->f_blocks = sbi->s_size;
|
||||
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);
|
||||
|
||||
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 = (struct adfs_inode_info *)kmem_cache_alloc(adfs_inode_cachep, GFP_KERNEL);
|
||||
if (!ei)
|
||||
return NULL;
|
||||
return &ei->vfs_inode;
|
||||
}
|
||||
|
||||
static void adfs_i_callback(struct rcu_head *head)
|
||||
{
|
||||
struct inode *inode = container_of(head, struct inode, i_rcu);
|
||||
kmem_cache_free(adfs_inode_cachep, ADFS_I(inode));
|
||||
}
|
||||
|
||||
static void adfs_destroy_inode(struct inode *inode)
|
||||
{
|
||||
call_rcu(&inode->i_rcu, adfs_i_callback);
|
||||
}
|
||||
|
||||
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_MEM_SPREAD),
|
||||
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,
|
||||
.destroy_inode = adfs_destroy_inode,
|
||||
.write_inode = adfs_write_inode,
|
||||
.put_super = adfs_put_super,
|
||||
.statfs = adfs_statfs,
|
||||
.remount_fs = adfs_remount,
|
||||
.show_options = adfs_show_options,
|
||||
};
|
||||
|
||||
static struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecord *dr)
|
||||
{
|
||||
struct adfs_discmap *dm;
|
||||
unsigned int map_addr, zone_size, nzones;
|
||||
int i, zone;
|
||||
struct adfs_sb_info *asb = ADFS_SB(sb);
|
||||
|
||||
nzones = asb->s_map_size;
|
||||
zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare);
|
||||
map_addr = (nzones >> 1) * zone_size -
|
||||
((nzones > 1) ? ADFS_DR_SIZE_BITS : 0);
|
||||
map_addr = signed_asl(map_addr, asb->s_map2blk);
|
||||
|
||||
asb->s_ids_per_zone = zone_size / (asb->s_idlen + 1);
|
||||
|
||||
dm = kmalloc(nzones * sizeof(*dm), GFP_KERNEL);
|
||||
if (dm == NULL) {
|
||||
adfs_error(sb, "not enough memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (zone = 0; zone < nzones; zone++, map_addr++) {
|
||||
dm[zone].dm_startbit = 0;
|
||||
dm[zone].dm_endbit = zone_size;
|
||||
dm[zone].dm_startblk = zone * zone_size - ADFS_DR_SIZE_BITS;
|
||||
dm[zone].dm_bh = sb_bread(sb, map_addr);
|
||||
|
||||
if (!dm[zone].dm_bh) {
|
||||
adfs_error(sb, "unable to read map");
|
||||
goto error_free;
|
||||
}
|
||||
}
|
||||
|
||||
/* adjust the limits for the first and last map zones */
|
||||
i = zone - 1;
|
||||
dm[0].dm_startblk = 0;
|
||||
dm[0].dm_startbit = ADFS_DR_SIZE_BITS;
|
||||
dm[i].dm_endbit = (le32_to_cpu(dr->disc_size_high) << (32 - dr->log2bpmb)) +
|
||||
(le32_to_cpu(dr->disc_size) >> dr->log2bpmb) +
|
||||
(ADFS_DR_SIZE_BITS - i * zone_size);
|
||||
|
||||
if (adfs_checkmap(sb, dm))
|
||||
return dm;
|
||||
|
||||
adfs_error(sb, "map corrupted");
|
||||
|
||||
error_free:
|
||||
while (--zone >= 0)
|
||||
brelse(dm[zone].dm_bh);
|
||||
|
||||
kfree(dm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline unsigned long adfs_discsize(struct adfs_discrecord *dr, int block_bits)
|
||||
{
|
||||
unsigned long discsize;
|
||||
|
||||
discsize = le32_to_cpu(dr->disc_size_high) << (32 - block_bits);
|
||||
discsize |= le32_to_cpu(dr->disc_size) >> block_bits;
|
||||
|
||||
return discsize;
|
||||
}
|
||||
|
||||
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;
|
||||
struct adfs_sb_info *asb;
|
||||
struct inode *root;
|
||||
|
||||
sb->s_flags |= MS_NODIRATIME;
|
||||
|
||||
asb = kzalloc(sizeof(*asb), GFP_KERNEL);
|
||||
if (!asb)
|
||||
return -ENOMEM;
|
||||
sb->s_fs_info = asb;
|
||||
|
||||
/* 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;
|
||||
|
||||
if (parse_options(sb, data))
|
||||
goto error;
|
||||
|
||||
sb_set_blocksize(sb, BLOCK_SIZE);
|
||||
if (!(bh = sb_bread(sb, ADFS_DISCRECORD / BLOCK_SIZE))) {
|
||||
adfs_error(sb, "unable to read superblock");
|
||||
goto error;
|
||||
}
|
||||
|
||||
b_data = bh->b_data + (ADFS_DISCRECORD % BLOCK_SIZE);
|
||||
|
||||
if (adfs_checkbblk(b_data)) {
|
||||
if (!silent)
|
||||
printk("VFS: Can't find an adfs filesystem on dev "
|
||||
"%s.\n", sb->s_id);
|
||||
goto error_free_bh;
|
||||
}
|
||||
|
||||
dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET);
|
||||
|
||||
/*
|
||||
* Do some sanity checks on the ADFS disc record
|
||||
*/
|
||||
if (adfs_checkdiscrecord(dr)) {
|
||||
if (!silent)
|
||||
printk("VPS: Can't find an adfs filesystem on dev "
|
||||
"%s.\n", sb->s_id);
|
||||
goto error_free_bh;
|
||||
}
|
||||
|
||||
brelse(bh);
|
||||
if (sb_set_blocksize(sb, 1 << dr->log2secsize)) {
|
||||
bh = sb_bread(sb, ADFS_DISCRECORD / sb->s_blocksize);
|
||||
if (!bh) {
|
||||
adfs_error(sb, "couldn't read superblock on "
|
||||
"2nd try.");
|
||||
goto error;
|
||||
}
|
||||
b_data = bh->b_data + (ADFS_DISCRECORD % sb->s_blocksize);
|
||||
if (adfs_checkbblk(b_data)) {
|
||||
adfs_error(sb, "disc record mismatch, very weird!");
|
||||
goto error_free_bh;
|
||||
}
|
||||
dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET);
|
||||
} else {
|
||||
if (!silent)
|
||||
printk(KERN_ERR "VFS: Unsupported blocksize on dev "
|
||||
"%s.\n", sb->s_id);
|
||||
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_size = adfs_discsize(dr, sb->s_blocksize_bits);
|
||||
asb->s_version = dr->format_version;
|
||||
asb->s_log2sharesize = dr->log2sharesize;
|
||||
|
||||
asb->s_map = adfs_read_map(sb, dr);
|
||||
if (!asb->s_map)
|
||||
goto error_free_bh;
|
||||
|
||||
brelse(bh);
|
||||
|
||||
/*
|
||||
* set up enough so that we can read an inode
|
||||
*/
|
||||
sb->s_op = &adfs_sops;
|
||||
|
||||
dr = (struct adfs_discrecord *)(asb->s_map[0].dm_bh->b_data + 4);
|
||||
|
||||
root_obj.parent_id = root_obj.file_id = 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;
|
||||
root_obj.filetype = -1;
|
||||
|
||||
/*
|
||||
* If this is a F+ disk with variable length directories,
|
||||
* get the root_size from the disc record.
|
||||
*/
|
||||
if (asb->s_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;
|
||||
|
||||
sb->s_d_op = &adfs_dentry_operations;
|
||||
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_error(sb, "get root inode failed\n");
|
||||
goto error;
|
||||
}
|
||||
return 0;
|
||||
|
||||
error_free_bh:
|
||||
brelse(bh);
|
||||
error:
|
||||
sb->s_fs_info = NULL;
|
||||
kfree(asb);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static struct dentry *adfs_mount(struct file_system_type *fs_type,
|
||||
int flags, const char *dev_name, void *data)
|
||||
{
|
||||
return mount_bdev(fs_type, flags, dev_name, data, adfs_fill_super);
|
||||
}
|
||||
|
||||
static struct file_system_type adfs_fs_type = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "adfs",
|
||||
.mount = adfs_mount,
|
||||
.kill_sb = kill_block_super,
|
||||
.fs_flags = FS_REQUIRES_DEV,
|
||||
};
|
||||
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_LICENSE("GPL");
|
Loading…
Add table
Add a link
Reference in a new issue