summaryrefslogtreecommitdiffstats
path: root/fs/namei.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1999-02-15 02:15:32 +0000
committerRalf Baechle <ralf@linux-mips.org>1999-02-15 02:15:32 +0000
commit86464aed71025541805e7b1515541aee89879e33 (patch)
treee01a457a4912a8553bc65524aa3125d51f29f810 /fs/namei.c
parent88f99939ecc6a95a79614574cb7d95ffccfc3466 (diff)
Merge with Linux 2.2.1.
Diffstat (limited to 'fs/namei.c')
-rw-r--r--fs/namei.c508
1 files changed, 267 insertions, 241 deletions
diff --git a/fs/namei.c b/fs/namei.c
index 84208808a..d89d6d64c 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -95,18 +95,6 @@
* [10-Sep-98 Alan Modra] Another symlink change.
*/
-static inline char * get_page(void)
-{
- char * res;
- res = (char*)__get_free_page(GFP_KERNEL);
- return res;
-}
-
-inline void putname(char * name)
-{
- free_page((unsigned long) name);
-}
-
/* In order to reduce some races, while at the same time doing additional
* checking and hopefully speeding things up, we copy filenames to the
* kernel data space before using them..
@@ -139,7 +127,7 @@ char * getname(const char * filename)
char *tmp, *result;
result = ERR_PTR(-ENOMEM);
- tmp = get_page();
+ tmp = __getname();
if (tmp) {
int retval = do_getname(filename, tmp);
@@ -490,6 +478,77 @@ struct dentry * __namei(const char *pathname, unsigned int lookup_flags)
return dentry;
}
+/*
+ * It's inline, so penalty for filesystems that don't use sticky bit is
+ * minimal.
+ */
+static inline int check_sticky(struct inode *dir, struct inode *inode)
+{
+ if (!(dir->i_mode & S_ISVTX))
+ return 0;
+ if (inode->i_uid == current->fsuid)
+ return 0;
+ if (dir->i_uid == current->fsuid)
+ return 0;
+ return !capable(CAP_FOWNER);
+}
+
+/*
+ * Check whether we can remove a link victim from directory dir, check
+ * whether the type of victim is right.
+ * 1. We can't do it if dir is read-only (done in permission())
+ * 2. We should have write and exec permissions on dir
+ * 3. We can't remove anything from append-only dir
+ * 4. We can't do anything with immutable dir (done in permission())
+ * 5. If the sticky bit on dir is set we should either
+ * a. be owner of dir, or
+ * b. be owner of victim, or
+ * c. have CAP_FOWNER capability
+ * 6. If the victim is append-only or immutable we can't do antyhing with
+ * links pointing to it.
+ * 7. If we were asked to remove a directory and victim isn't one - ENOTDIR.
+ * 8. If we were asked to remove a non-directory and victim isn't one - EISDIR.
+ * 9. We can't remove a root or mountpoint.
+ */
+static inline int may_delete(struct inode *dir,struct dentry *victim, int isdir)
+{
+ int error;
+ if (!victim->d_inode || victim->d_parent->d_inode != dir)
+ return -ENOENT;
+ error = permission(dir,MAY_WRITE | MAY_EXEC);
+ if (error)
+ return error;
+ if (IS_APPEND(dir))
+ return -EPERM;
+ if (check_sticky(dir, victim->d_inode)||IS_APPEND(victim->d_inode)||
+ IS_IMMUTABLE(victim->d_inode))
+ return -EPERM;
+ if (isdir) {
+ if (!S_ISDIR(victim->d_inode->i_mode))
+ return -ENOTDIR;
+ if (IS_ROOT(victim))
+ return -EBUSY;
+ if (victim->d_mounts != victim->d_covers)
+ return -EBUSY;
+ } else if (S_ISDIR(victim->d_inode->i_mode))
+ return -EISDIR;
+ return 0;
+}
+
+/* Check whether we can create an object with dentry child in directory
+ * dir.
+ * 1. We can't do it if child already exists (open has special treatment for
+ * this case, but since we are inlined it's OK)
+ * 2. We can't do it if dir is read-only (done in permission())
+ * 3. We should have write and exec permissions on dir
+ * 4. We can't do it if dir is immutable (done in permission())
+ */
+static inline int may_create(struct inode *dir, struct dentry *child) {
+ if (child->d_inode)
+ return -EEXIST;
+ return permission(dir,MAY_WRITE | MAY_EXEC);
+}
+
static inline struct dentry *get_parent(struct dentry *dentry)
{
return dget(dentry->d_parent);
@@ -502,6 +561,20 @@ static inline void unlock_dir(struct dentry *dir)
}
/*
+ * We need to do a check-parent every time
+ * after we have locked the parent - to verify
+ * that the parent is still our parent and
+ * that we are still hashed onto it..
+ *
+ * This is requied in case two processes race
+ * on removing (or moving) the same entry: the
+ * parent lock will serialize them, but the
+ * other process will be too late..
+ */
+#define check_parent(dir, dentry) \
+ ((dir) == (dentry)->d_parent && !list_empty(&dentry->d_hash))
+
+/*
* Locking the parent is needed to:
* - serialize directory operations
* - make sure the parent doesn't change from
@@ -518,17 +591,41 @@ static inline struct dentry *lock_parent(struct dentry *dentry)
struct dentry *dir = dget(dentry->d_parent);
down(&dir->d_inode->i_sem);
+ return dir;
+}
- /* Un-hashed or moved? Punt if so.. */
- if (dir != dentry->d_parent || list_empty(&dentry->d_hash)) {
- if (dir != dentry) {
- unlock_dir(dir);
- dir = ERR_PTR(-ENOENT);
+/*
+ * Whee.. Deadlock country. Happily there are only two VFS
+ * operations that do this..
+ */
+static inline void double_lock(struct dentry *d1, struct dentry *d2)
+{
+ struct semaphore *s1 = &d1->d_inode->i_sem;
+ struct semaphore *s2 = &d2->d_inode->i_sem;
+
+ if (s1 != s2) {
+ if ((unsigned long) s1 < (unsigned long) s2) {
+ struct semaphore *tmp = s2;
+ s2 = s1; s1 = tmp;
}
+ down(s1);
}
- return dir;
+ down(s2);
+}
+
+static inline void double_unlock(struct dentry *d1, struct dentry *d2)
+{
+ struct semaphore *s1 = &d1->d_inode->i_sem;
+ struct semaphore *s2 = &d2->d_inode->i_sem;
+
+ up(s1);
+ if (s1 != s2)
+ up(s2);
+ dput(d1);
+ dput(d2);
}
+
/*
* Special case: O_CREAT|O_EXCL implies O_NOFOLLOW for security
* reasons.
@@ -581,14 +678,28 @@ struct dentry * open_namei(const char * pathname, int flag, int mode)
if (flag & O_CREAT) {
struct dentry *dir;
- error = -EEXIST;
- if (dentry->d_inode && (flag & O_EXCL))
+ if (dentry->d_inode) {
+ if (!(flag & O_EXCL))
+ goto nocreate;
+ error = -EEXIST;
goto exit;
+ }
dir = lock_parent(dentry);
- error = PTR_ERR(dir);
- if (IS_ERR(dir))
+ if (!check_parent(dir, dentry)) {
+ /*
+ * Really nasty race happened. What's the
+ * right error code? We had a dentry, but
+ * before we could use it it was removed
+ * by somebody else. We could just re-try
+ * everything, I guess.
+ *
+ * ENOENT is definitely wrong.
+ */
+ error = -ENOENT;
+ unlock_dir(dir);
goto exit;
+ }
/*
* Somebody might have created the file while we
@@ -599,22 +710,23 @@ struct dentry * open_namei(const char * pathname, int flag, int mode)
error = 0;
if (flag & O_EXCL)
error = -EEXIST;
- } else if (IS_RDONLY(dir->d_inode))
- error = -EROFS;
- else if (!dir->d_inode->i_op || !dir->d_inode->i_op->create)
- error = -EACCES;
- else if ((error = permission(dir->d_inode,MAY_WRITE | MAY_EXEC)) == 0) {
- DQUOT_INIT(dir->d_inode);
- error = dir->d_inode->i_op->create(dir->d_inode, dentry, mode);
- /* Don't check for write permission, don't truncate */
- acc_mode = 0;
- flag &= ~O_TRUNC;
+ } else if ((error = may_create(dir->d_inode, dentry)) == 0) {
+ if (!dir->d_inode->i_op || !dir->d_inode->i_op->create)
+ error = -EACCES;
+ else {
+ DQUOT_INIT(dir->d_inode);
+ error = dir->d_inode->i_op->create(dir->d_inode, dentry, mode);
+ /* Don't check for write permission, don't truncate */
+ acc_mode = 0;
+ flag &= ~O_TRUNC;
+ }
}
unlock_dir(dir);
if (error)
goto exit;
}
+nocreate:
error = -ENOENT;
inode = dentry->d_inode;
if (!inode)
@@ -701,36 +813,25 @@ struct dentry * do_mknod(const char * filename, int mode, dev_t dev)
return dentry;
dir = lock_parent(dentry);
- retval = dir;
- if (IS_ERR(dir))
- goto exit;
-
- retval = ERR_PTR(-EEXIST);
- if (dentry->d_inode)
- goto exit_lock;
-
- retval = ERR_PTR(-EROFS);
- if (IS_RDONLY(dir->d_inode))
+ error = -ENOENT;
+ if (!check_parent(dir, dentry))
goto exit_lock;
- error = permission(dir->d_inode,MAY_WRITE | MAY_EXEC);
- retval = ERR_PTR(error);
+ error = may_create(dir->d_inode, dentry);
if (error)
goto exit_lock;
- retval = ERR_PTR(-EPERM);
+ error = -EPERM;
if (!dir->d_inode->i_op || !dir->d_inode->i_op->mknod)
goto exit_lock;
DQUOT_INIT(dir->d_inode);
error = dir->d_inode->i_op->mknod(dir->d_inode, dentry, mode, dev);
+exit_lock:
retval = ERR_PTR(error);
if (!error)
retval = dget(dentry);
-
-exit_lock:
unlock_dir(dir);
-exit:
dput(dentry);
return retval;
}
@@ -785,20 +886,20 @@ static inline int do_mkdir(const char * pathname, int mode)
if (IS_ERR(dentry))
goto exit;
+ /*
+ * EEXIST is kind of a strange error code to
+ * return, but basically if the dentry was moved
+ * or unlinked while we locked the parent, we
+ * do know that it _did_ exist before, and as
+ * such it makes perfect sense.. In contrast,
+ * ENOENT doesn't make sense for mkdir.
+ */
dir = lock_parent(dentry);
- error = PTR_ERR(dir);
- if (IS_ERR(dir))
- goto exit_dput;
-
error = -EEXIST;
- if (dentry->d_inode)
- goto exit_lock;
-
- error = -EROFS;
- if (IS_RDONLY(dir->d_inode))
+ if (!check_parent(dir, dentry))
goto exit_lock;
- error = permission(dir->d_inode,MAY_WRITE | MAY_EXEC);
+ error = may_create(dir->d_inode, dentry);
if (error)
goto exit_lock;
@@ -812,7 +913,6 @@ static inline int do_mkdir(const char * pathname, int mode)
exit_lock:
unlock_dir(dir);
-exit_dput:
dput(dentry);
exit:
return error;
@@ -834,89 +934,18 @@ asmlinkage int sys_mkdir(const char * pathname, int mode)
return error;
}
-/*
- * Whee.. Deadlock country. Happily there are only two VFS
- * operations that do this..
- */
-static inline void double_lock(struct dentry *d1, struct dentry *d2)
-{
- struct semaphore *s1 = &d1->d_inode->i_sem;
- struct semaphore *s2 = &d2->d_inode->i_sem;
-
- if (s1 != s2) {
- if ((unsigned long) s1 < (unsigned long) s2) {
- struct semaphore *tmp = s2;
- s2 = s1; s1 = tmp;
- }
- down(s1);
- }
- down(s2);
-}
-
-static inline void double_unlock(struct dentry *d1, struct dentry *d2)
-{
- struct semaphore *s1 = &d1->d_inode->i_sem;
- struct semaphore *s2 = &d2->d_inode->i_sem;
-
- up(s1);
- if (s1 != s2)
- up(s2);
- dput(d1);
- dput(d2);
-}
-
-static inline int do_rmdir(const char * name)
+int vfs_rmdir(struct inode *dir, struct dentry *dentry)
{
int error;
- struct dentry *dir;
- struct dentry *dentry;
-
- dentry = lookup_dentry(name, NULL, 0);
- error = PTR_ERR(dentry);
- if (IS_ERR(dentry))
- goto exit;
-
- dir = dget(dentry->d_parent);
-
- error = -ENOENT;
- if (!dentry->d_inode)
- goto exit;
- /*
- * The dentry->d_count stuff confuses d_delete() enough to
- * not kill the inode from under us while it is locked. This
- * wouldn't be needed, except the dentry semaphore is really
- * in the inode, not in the dentry..
- */
- dentry->d_count++;
- double_lock(dir, dentry);
- if (dentry->d_parent != dir)
- goto exit_lock;
- error = -EROFS;
- if (IS_RDONLY(dir->d_inode))
- goto exit_lock;
-
- error = permission(dir->d_inode,MAY_WRITE | MAY_EXEC);
+ error = may_delete(dir, dentry, 1);
if (error)
- goto exit_lock;
-
- /*
- * A subdirectory cannot be removed from an append-only directory.
- */
- error = -EPERM;
- if (IS_APPEND(dir->d_inode))
- goto exit_lock;
-
- /* Disallow removals of mountpoints. */
- error = -EBUSY;
- if (dentry->d_mounts != dentry->d_covers)
- goto exit_lock;
+ return error;
- error = -EPERM;
- if (!dir->d_inode->i_op || !dir->d_inode->i_op->rmdir)
- goto exit_lock;
+ if (!dir->i_op || !dir->i_op->rmdir)
+ return -EPERM;
- DQUOT_INIT(dir->d_inode);
+ DQUOT_INIT(dir);
/*
* We try to drop the dentry early: we should have
@@ -942,11 +971,44 @@ static inline int do_rmdir(const char * name)
d_drop(dentry);
}
- error = dir->d_inode->i_op->rmdir(dir->d_inode, dentry);
+ error = dir->i_op->rmdir(dir, dentry);
+
+ return error;
+}
+
+static inline int do_rmdir(const char * name)
+{
+ int error;
+ struct dentry *dir;
+ struct dentry *dentry;
+
+ dentry = lookup_dentry(name, NULL, 0);
+ error = PTR_ERR(dentry);
+ if (IS_ERR(dentry))
+ goto exit;
+
+ error = -ENOENT;
+ if (!dentry->d_inode)
+ goto exit_dput;
+
+ dir = dget(dentry->d_parent);
+
+ /*
+ * The dentry->d_count stuff confuses d_delete() enough to
+ * not kill the inode from under us while it is locked. This
+ * wouldn't be needed, except the dentry semaphore is really
+ * in the inode, not in the dentry..
+ */
+ dentry->d_count++;
+ double_lock(dir, dentry);
+
+ error = -ENOENT;
+ if (check_parent(dir, dentry))
+ error = vfs_rmdir(dir->d_inode, dentry);
-exit_lock:
- dentry->d_count--;
double_unlock(dentry, dir);
+exit_dput:
+ dput(dentry);
exit:
return error;
}
@@ -967,6 +1029,25 @@ asmlinkage int sys_rmdir(const char * pathname)
return error;
}
+int vfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+ int error;
+
+ error = may_delete(dir, dentry, 0);
+ if (error)
+ goto exit_lock;
+
+ if (!dir->i_op || !dir->i_op->unlink)
+ goto exit_lock;
+
+ DQUOT_INIT(dir);
+
+ error = dir->i_op->unlink(dir, dentry);
+
+exit_lock:
+ return error;
+}
+
static inline int do_unlink(const char * name)
{
int error;
@@ -979,48 +1060,11 @@ static inline int do_unlink(const char * name)
goto exit;
dir = lock_parent(dentry);
- error = PTR_ERR(dir);
- if (IS_ERR(dir))
- goto exit_dput;
-
error = -ENOENT;
- if (!dentry->d_inode)
- goto exit_lock;
-
- /* Mount point? */
- error = -EBUSY;
- if (dentry == dir)
- goto exit_lock;
-
- error = -EROFS;
- if (IS_RDONLY(dir->d_inode))
- goto exit_lock;
+ if (check_parent(dir, dentry))
+ error = vfs_unlink(dir->d_inode, dentry);
- error = permission(dir->d_inode,MAY_WRITE | MAY_EXEC);
- if (error)
- goto exit_lock;
-
- /*
- * A directory can't be unlink'ed.
- * A file cannot be removed from an append-only directory.
- */
- error = -EPERM;
- if (S_ISDIR(dentry->d_inode->i_mode))
- goto exit_lock;
-
- if (IS_APPEND(dir->d_inode))
- goto exit_lock;
-
- if (!dir->d_inode->i_op || !dir->d_inode->i_op->unlink)
- goto exit_lock;
-
- DQUOT_INIT(dir->d_inode);
-
- error = dir->d_inode->i_op->unlink(dir->d_inode, dentry);
-
-exit_lock:
unlock_dir(dir);
-exit_dput:
dput(dentry);
exit:
return error;
@@ -1055,19 +1099,11 @@ static inline int do_symlink(const char * oldname, const char * newname)
goto exit;
dir = lock_parent(dentry);
- error = PTR_ERR(dir);
- if (IS_ERR(dir))
- goto exit_dput;
-
- error = -EEXIST;
- if (dentry->d_inode)
- goto exit_lock;
-
- error = -EROFS;
- if (IS_RDONLY(dir->d_inode))
+ error = -ENOENT;
+ if (!check_parent(dir, dentry))
goto exit_lock;
- error = permission(dir->d_inode,MAY_WRITE | MAY_EXEC);
+ error = may_create(dir->d_inode, dentry);
if (error)
goto exit_lock;
@@ -1080,7 +1116,6 @@ static inline int do_symlink(const char * oldname, const char * newname)
exit_lock:
unlock_dir(dir);
-exit_dput:
dput(dentry);
exit:
return error;
@@ -1134,31 +1169,23 @@ static inline int do_link(const char * oldname, const char * newname)
goto exit_old;
dir = lock_parent(new_dentry);
- error = PTR_ERR(dir);
- if (IS_ERR(dir))
- goto exit_new;
+ error = -ENOENT;
+ if (!check_parent(dir, new_dentry))
+ goto exit_lock;
error = -ENOENT;
inode = old_dentry->d_inode;
if (!inode)
goto exit_lock;
- error = -EEXIST;
- if (new_dentry->d_inode)
- goto exit_lock;
-
- error = -EROFS;
- if (IS_RDONLY(dir->d_inode))
+ error = may_create(dir->d_inode, new_dentry);
+ if (error)
goto exit_lock;
error = -EXDEV;
if (dir->d_inode->i_dev != inode->i_dev)
goto exit_lock;
- error = permission(dir->d_inode, MAY_WRITE | MAY_EXEC);
- if (error)
- goto exit_lock;
-
/*
* A link to an append-only or immutable file cannot be created.
*/
@@ -1175,7 +1202,6 @@ static inline int do_link(const char * oldname, const char * newname)
exit_lock:
unlock_dir(dir);
-exit_new:
dput(new_dentry);
exit_old:
dput(old_dentry);
@@ -1205,6 +1231,38 @@ asmlinkage int sys_link(const char * oldname, const char * newname)
return error;
}
+int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
+ struct inode *new_dir, struct dentry *new_dentry)
+{
+ int error;
+ int isdir;
+
+ isdir = S_ISDIR(old_dentry->d_inode->i_mode);
+
+ error = may_delete(old_dir, old_dentry, isdir); /* XXX */
+ if (error)
+ return error;
+
+ if (new_dir->i_dev != old_dir->i_dev)
+ return -EXDEV;
+
+ if (!new_dentry->d_inode)
+ error = may_create(new_dir, new_dentry);
+ else
+ error = may_delete(new_dir, new_dentry, isdir);
+ if (error)
+ return error;
+
+ if (!old_dir->i_op || !old_dir->i_op->rename)
+ return -EPERM;
+
+ DQUOT_INIT(old_dir);
+ DQUOT_INIT(new_dir);
+ error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
+
+ return error;
+}
+
static inline int do_rename(const char * oldname, const char * newname)
{
int error;
@@ -1237,43 +1295,11 @@ static inline int do_rename(const char * oldname, const char * newname)
double_lock(new_dir, old_dir);
- error = permission(old_dir->d_inode,MAY_WRITE | MAY_EXEC);
- if (error)
- goto exit_lock;
- error = permission(new_dir->d_inode,MAY_WRITE | MAY_EXEC);
- if (error)
- goto exit_lock;
-
- /* Disallow moves of mountpoints. */
- error = -EBUSY;
- if (old_dir == old_dentry || new_dir == new_dentry)
- goto exit_lock;
-
- error = -EXDEV;
- if (new_dir->d_inode->i_dev != old_dir->d_inode->i_dev)
- goto exit_lock;
-
- error = -EROFS;
- if (IS_RDONLY(new_dir->d_inode) || IS_RDONLY(old_dir->d_inode))
- goto exit_lock;
-
- /*
- * A file cannot be removed from an append-only directory.
- */
- error = -EPERM;
- if (IS_APPEND(old_dir->d_inode))
- goto exit_lock;
-
- error = -EPERM;
- if (!old_dir->d_inode->i_op || !old_dir->d_inode->i_op->rename)
- goto exit_lock;
-
- DQUOT_INIT(old_dir->d_inode);
- DQUOT_INIT(new_dir->d_inode);
- error = old_dir->d_inode->i_op->rename(old_dir->d_inode, old_dentry,
- new_dir->d_inode, new_dentry);
+ error = -ENOENT;
+ if (check_parent(old_dir, old_dentry) && check_parent(new_dir, new_dentry))
+ error = vfs_rename(old_dir->d_inode, old_dentry,
+ new_dir->d_inode, new_dentry);
-exit_lock:
double_unlock(new_dir, old_dir);
dput(new_dentry);
exit_old: