/* * linux/fs/hfs/dir.c * * Copyright (C) 1995-1997 Paul H. Hargrove * This file may be distributed under the terms of the GNU Public License. * * This file contains directory-related functions independent of which * scheme is being used to represent forks. * * Based on the minix file system code, (C) 1991, 1992 by Linus Torvalds * * "XXX" in a comment is a note to myself to consider changing something. * * In function preconditions the term "valid" applied to a pointer to * a structure means that the pointer is non-NULL and the structure it * points to has all fields initialized to consistent values. */ #include "hfs.h" #include #include #include /*================ File-local functions ================*/ /* * build_key() * * Build a key for a file by the given name in the given directory. * If the name matches one of the reserved names returns 1 otherwise 0. */ static int build_key(struct hfs_cat_key *key, struct inode *dir, const char *name, int len) { struct hfs_name cname; const struct hfs_name *reserved; /* mangle the name */ hfs_nameout(dir, &cname, name, len); /* check against reserved names */ reserved = HFS_SB(dir->i_sb)->s_reserved1; while (reserved->Len) { if (hfs_streq(reserved, &cname)) { return 1; } ++reserved; } /* check against the names reserved only in the root directory */ if (HFS_I(dir)->entry->cnid == htonl(HFS_ROOT_CNID)) { reserved = HFS_SB(dir->i_sb)->s_reserved2; while (reserved->Len) { if (hfs_streq(reserved, &cname)) { return 1; } ++reserved; } } /* build the key */ hfs_cat_build_key(HFS_I(dir)->entry->cnid, &cname, key); return 0; } /* * update_dirs_plus() * * Update the fields 'i_size', 'i_nlink', 'i_ctime', 'i_mtime' and * 'i_version' of the inodes associated with a directory that has * had a file ('is_dir'==0) or directory ('is_dir'!=0) added to it. */ static inline void update_dirs_plus(struct hfs_cat_entry *dir, int is_dir) { int i; for (i = 0; i < 4; ++i) { struct dentry *de = dir->sys_entry[i]; if (de) { struct inode *tmp = de->d_inode; if (S_ISDIR(tmp->i_mode)) { if (is_dir && (i == HFS_ITYPE_TO_INT(HFS_ITYPE_NORM))) { /* In "normal" directory only */ ++(tmp->i_nlink); } tmp->i_size += HFS_I(tmp)->dir_size; tmp->i_version = ++event; } tmp->i_ctime = tmp->i_mtime = CURRENT_TIME; mark_inode_dirty(tmp); } } } /* * update_dirs_minus() * * Update the fields 'i_size', 'i_nlink', 'i_ctime', 'i_mtime' and * 'i_version' of the inodes associated with a directory that has * had a file ('is_dir'==0) or directory ('is_dir'!=0) removed. */ static inline void update_dirs_minus(struct hfs_cat_entry *dir, int is_dir) { int i; for (i = 0; i < 4; ++i) { struct dentry *de = dir->sys_entry[i]; if (de) { struct inode *tmp = de->d_inode; if (S_ISDIR(tmp->i_mode)) { if (is_dir && (i == HFS_ITYPE_TO_INT(HFS_ITYPE_NORM))) { /* In "normal" directory only */ --(tmp->i_nlink); } tmp->i_size -= HFS_I(tmp)->dir_size; tmp->i_version = ++event; } tmp->i_ctime = tmp->i_mtime = CURRENT_TIME; mark_inode_dirty(tmp); } } } /* * mark_inodes_deleted() * * Update inodes associated with a deleted entry to reflect its deletion. * Well, we really just drop the dentry. */ static inline void mark_inodes_deleted(struct hfs_cat_entry *entry, struct dentry *dentry) { struct dentry *de; int i; for (i = 0; i < 4; ++i) { if ((de = entry->sys_entry[i]) && (dentry != de)) { dget(de); d_delete(de); dput(de); } } } /*================ Global functions ================*/ /* * hfs_dir_read() * * This is the read() entry in the file_operations structure for HFS * directories. It simply returns an error code, since reading is not * supported. */ hfs_rwret_t hfs_dir_read(struct file * filp, char *buf, hfs_rwarg_t count, loff_t *ppos) { return -EISDIR; } /* * hfs_create() * * This is the create() entry in the inode_operations structure for * regular HFS directories. The purpose is to create a new file in * a directory and return a corresponding inode, given the inode for * the directory and the name (and its length) of the new file. */ int hfs_create(struct inode * dir, struct dentry *dentry, int mode) { struct hfs_cat_entry *entry = HFS_I(dir)->entry; struct hfs_cat_entry *new; struct hfs_cat_key key; struct inode *inode; int error; /* build the key, checking against reserved names */ if (build_key(&key, dir, dentry->d_name.name, dentry->d_name.len)) { error = -EEXIST; } else { /* try to create the file */ error = hfs_cat_create(entry, &key, (mode & S_IWUSR) ? 0 : HFS_FIL_LOCK, HFS_SB(dir->i_sb)->s_type, HFS_SB(dir->i_sb)->s_creator, &new); } if (!error) { update_dirs_plus(entry, 0); /* create an inode for the new file */ inode = hfs_iget(new, HFS_I(dir)->file_type, dentry); if (!inode) { /* XXX correct error? */ error = -EIO; } else { if (HFS_I(dir)->d_drop_op) HFS_I(dir)->d_drop_op(dentry, HFS_I(dir)->file_type); d_instantiate(dentry, inode); } } return error; } /* * hfs_mkdir() * * This is the mkdir() entry in the inode_operations structure for * regular HFS directories. The purpose is to create a new directory * in a directory, given the inode for the parent directory and the * name (and its length) of the new directory. */ int hfs_mkdir(struct inode * parent, struct dentry *dentry, int mode) { struct hfs_cat_entry *entry = HFS_I(parent)->entry; struct hfs_cat_entry *new; struct hfs_cat_key key; struct inode *inode; int error; /* build the key, checking against reserved names */ if (build_key(&key, parent, dentry->d_name.name, dentry->d_name.len)) { error = -EEXIST; } else { /* try to create the directory */ error = hfs_cat_mkdir(entry, &key, &new); } if (!error) { update_dirs_plus(entry, 1); inode = hfs_iget(new, HFS_I(parent)->file_type, dentry); if (!inode) { error = -EIO; } else d_instantiate(dentry, inode); } return error; } /* * hfs_mknod() * * This is the mknod() entry in the inode_operations structure for * regular HFS directories. The purpose is to create a new entry * in a directory, given the inode for the parent directory and the * name (and its length) and the mode of the new entry (and the device * number if the entry is to be a device special file). * * HFS only supports regular files and directories and Linux disallows * using mknod() to create directories. Thus we just check the arguments * and call hfs_create(). */ int hfs_mknod(struct inode *dir, struct dentry *dentry, int mode, int rdev) { int error; if (!dir) { error = -ENOENT; } else if (S_ISREG(mode)) { error = hfs_create(dir, dentry, mode); } else { error = -EPERM; } return error; } /* * hfs_unlink() * * This is the unlink() entry in the inode_operations structure for * regular HFS directories. The purpose is to delete an existing * file, given the inode for the parent directory and the name * (and its length) of the existing file. */ int hfs_unlink(struct inode * dir, struct dentry *dentry) { struct hfs_cat_entry *entry = HFS_I(dir)->entry; struct hfs_cat_entry *victim = NULL; struct hfs_cat_key key; int error; if (build_key(&key, dir, dentry->d_name.name, dentry->d_name.len)) { error = -EPERM; } else if (!(victim = hfs_cat_get(entry->mdb, &key))) { error = -ENOENT; } else if (victim->type != HFS_CDR_FIL) { error = -EPERM; } else if (!(error = hfs_cat_delete(entry, victim, 1))) { mark_inodes_deleted(victim, dentry); d_delete(dentry); update_dirs_minus(entry, 0); } hfs_cat_put(victim); /* Note that hfs_cat_put(NULL) is safe. */ return error; } /* * hfs_rmdir() * * This is the rmdir() entry in the inode_operations structure for * regular HFS directories. The purpose is to delete an existing * directory, given the inode for the parent directory and the name * (and its length) of the existing directory. */ int hfs_rmdir(struct inode * parent, struct dentry *dentry) { struct hfs_cat_entry *entry = HFS_I(parent)->entry; struct hfs_cat_entry *victim = NULL; struct hfs_cat_key key; int error; if (build_key(&key, parent, dentry->d_name.name, dentry->d_name.len)) { error = -EPERM; } else if (!(victim = hfs_cat_get(entry->mdb, &key))) { error = -ENOENT; } else if (victim->type != HFS_CDR_DIR) { error = -ENOTDIR; } else if (/* we only have to worry about 2 and 3 for mount points */ (victim->sys_entry[2] && (victim->sys_entry[2] != victim->sys_entry[2]->d_mounts)) || (victim->sys_entry[3] && (victim->sys_entry[3] != victim->sys_entry[3]->d_mounts)) ) { error = -EBUSY; } else if (!(error = hfs_cat_delete(entry, victim, 1))) { mark_inodes_deleted(victim, dentry); d_delete(dentry); update_dirs_minus(entry, 1); } hfs_cat_put(victim); /* Note that hfs_cat_put(NULL) is safe. */ return error; } /* * hfs_rename() * * This is the rename() entry in the inode_operations structure for * regular HFS directories. The purpose is to rename an existing * file or directory, given the inode for the current directory and * the name (and its length) of the existing file/directory and the * inode for the new directory and the name (and its length) of the * new file/directory. * XXX: how do you handle must_be dir? */ int hfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { struct hfs_cat_entry *old_parent = HFS_I(old_dir)->entry; struct hfs_cat_entry *new_parent = HFS_I(new_dir)->entry; struct hfs_cat_entry *victim = NULL; struct hfs_cat_entry *deleted; struct hfs_cat_key key; int error; if (build_key(&key, old_dir, old_dentry->d_name.name, old_dentry->d_name.len) || (HFS_ITYPE(old_dir->i_ino) != HFS_ITYPE(new_dir->i_ino))) { error = -EPERM; } else if (!(victim = hfs_cat_get(old_parent->mdb, &key))) { error = -ENOENT; } else if (build_key(&key, new_dir, new_dentry->d_name.name, new_dentry->d_name.len)) { error = -EPERM; } else if (!(error = hfs_cat_move(old_parent, new_parent, victim, &key, &deleted))) { int is_dir = (victim->type == HFS_CDR_DIR); /* drop the old dentries */ mark_inodes_deleted(victim, old_dentry); update_dirs_minus(old_parent, is_dir); if (deleted) { mark_inodes_deleted(deleted, new_dentry); hfs_cat_put(deleted); } else { /* no existing inodes. just drop negative dentries */ if (HFS_I(new_dir)->d_drop_op) HFS_I(new_dir)->d_drop_op(new_dentry, HFS_I(new_dir)->file_type); update_dirs_plus(new_parent, is_dir); } /* update dcache */ d_move(old_dentry, new_dentry); } hfs_cat_put(victim); /* Note that hfs_cat_put(NULL) is safe. */ return error; }