diff options
Diffstat (limited to 'fs/adfs')
-rw-r--r-- | fs/adfs/Makefile | 7 | ||||
-rw-r--r-- | fs/adfs/adfs.h | 127 | ||||
-rw-r--r-- | fs/adfs/dir.c | 302 | ||||
-rw-r--r-- | fs/adfs/dir_f.c | 460 | ||||
-rw-r--r-- | fs/adfs/dir_f.h | 65 | ||||
-rw-r--r-- | fs/adfs/dir_fplus.c | 179 | ||||
-rw-r--r-- | fs/adfs/dir_fplus.h | 45 | ||||
-rw-r--r-- | fs/adfs/file.c | 43 | ||||
-rw-r--r-- | fs/adfs/inode.c | 395 | ||||
-rw-r--r-- | fs/adfs/map.c | 296 | ||||
-rw-r--r-- | fs/adfs/super.c | 508 |
11 files changed, 2427 insertions, 0 deletions
diff --git a/fs/adfs/Makefile b/fs/adfs/Makefile new file mode 100644 index 0000000..9b2d71a --- /dev/null +++ b/fs/adfs/Makefile @@ -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 diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h new file mode 100644 index 0000000..63f5df9 --- /dev/null +++ b/fs/adfs/adfs.h @@ -0,0 +1,127 @@ +/* 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 <linux/version.h> +#include "dir_f.h" + +struct buffer_head; + +/* + * Directory handling + */ +struct adfs_dir { + struct super_block *sb; + + int nr_buffers; + struct buffer_head *bh[4]; + 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 +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 char name_len; /* name length */ + char name[ADFS_MAX_NAME_LEN];/* file name */ +}; + +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); + 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,int unused); +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 */ +void __adfs_error(struct super_block *sb, const char *function, + const char *fmt, ...); +#define adfs_error(sb, fmt...) __adfs_error(sb, __FUNCTION__, fmt) + +/* super.c */ + +/* + * Inodes and file operations + */ + +/* dir_*.c */ +extern struct inode_operations adfs_dir_inode_operations; +extern struct file_operations adfs_dir_operations; +extern 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); + +/* file.c */ +extern struct inode_operations adfs_file_inode_operations; +extern struct file_operations adfs_file_operations; + +extern 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] + */ +extern 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); +} diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c new file mode 100644 index 0000000..0b4c3a0 --- /dev/null +++ b/fs/adfs/dir.c @@ -0,0 +1,302 @@ +/* + * 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 <linux/config.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/adfs_fs.h> +#include <linux/time.h> +#include <linux/stat.h> +#include <linux/spinlock.h> +#include <linux/smp_lock.h> +#include <linux/buffer_head.h> /* for file_fsync() */ + +#include "adfs.h" + +/* + * For future. This should probably be per-directory. + */ +static DEFINE_RWLOCK(adfs_dir_lock); + +static int +adfs_readdir(struct file *filp, void *dirent, filldir_t filldir) +{ + struct inode *inode = filp->f_dentry->d_inode; + 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; + + lock_kernel(); + + if (filp->f_pos >> 32) + goto out; + + ret = ops->read(sb, inode->i_ino, inode->i_size, &dir); + if (ret) + goto out; + + switch ((unsigned long)filp->f_pos) { + case 0: + if (filldir(dirent, ".", 1, 0, inode->i_ino, DT_DIR) < 0) + goto free_out; + filp->f_pos += 1; + + case 1: + if (filldir(dirent, "..", 2, 1, dir.parent_id, DT_DIR) < 0) + goto free_out; + filp->f_pos += 1; + + default: + break; + } + + read_lock(&adfs_dir_lock); + + ret = ops->setpos(&dir, filp->f_pos - 2); + if (ret) + goto unlock_out; + while (ops->getnext(&dir, &obj) == 0) { + if (filldir(dirent, obj.name, obj.name_len, + filp->f_pos, obj.file_id, DT_UNKNOWN) < 0) + goto unlock_out; + filp->f_pos += 1; + } + +unlock_out: + read_unlock(&adfs_dir_lock); + +free_out: + ops->free(&dir); + +out: + unlock_kernel(); + return ret; +} + +int +adfs_dir_update(struct super_block *sb, struct object_info *obj) +{ + 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); + + 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 %lx)\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; +} + +struct file_operations adfs_dir_operations = { + .read = generic_read_dir, + .readdir = adfs_readdir, + .fsync = file_fsync, +}; + +static int +adfs_hash(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(struct dentry *parent, struct qstr *entry, struct qstr *name) +{ + int i; + + if (entry->len != name->len) + return 1; + + for (i = 0; i < name->len; i++) { + char a, b; + + a = entry->name[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; +} + +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, struct nameidata *nd) +{ + struct inode *inode = NULL; + struct object_info obj; + int error; + + dentry->d_op = &adfs_dentry_operations; + lock_kernel(); + 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; + } + unlock_kernel(); + d_add(dentry, inode); + return ERR_PTR(error); +} + +/* + * directories can handle most operations... + */ +struct inode_operations adfs_dir_inode_operations = { + .lookup = adfs_lookup, + .setattr = adfs_notify_change, +}; diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c new file mode 100644 index 0000000..bbfc862 --- /dev/null +++ b/fs/adfs/dir_f.c @@ -0,0 +1,460 @@ +/* + * 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/errno.h> +#include <linux/fs.h> +#include <linux/adfs_fs.h> +#include <linux/time.h> +#include <linux/stat.h> +#include <linux/spinlock.h> +#include <linux/buffer_head.h> +#include <linux/string.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 (*ptr >= ' ' && maxlen--) { + if (*ptr == '/') + *buf++ = '.'; + else + *buf++ = *ptr; + ptr++; + } + *buf = '\0'; + + 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 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; +} + +/* + * 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(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 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, + .free = adfs_f_free +}; diff --git a/fs/adfs/dir_f.h b/fs/adfs/dir_f.h new file mode 100644 index 0000000..e471340 --- /dev/null +++ b/fs/adfs/dir_f.h @@ -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 diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c new file mode 100644 index 0000000..1ec644e --- /dev/null +++ b/fs/adfs/dir_fplus.c @@ -0,0 +1,179 @@ +/* + * 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/errno.h> +#include <linux/fs.h> +#include <linux/adfs_fs.h> +#include <linux/time.h> +#include <linux/stat.h> +#include <linux/spinlock.h> +#include <linux/buffer_head.h> +#include <linux/string.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; + + 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[0] = sb_bread(sb, block); + if (!dir->bh[0]) + goto out; + dir->nr_buffers += 1; + + h = (struct adfs_bigdirheader *)dir->bh[0]->b_data; + size = le32_to_cpu(h->bigdirsize); + if (size != sz) { + printk(KERN_WARNING "adfs: adfs_fplus_read: directory header size\n" + " does not match directory size\n"); + } + + if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || + h->bigdirversion[2] != 0 || size & 2047 || + h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) + goto out; + + size >>= sb->s_blocksize_bits; + 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[blk] = sb_bread(sb, block); + if (!dir->bh[blk]) + goto out; + dir->nr_buffers = blk; + } + + t = (struct adfs_bigdirtail *)(dir->bh[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) + goto out; + + dir->parent_id = le32_to_cpu(h->bigdirparent); + dir->sb = sb; + return 0; +out: + for (i = 0; i < dir->nr_buffers; i++) + brelse(dir->bh[i]); + 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[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[buffer]->b_data + offset, len); + else { + char *c = (char *)to; + + remainder = len - partial; + + memcpy(c, dir->bh[buffer]->b_data + offset, partial); + memcpy(c + partial, dir->bh[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[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] = '.'; + + dir->pos += 1; + ret = 0; +out: + return ret; +} + +static void +adfs_fplus_free(struct adfs_dir *dir) +{ + int i; + + for (i = 0; i < dir->nr_buffers; i++) + brelse(dir->bh[i]); + dir->sb = NULL; +} + +struct adfs_dir_ops adfs_fplus_dir_ops = { + .read = adfs_fplus_read, + .setpos = adfs_fplus_setpos, + .getnext = adfs_fplus_getnext, + .free = adfs_fplus_free +}; diff --git a/fs/adfs/dir_fplus.h b/fs/adfs/dir_fplus.h new file mode 100644 index 0000000..b55aa41 --- /dev/null +++ b/fs/adfs/dir_fplus.h @@ -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; +}; diff --git a/fs/adfs/file.c b/fs/adfs/file.c new file mode 100644 index 0000000..afebbfd --- /dev/null +++ b/fs/adfs/file.c @@ -0,0 +1,43 @@ +/* + * 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 <linux/errno.h> +#include <linux/fs.h> +#include <linux/fcntl.h> +#include <linux/time.h> +#include <linux/stat.h> +#include <linux/buffer_head.h> /* for file_fsync() */ +#include <linux/adfs_fs.h> + +#include "adfs.h" + +struct file_operations adfs_file_operations = { + .llseek = generic_file_llseek, + .read = generic_file_read, + .mmap = generic_file_mmap, + .fsync = file_fsync, + .write = generic_file_write, + .sendfile = generic_file_sendfile, +}; + +struct inode_operations adfs_file_inode_operations = { + .setattr = adfs_notify_change, +}; diff --git a/fs/adfs/inode.c b/fs/adfs/inode.c new file mode 100644 index 0000000..a02802a --- /dev/null +++ b/fs/adfs/inode.c @@ -0,0 +1,395 @@ +/* + * 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/errno.h> +#include <linux/fs.h> +#include <linux/adfs_fs.h> +#include <linux/time.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/smp_lock.h> +#include <linux/module.h> +#include <linux/buffer_head.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 (block < 0) + goto abort_negative; + + 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_negative: + adfs_error(inode->i_sb, "block %d < 0", block); + 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 int adfs_prepare_write(struct file *file, struct page *page, unsigned int from, unsigned int to) +{ + return cont_prepare_write(page, from, to, adfs_get_block, + &ADFS_I(page->mapping->host)->mmu_private); +} + +static sector_t _adfs_bmap(struct address_space *mapping, sector_t block) +{ + return generic_block_bmap(mapping, block, adfs_get_block); +} + +static struct address_space_operations adfs_aops = { + .readpage = adfs_readpage, + .writepage = adfs_writepage, + .sync_page = block_sync_page, + .prepare_write = adfs_prepare_write, + .commit_write = generic_commit_write, + .bmap = _adfs_bmap +}; + +static inline unsigned int +adfs_filetype(struct inode *inode) +{ + unsigned int type; + + if (ADFS_I(inode)->stamped) + type = (ADFS_I(inode)->loadaddr >> 8) & 0xfff; + else + type = (unsigned int) -1; + + return type; +} + +/* + * Convert ADFS attributes and filetype to Linux permission. + */ +static umode_t +adfs_atts2mode(struct super_block *sb, struct inode *inode) +{ + unsigned int filetype, 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; + } + + filetype = adfs_filetype(inode); + + switch (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) + */ +static void +adfs_adfs2unix_time(struct timespec *tv, struct inode *inode) +{ + unsigned int high, low; + + if (ADFS_I(inode)->stamped == 0) + goto cur_time; + + high = ADFS_I(inode)->loadaddr << 24; + low = ADFS_I(inode)->execaddr; + + high |= low >> 8; + low &= 255; + + /* Files dated pre 01 Jan 1970 00:00:00. */ + if (high < 0x336e996a) + goto too_early; + + /* Files dated post 18 Jan 2038 03:14:05. */ + if (high >= 0x656e9969) + goto too_late; + + /* discard 2208988800 (0x336e996a00) seconds of time */ + high -= 0x336e996a; + + /* convert 40-bit centi-seconds to 32-bit seconds */ + tv->tv_sec = (((high % 100) << 8) + low) / 100 + (high / 100 << 8); + tv->tv_nsec = 0; + return; + + cur_time: + *tv = CURRENT_TIME_SEC; + return; + + too_early: + tv->tv_sec = tv->tv_nsec = 0; + return; + + too_late: + tv->tv_sec = 0x7ffffffd; + 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; + inode->i_nlink = 2; + inode->i_blksize = PAGE_SIZE; + 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)->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; + + lock_kernel(); + + 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 && attr->ia_uid != ADFS_SB(sb)->s_uid) || + (ia_valid & ATTR_GID && attr->ia_gid != ADFS_SB(sb)->s_gid)) + error = -EPERM; + + if (error) + goto out; + + if (ia_valid & ATTR_SIZE) + error = vmtruncate(inode, attr->ia_size); + + if (error) + goto out; + + 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: + unlock_kernel(); + 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, int unused) +{ + struct super_block *sb = inode->i_sb; + struct object_info obj; + int ret; + + lock_kernel(); + 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); + unlock_kernel(); + return ret; +} +MODULE_LICENSE("GPL"); diff --git a/fs/adfs/map.c b/fs/adfs/map.c new file mode 100644 index 0000000..92ab4fb --- /dev/null +++ b/fs/adfs/map.c @@ -0,0 +1,296 @@ +/* + * 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/errno.h> +#include <linux/fs.h> +#include <linux/adfs_fs.h> +#include <linux/spinlock.h> +#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 + * arbitary 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((u32 *)_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; +} diff --git a/fs/adfs/super.c b/fs/adfs/super.c new file mode 100644 index 0000000..2439632 --- /dev/null +++ b/fs/adfs/super.c @@ -0,0 +1,508 @@ +/* + * 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/errno.h> +#include <linux/fs.h> +#include <linux/adfs_fs.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/buffer_head.h> +#include <linux/vfs.h> +#include <linux/parser.h> +#include <linux/bitops.h> + +#include <asm/uaccess.h> +#include <asm/system.h> + +#include <stdarg.h> + +#include "adfs.h" +#include "dir_f.h" +#include "dir_fplus.h" + +void __adfs_error(struct super_block *sb, const char *function, const char *fmt, ...) +{ + char error_buf[128]; + va_list args; + + va_start(args, fmt); + vsprintf(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(asb); + sb->s_fs_info = NULL; +} + +enum {Opt_uid, Opt_gid, Opt_ownmask, Opt_othmask, Opt_err}; + +static match_table_t tokens = { + {Opt_uid, "uid=%u"}, + {Opt_gid, "gid=%u"}, + {Opt_ownmask, "ownmask=%o"}, + {Opt_othmask, "othmask=%o"}, + {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 = option; + break; + case Opt_gid: + if (match_int(args, &option)) + return -EINVAL; + asb->s_gid = option; + 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; + 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) +{ + *flags |= MS_NODIRATIME; + return parse_options(sb, data); +} + +static int adfs_statfs(struct super_block *sb, struct kstatfs *buf) +{ + struct adfs_sb_info *asb = ADFS_SB(sb); + + buf->f_type = ADFS_SUPER_MAGIC; + buf->f_namelen = asb->s_namelen; + buf->f_bsize = sb->s_blocksize; + buf->f_blocks = asb->s_size; + buf->f_files = asb->s_ids_per_zone * asb->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; + + return 0; +} + +static kmem_cache_t *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, SLAB_KERNEL); + if (!ei) + return NULL; + return &ei->vfs_inode; +} + +static void adfs_destroy_inode(struct inode *inode) +{ + kmem_cache_free(adfs_inode_cachep, ADFS_I(inode)); +} + +static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags) +{ + struct adfs_inode_info *ei = (struct adfs_inode_info *) foo; + + if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) == + SLAB_CTOR_CONSTRUCTOR) + inode_init_once(&ei->vfs_inode); +} + +static int init_inodecache(void) +{ + adfs_inode_cachep = kmem_cache_create("adfs_inode_cache", + sizeof(struct adfs_inode_info), + 0, SLAB_RECLAIM_ACCOUNT, + init_once, NULL); + if (adfs_inode_cachep == NULL) + return -ENOMEM; + return 0; +} + +static void destroy_inodecache(void) +{ + if (kmem_cache_destroy(adfs_inode_cachep)) + printk(KERN_INFO "adfs_inode_cache: not all structures were freed\n"); +} + +static 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, +}; + +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, NULL, "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 = kmalloc(sizeof(*asb), GFP_KERNEL); + if (!asb) + return -ENOMEM; + sb->s_fs_info = asb; + memset(asb, 0, sizeof(*asb)); + + /* set default options */ + asb->s_uid = 0; + asb->s_gid = 0; + asb->s_owner_mask = S_IRWXU; + asb->s_other_mask = S_IRWXG | S_IRWXO; + + 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; + root_obj.loadaddr = 0; + root_obj.execaddr = 0; + 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 (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; + } + + root = adfs_iget(sb, &root_obj); + sb->s_root = d_alloc_root(root); + if (!sb->s_root) { + int i; + iput(root); + 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; + } else + sb->s_root->d_op = &adfs_dentry_operations; + return 0; + +error_free_bh: + brelse(bh); +error: + sb->s_fs_info = NULL; + kfree(asb); + return -EINVAL; +} + +static struct super_block *adfs_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return get_sb_bdev(fs_type, flags, dev_name, data, adfs_fill_super); +} + +static struct file_system_type adfs_fs_type = { + .owner = THIS_MODULE, + .name = "adfs", + .get_sb = adfs_get_sb, + .kill_sb = kill_block_super, + .fs_flags = FS_REQUIRES_DEV, +}; + +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) |