/* * linux/fs/affs/namei.c * * (c) 1996 Hans-Joachim Widmaier - Rewritten * * (C) 1993 Ray Burr - Modified for Amiga FFS filesystem. * * (C) 1991 Linus Torvalds - minix filesystem */ #define DEBUG 0 #include #include #include #include #include #include #include #include #include #include /* Simple toupper() for DOS\1 */ static unsigned int affs_toupper(unsigned int ch) { return ch >= 'a' && ch <= 'z' ? ch -= ('a' - 'A') : ch; } /* International toupper() for DOS\3 ("international") */ static unsigned int affs_intl_toupper(unsigned int ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 0xE0 && ch <= 0xFE && ch != 0xF7) ? 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. */ 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; if (len > 30) len = 30; if (dlen > 30) dlen = 30; /* "" means "." ---> so paths like "/usr/lib//libc.a" work */ if (!len && dlen == 1 && compare[0] == '.') return 1; if (dlen != len) return 0; for (i = 0; i < len; i++) if (toupper(name[i]) != toupper(compare[i])) return 0; return 1; } int affs_hash_name(const unsigned char *name, int len, int intl, int hashsize) { unsigned int i, x; if (len > 30) len = 30; x = len; for (i = 0; i < len; i++) if (intl) x = (x * 13 + affs_intl_toupper(name[i] & 0xFF)) & 0x7ff; else x = (x * 13 + affs_toupper(name[i] & 0xFF)) & 0x7ff; return x % hashsize; } static struct buffer_head * affs_find_entry(struct inode *dir, struct dentry *dentry, unsigned long *ino) { struct buffer_head *bh; 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); bh = affs_bread(dir->i_dev,dir->i_ino,AFFS_I2BSIZE(dir)); if (!bh) return NULL; if (namelen == 1 && name[0] == '.') { *ino = dir->i_ino; return bh; } if (namelen == 2 && name[0] == '.' && name[1] == '.') { *ino = affs_parent_ino(dir); return bh; } key = AFFS_GET_HASHENTRY(bh->b_data,affs_hash_name(name,namelen,intl,AFFS_I2HSIZE(dir))); for (;;) { unsigned char *cname; int cnamelen; affs_brelse(bh); bh = NULL; if (key == 0) break; bh = affs_bread(dir->i_dev,key,AFFS_I2BSIZE(dir)); if (!bh) break; cnamelen = affs_get_file_name(AFFS_I2BSIZE(dir),bh->b_data,&cname); if (affs_match(name,namelen,cname,cnamelen,intl)) break; key = be32_to_cpu(FILE_END(bh->b_data,dir)->hash_chain); } *ino = key; return bh; } int affs_lookup(struct inode *dir, struct dentry *dentry) { unsigned long ino; struct buffer_head *bh; struct inode *inode; pr_debug("AFFS: lookup(\"%.*s\")\n",(int)dentry->d_name.len,dentry->d_name.name); inode = NULL; bh = affs_find_entry(dir,dentry,&ino); if (bh) { if (FILE_END(bh->b_data,dir)->original) ino = be32_to_cpu(FILE_END(bh->b_data,dir)->original); affs_brelse(bh); inode = iget(dir->i_sb,ino); if (!inode) return -EACCES; } dentry->d_op = &affs_dentry_operations; d_add(dentry,inode); return 0; } int affs_unlink(struct inode *dir, struct dentry *dentry) { int retval; struct buffer_head *bh; unsigned long ino; struct inode *inode; pr_debug("AFFS: unlink(dir=%ld,\"%.*s\")\n",dir->i_ino, (int)dentry->d_name.len,dentry->d_name.name); retval = -ENOENT; if (!(bh = affs_find_entry(dir,dentry,&ino))) goto unlink_done; inode = dentry->d_inode; retval = -EPERM; if (S_ISDIR(inode->i_mode)) goto unlink_done; if (current->fsuid != inode->i_uid && current->fsuid != dir->i_uid && !fsuser()) goto unlink_done; if ((retval = affs_remove_header(bh,inode)) < 0) goto unlink_done; inode->i_nlink = retval; inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME; mark_inode_dirty(inode); d_delete(dentry); mark_inode_dirty(dir); retval = 0; unlink_done: affs_brelse(bh); return retval; } int affs_create(struct inode *dir, struct dentry *dentry, int mode) { struct inode *inode; int error; pr_debug("AFFS: create(%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) goto out; pr_debug(" -- ino=%lu\n",inode->i_ino); if (dir->i_sb->u.affs_sb.s_flags & SF_OFS) inode->i_op = &affs_file_inode_operations_ofs; else inode->i_op = &affs_file_inode_operations; error = affs_add_entry(dir,NULL,inode,dentry,ST_FILE); 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); out: return error; out_iput: inode->i_nlink = 0; mark_inode_dirty(inode); iput(inode); goto out; } int affs_mkdir(struct inode *dir, struct dentry *dentry, int mode) { struct inode *inode; int error; 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) goto out; inode->i_op = &affs_dir_inode_operations; error = affs_add_entry(dir,NULL,inode,dentry,ST_USERDIR); 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); out: return error; out_iput: inode->i_nlink = 0; mark_inode_dirty(inode); iput(inode); goto out; } static int empty_dir(struct buffer_head *bh, int hashsize) { while (--hashsize >= 0) { if (((struct dir_front *)bh->b_data)->hashtable[hashsize]) return 0; } return 1; } int affs_rmdir(struct inode *dir, struct dentry *dentry) { struct inode *inode = dentry->d_inode; int retval; unsigned long ino; struct buffer_head *bh; pr_debug("AFFS: rmdir(dir=%lu,\"%.*s\")\n",dir->i_ino, (int)dentry->d_name.len,dentry->d_name.name); retval = -ENOENT; if (!(bh = affs_find_entry(dir,dentry,&ino))) goto rmdir_done; retval = -EPERM; if (current->fsuid != inode->i_uid && current->fsuid != dir->i_uid && !fsuser()) goto rmdir_done; if (inode->i_dev != dir->i_dev) goto rmdir_done; if (inode == dir) /* we may not delete ".", but "../dir" is ok */ goto rmdir_done; retval = -ENOTDIR; if (!S_ISDIR(inode->i_mode)) goto rmdir_done; /* * Make sure the directory is empty and the dentry isn't busy. */ if (dentry->d_count > 1) shrink_dcache_parent(dentry); retval = -ENOTEMPTY; if (!empty_dir(bh,AFFS_I2HSIZE(inode))) goto rmdir_done; 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; } int affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) { struct buffer_head *bh; struct inode *inode; char *p; unsigned long tmp; 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) 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) goto out_iput; i = 0; p = ((struct slink_front *)bh->b_data)->symname; lc = '/'; if (*symname == '/') { while (*symname == '/') symname++; while (inode->i_sb->u.affs_sb.s_volume[i]) /* Cannot overflow */ *p++ = inode->i_sb->u.affs_sb.s_volume[i++]; } while (i < maxlen && (c = *symname++)) { if (c == '.' && lc == '/' && *symname == '.' && symname[1] == '/') { *p++ = '/'; i++; symname += 2; lc = '/'; } else if (c == '.' && lc == '/' && *symname == '/') { symname++; lc = '/'; } else { *p++ = c; lc = c; i++; } if (lc == '/') while (*symname == '/') symname++; } *p = 0; 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) 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); 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 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; int error; 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); return -EEXIST; } if (oldinode->u.affs_i.i_hlink) { /* Cannot happen */ affs_warning(dir->i_sb,"link","Impossible link to link"); return -EINVAL; } error = -ENOSPC; if (!(inode = affs_new_inode(dir))) goto out; inode->i_op = oldinode->i_op; inode->u.affs_i.i_protect = mode_to_prot(oldinode->i_mode); inode->u.affs_i.i_original = oldinode->i_ino; inode->u.affs_i.i_hlink = 1; inode->i_mtime = oldinode->i_mtime; if (S_ISDIR(oldinode->i_mode)) error = affs_add_entry(dir,oldinode,inode,dentry,ST_LINKDIR); else error = affs_add_entry(dir,oldinode,inode,dentry,ST_LINKFILE); if (error) inode->i_nlink = 0; else { dir->i_version = ++event; mark_inode_dirty(dir); mark_inode_dirty(oldinode); oldinode->i_count++; d_instantiate(dentry,oldinode); } mark_inode_dirty(inode); iput(inode); out: return error; } int affs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { struct inode *old_inode = old_dentry->d_inode; struct inode *new_inode = new_dentry->d_inode; struct buffer_head *old_bh; struct buffer_head *new_bh; unsigned long old_ino; unsigned long new_ino; int retval; pr_debug("AFFS: rename(old=%lu,\"%*s\" (inode=%p) to new=%lu,\"%*s\" (inode=%p) )\n", old_dir->i_ino,old_dentry->d_name.len,old_dentry->d_name.name,old_inode, 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))) goto out; new_bh = NULL; retval = -ENOENT; old_bh = affs_find_entry(old_dir,old_dentry,&old_ino); if (!old_bh) goto end_rename; new_bh = affs_find_entry(new_dir,new_dentry,&new_ino); if (new_bh && !new_inode) { affs_error(old_inode->i_sb,"affs_rename", "No inode for entry found (key=%lu)\n",new_ino); goto end_rename; } if (new_inode == old_inode) { if (old_ino == new_ino) { /* Filename might have changed case */ retval = new_dentry->d_name.len < 31 ? new_dentry->d_name.len : 30; strncpy(DIR_END(old_bh->b_data,old_inode)->dir_name + 1, new_dentry->d_name.name,retval); DIR_END(old_bh->b_data,old_inode)->dir_name[0] = retval; goto new_checksum; } retval = 0; goto end_rename; } if (new_inode && S_ISDIR(new_inode->i_mode)) { retval = -EISDIR; if (!S_ISDIR(old_inode->i_mode)) goto end_rename; retval = -EINVAL; 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; retval = -EBUSY; if (new_dentry->d_count > 1) goto end_rename; } if (S_ISDIR(old_inode->i_mode)) { retval = -ENOTDIR; if (new_inode && !S_ISDIR(new_inode->i_mode)) goto end_rename; retval = -EINVAL; if (is_subdir(new_dentry, old_dentry)) goto end_rename; if (affs_parent_ino(old_inode) != old_dir->i_ino) goto end_rename; } /* Unlink destination if it already exists */ if (new_inode) { if ((retval = affs_remove_header(new_bh,new_dir)) < 0) goto end_rename; new_inode->i_nlink = retval; mark_inode_dirty(new_inode); if (new_inode->i_ino == new_ino) new_inode->i_nlink = 0; } /* Remove header from its parent directory. */ if ((retval = affs_remove_hash(old_bh,old_dir))) goto end_rename; /* And insert it into the new directory with the new name. */ affs_copy_name(FILE_END(old_bh->b_data,old_inode)->file_name,new_dentry->d_name.name); if ((retval = affs_insert_hash(new_dir->i_ino,old_bh,new_dir))) goto end_rename; new_checksum: affs_fix_checksum(AFFS_I2BSIZE(new_dir),old_bh->b_data,5); new_dir->i_ctime = new_dir->i_mtime = old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME; new_dir->i_version = ++event; old_dir->i_version = ++event; retval = 0; mark_inode_dirty(new_dir); mark_inode_dirty(old_dir); mark_buffer_dirty(old_bh,1); d_move(old_dentry,new_dentry); end_rename: affs_brelse(old_bh); affs_brelse(new_bh); out: return retval; }