diff options
Diffstat (limited to 'fs/adfs/dir.c')
-rw-r--r-- | fs/adfs/dir.c | 302 |
1 files changed, 302 insertions, 0 deletions
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, +}; |