blob: bebe2ab86aae2f808e068d2453f8d6efb482311f [file] [log] [blame]
/*
* 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,
};