summaryrefslogtreecommitdiffstats
path: root/fs/affs/namei.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/affs/namei.c')
-rw-r--r--fs/affs/namei.c292
1 files changed, 169 insertions, 123 deletions
diff --git a/fs/affs/namei.c b/fs/affs/namei.c
index 2ec0401d5..96d8c6f5a 100644
--- a/fs/affs/namei.c
+++ b/fs/affs/namei.c
@@ -23,15 +23,15 @@
/* Simple toupper() for DOS\1 */
-static inline unsigned int
+static unsigned int
affs_toupper(unsigned int ch)
{
return ch >= 'a' && ch <= 'z' ? ch -= ('a' - 'A') : ch;
}
-/* International toupper() for DOS\3 */
+/* International toupper() for DOS\3 ("international") */
-static inline unsigned int
+static unsigned int
affs_intl_toupper(unsigned int ch)
{
return (ch >= 'a' && ch <= 'z') || (ch >= 0xE0
@@ -39,6 +39,75 @@ affs_intl_toupper(unsigned int ch)
ch - ('a' - 'A') : ch;
}
+static int affs_hash_dentry(struct dentry *, struct qstr *);
+static int affs_compare_dentry(struct dentry *, struct qstr *, struct qstr *);
+struct dentry_operations affs_dentry_operations = {
+ NULL, /* d_validate */
+ affs_hash_dentry, /* d_hash */
+ affs_compare_dentry, /* d_compare */
+ NULL /* d_delete */
+};
+
+/*
+ * Note: the dentry argument is the parent dentry.
+ */
+static int
+affs_hash_dentry(struct dentry *dentry, struct qstr *qstr)
+{
+ unsigned int (*toupper)(unsigned int) = affs_toupper;
+ unsigned long hash;
+ int i;
+
+ if ((i = affs_check_name(qstr->name,qstr->len)))
+ return i;
+
+ /* Check whether to use the international 'toupper' routine */
+ if (AFFS_I2FSTYPE(dentry->d_inode))
+ toupper = affs_intl_toupper;
+ hash = init_name_hash();
+ for (i = 0; i < qstr->len && i < 30; i++)
+ hash = partial_name_hash(toupper(qstr->name[i]), hash);
+ qstr->hash = end_name_hash(hash);
+
+ return 0;
+}
+
+static int
+affs_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b)
+{
+ unsigned int (*toupper)(unsigned int) = affs_toupper;
+ int alen = a->len;
+ int blen = b->len;
+ int i;
+
+ /* 'a' is the qstr of an already existing dentry, so the name
+ * must be valid. 'b' must be validated first.
+ */
+
+ if (affs_check_name(b->name,b->len))
+ return 1;
+
+ /* If the names are longer than the allowed 30 chars,
+ * the excess is ignored, so their length may differ.
+ */
+ if (alen > 30)
+ alen = 30;
+ if (blen > 30)
+ blen = 30;
+ if (alen != blen)
+ return 1;
+
+ /* Check whether to use the international 'toupper' routine */
+ if (AFFS_I2FSTYPE(dentry->d_inode))
+ toupper = affs_intl_toupper;
+
+ for (i = 0; i < alen; i++)
+ if (toupper(a->name[i]) != toupper(b->name[i]))
+ return 1;
+
+ return 0;
+}
+
/*
* NOTE! unlike strncmp, affs_match returns 1 for success, 0 for failure.
*/
@@ -46,6 +115,9 @@ affs_intl_toupper(unsigned int ch)
static int
affs_match(const unsigned char *name, int len, const unsigned char *compare, int dlen, int intl)
{
+ unsigned int (*toupper)(unsigned int) = intl ? affs_intl_toupper : affs_toupper;
+ int i;
+
if (!compare)
return 0;
@@ -59,21 +131,9 @@ affs_match(const unsigned char *name, int len, const unsigned char *compare, int
return 1;
if (dlen != len)
return 0;
- if (intl) {
- while (dlen--) {
- if (affs_intl_toupper(*name) != affs_intl_toupper(*compare))
- return 0;
- name++;
- compare++;
- }
- } else {
- while (dlen--) {
- if (affs_toupper(*name) != affs_toupper(*compare))
- return 0;
- name++;
- compare++;
- }
- }
+ for (i = 0; i < len; i++)
+ if (toupper(name[i]) != toupper(compare[i]))
+ return 0;
return 1;
}
@@ -99,23 +159,22 @@ static struct buffer_head *
affs_find_entry(struct inode *dir, struct dentry *dentry, unsigned long *ino)
{
struct buffer_head *bh;
- int intl;
+ int intl = AFFS_I2FSTYPE(dir);
s32 key;
const char *name = dentry->d_name.name;
int namelen = dentry->d_name.len;
pr_debug("AFFS: find_entry(\"%.*s\")\n",namelen,name);
- intl = AFFS_I2FSTYPE(dir);
- bh = affs_bread(dir->i_dev,dir->i_ino,AFFS_I2BSIZE(dir));
+ bh = affs_bread(dir->i_dev,dir->i_ino,AFFS_I2BSIZE(dir));
if (!bh)
return NULL;
- if (affs_match(name,namelen,".",1,intl)) {
+ if (namelen == 1 && name[0] == '.') {
*ino = dir->i_ino;
return bh;
}
- if (affs_match(name,namelen,"..",2,intl)) {
+ if (namelen == 2 && name[0] == '.' && name[1] == '.') {
*ino = affs_parent_ino(dir);
return bh;
}
@@ -127,10 +186,9 @@ affs_find_entry(struct inode *dir, struct dentry *dentry, unsigned long *ino)
int cnamelen;
affs_brelse(bh);
- if (key == 0) {
- bh = NULL;
+ bh = NULL;
+ if (key == 0)
break;
- }
bh = affs_bread(dir->i_dev,key,AFFS_I2BSIZE(dir));
if (!bh)
break;
@@ -162,6 +220,7 @@ affs_lookup(struct inode *dir, struct dentry *dentry)
if (!inode)
return -EACCES;
}
+ dentry->d_op = &affs_dentry_operations;
d_add(dentry,inode);
return 0;
}
@@ -177,10 +236,7 @@ affs_unlink(struct inode *dir, struct dentry *dentry)
pr_debug("AFFS: unlink(dir=%ld,\"%.*s\")\n",dir->i_ino,
(int)dentry->d_name.len,dentry->d_name.name);
- bh = NULL;
retval = -ENOENT;
- if (!dir)
- goto unlink_done;
if (!(bh = affs_find_entry(dir,dentry,&ino)))
goto unlink_done;
@@ -198,8 +254,10 @@ affs_unlink(struct inode *dir, struct dentry *dentry)
inode->i_nlink = retval;
inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME;
mark_inode_dirty(inode);
- retval = 0;
d_delete(dentry);
+ mark_inode_dirty(dir);
+ retval = 0;
+
unlink_done:
affs_brelse(bh);
return retval;
@@ -214,11 +272,10 @@ affs_create(struct inode *dir, struct dentry *dentry, int mode)
pr_debug("AFFS: create(%lu,\"%.*s\",0%o)\n",dir->i_ino,(int)dentry->d_name.len,
dentry->d_name.name,mode);
- if (!dir)
- return -ENOENT;
+ error = -ENOSPC;
inode = affs_new_inode(dir);
if (!inode)
- return -ENOSPC;
+ goto out;
pr_debug(" -- ino=%lu\n",inode->i_ino);
if (dir->i_sb->u.affs_sb.s_flags & SF_OFS)
@@ -227,19 +284,22 @@ affs_create(struct inode *dir, struct dentry *dentry, int mode)
inode->i_op = &affs_file_inode_operations;
error = affs_add_entry(dir,NULL,inode,dentry,ST_FILE);
- if (error) {
- inode->i_nlink = 0;
- mark_inode_dirty(inode);
- iput(inode);
- return error;
- }
+ if (error)
+ goto out_iput;
inode->i_mode = mode;
inode->u.affs_i.i_protect = mode_to_prot(inode->i_mode);
+ d_instantiate(dentry,inode);
+ mark_inode_dirty(inode);
dir->i_version = ++event;
mark_inode_dirty(dir);
- d_instantiate(dentry,inode);
+out:
+ return error;
- return 0;
+out_iput:
+ inode->i_nlink = 0;
+ mark_inode_dirty(inode);
+ iput(inode);
+ goto out;
}
int
@@ -251,25 +311,29 @@ affs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
pr_debug("AFFS: mkdir(%lu,\"%.*s\",0%o)\n",dir->i_ino,
(int)dentry->d_name.len,dentry->d_name.name,mode);
+ error = -ENOSPC;
inode = affs_new_inode(dir);
if (!inode)
- return -ENOSPC;
+ goto out;
inode->i_op = &affs_dir_inode_operations;
error = affs_add_entry(dir,NULL,inode,dentry,ST_USERDIR);
- if (error) {
- inode->i_nlink = 0;
- mark_inode_dirty(inode);
- iput(inode);
- return error;
- }
+ if (error)
+ goto out_iput;
inode->i_mode = S_IFDIR | (mode & 0777 & ~current->fs->umask);
inode->u.affs_i.i_protect = mode_to_prot(inode->i_mode);
+ d_instantiate(dentry,inode);
+ mark_inode_dirty(inode);
dir->i_version = ++event;
mark_inode_dirty(dir);
- d_instantiate(dentry,inode);
+out:
+ return error;
- return 0;
+out_iput:
+ inode->i_nlink = 0;
+ mark_inode_dirty(inode);
+ iput(inode);
+ goto out;
}
static int
@@ -285,24 +349,18 @@ empty_dir(struct buffer_head *bh, int hashsize)
int
affs_rmdir(struct inode *dir, struct dentry *dentry)
{
+ struct inode *inode = dentry->d_inode;
int retval;
unsigned long ino;
- struct inode *inode;
struct buffer_head *bh;
pr_debug("AFFS: rmdir(dir=%lu,\"%.*s\")\n",dir->i_ino,
(int)dentry->d_name.len,dentry->d_name.name);
- inode = NULL;
- bh = NULL;
retval = -ENOENT;
- if (!dir)
- goto rmdir_done;
if (!(bh = affs_find_entry(dir,dentry,&ino)))
goto rmdir_done;
- inode = dentry->d_inode;
-
retval = -EPERM;
if (current->fsuid != inode->i_uid &&
current->fsuid != dir->i_uid && !fsuser())
@@ -311,31 +369,31 @@ affs_rmdir(struct inode *dir, struct dentry *dentry)
goto rmdir_done;
if (inode == dir) /* we may not delete ".", but "../dir" is ok */
goto rmdir_done;
- if (!S_ISDIR(inode->i_mode)) {
- retval = -ENOTDIR;
+ retval = -ENOTDIR;
+ if (!S_ISDIR(inode->i_mode))
goto rmdir_done;
- }
- down(&inode->i_sem);
- if (dentry->d_count > 1) {
+ /*
+ * Make sure the directory is empty and the dentry isn't busy.
+ */
+ if (dentry->d_count > 1)
shrink_dcache_parent(dentry);
- }
- up(&inode->i_sem);
- if (!empty_dir(bh,AFFS_I2HSIZE(inode))) {
- retval = -ENOTEMPTY;
+ retval = -ENOTEMPTY;
+ if (!empty_dir(bh,AFFS_I2HSIZE(inode)))
goto rmdir_done;
- }
- if (inode->i_count > 1) {
- retval = -EBUSY;
+ retval = -EBUSY;
+ if (dentry->d_count > 1)
goto rmdir_done;
- }
+
if ((retval = affs_remove_header(bh,inode)) < 0)
goto rmdir_done;
inode->i_nlink = retval;
inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME;
retval = 0;
+ mark_inode_dirty(dir);
mark_inode_dirty(inode);
d_delete(dentry);
+
rmdir_done:
affs_brelse(bh);
return retval;
@@ -348,27 +406,25 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
struct inode *inode;
char *p;
unsigned long tmp;
- int i, maxlen;
+ int i, maxlen, error;
char c, lc;
pr_debug("AFFS: symlink(%lu,\"%.*s\" -> \"%s\")\n",dir->i_ino,
(int)dentry->d_name.len,dentry->d_name.name,symname);
maxlen = 4 * AFFS_I2HSIZE(dir) - 1;
+ error = -ENOSPC;
inode = affs_new_inode(dir);
if (!inode)
- return -ENOSPC;
+ goto out;
inode->i_op = &affs_symlink_inode_operations;
inode->i_mode = S_IFLNK | 0777;
inode->u.affs_i.i_protect = mode_to_prot(inode->i_mode);
+ error = -EIO;
bh = affs_bread(inode->i_dev,inode->i_ino,AFFS_I2BSIZE(inode));
- if (!bh) {
- inode->i_nlink = 0;
- mark_inode_dirty(inode);
- iput(inode);
- return -EIO;
- }
+ if (!bh)
+ goto out_iput;
i = 0;
p = ((struct slink_front *)bh->b_data)->symname;
lc = '/';
@@ -400,30 +456,36 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
mark_buffer_dirty(bh,1);
affs_brelse(bh);
mark_inode_dirty(inode);
+
+ /* N.B. This test shouldn't be necessary ... dentry must be negative */
+ error = -EEXIST;
bh = affs_find_entry(dir,dentry,&tmp);
- if (bh) {
- inode->i_nlink = 0;
- iput(inode);
- affs_brelse(bh);
- return -EEXIST;
- }
- i = affs_add_entry(dir,NULL,inode,dentry,ST_SOFTLINK);
- if (i) {
- inode->i_nlink = 0;
- mark_inode_dirty(inode);
- iput(inode);
- affs_brelse(bh);
- return i;
- }
- dir->i_version = ++event;
+ if (bh)
+ goto out_release;
+ /* N.B. Shouldn't we add the entry before dirtying the buffer? */
+ error = affs_add_entry(dir,NULL,inode,dentry,ST_SOFTLINK);
+ if (error)
+ goto out_release;
d_instantiate(dentry,inode);
-
- return 0;
+ dir->i_version = ++event;
+ mark_inode_dirty(dir);
+
+out:
+ return error;
+
+out_release:
+ affs_brelse(bh);
+out_iput:
+ inode->i_nlink = 0;
+ mark_inode_dirty(inode);
+ iput(inode);
+ goto out;
}
int
-affs_link(struct inode *oldinode, struct inode *dir, struct dentry *dentry)
+affs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
{
+ struct inode *oldinode = old_dentry->d_inode;
struct inode *inode;
struct buffer_head *bh;
unsigned long i;
@@ -432,6 +494,7 @@ affs_link(struct inode *oldinode, struct inode *dir, struct dentry *dentry)
pr_debug("AFFS: link(%lu,%lu,\"%.*s\")\n",oldinode->i_ino,dir->i_ino,
(int)dentry->d_name.len,dentry->d_name.name);
+ /* N.B. Do we need this test? The dentry must be negative ... */
bh = affs_find_entry(dir,dentry,&i);
if (bh) {
affs_brelse(bh);
@@ -441,8 +504,9 @@ affs_link(struct inode *oldinode, struct inode *dir, struct dentry *dentry)
affs_warning(dir->i_sb,"link","Impossible link to link");
return -EINVAL;
}
+ error = -ENOSPC;
if (!(inode = affs_new_inode(dir)))
- return -ENOSPC;
+ goto out;
inode->i_op = oldinode->i_op;
inode->u.affs_i.i_protect = mode_to_prot(oldinode->i_mode);
@@ -458,6 +522,7 @@ affs_link(struct inode *oldinode, struct inode *dir, struct dentry *dentry)
inode->i_nlink = 0;
else {
dir->i_version = ++event;
+ mark_inode_dirty(dir);
mark_inode_dirty(oldinode);
oldinode->i_count++;
d_instantiate(dentry,oldinode);
@@ -465,31 +530,10 @@ affs_link(struct inode *oldinode, struct inode *dir, struct dentry *dentry)
mark_inode_dirty(inode);
iput(inode);
+out:
return error;
}
-/* This is copied from the ext2 fs. No need to reinvent the wheel. */
-
-static int
-subdir(struct dentry * new_dentry, struct dentry * old_dentry)
-{
- int result;
-
- result = 0;
- for (;;) {
- if (new_dentry != old_dentry) {
- struct dentry * parent = new_dentry->d_parent;
- if (parent == new_dentry)
- break;
- new_dentry = parent;
- continue;
- }
- result = 1;
- break;
- }
- return result;
-}
-
int
affs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry)
@@ -507,7 +551,7 @@ affs_rename(struct inode *old_dir, struct dentry *old_dentry,
new_dir->i_ino,new_dentry->d_name.len,new_dentry->d_name.name,new_inode);
if ((retval = affs_check_name(new_dentry->d_name.name,new_dentry->d_name.len)))
- return retval;
+ goto out;
new_bh = NULL;
retval = -ENOENT;
@@ -537,8 +581,10 @@ affs_rename(struct inode *old_dir, struct dentry *old_dentry,
if (!S_ISDIR(old_inode->i_mode))
goto end_rename;
retval = -EINVAL;
- if (subdir(new_dentry,old_dentry))
+ if (is_subdir(new_dentry, old_dentry))
goto end_rename;
+ if (new_dentry->d_count > 1)
+ shrink_dcache_parent(new_dentry);
retval = -ENOTEMPTY;
if (!empty_dir(new_bh,AFFS_I2HSIZE(new_inode)))
goto end_rename;
@@ -551,7 +597,7 @@ affs_rename(struct inode *old_dir, struct dentry *old_dentry,
if (new_inode && !S_ISDIR(new_inode->i_mode))
goto end_rename;
retval = -EINVAL;
- if (subdir(new_dentry,old_dentry))
+ if (is_subdir(new_dentry, old_dentry))
goto end_rename;
if (affs_parent_ino(old_inode) != old_dir->i_ino)
goto end_rename;
@@ -588,6 +634,6 @@ new_checksum:
end_rename:
affs_brelse(old_bh);
affs_brelse(new_bh);
-
+out:
return retval;
}