/* * 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 */ #include #include #include #include #include #include #include #include #include #include static int affs_fixup(struct buffer_head *bh, struct inode *inode); /* Simple toupper() for DOS\1 */ static inline unsigned int affs_toupper(unsigned int ch) { return ch >= 'a' && ch <= 'z' ? ch -= ('a' - 'A') : ch; } /* International toupper() for DOS\3 */ static inline unsigned int affs_intl_toupper(unsigned int ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 0xE0 && ch <= 0xFE && ch != 0xF7) ? ch - ('a' - 'A') : ch; } /* * NOTE! unlike strncmp, affs_match returns 1 for success, 0 for failure. */ static int affs_match(const char *name, int len, const char *compare, int dlen, int intl) { 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; if (intl) { while (dlen--) { if (affs_intl_toupper(*name & 0xFF) != affs_intl_toupper(*compare & 0xFF)) return 0; name++; compare++; } } else { while (dlen--) { if (affs_toupper(*name & 0xFF) != affs_toupper(*compare & 0xFF)) return 0; name++; compare++; } } return 1; } int affs_hash_name(const 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, const char *name, int namelen, unsigned long *ino) { struct buffer_head *bh; int intl; s32 key; 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)); if (!bh) return NULL; if (affs_match(name,namelen,".",1,intl)) { *ino = dir->i_ino; return bh; } if (affs_match(name,namelen,"..",2,intl)) { *ino = affs_parent_ino(dir); return bh; } key = AFFS_GET_HASHENTRY(bh->b_data,affs_hash_name(name,namelen,intl,AFFS_I2HSIZE(dir))); for (;;) { char *cname; int cnamelen; affs_brelse(bh); if (key == 0) { bh = NULL; 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 = htonl(FILE_END(bh->b_data,dir)->hash_chain); } *ino = key; return bh; } int affs_lookup(struct inode *dir, const char *name, int len, struct inode **result) { int res; unsigned long ino; struct buffer_head *bh; pr_debug("AFFS: lookup(%.*s)\n",len,name); *result = NULL; if (!dir) return -ENOENT; res = -ENOENT; if (S_ISDIR(dir->i_mode)) { if ((bh = affs_find_entry(dir,name,len,&ino))) { if (FILE_END(bh->b_data,dir)->original) ino = htonl(FILE_END(bh->b_data,dir)->original); affs_brelse(bh); if ((*result = iget(dir->i_sb,ino))) res = 0; else res = -EACCES; } } iput(dir); return res; } int affs_unlink(struct inode *dir, const char *name, int len) { int retval; struct buffer_head *bh; unsigned long ino; struct inode *inode; pr_debug("AFFS: unlink(dir=%ld,\"%.*s\")\n",dir->i_ino,len,name); bh = NULL; inode = NULL; retval = -ENOENT; if (!(bh = affs_find_entry(dir,name,len,&ino))) { goto unlink_done; } if (!(inode = iget(dir->i_sb,ino))) { goto unlink_done; } if (S_ISDIR(inode->i_mode)) { retval = -EPERM; goto unlink_done; } if ((retval = affs_fix_hash_pred(dir,affs_hash_name(name,len,AFFS_I2FSTYPE(dir), AFFS_I2HSIZE(dir)) + 6,ino, FILE_END(bh->b_data,dir)->hash_chain))) goto unlink_done; if ((retval = affs_fixup(bh,inode))) goto unlink_done; inode->i_nlink=0; inode->i_dirt=1; inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME; dir->i_version = ++event; dir->i_dirt=1; unlink_done: affs_brelse(bh); iput(inode); iput(dir); return retval; } int affs_create(struct inode *dir, const char *name, int len, int mode, struct inode **result) { struct inode *inode; int error; pr_debug("AFFS: create(%lu,\"%.*s\",0%o)\n",dir->i_ino,len,name,mode); *result = NULL; if (!dir || !dir->i_sb) { iput(dir); return -EINVAL; } inode = affs_new_inode(dir); if (!inode) { iput (dir); return -ENOSPC; } inode->i_mode = mode; 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,name,len,ST_FILE); if (error) { iput(dir); inode->i_nlink = 0; inode->i_dirt = 1; iput(inode); return -ENOSPC; } inode->u.affs_i.i_protect = mode_to_prot(inode->i_mode); iput(dir); *result = inode; return 0; } int affs_mkdir(struct inode *dir, const char *name, int len, int mode) { struct inode *inode; struct buffer_head *bh; unsigned long i; int error; pr_debug("AFFS: mkdir(%lu,\"%.*s\",0%o)\n",dir->i_ino,len,name,mode); if (!dir || !dir->i_sb) { iput(dir); return -EINVAL; } bh = affs_find_entry(dir,name,len,&i); if (bh) { affs_brelse(bh); iput(dir); return -EEXIST; } inode = affs_new_inode(dir); if (!inode) { iput (dir); return -ENOSPC; } inode->i_op = &affs_dir_inode_operations; error = affs_add_entry(dir,NULL,inode,name,len,ST_USERDIR); if (error) { iput(dir); inode->i_nlink = 0; inode->i_dirt = 1; iput(inode); return error; } inode->i_mode = S_IFDIR | (mode & 0777 & ~current->fs->umask); inode->u.affs_i.i_protect = mode_to_prot(inode->i_mode); iput(dir); iput(inode); return 0; } 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, const char *name, int len) { int retval; unsigned long ino; struct inode *inode; struct buffer_head *bh; pr_debug("AFFS: rmdir(dir=%lu,\"%.*s\")\n",dir->i_ino,len,name); inode = NULL; retval = -ENOENT; if (!(bh = affs_find_entry(dir,name,len,&ino))) { goto rmdir_done; } if (!(inode = iget(dir->i_sb,ino))) { goto rmdir_done; } retval = -EPERM; if (!fsuser() && current->fsuid != inode->i_uid && current->fsuid != dir->i_uid) 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; if (!S_ISDIR(inode->i_mode)) { retval = -ENOTDIR; goto rmdir_done; } if (!empty_dir(bh,AFFS_I2HSIZE(inode))) { retval = -ENOTEMPTY; goto rmdir_done; } if (inode->i_count > 1) { retval = -EBUSY; goto rmdir_done; } if ((retval = affs_fix_hash_pred(dir,affs_hash_name(name,len,AFFS_I2FSTYPE(dir), AFFS_I2HSIZE(dir)) + 6,ino, FILE_END(bh->b_data,dir)->hash_chain))) goto rmdir_done; if ((retval = affs_fixup(bh,inode))) goto rmdir_done; inode->i_nlink=0; inode->i_dirt=1; inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME; dir->i_version = ++event; dir->i_dirt=1; rmdir_done: iput(dir); iput(inode); affs_brelse(bh); return retval; } int affs_symlink(struct inode *dir, const char *name, int len, const char *symname) { struct buffer_head *bh; struct inode *inode; char *p; unsigned long tmp; int i, maxlen; char c, lc; pr_debug("AFFS: symlink(%lu,\"%.*s\" -> \"%s\")\n",dir->i_ino,len,name,symname); maxlen = 4 * AFFS_I2HSIZE(dir) - 1; inode = affs_new_inode(dir); if (!inode) { iput(dir); return -ENOSPC; } 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); bh = affs_bread(inode->i_dev,inode->i_ino,AFFS_I2BSIZE(inode)); if (!bh) { iput(dir); inode->i_nlink = 0; inode->i_dirt = 1; iput(inode); return -EIO; } 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); inode->i_dirt = 1; bh = affs_find_entry(dir,name,len,&tmp); if (bh) { inode->i_nlink = 0; iput(inode); affs_brelse(bh); iput(dir); return -EEXIST; } i = affs_add_entry(dir,NULL,inode,name,len,ST_SOFTLINK); if (i) { inode->i_nlink = 0; inode->i_dirt = 1; iput(inode); affs_brelse(bh); iput(dir); return i; } iput(dir); iput(inode); return 0; } int affs_link(struct inode *oldinode, struct inode *dir, const char *name, int len) { 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,len,name); bh = affs_find_entry(dir,name,len,&i); if (bh) { affs_brelse(bh); iput(oldinode); iput(dir); return -EEXIST; } if (oldinode->u.affs_i.i_hlink) { i = oldinode->u.affs_i.i_original; iput(oldinode); oldinode = iget(dir->i_sb,i); if (!oldinode) { affs_error(oldinode->i_sb,"link","Cannot get original from link"); iput(dir); return -ENOENT; } } inode = affs_new_inode(dir); if (!inode) { iput(oldinode); iput(dir); return -ENOSPC; } inode->i_op = oldinode->i_op; inode->i_mode = oldinode->i_mode; inode->i_uid = oldinode->i_uid; inode->i_gid = oldinode->i_gid; inode->u.affs_i.i_protect = mode_to_prot(inode->i_mode); inode->u.affs_i.i_original = oldinode->i_ino; inode->u.affs_i.i_hlink = 1; if (S_ISDIR(oldinode->i_mode)) error = affs_add_entry(dir,oldinode,inode,name,len,ST_LINKDIR); else error = affs_add_entry(dir,oldinode,inode,name,len,ST_LINKFILE); if (error) { inode->i_nlink = 0; inode->i_dirt = 1; } iput(dir); iput(inode); iput(oldinode); return error; } static int subdir(struct inode *new_inode, struct inode *old_inode) { int ino; int result; new_inode->i_count++; result = 0; for (;;) { if (new_inode == old_inode) { result = 1; break; } if (new_inode->i_dev != old_inode->i_dev) break; ino = new_inode->i_ino; if (affs_lookup(new_inode,"..",2,&new_inode)) break; if (new_inode->i_ino == ino) break; } iput(new_inode); return result; } /* I'm afraid this might not be race proof. Maybe next time. */ int affs_rename(struct inode *old_dir, const char *old_name, int old_len, struct inode *new_dir, const char *new_name, int new_len) { struct inode *old_inode; struct inode *new_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\" to new=%lu,\"%*s\")\n",old_dir->i_ino,old_len,old_name, new_dir->i_ino,new_len,new_name); if (new_len > 30) new_len = 30; goto start_up; retry: affs_brelse(old_bh); affs_brelse(new_bh); iput(new_inode); iput(old_inode); current->counter = 0; schedule(); start_up: old_inode = new_inode = NULL; old_bh = new_bh = NULL; retval = -ENOENT; old_bh = affs_find_entry(old_dir,old_name,old_len,&old_ino); if (!old_bh) goto end_rename; old_inode = iget(old_dir->i_sb,old_ino); if (!old_inode) goto end_rename; new_bh = affs_find_entry(new_dir,new_name,new_len,&new_ino); if (new_bh) { new_inode = iget(new_dir->i_sb,new_ino); if (!new_inode) { /* What does this mean? */ affs_brelse(new_bh); new_bh = NULL; } } if (new_inode == old_inode) { /* Won't happen */ 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 (subdir(new_dir,old_inode)) goto end_rename; retval = -ENOTEMPTY; if (!empty_dir(new_bh,AFFS_I2HSIZE(new_inode))) goto end_rename; retval = -EBUSY; if (new_inode->i_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 (subdir(new_dir,old_inode)) goto end_rename; if (affs_parent_ino(old_inode) != old_dir->i_ino) goto end_rename; } /* Unlink destination if existent */ if (new_inode) { if ((retval = affs_fix_hash_pred(new_dir,affs_hash_name(new_name,new_len, AFFS_I2FSTYPE(new_dir),AFFS_I2HSIZE(new_dir)) + 6, new_ino, FILE_END(new_bh->b_data,new_dir)->hash_chain))) goto retry; if ((retval = affs_fixup(new_bh,new_inode))) goto retry; mark_buffer_dirty(new_bh,1); new_dir->i_version = ++event; new_dir->i_dirt = 1; new_inode->i_nlink = 0; new_inode->i_dirt = 1; } retval = affs_fix_hash_pred(old_dir,affs_hash_name(old_name,old_len,AFFS_I2FSTYPE(old_dir), AFFS_I2HSIZE(old_dir)) + 6,old_ino, FILE_END(old_bh->b_data,old_dir)->hash_chain); if (retval) goto retry; retval = affs_add_entry(new_dir,NULL,old_inode,new_name,new_len, htonl(FILE_END(old_bh->b_data,old_dir)->secondary_type)); 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; new_dir->i_dirt = 1; old_dir->i_dirt = 1; mark_buffer_dirty(old_bh,1); end_rename: affs_brelse(old_bh); affs_brelse(new_bh); iput(new_inode); iput(old_inode); iput(old_dir); iput(new_dir); return retval; } static int affs_fixup(struct buffer_head *bh, struct inode *inode) { s32 key, link_key; s32 type; struct buffer_head *nbh; struct inode *ofinode; type = htonl(FILE_END(bh->b_data,inode)->secondary_type); if (type == ST_LINKFILE || type == ST_LINKDIR) { key = htonl(LINK_END(bh->b_data,inode)->original); LINK_END(bh->b_data,inode)->original = 0; if (!key) { affs_error(inode->i_sb,"fixup","Hard link without original: ino=%lu", inode->i_ino); return -ENOENT; } if (!(ofinode = iget(inode->i_sb,key))) return -ENOENT; type = affs_fix_link_pred(ofinode,inode->i_ino, FILE_END(bh->b_data,inode)->link_chain); iput(ofinode); return type; } else if (type == ST_FILE || type == ST_USERDIR) { if ((key = htonl(FILE_END(bh->b_data,inode)->link_chain))) { /* Get first link, turn it to a file */ if (!(ofinode = iget(inode->i_sb,key))) { affs_error(inode->i_sb,"fixup","Cannot read block %d",key); return -ENOENT; } if (!ofinode->u.affs_i.i_hlink) { affs_error(inode->i_sb,"fixup", "First link to %lu (%d) is not a link", inode->i_ino,key); iput(ofinode); return -ENOENT; } if (!(nbh = affs_bread(inode->i_dev,key,AFFS_I2BSIZE(inode)))) { affs_error(inode->i_sb,"fixup","Cannot read block %d",key); iput(ofinode); return -ENOENT; } lock_super(inode->i_sb); memcpy(nbh->b_data + 8,bh->b_data + 8,AFFS_I2BSIZE(inode) - 208); FILE_END(nbh->b_data,inode)->byte_size = FILE_END(bh->b_data,inode)-> byte_size; FILE_END(nbh->b_data,inode)->extension = FILE_END(bh->b_data,inode)-> extension; FILE_END(nbh->b_data,inode)->secondary_type = FILE_END(bh->b_data,inode)-> secondary_type; FILE_END(nbh->b_data,inode)->original = 0; ofinode->u.affs_i.i_original = 0; ofinode->u.affs_i.i_hlink = 0; ofinode->i_size = inode->i_size; ofinode->i_uid = inode->i_uid; ofinode->i_gid = inode->i_gid; ofinode->i_dirt = 1; link_key = ofinode->i_ino; /* Let all remaining links point to the new file */ while (1) { affs_fix_checksum(AFFS_I2BSIZE(inode),nbh->b_data,5); mark_buffer_dirty(nbh,1); key = htonl(FILE_END(nbh->b_data,inode)->link_chain); affs_brelse(nbh); iput(ofinode); if (!key || !(nbh = affs_bread(inode->i_dev,key,AFFS_I2BSIZE(inode)))) break; if ((ofinode = iget(inode->i_sb,key))) { if (!ofinode->u.affs_i.i_hlink) affs_error(inode->i_sb,"fixup", "Inode %d in link chain is not a link", key); ofinode->u.affs_i.i_original = link_key; ofinode->i_dirt = 1; FILE_END(nbh->b_data,inode)->original = htonl(link_key); } else affs_error(inode->i_sb,"fixup","Cannot read block %d",key); } /* Turn old inode to a link */ inode->u.affs_i.i_hlink = 1; unlock_super(inode->i_sb); } return 0; } else if (type == ST_SOFTLINK) { return 0; } else { affs_error(inode->i_sb,"fixup","Bad secondary type (%d)",type); return -EBADF; } }