| /* |
| * 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; |
| const 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 |
| const 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_compare(const unsigned char *qstr, u32 qlen, |
| const char *str, u32 len) |
| { |
| u32 i; |
| |
| if (qlen != len) |
| return 1; |
| |
| for (i = 0; i < qlen; i++) { |
| unsigned char qc, c; |
| |
| qc = qstr[i]; |
| c = str[i]; |
| |
| if (qc >= 'A' && qc <= 'Z') |
| qc += 'a' - 'A'; |
| if (c >= 'A' && c <= 'Z') |
| c += 'a' - 'A'; |
| |
| if (qc != c) |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr, |
| struct object_info *obj) |
| { |
| struct super_block *sb = inode->i_sb; |
| const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; |
| const unsigned char *name; |
| struct adfs_dir dir; |
| u32 name_len; |
| int ret; |
| |
| 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; |
| |
| read_lock(&adfs_dir_lock); |
| |
| ret = ops->setpos(&dir, 0); |
| if (ret) |
| goto unlock_out; |
| |
| ret = -ENOENT; |
| name = qstr->name; |
| name_len = qstr->len; |
| while (ops->getnext(&dir, obj) == 0) { |
| if (!__adfs_compare(name, name_len, obj->name, obj->name_len)) { |
| ret = 0; |
| break; |
| } |
| } |
| |
| 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(parent); |
| 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 *dentry, unsigned int len, |
| const char *str, const struct qstr *qstr) |
| { |
| return __adfs_compare(qstr->name, qstr->len, str, len); |
| } |
| |
| const struct dentry_operations adfs_dentry_operations = { |
| .d_hash = adfs_hash, |
| .d_compare = adfs_compare, |
| }; |
| |
| static struct dentry * |
| adfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) |
| { |
| struct inode *inode = NULL; |
| struct object_info obj; |
| int error; |
| |
| error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj); |
| if (error == 0) { |
| /* |
| * This only returns NULL if get_empty_inode |
| * fails. |
| */ |
| inode = adfs_iget(dir->i_sb, &obj); |
| if (!inode) |
| inode = ERR_PTR(-EACCES); |
| } else if (error != -ENOENT) { |
| inode = ERR_PTR(error); |
| } |
| return d_splice_alias(inode, dentry); |
| } |
| |
| /* |
| * directories can handle most operations... |
| */ |
| const struct inode_operations adfs_dir_inode_operations = { |
| .lookup = adfs_lookup, |
| .setattr = adfs_notify_change, |
| }; |