diff options
Diffstat (limited to 'fs/affs/namei.c')
-rw-r--r-- | fs/affs/namei.c | 292 |
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; } |