X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=fs%2Fnamei.c;h=90a0e3750e0af7bcba9918fbf61dd67854005031;hb=43bc926fffd92024b46cafaf7350d669ba9ca884;hp=aac4502614d82cfc9c01cbeafe8ea7eb21d87695;hpb=c7b5ebbddf7bcd3651947760f423e3783bbe6573;p=linux-2.6.git diff --git a/fs/namei.c b/fs/namei.c index aac450261..90a0e3750 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -21,14 +21,20 @@ #include #include #include -#include +#include #include #include #include +#include #include #include -#include - +#include +#include +#include +#include +#include +#include +#include #include #include @@ -101,7 +107,7 @@ */ /* * [Sep 2001 AV] Single-semaphore locking scheme (kudos to David Holland) - * implemented. Let's see if raised priority of ->s_vfs_rename_sem gives + * implemented. Let's see if raised priority of ->s_vfs_rename_mutex gives * any extra contention... */ @@ -112,18 +118,19 @@ * POSIX.1 2.4: an empty pathname is invalid (ENOENT). * PATH_MAX includes the nul terminator --RR. */ -static inline int do_getname(const char __user *filename, char *page) +static int do_getname(const char __user *filename, char *page) { int retval; unsigned long len = PATH_MAX; - if ((unsigned long) filename >= TASK_SIZE) { - if (!segment_eq(get_fs(), KERNEL_DS)) + if (!segment_eq(get_fs(), KERNEL_DS)) { + if ((unsigned long) filename >= TASK_SIZE) return -EFAULT; - } else if (TASK_SIZE - (unsigned long) filename < PATH_MAX) - len = TASK_SIZE - (unsigned long) filename; + if (TASK_SIZE - (unsigned long) filename < PATH_MAX) + len = TASK_SIZE - (unsigned long) filename; + } - retval = strncpy_from_user((char *)page, filename, len); + retval = strncpy_from_user(page, filename, len); if (retval > 0) { if (retval < len) return 0; @@ -148,42 +155,52 @@ char * getname(const char __user * filename) result = ERR_PTR(retval); } } - if (unlikely(current->audit_context) && !IS_ERR(result) && result) - audit_getname(result); + audit_getname(result); return result; } -/* - * vfs_permission() +#ifdef CONFIG_AUDITSYSCALL +void putname(const char *name) +{ + if (unlikely(current->audit_context)) + audit_putname(name); + else + __putname(name); +} +EXPORT_SYMBOL(putname); +#endif + + +/** + * generic_permission - check for access rights on a Posix-like filesystem + * @inode: inode to check access rights for + * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC) + * @check_acl: optional callback to check for Posix ACLs * - * is used to check for read/write/execute permissions on a file. + * Used to check for read/write/execute permissions on a file. * We use "fsuid" for this, letting us set arbitrary permissions * for filesystem access without changing the "normal" uids which * are used for other things.. */ -int vfs_permission(struct inode * inode, int mask) +int generic_permission(struct inode *inode, int mask, + int (*check_acl)(struct inode *inode, int mask)) { umode_t mode = inode->i_mode; - if (mask & MAY_WRITE) { - /* - * Nobody gets write access to a read-only fs. - */ - if (IS_RDONLY(inode) && - (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) - return -EROFS; - - /* - * Nobody gets write access to an immutable file. - */ - if (IS_IMMUTABLE(inode)) - return -EACCES; - } - if (current->fsuid == inode->i_uid) mode >>= 6; - else if (in_group_p(inode->i_gid)) - mode >>= 3; + else { + if (IS_POSIXACL(inode) && (mode & S_IRWXG) && check_acl) { + int error = check_acl(inode, mask); + if (error == -EACCES) + goto check_capabilities; + else if (error != -EAGAIN) + return error; + } + + if (in_group_p(inode->i_gid)) + mode >>= 3; + } /* * If the DACs are ok we don't need any capability check. @@ -191,6 +208,7 @@ int vfs_permission(struct inode * inode, int mask) if (((mode & mask & (MAY_READ|MAY_WRITE|MAY_EXEC)) == mask)) return 0; + check_capabilities: /* * Read/write DACs are always overridable. * Executable DACs are overridable if at least one exec bit is set. @@ -212,38 +230,90 @@ int vfs_permission(struct inode * inode, int mask) static inline int xid_permission(struct inode *inode, int mask, struct nameidata *nd) { + if (IS_BARRIER(inode) && !vx_check(0, VX_ADMIN)) { + vxwprintk(1, "xid=%d did hit the barrier.", + vx_current_xid()); + return -EACCES; + } if (inode->i_xid == 0) return 0; if (vx_check(inode->i_xid, VX_ADMIN|VX_WATCH|VX_IDENT)) return 0; -/* - printk("VSW: xid=%d denied access to %p[#%d,%lu] »%*s«.\n", + + vxwprintk(1, "xid=%d denied access to %p[#%d,%lu] »%s«.", vx_current_xid(), inode, inode->i_xid, inode->i_ino, - nd->dentry->d_name.len, nd->dentry->d_name.name); -*/ + vxd_cond_path(nd)); return -EACCES; } -int permission(struct inode * inode,int mask, struct nameidata *nd) +int permission(struct inode *inode, int mask, struct nameidata *nd) { - int retval; - int submask; + int retval, submask; + + if (mask & MAY_WRITE) { + umode_t mode = inode->i_mode; + + /* + * Nobody gets write access to a read-only fs. + */ + if ((IS_RDONLY(inode) || (nd && MNT_IS_RDONLY(nd->mnt))) && + (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) + return -EROFS; + + /* + * Nobody gets write access to an immutable file. + */ + if (IS_IMMUTABLE(inode)) + return -EACCES; + } + /* Ordinary permission routines do not understand MAY_APPEND. */ submask = mask & ~MAY_APPEND; - if ((retval = xid_permission(inode, mask, nd))) return retval; if (inode->i_op && inode->i_op->permission) retval = inode->i_op->permission(inode, submask, nd); else - retval = vfs_permission(inode, submask); + retval = generic_permission(inode, submask, NULL); if (retval) return retval; return security_inode_permission(inode, mask, nd); } +/** + * vfs_permission - check for access rights to a given path + * @nd: lookup result that describes the path + * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC) + * + * Used to check for read/write/execute permissions on a path. + * We use "fsuid" for this, letting us set arbitrary permissions + * for filesystem access without changing the "normal" uids which + * are used for other things. + */ +int vfs_permission(struct nameidata *nd, int mask) +{ + return permission(nd->dentry->d_inode, mask, nd); +} + +/** + * file_permission - check for additional access rights to a given file + * @file: file to check access rights for + * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC) + * + * Used to check for read/write/execute permissions on an already opened + * file. + * + * Note: + * Do not use this function in new code. All access checks should + * be done using vfs_permission(). + */ +int file_permission(struct file *file, int mask) +{ + return permission(file->f_dentry->d_inode, mask, NULL); +} + /* * get_write_access() gets write permission for a file. * put_write_access() releases this write permission. @@ -303,7 +373,19 @@ void path_release(struct nameidata *nd) void path_release_on_umount(struct nameidata *nd) { dput(nd->dentry); - _mntput(nd->mnt); + mntput_no_expire(nd->mnt); +} + +/** + * release_open_intent - free up open intent resources + * @nd: pointer to nameidata + */ +void release_open_intent(struct nameidata *nd) +{ + if (nd->intent.open.file->f_dentry == NULL) + put_filp(nd->intent.open.file); + else + fput(nd->intent.open.file); } /* @@ -332,14 +414,14 @@ static struct dentry * cached_lookup(struct dentry * parent, struct qstr * name, /* * Short-cut version of permission(), for calling by * path_walk(), when dcache lock is held. Combines parts - * of permission() and vfs_permission(), and tests ONLY for + * of permission() and generic_permission(), and tests ONLY for * MAY_EXEC permission. * * If appropriate, check DAC only. If not appropriate, or * short-cut DAC fails, then call permission() to do more * complete permission check. */ -static inline int exec_permission_lite(struct inode *inode, +static int exec_permission_lite(struct inode *inode, struct nameidata *nd) { umode_t mode = inode->i_mode; @@ -382,7 +464,7 @@ static struct dentry * real_lookup(struct dentry * parent, struct qstr * name, s struct dentry * result; struct inode *dir = parent->d_inode; - down(&dir->i_sem); + mutex_lock(&dir->i_mutex); /* * First re-do the cached lookup just in case it was created * while we waited for the directory semaphore.. @@ -408,7 +490,7 @@ static struct dentry * real_lookup(struct dentry * parent, struct qstr * name, s else result = dentry; } - up(&dir->i_sem); + mutex_unlock(&dir->i_mutex); return result; } @@ -416,7 +498,7 @@ static struct dentry * real_lookup(struct dentry * parent, struct qstr * name, s * Uhhuh! Nasty case: the cache was re-populated while * we waited on the semaphore. Need to revalidate. */ - up(&dir->i_sem); + mutex_unlock(&dir->i_mutex); if (result->d_op && result->d_op->d_revalidate) { if (!result->d_op->d_revalidate(result, nd) && !d_invalidate(result)) { dput(result); @@ -429,7 +511,7 @@ static struct dentry * real_lookup(struct dentry * parent, struct qstr * name, s static int __emul_lookup_dentry(const char *, struct nameidata *); /* SMP-safe */ -static inline int +static __always_inline int walk_init_root(const char *name, struct nameidata *nd) { read_lock(¤t->fs->lock); @@ -447,7 +529,7 @@ walk_init_root(const char *name, struct nameidata *nd) return 1; } -static inline int __vfs_follow_link(struct nameidata *nd, const char *link) +static __always_inline int __vfs_follow_link(struct nameidata *nd, const char *link) { int res = 0; char *name; @@ -482,6 +564,57 @@ fail: return PTR_ERR(link); } +struct path { + struct vfsmount *mnt; + struct dentry *dentry; +}; + +static inline void dput_path(struct path *path, struct nameidata *nd) +{ + dput(path->dentry); + if (path->mnt != nd->mnt) + mntput(path->mnt); +} + +static inline void path_to_nameidata(struct path *path, struct nameidata *nd) +{ + dput(nd->dentry); + if (nd->mnt != path->mnt) + mntput(nd->mnt); + nd->mnt = path->mnt; + nd->dentry = path->dentry; +} + +static __always_inline int __do_follow_link(struct path *path, struct nameidata *nd) +{ + int error; + void *cookie; + struct dentry *dentry = path->dentry; + + touch_atime(path->mnt, dentry); + nd_set_link(nd, NULL); + + if (path->mnt != nd->mnt) { + path_to_nameidata(path, nd); + dget(dentry); + } + mntget(path->mnt); + cookie = dentry->d_inode->i_op->follow_link(dentry, nd); + error = PTR_ERR(cookie); + if (!IS_ERR(cookie)) { + char *s = nd_get_link(nd); + error = 0; + if (s) + error = __vfs_follow_link(nd, s); + if (dentry->d_inode->i_op->put_link) + dentry->d_inode->i_op->put_link(dentry, nd, cookie); + } + dput(dentry); + mntput(path->mnt); + + return error; +} + /* * This limits recursive symlink follows to 8, while * limiting consecutive symlinks to 40. @@ -489,7 +622,7 @@ fail: * Without that kind of total limit, nasty chains of consecutive * symlinks can cause almost arbitrarily long lookups. */ -static inline int do_follow_link(struct dentry *dentry, struct nameidata *nd) +static inline int do_follow_link(struct path *path, struct nameidata *nd) { int err = -ELOOP; if (current->link_count >= MAX_NESTED_LINKS) @@ -498,26 +631,18 @@ static inline int do_follow_link(struct dentry *dentry, struct nameidata *nd) goto loop; BUG_ON(nd->depth >= MAX_NESTED_LINKS); cond_resched(); - err = security_inode_follow_link(dentry, nd); + err = security_inode_follow_link(path->dentry, nd); if (err) goto loop; current->link_count++; current->total_link_count++; nd->depth++; - touch_atime(nd->mnt, dentry); - nd_set_link(nd, NULL); - err = dentry->d_inode->i_op->follow_link(dentry, nd); - if (!err) { - char *s = nd_get_link(nd); - if (s) - err = __vfs_follow_link(nd, s); - if (dentry->d_inode->i_op->put_link) - dentry->d_inode->i_op->put_link(dentry, nd); - } + err = __do_follow_link(path, nd); current->link_count--; nd->depth--; return err; loop: + dput_path(path, nd); path_release(nd); return err; } @@ -545,114 +670,142 @@ int follow_up(struct vfsmount **mnt, struct dentry **dentry) /* no need for dcache_lock, as serialization is taken care in * namespace.c */ -static int follow_mount(struct vfsmount **mnt, struct dentry **dentry) +static int __follow_mount(struct path *path) { int res = 0; + while (d_mountpoint(path->dentry)) { + struct vfsmount *mounted = lookup_mnt(path->mnt, path->dentry); + if (!mounted) + break; + dput(path->dentry); + if (res) + mntput(path->mnt); + path->mnt = mounted; + path->dentry = dget(mounted->mnt_root); + res = 1; + } + return res; +} + +static void follow_mount(struct vfsmount **mnt, struct dentry **dentry) +{ while (d_mountpoint(*dentry)) { struct vfsmount *mounted = lookup_mnt(*mnt, *dentry); if (!mounted) break; + dput(*dentry); mntput(*mnt); *mnt = mounted; - dput(*dentry); *dentry = dget(mounted->mnt_root); - res = 1; } - return res; } /* no need for dcache_lock, as serialization is taken care in * namespace.c */ -static inline int __follow_down(struct vfsmount **mnt, struct dentry **dentry) +int follow_down(struct vfsmount **mnt, struct dentry **dentry) { struct vfsmount *mounted; mounted = lookup_mnt(*mnt, *dentry); if (mounted) { + dput(*dentry); mntput(*mnt); *mnt = mounted; - dput(*dentry); *dentry = dget(mounted->mnt_root); return 1; } return 0; } -int follow_down(struct vfsmount **mnt, struct dentry **dentry) -{ - return __follow_down(mnt,dentry); -} - -static inline void follow_dotdot(struct vfsmount **mnt, struct dentry **dentry) +static __always_inline void follow_dotdot(struct nameidata *nd) { while(1) { struct vfsmount *parent; - struct dentry *old = *dentry; + struct dentry *old = nd->dentry; read_lock(¤t->fs->lock); - if (*dentry == current->fs->root && - *mnt == current->fs->rootmnt) { + if (nd->dentry == current->fs->root && + nd->mnt == current->fs->rootmnt) { read_unlock(¤t->fs->lock); - break; + /* for sane '/' avoid follow_mount() */ + return; } read_unlock(¤t->fs->lock); spin_lock(&dcache_lock); - if (*dentry != (*mnt)->mnt_root) { - *dentry = dget((*dentry)->d_parent); + if (nd->dentry != nd->mnt->mnt_root) { + nd->dentry = dget(nd->dentry->d_parent); spin_unlock(&dcache_lock); dput(old); break; } spin_unlock(&dcache_lock); spin_lock(&vfsmount_lock); - parent = (*mnt)->mnt_parent; - if (parent == *mnt) { + parent = nd->mnt->mnt_parent; + if (parent == nd->mnt) { spin_unlock(&vfsmount_lock); break; } mntget(parent); - *dentry = dget((*mnt)->mnt_mountpoint); + nd->dentry = dget(nd->mnt->mnt_mountpoint); spin_unlock(&vfsmount_lock); dput(old); - mntput(*mnt); - *mnt = parent; + mntput(nd->mnt); + nd->mnt = parent; } - follow_mount(mnt, dentry); + follow_mount(&nd->mnt, &nd->dentry); } -struct path { - struct vfsmount *mnt; - struct dentry *dentry; -}; - /* * It's more convoluted than I'd like it to be, but... it's still fairly * small and for now I'd prefer to have fast path as straight as possible. * It _is_ time-critical. */ static int do_lookup(struct nameidata *nd, struct qstr *name, - struct path *path) + struct path *path, int atomic) { struct vfsmount *mnt = nd->mnt; struct dentry *dentry = __d_lookup(nd->dentry, name); + struct inode *inode; if (!dentry) goto need_lookup; if (dentry->d_op && dentry->d_op->d_revalidate) goto need_revalidate; + inode = dentry->d_inode; + if (!inode) + goto done; + if (!vx_check(inode->i_xid, VX_WATCH|VX_ADMIN|VX_HOSTID|VX_IDENT)) + goto hidden; + if (inode->i_sb->s_magic == PROC_SUPER_MAGIC) { + struct proc_dir_entry *de = PDE(inode); + + if (de && !vx_hide_check(0, de->vx_flags)) + goto hidden; + } done: path->mnt = mnt; path->dentry = dentry; + __follow_mount(path); return 0; +hidden: + vxwprintk(1, "xid=%d did lookup hidden %p[#%d,%lu] »%s«.", + vx_current_xid(), inode, inode->i_xid, inode->i_ino, + vxd_path(dentry, mnt)); + dput(dentry); + return -ENOENT; need_lookup: + if (atomic) + return -EWOULDBLOCKIO; dentry = real_lookup(nd->dentry, name, nd); if (IS_ERR(dentry)) goto fail; goto done; need_revalidate: + if (atomic) + return -EWOULDBLOCKIO; if (dentry->d_op->d_revalidate(dentry, nd)) goto done; if (d_invalidate(dentry)) @@ -666,19 +819,21 @@ fail: /* * Name resolution. + * This is the basic name resolution function, turning a pathname into + * the final dentry. We expect 'base' to be positive and a directory. * - * This is the basic name resolution function, turning a pathname - * into the final dentry. - * - * We expect 'base' to be positive and a directory. + * Returns 0 and nd will have valid dentry and mnt on success. + * Returns error and drops reference to input namei data on failure. */ -int fastcall link_path_walk(const char * name, struct nameidata *nd) +static fastcall int __link_path_walk(const char * name, struct nameidata *nd) { struct path next; struct inode *inode; - int err; + int err, atomic; unsigned int lookup_flags = nd->flags; - + + atomic = (lookup_flags & LOOKUP_ATOMIC); + while (*name=='/') name++; if (!*name) @@ -686,7 +841,7 @@ int fastcall link_path_walk(const char * name, struct nameidata *nd) inode = nd->dentry->d_inode; if (nd->depth) - lookup_flags = LOOKUP_FOLLOW; + lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE); /* At this point we know we have a real path component. */ for(;;) { @@ -694,10 +849,10 @@ int fastcall link_path_walk(const char * name, struct nameidata *nd) struct qstr this; unsigned int c; + nd->flags |= LOOKUP_CONTINUE; err = exec_permission_lite(inode, nd); - if (err == -EAGAIN) { - err = permission(inode, MAY_EXEC, nd); - } + if (err == -EAGAIN) + err = vfs_permission(nd, MAY_EXEC); if (err) break; @@ -731,7 +886,7 @@ int fastcall link_path_walk(const char * name, struct nameidata *nd) case 2: if (this.name[1] != '.') break; - follow_dotdot(&nd->mnt, &nd->dentry); + follow_dotdot(nd); inode = nd->dentry->d_inode; /* fallthrough */ case 1: @@ -746,13 +901,10 @@ int fastcall link_path_walk(const char * name, struct nameidata *nd) if (err < 0) break; } - nd->flags |= LOOKUP_CONTINUE; /* This does the actual lookups.. */ - err = do_lookup(nd, &this, &next); + err = do_lookup(nd, &this, &next, atomic); if (err) break; - /* Check mountpoints.. */ - follow_mount(&next.mnt, &next.dentry); err = -ENOENT; inode = next.dentry->d_inode; @@ -763,10 +915,7 @@ int fastcall link_path_walk(const char * name, struct nameidata *nd) goto out_dput; if (inode->i_op->follow_link) { - mntget(next.mnt); - err = do_follow_link(next.dentry, nd); - dput(next.dentry); - mntput(next.mnt); + err = do_follow_link(&next, nd); if (err) goto return_err; err = -ENOENT; @@ -776,11 +925,8 @@ int fastcall link_path_walk(const char * name, struct nameidata *nd) err = -ENOTDIR; if (!inode->i_op) break; - } else { - dput(nd->dentry); - nd->mnt = next.mnt; - nd->dentry = next.dentry; - } + } else + path_to_nameidata(&next, nd); err = -ENOTDIR; if (!inode->i_op->lookup) break; @@ -790,7 +936,8 @@ int fastcall link_path_walk(const char * name, struct nameidata *nd) last_with_slashes: lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY; last_component: - nd->flags &= ~LOOKUP_CONTINUE; + /* Clear LOOKUP_CONTINUE iff it was previously unset */ + nd->flags &= lookup_flags | ~LOOKUP_CONTINUE; if (lookup_flags & LOOKUP_PARENT) goto lookup_parent; if (this.name[0] == '.') switch (this.len) { @@ -799,7 +946,7 @@ last_component: case 2: if (this.name[1] != '.') break; - follow_dotdot(&nd->mnt, &nd->dentry); + follow_dotdot(nd); inode = nd->dentry->d_inode; /* fallthrough */ case 1: @@ -810,25 +957,18 @@ last_component: if (err < 0) break; } - err = do_lookup(nd, &this, &next); + err = do_lookup(nd, &this, &next, atomic); if (err) break; - follow_mount(&next.mnt, &next.dentry); inode = next.dentry->d_inode; if ((lookup_flags & LOOKUP_FOLLOW) && inode && inode->i_op && inode->i_op->follow_link) { - mntget(next.mnt); - err = do_follow_link(next.dentry, nd); - dput(next.dentry); - mntput(next.mnt); + err = do_follow_link(&next, nd); if (err) goto return_err; inode = nd->dentry->d_inode; - } else { - dput(nd->dentry); - nd->mnt = next.mnt; - nd->dentry = next.dentry; - } + } else + path_to_nameidata(&next, nd); err = -ENOENT; if (!inode) break; @@ -864,7 +1004,7 @@ return_reval: return_base: return 0; out_dput: - dput(next.dentry); + dput_path(&next, nd); break; } path_release(nd); @@ -872,14 +1012,47 @@ return_err: return err; } +/* + * Wrapper to retry pathname resolution whenever the underlying + * file system returns an ESTALE. + * + * Retry the whole path once, forcing real lookup requests + * instead of relying on the dcache. + */ +int fastcall link_path_walk(const char *name, struct nameidata *nd) +{ + struct nameidata save = *nd; + int result; + + /* make sure the stuff we saved doesn't go away */ + dget(save.dentry); + mntget(save.mnt); + + result = __link_path_walk(name, nd); + if (result == -ESTALE) { + *nd = save; + dget(nd->dentry); + mntget(nd->mnt); + nd->flags |= LOOKUP_REVAL; + result = __link_path_walk(name, nd); + } + + dput(save.dentry); + mntput(save.mnt); + + return result; +} + int fastcall path_walk(const char * name, struct nameidata *nd) { current->total_link_count = 0; return link_path_walk(name, nd); } -/* SMP-safe */ -/* returns 1 if everything is done */ +/* + * SMP-safe: Returns 1 and nd will have valid dentry and mnt, if + * everything is done. Returns 0 and drops input nd, if lookup failed; + */ static int __emul_lookup_dentry(const char *name, struct nameidata *nd) { if (path_walk(name, nd)) @@ -943,39 +1116,147 @@ set_it: } } -int fastcall path_lookup(const char *name, unsigned int flags, struct nameidata *nd) +/* Returns 0 and nd will be valid on success; Retuns error, otherwise. */ +static int fastcall do_path_lookup(int dfd, const char *name, + unsigned int flags, struct nameidata *nd) { - int retval; + int retval = 0; + int fput_needed; + struct file *file; nd->last_type = LAST_ROOT; /* if there are only slashes... */ nd->flags = flags; nd->depth = 0; - read_lock(¤t->fs->lock); if (*name=='/') { + read_lock(¤t->fs->lock); if (current->fs->altroot && !(nd->flags & LOOKUP_NOALT)) { nd->mnt = mntget(current->fs->altrootmnt); nd->dentry = dget(current->fs->altroot); read_unlock(¤t->fs->lock); if (__emul_lookup_dentry(name,nd)) - return 0; + goto out; /* found in altroot */ read_lock(¤t->fs->lock); } nd->mnt = mntget(current->fs->rootmnt); nd->dentry = dget(current->fs->root); - } else { + read_unlock(¤t->fs->lock); + } else if (dfd == AT_FDCWD) { + read_lock(¤t->fs->lock); nd->mnt = mntget(current->fs->pwdmnt); nd->dentry = dget(current->fs->pwd); + read_unlock(¤t->fs->lock); + } else { + struct dentry *dentry; + + file = fget_light(dfd, &fput_needed); + retval = -EBADF; + if (!file) + goto out_fail; + + dentry = file->f_dentry; + + retval = -ENOTDIR; + if (!S_ISDIR(dentry->d_inode->i_mode)) + goto fput_fail; + + retval = file_permission(file, MAY_EXEC); + if (retval) + goto fput_fail; + + nd->mnt = mntget(file->f_vfsmnt); + nd->dentry = dget(dentry); + + fput_light(file, fput_needed); } - read_unlock(¤t->fs->lock); current->total_link_count = 0; retval = link_path_walk(name, nd); - if (unlikely(current->audit_context - && nd && nd->dentry && nd->dentry->d_inode)) - audit_inode(name, - nd->dentry->d_inode->i_ino, - nd->dentry->d_inode->i_rdev); +out: + if (likely(retval == 0)) { + if (unlikely(current->audit_context && nd && nd->dentry && + nd->dentry->d_inode)) + audit_inode(name, nd->dentry->d_inode, flags); + } +out_fail: return retval; + +fput_fail: + fput_light(file, fput_needed); + goto out_fail; +} + +int fastcall path_lookup(const char *name, unsigned int flags, + struct nameidata *nd) +{ + return do_path_lookup(AT_FDCWD, name, flags, nd); +} + +static int __path_lookup_intent_open(int dfd, const char *name, + unsigned int lookup_flags, struct nameidata *nd, + int open_flags, int create_mode) +{ + struct file *filp = get_empty_filp(); + int err; + + if (filp == NULL) + return -ENFILE; + nd->intent.open.file = filp; + nd->intent.open.flags = open_flags; + nd->intent.open.create_mode = create_mode; + err = do_path_lookup(dfd, name, lookup_flags|LOOKUP_OPEN, nd); + if (IS_ERR(nd->intent.open.file)) { + if (err == 0) { + err = PTR_ERR(nd->intent.open.file); + path_release(nd); + } + } else if (err != 0) + release_open_intent(nd); + return err; +} + +/** + * path_lookup_open - lookup a file path with open intent + * @dfd: the directory to use as base, or AT_FDCWD + * @name: pointer to file name + * @lookup_flags: lookup intent flags + * @nd: pointer to nameidata + * @open_flags: open intent flags + */ +int path_lookup_open(int dfd, const char *name, unsigned int lookup_flags, + struct nameidata *nd, int open_flags) +{ + return __path_lookup_intent_open(dfd, name, lookup_flags, nd, + open_flags, 0); +} + +/** + * path_lookup_create - lookup a file path with open + create intent + * @dfd: the directory to use as base, or AT_FDCWD + * @name: pointer to file name + * @lookup_flags: lookup intent flags + * @nd: pointer to nameidata + * @open_flags: open intent flags + * @create_mode: create intent flags + */ +static int path_lookup_create(int dfd, const char *name, + unsigned int lookup_flags, struct nameidata *nd, + int open_flags, int create_mode) +{ + return __path_lookup_intent_open(dfd, name, lookup_flags|LOOKUP_CREATE, + nd, open_flags, create_mode); +} + +int __user_path_lookup_open(const char __user *name, unsigned int lookup_flags, + struct nameidata *nd, int open_flags) +{ + char *tmp = getname(name); + int err = PTR_ERR(tmp); + + if (!IS_ERR(tmp)) { + err = __path_lookup_intent_open(AT_FDCWD, tmp, lookup_flags, nd, open_flags, 0); + putname(tmp); + } + return err; } /* @@ -1022,9 +1303,9 @@ out: return dentry; } -struct dentry * lookup_hash(struct qstr *name, struct dentry * base) +static struct dentry *lookup_hash(struct nameidata *nd) { - return __lookup_hash(name, base, NULL); + return __lookup_hash(&nd->last, nd->dentry, nd); } /* SMP-safe */ @@ -1048,7 +1329,7 @@ struct dentry * lookup_one_len(const char * name, struct dentry * base, int len) } this.hash = end_name_hash(hash); - return lookup_hash(&this, base); + return __lookup_hash(&this, base, NULL); access: return ERR_PTR(-EACCES); } @@ -1064,18 +1345,24 @@ access: * that namei follows links, while lnamei does not. * SMP-safe */ -int fastcall __user_walk(const char __user *name, unsigned flags, struct nameidata *nd) +int fastcall __user_walk_fd(int dfd, const char __user *name, unsigned flags, + struct nameidata *nd) { char *tmp = getname(name); int err = PTR_ERR(tmp); if (!IS_ERR(tmp)) { - err = path_lookup(tmp, flags, nd); + err = do_path_lookup(dfd, tmp, flags, nd); putname(tmp); } return err; } +int fastcall __user_walk(const char __user *name, unsigned flags, struct nameidata *nd) +{ + return __user_walk_fd(AT_FDCWD, name, flags, nd); +} + /* * It's inline, so penalty for filesystems that don't use sticky bit is * minimal. @@ -1110,7 +1397,8 @@ static inline int check_sticky(struct inode *dir, struct inode *inode) * 10. We don't allow removal of NFS sillyrenamed files; it's handled by * nfs_async_unlink(). */ -static inline int may_delete(struct inode *dir,struct dentry *victim,int isdir) +static int may_delete(struct inode *dir, struct dentry *victim, + int isdir, struct nameidata *nd) { int error; @@ -1118,8 +1406,9 @@ static inline int may_delete(struct inode *dir,struct dentry *victim,int isdir) return -ENOENT; BUG_ON(victim->d_parent->d_inode != dir); + audit_inode_child(victim->d_name.name, victim->d_inode, dir->i_ino); - error = permission(dir,MAY_WRITE | MAY_EXEC, NULL); + error = permission(dir,MAY_WRITE | MAY_EXEC, nd); if (error) return error; if (IS_APPEND(dir)) @@ -1160,9 +1449,6 @@ static inline int may_create(struct inode *dir, struct dentry *child, } /* - * Special case: O_CREAT|O_EXCL implies O_NOFOLLOW for security - * reasons. - * * O_DIRECTORY translates into forcing a directory lookup. */ static inline int lookup_flags(unsigned int f) @@ -1172,11 +1458,10 @@ static inline int lookup_flags(unsigned int f) if (f & O_NOFOLLOW) retval &= ~LOOKUP_FOLLOW; - if ((f & (O_CREAT|O_EXCL)) == (O_CREAT|O_EXCL)) - retval &= ~LOOKUP_FOLLOW; - if (f & O_DIRECTORY) retval |= LOOKUP_DIRECTORY; + if (f & O_ATOMICLOOKUP) + retval |= LOOKUP_ATOMIC; return retval; } @@ -1189,39 +1474,39 @@ struct dentry *lock_rename(struct dentry *p1, struct dentry *p2) struct dentry *p; if (p1 == p2) { - down(&p1->d_inode->i_sem); + mutex_lock(&p1->d_inode->i_mutex); return NULL; } - down(&p1->d_inode->i_sb->s_vfs_rename_sem); + mutex_lock(&p1->d_inode->i_sb->s_vfs_rename_mutex); for (p = p1; p->d_parent != p; p = p->d_parent) { if (p->d_parent == p2) { - down(&p2->d_inode->i_sem); - down(&p1->d_inode->i_sem); + mutex_lock(&p2->d_inode->i_mutex); + mutex_lock(&p1->d_inode->i_mutex); return p; } } for (p = p2; p->d_parent != p; p = p->d_parent) { if (p->d_parent == p1) { - down(&p1->d_inode->i_sem); - down(&p2->d_inode->i_sem); + mutex_lock(&p1->d_inode->i_mutex); + mutex_lock(&p2->d_inode->i_mutex); return p; } } - down(&p1->d_inode->i_sem); - down(&p2->d_inode->i_sem); + mutex_lock(&p1->d_inode->i_mutex); + mutex_lock(&p2->d_inode->i_mutex); return NULL; } void unlock_rename(struct dentry *p1, struct dentry *p2) { - up(&p1->d_inode->i_sem); + mutex_unlock(&p1->d_inode->i_mutex); if (p1 != p2) { - up(&p2->d_inode->i_sem); - up(&p1->d_inode->i_sb->s_vfs_rename_sem); + mutex_unlock(&p2->d_inode->i_mutex); + mutex_unlock(&p1->d_inode->i_sb->s_vfs_rename_mutex); } } @@ -1242,10 +1527,8 @@ int vfs_create(struct inode *dir, struct dentry *dentry, int mode, return error; DQUOT_INIT(dir); error = dir->i_op->create(dir, dentry, mode, nd); - if (!error) { - inode_dir_notify(dir, DN_CREATE); - security_inode_post_create(dir, dentry, mode); - } + if (!error) + fsnotify_create(dir, dentry); return error; } @@ -1264,7 +1547,7 @@ int may_open(struct nameidata *nd, int acc_mode, int flag) if (S_ISDIR(inode->i_mode) && (flag & FMODE_WRITE)) return -EISDIR; - error = permission(inode, acc_mode, nd); + error = vfs_permission(nd, acc_mode); if (error) return error; @@ -1280,7 +1563,8 @@ int may_open(struct nameidata *nd, int acc_mode, int flag) return -EACCES; flag &= ~O_TRUNC; - } else if (IS_RDONLY(inode) && (flag & FMODE_WRITE)) + } else if ((IS_RDONLY(inode) || MNT_IS_RDONLY(nd->mnt)) + && (flag & FMODE_WRITE)) return -EROFS; /* * An append-only file must be opened in append mode for writing. @@ -1316,7 +1600,7 @@ int may_open(struct nameidata *nd, int acc_mode, int flag) if (!error) { DQUOT_INIT(inode); - error = do_truncate(dentry, 0); + error = do_truncate(dentry, 0, ATTR_MTIME|ATTR_CTIME, NULL); } put_write_access(inode); if (error) @@ -1342,29 +1626,31 @@ int may_open(struct nameidata *nd, int acc_mode, int flag) * for symlinks (where the permissions are checked later). * SMP-safe */ -int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd) +int open_namei(int dfd, const char *pathname, int flag, + int mode, struct nameidata *nd) { - int acc_mode, error = 0; - struct dentry *dentry; + int acc_mode, error; + struct path path; struct dentry *dir; int count = 0; acc_mode = ACC_MODE(flag); + /* O_TRUNC implies we need access checks for write permissions */ + if (flag & O_TRUNC) + acc_mode |= MAY_WRITE; + /* Allow the LSM permission hook to distinguish append access from general write access. */ if (flag & O_APPEND) acc_mode |= MAY_APPEND; - /* Fill in the open() intent data */ - nd->intent.open.flags = flag; - nd->intent.open.create_mode = mode; - /* * The simplest case - just a plain lookup. */ if (!(flag & O_CREAT)) { - error = path_lookup(pathname, lookup_flags(flag)|LOOKUP_OPEN, nd); + error = path_lookup_open(dfd, pathname, lookup_flags(flag), + nd, flag); if (error) return error; goto ok; @@ -1373,7 +1659,7 @@ int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd) /* * Create - we need to know the parent. */ - error = path_lookup(pathname, LOOKUP_PARENT|LOOKUP_OPEN|LOOKUP_CREATE, nd); + error = path_lookup_create(dfd,pathname,LOOKUP_PARENT,nd,flag,mode); if (error) return error; @@ -1388,24 +1674,31 @@ int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd) dir = nd->dentry; nd->flags &= ~LOOKUP_PARENT; - down(&dir->d_inode->i_sem); - dentry = __lookup_hash(&nd->last, nd->dentry, nd); + mutex_lock(&dir->d_inode->i_mutex); + path.dentry = lookup_hash(nd); + path.mnt = nd->mnt; do_last: - error = PTR_ERR(dentry); - if (IS_ERR(dentry)) { - up(&dir->d_inode->i_sem); + error = PTR_ERR(path.dentry); + if (IS_ERR(path.dentry)) { + mutex_unlock(&dir->d_inode->i_mutex); goto exit; } + if (IS_ERR(nd->intent.open.file)) { + mutex_unlock(&dir->d_inode->i_mutex); + error = PTR_ERR(nd->intent.open.file); + goto exit_dput; + } + /* Negative dentry, just create the file */ - if (!dentry->d_inode) { + if (!path.dentry->d_inode) { if (!IS_POSIXACL(dir->d_inode)) mode &= ~current->fs->umask; - error = vfs_create(dir->d_inode, dentry, mode, nd); - up(&dir->d_inode->i_sem); + error = vfs_create(dir->d_inode, path.dentry, mode, nd); + mutex_unlock(&dir->d_inode->i_mutex); dput(nd->dentry); - nd->dentry = dentry; + nd->dentry = path.dentry; if (error) goto exit; /* Don't check for write permission, don't truncate */ @@ -1417,28 +1710,26 @@ do_last: /* * It already exists. */ - up(&dir->d_inode->i_sem); + mutex_unlock(&dir->d_inode->i_mutex); error = -EEXIST; if (flag & O_EXCL) goto exit_dput; - if (d_mountpoint(dentry)) { + if (__follow_mount(&path)) { error = -ELOOP; if (flag & O_NOFOLLOW) goto exit_dput; - while (__follow_down(&nd->mnt,&dentry) && d_mountpoint(dentry)); } error = -ENOENT; - if (!dentry->d_inode) + if (!path.dentry->d_inode) goto exit_dput; - if (dentry->d_inode->i_op && dentry->d_inode->i_op->follow_link) + if (path.dentry->d_inode->i_op && path.dentry->d_inode->i_op->follow_link) goto do_link; - dput(nd->dentry); - nd->dentry = dentry; + path_to_nameidata(&path, nd); error = -EISDIR; - if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) + if (path.dentry->d_inode && S_ISDIR(path.dentry->d_inode->i_mode)) goto exit; ok: error = may_open(nd, acc_mode, flag); @@ -1447,8 +1738,10 @@ ok: return 0; exit_dput: - dput(dentry); + dput_path(&path, nd); exit: + if (!IS_ERR(nd->intent.open.file)) + release_open_intent(nd); path_release(nd); return error; @@ -1467,43 +1760,32 @@ do_link: * are done. Procfs-like symlinks just set LAST_BIND. */ nd->flags |= LOOKUP_PARENT; - error = security_inode_follow_link(dentry, nd); + error = security_inode_follow_link(path.dentry, nd); if (error) goto exit_dput; - touch_atime(nd->mnt, dentry); - nd_set_link(nd, NULL); - error = dentry->d_inode->i_op->follow_link(dentry, nd); - if (!error) { - char *s = nd_get_link(nd); - if (s) - error = __vfs_follow_link(nd, s); - if (dentry->d_inode->i_op->put_link) - dentry->d_inode->i_op->put_link(dentry, nd); - } - dput(dentry); + error = __do_follow_link(&path, nd); if (error) return error; nd->flags &= ~LOOKUP_PARENT; - if (nd->last_type == LAST_BIND) { - dentry = nd->dentry; + if (nd->last_type == LAST_BIND) goto ok; - } error = -EISDIR; if (nd->last_type != LAST_NORM) goto exit; if (nd->last.name[nd->last.len]) { - putname(nd->last.name); + __putname(nd->last.name); goto exit; } error = -ELOOP; if (count++==32) { - putname(nd->last.name); + __putname(nd->last.name); goto exit; } dir = nd->dentry; - down(&dir->d_inode->i_sem); - dentry = __lookup_hash(&nd->last, nd->dentry, nd); - putname(nd->last.name); + mutex_lock(&dir->d_inode->i_mutex); + path.dentry = lookup_hash(nd); + path.mnt = nd->mnt; + __putname(nd->last.name); goto do_last; } @@ -1514,19 +1796,35 @@ do_link: * * Simple function to lookup and return a dentry and create it * if it doesn't exist. Is SMP-safe. + * + * Returns with nd->dentry->d_inode->i_mutex locked. */ struct dentry *lookup_create(struct nameidata *nd, int is_dir) { - struct dentry *dentry; + struct dentry *dentry = ERR_PTR(-EEXIST); - down(&nd->dentry->d_inode->i_sem); - dentry = ERR_PTR(-EEXIST); + mutex_lock(&nd->dentry->d_inode->i_mutex); + /* + * Yucky last component or no last component at all? + * (foo/., foo/.., /////) + */ if (nd->last_type != LAST_NORM) goto fail; nd->flags &= ~LOOKUP_PARENT; - dentry = lookup_hash(&nd->last, nd->dentry); + + /* + * Do the final lookup. + */ + dentry = lookup_hash(nd); if (IS_ERR(dentry)) goto fail; + + /* + * Special case - lookup gave negative, but... we had foo/bar/ + * From the vfs_mknod() POV we just have a negative dentry - + * all is fine. Let's be bastards - you had / on the end, you've + * been asking for (non-existent) directory. -ENOENT for you. + */ if (!is_dir && nd->last.name[nd->last.len] && !dentry->d_inode) goto enoent; return dentry; @@ -1536,10 +1834,12 @@ enoent: fail: return dentry; } +EXPORT_SYMBOL_GPL(lookup_create); -int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) +int vfs_mknod(struct inode *dir, struct dentry *dentry, + int mode, dev_t dev, struct nameidata *nd) { - int error = may_create(dir, dentry, NULL); + int error = may_create(dir, dentry, nd); if (error) return error; @@ -1556,14 +1856,13 @@ int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) DQUOT_INIT(dir); error = dir->i_op->mknod(dir, dentry, mode, dev); - if (!error) { - inode_dir_notify(dir, DN_CREATE); - security_inode_post_mknod(dir, dentry, mode, dev); - } + if (!error) + fsnotify_create(dir, dentry); return error; } -asmlinkage long sys_mknod(const char __user * filename, int mode, unsigned dev) +asmlinkage long sys_mknodat(int dfd, const char __user *filename, int mode, + unsigned dev) { int error = 0; char * tmp; @@ -1576,7 +1875,7 @@ asmlinkage long sys_mknod(const char __user * filename, int mode, unsigned dev) if (IS_ERR(tmp)) return PTR_ERR(tmp); - error = path_lookup(tmp, LOOKUP_PARENT, &nd); + error = do_path_lookup(dfd, tmp, LOOKUP_PARENT, &nd); if (error) goto out; dentry = lookup_create(&nd, 0); @@ -1590,11 +1889,12 @@ asmlinkage long sys_mknod(const char __user * filename, int mode, unsigned dev) error = vfs_create(nd.dentry->d_inode,dentry,mode,&nd); break; case S_IFCHR: case S_IFBLK: - error = vfs_mknod(nd.dentry->d_inode,dentry,mode, - new_decode_dev(dev)); + error = vfs_mknod(nd.dentry->d_inode, dentry, mode, + new_decode_dev(dev), &nd); break; case S_IFIFO: case S_IFSOCK: - error = vfs_mknod(nd.dentry->d_inode,dentry,mode,0); + error = vfs_mknod(nd.dentry->d_inode, dentry, mode, + 0, &nd); break; case S_IFDIR: error = -EPERM; @@ -1604,7 +1904,7 @@ asmlinkage long sys_mknod(const char __user * filename, int mode, unsigned dev) } dput(dentry); } - up(&nd.dentry->d_inode->i_sem); + mutex_unlock(&nd.dentry->d_inode->i_mutex); path_release(&nd); out: putname(tmp); @@ -1612,9 +1912,15 @@ out: return error; } -int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) +asmlinkage long sys_mknod(const char __user *filename, int mode, unsigned dev) { - int error = may_create(dir, dentry, NULL); + return sys_mknodat(AT_FDCWD, filename, mode, dev); +} + +int vfs_mkdir(struct inode *dir, struct dentry *dentry, + int mode, struct nameidata *nd) +{ + int error = may_create(dir, dentry, nd); if (error) return error; @@ -1629,14 +1935,12 @@ int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) DQUOT_INIT(dir); error = dir->i_op->mkdir(dir, dentry, mode); - if (!error) { - inode_dir_notify(dir, DN_CREATE); - security_inode_post_mkdir(dir,dentry, mode); - } + if (!error) + fsnotify_mkdir(dir, dentry); return error; } -asmlinkage long sys_mkdir(const char __user * pathname, int mode) +asmlinkage long sys_mkdirat(int dfd, const char __user *pathname, int mode) { int error = 0; char * tmp; @@ -1647,7 +1951,7 @@ asmlinkage long sys_mkdir(const char __user * pathname, int mode) struct dentry *dentry; struct nameidata nd; - error = path_lookup(tmp, LOOKUP_PARENT, &nd); + error = do_path_lookup(dfd, tmp, LOOKUP_PARENT, &nd); if (error) goto out; dentry = lookup_create(&nd, 1); @@ -1655,10 +1959,11 @@ asmlinkage long sys_mkdir(const char __user * pathname, int mode) if (!IS_ERR(dentry)) { if (!IS_POSIXACL(nd.dentry->d_inode)) mode &= ~current->fs->umask; - error = vfs_mkdir(nd.dentry->d_inode, dentry, mode); + error = vfs_mkdir(nd.dentry->d_inode, dentry, + mode, &nd); dput(dentry); } - up(&nd.dentry->d_inode->i_sem); + mutex_unlock(&nd.dentry->d_inode->i_mutex); path_release(&nd); out: putname(tmp); @@ -1667,6 +1972,11 @@ out: return error; } +asmlinkage long sys_mkdir(const char __user *pathname, int mode) +{ + return sys_mkdirat(AT_FDCWD, pathname, mode); +} + /* * We try to drop the dentry early: we should have * a usage count of 2 if we're the only user of this @@ -1685,23 +1995,20 @@ out: void dentry_unhash(struct dentry *dentry) { dget(dentry); - spin_lock(&dcache_lock); - switch (atomic_read(&dentry->d_count)) { - default: - spin_unlock(&dcache_lock); + if (atomic_read(&dentry->d_count)) shrink_dcache_parent(dentry); - spin_lock(&dcache_lock); - if (atomic_read(&dentry->d_count) != 2) - break; - case 2: + spin_lock(&dcache_lock); + spin_lock(&dentry->d_lock); + if (atomic_read(&dentry->d_count) == 2) __d_drop(dentry); - } + spin_unlock(&dentry->d_lock); spin_unlock(&dcache_lock); } -int vfs_rmdir(struct inode *dir, struct dentry *dentry) +int vfs_rmdir(struct inode *dir, struct dentry *dentry, + struct nameidata *nd) { - int error = may_delete(dir, dentry, 1); + int error = may_delete(dir, dentry, 1, nd); if (error) return error; @@ -1711,7 +2018,7 @@ int vfs_rmdir(struct inode *dir, struct dentry *dentry) DQUOT_INIT(dir); - down(&dentry->d_inode->i_sem); + mutex_lock(&dentry->d_inode->i_mutex); dentry_unhash(dentry); if (d_mountpoint(dentry)) error = -EBUSY; @@ -1723,9 +2030,8 @@ int vfs_rmdir(struct inode *dir, struct dentry *dentry) dentry->d_inode->i_flags |= S_DEAD; } } - up(&dentry->d_inode->i_sem); + mutex_unlock(&dentry->d_inode->i_mutex); if (!error) { - inode_dir_notify(dir, DN_DELETE); d_delete(dentry); } dput(dentry); @@ -1733,7 +2039,7 @@ int vfs_rmdir(struct inode *dir, struct dentry *dentry) return error; } -asmlinkage long sys_rmdir(const char __user * pathname) +static long do_rmdir(int dfd, const char __user *pathname) { int error = 0; char * name; @@ -1744,7 +2050,7 @@ asmlinkage long sys_rmdir(const char __user * pathname) if(IS_ERR(name)) return PTR_ERR(name); - error = path_lookup(name, LOOKUP_PARENT, &nd); + error = do_path_lookup(dfd, name, LOOKUP_PARENT, &nd); if (error) goto exit; @@ -1759,14 +2065,14 @@ asmlinkage long sys_rmdir(const char __user * pathname) error = -EBUSY; goto exit1; } - down(&nd.dentry->d_inode->i_sem); - dentry = lookup_hash(&nd.last, nd.dentry); + mutex_lock(&nd.dentry->d_inode->i_mutex); + dentry = lookup_hash(&nd); error = PTR_ERR(dentry); if (!IS_ERR(dentry)) { - error = vfs_rmdir(nd.dentry->d_inode, dentry); + error = vfs_rmdir(nd.dentry->d_inode, dentry, &nd); dput(dentry); } - up(&nd.dentry->d_inode->i_sem); + mutex_unlock(&nd.dentry->d_inode->i_mutex); exit1: path_release(&nd); exit: @@ -1774,9 +2080,15 @@ exit: return error; } -int vfs_unlink(struct inode *dir, struct dentry *dentry) +asmlinkage long sys_rmdir(const char __user *pathname) +{ + return do_rmdir(AT_FDCWD, pathname); +} + +int vfs_unlink(struct inode *dir, struct dentry *dentry, + struct nameidata *nd) { - int error = may_delete(dir, dentry, 0); + int error = may_delete(dir, dentry, 0, nd); if (error) return error; @@ -1786,7 +2098,7 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry) DQUOT_INIT(dir); - down(&dentry->d_inode->i_sem); + mutex_lock(&dentry->d_inode->i_mutex); if (d_mountpoint(dentry)) error = -EBUSY; else { @@ -1794,23 +2106,23 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry) if (!error) error = dir->i_op->unlink(dir, dentry); } - up(&dentry->d_inode->i_sem); + mutex_unlock(&dentry->d_inode->i_mutex); /* We don't d_delete() NFS sillyrenamed files--they still exist. */ if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) { d_delete(dentry); - inode_dir_notify(dir, DN_DELETE); } + return error; } /* * Make sure that the actual truncation of the file will occur outside its - * directory's i_sem. Truncate can take a long time if there is a lot of + * directory's i_mutex. Truncate can take a long time if there is a lot of * writeout happening, and we don't want to prevent access to the directory * while waiting on the I/O. */ -asmlinkage long sys_unlink(const char __user * pathname) +static long do_unlinkat(int dfd, const char __user *pathname) { int error = 0; char * name; @@ -1822,14 +2134,14 @@ asmlinkage long sys_unlink(const char __user * pathname) if(IS_ERR(name)) return PTR_ERR(name); - error = path_lookup(name, LOOKUP_PARENT, &nd); + error = do_path_lookup(dfd, name, LOOKUP_PARENT, &nd); if (error) goto exit; error = -EISDIR; if (nd.last_type != LAST_NORM) goto exit1; - down(&nd.dentry->d_inode->i_sem); - dentry = lookup_hash(&nd.last, nd.dentry); + mutex_lock(&nd.dentry->d_inode->i_mutex); + dentry = lookup_hash(&nd); error = PTR_ERR(dentry); if (!IS_ERR(dentry)) { /* Why not before? Because we want correct error value */ @@ -1838,11 +2150,11 @@ asmlinkage long sys_unlink(const char __user * pathname) inode = dentry->d_inode; if (inode) atomic_inc(&inode->i_count); - error = vfs_unlink(nd.dentry->d_inode, dentry); + error = vfs_unlink(nd.dentry->d_inode, dentry, &nd); exit2: dput(dentry); } - up(&nd.dentry->d_inode->i_sem); + mutex_unlock(&nd.dentry->d_inode->i_mutex); if (inode) iput(inode); /* truncate the inode here */ exit1: @@ -1857,9 +2169,26 @@ slashes: goto exit2; } -int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname, int mode) +asmlinkage long sys_unlinkat(int dfd, const char __user *pathname, int flag) { - int error = may_create(dir, dentry, NULL); + if ((flag & ~AT_REMOVEDIR) != 0) + return -EINVAL; + + if (flag & AT_REMOVEDIR) + return do_rmdir(dfd, pathname); + + return do_unlinkat(dfd, pathname); +} + +asmlinkage long sys_unlink(const char __user *pathname) +{ + return do_unlinkat(AT_FDCWD, pathname); +} + +int vfs_symlink(struct inode *dir, struct dentry *dentry, + const char *oldname, int mode, struct nameidata *nd) +{ + int error = may_create(dir, dentry, nd); if (error) return error; @@ -1873,14 +2202,13 @@ int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname, i DQUOT_INIT(dir); error = dir->i_op->symlink(dir, dentry, oldname); - if (!error) { - inode_dir_notify(dir, DN_CREATE); - security_inode_post_symlink(dir, dentry, oldname); - } + if (!error) + fsnotify_create(dir, dentry); return error; } -asmlinkage long sys_symlink(const char __user * oldname, const char __user * newname) +asmlinkage long sys_symlinkat(const char __user *oldname, + int newdfd, const char __user *newname) { int error = 0; char * from; @@ -1895,16 +2223,17 @@ asmlinkage long sys_symlink(const char __user * oldname, const char __user * new struct dentry *dentry; struct nameidata nd; - error = path_lookup(to, LOOKUP_PARENT, &nd); + error = do_path_lookup(newdfd, to, LOOKUP_PARENT, &nd); if (error) goto out; dentry = lookup_create(&nd, 0); error = PTR_ERR(dentry); if (!IS_ERR(dentry)) { - error = vfs_symlink(nd.dentry->d_inode, dentry, from, S_IALLUGO); + error = vfs_symlink(nd.dentry->d_inode, dentry, + from, S_IALLUGO, &nd); dput(dentry); } - up(&nd.dentry->d_inode->i_sem); + mutex_unlock(&nd.dentry->d_inode->i_mutex); path_release(&nd); out: putname(to); @@ -1913,7 +2242,13 @@ out: return error; } -int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) +asmlinkage long sys_symlink(const char __user *oldname, const char __user *newname) +{ + return sys_symlinkat(oldname, AT_FDCWD, newname); +} + +int vfs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *new_dentry, struct nameidata *nd) { struct inode *inode = old_dentry->d_inode; int error; @@ -1921,7 +2256,7 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de if (!inode) return -ENOENT; - error = may_create(dir, new_dentry, NULL); + error = may_create(dir, new_dentry, nd); if (error) return error; @@ -1942,14 +2277,12 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de if (error) return error; - down(&old_dentry->d_inode->i_sem); + mutex_lock(&old_dentry->d_inode->i_mutex); DQUOT_INIT(dir); error = dir->i_op->link(old_dentry, dir, new_dentry); - up(&old_dentry->d_inode->i_sem); - if (!error) { - inode_dir_notify(dir, DN_CREATE); - security_inode_post_link(old_dentry, dir, new_dentry); - } + mutex_unlock(&old_dentry->d_inode->i_mutex); + if (!error) + fsnotify_create(dir, new_dentry); return error; } @@ -1962,21 +2295,26 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de * with linux 2.0, and to avoid hard-linking to directories * and other special files. --ADM */ -asmlinkage long sys_link(const char __user * oldname, const char __user * newname) +asmlinkage long sys_linkat(int olddfd, const char __user *oldname, + int newdfd, const char __user *newname, + int flags) { struct dentry *new_dentry; struct nameidata nd, old_nd; int error; char * to; + if (flags != 0) + return -EINVAL; + to = getname(newname); if (IS_ERR(to)) return PTR_ERR(to); - error = __user_walk(oldname, 0, &old_nd); + error = __user_walk_fd(olddfd, oldname, 0, &old_nd); if (error) goto exit; - error = path_lookup(to, LOOKUP_PARENT, &nd); + error = do_path_lookup(newdfd, to, LOOKUP_PARENT, &nd); if (error) goto out; error = -EXDEV; @@ -1985,10 +2323,11 @@ asmlinkage long sys_link(const char __user * oldname, const char __user * newnam new_dentry = lookup_create(&nd, 0); error = PTR_ERR(new_dentry); if (!IS_ERR(new_dentry)) { - error = vfs_link(old_nd.dentry, nd.dentry->d_inode, new_dentry); + error = vfs_link(old_nd.dentry, nd.dentry->d_inode, + new_dentry, &nd); dput(new_dentry); } - up(&nd.dentry->d_inode->i_sem); + mutex_unlock(&nd.dentry->d_inode->i_mutex); out_release: path_release(&nd); out: @@ -1999,6 +2338,11 @@ exit: return error; } +asmlinkage long sys_link(const char __user *oldname, const char __user *newname) +{ + return sys_linkat(AT_FDCWD, oldname, AT_FDCWD, newname, 0); +} + /* * The worst of all namespace operations - renaming directory. "Perverted" * doesn't even start to describe it. Somebody in UCB had a heck of a trip... @@ -2006,17 +2350,17 @@ exit: * a) we can get into loop creation. Check is done in is_subdir(). * b) race potential - two innocent renames can create a loop together. * That's where 4.4 screws up. Current fix: serialization on - * sb->s_vfs_rename_sem. We might be more accurate, but that's another + * sb->s_vfs_rename_mutex. We might be more accurate, but that's another * story. * c) we have to lock _three_ objects - parents and victim (if it exists). - * And that - after we got ->i_sem on parents (until then we don't know + * And that - after we got ->i_mutex on parents (until then we don't know * whether the target exists). Solution: try to be smart with locking * order for inodes. We rely on the fact that tree topology may change - * only under ->s_vfs_rename_sem _and_ that parent of the object we + * only under ->s_vfs_rename_mutex _and_ that parent of the object we * move will be locked. Thus we can rank directories by the tree * (ancestors first) and rank all non-directories after them. * That works since everybody except rename does "lock parent, lookup, - * lock child" and rename is under ->s_vfs_rename_sem. + * lock child" and rename is under ->s_vfs_rename_mutex. * HOWEVER, it relies on the assumption that any object with ->lookup() * has no more than 1 dentry. If "hybrid" objects will ever appear, * we'd better make sure that there's no link(2) for them. @@ -2026,13 +2370,13 @@ exit: * stuff into VFS), but the former is not going away. Solution: the same * trick as in rmdir(). * e) conversion from fhandle to dentry may come in the wrong moment - when - * we are removing the target. Solution: we will have to grab ->i_sem + * we are removing the target. Solution: we will have to grab ->i_mutex * in the fhandle_to_dentry code. [FIXME - current nfsfh.c relies on - * ->i_sem on parents, which works but leads to some truely excessive + * ->i_mutex on parents, which works but leads to some truely excessive * locking]. */ -int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) +static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) { int error = 0; struct inode *target; @@ -2053,7 +2397,7 @@ int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry, target = new_dentry->d_inode; if (target) { - down(&target->i_sem); + mutex_lock(&target->i_mutex); dentry_unhash(new_dentry); } if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry)) @@ -2063,21 +2407,18 @@ int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry, if (target) { if (!error) target->i_flags |= S_DEAD; - up(&target->i_sem); + mutex_unlock(&target->i_mutex); if (d_unhashed(new_dentry)) d_rehash(new_dentry); dput(new_dentry); } - if (!error) { + if (!error) d_move(old_dentry,new_dentry); - security_inode_post_rename(old_dir, old_dentry, - new_dir, new_dentry); - } return error; } -int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) +static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) { struct inode *target; int error; @@ -2089,7 +2430,7 @@ int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry, dget(new_dentry); target = new_dentry->d_inode; if (target) - down(&target->i_sem); + mutex_lock(&target->i_mutex); if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry)) error = -EBUSY; else @@ -2098,10 +2439,9 @@ int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry, /* The following d_move() should become unconditional */ if (!(old_dir->i_sb->s_type->fs_flags & FS_ODD_RENAME)) d_move(old_dentry, new_dentry); - security_inode_post_rename(old_dir, old_dentry, new_dir, new_dentry); } if (target) - up(&target->i_sem); + mutex_unlock(&target->i_mutex); dput(new_dentry); return error; } @@ -2111,18 +2451,19 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, { int error; int is_dir = S_ISDIR(old_dentry->d_inode->i_mode); + const char *old_name; if (old_dentry->d_inode == new_dentry->d_inode) return 0; - error = may_delete(old_dir, old_dentry, is_dir); + error = may_delete(old_dir, old_dentry, is_dir, NULL); if (error) return error; if (!new_dentry->d_inode) error = may_create(new_dir, new_dentry, NULL); else - error = may_delete(new_dir, new_dentry, is_dir); + error = may_delete(new_dir, new_dentry, is_dir, NULL); if (error) return error; @@ -2132,22 +2473,24 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, DQUOT_INIT(old_dir); DQUOT_INIT(new_dir); + old_name = fsnotify_oldname_init(old_dentry->d_name.name); + if (is_dir) error = vfs_rename_dir(old_dir,old_dentry,new_dir,new_dentry); else error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry); if (!error) { - if (old_dir == new_dir) - inode_dir_notify(old_dir, DN_RENAME); - else { - inode_dir_notify(old_dir, DN_DELETE); - inode_dir_notify(new_dir, DN_CREATE); - } + const char *new_name = old_dentry->d_name.name; + fsnotify_move(old_dir, new_dir, old_name, new_name, is_dir, + new_dentry->d_inode, old_dentry->d_inode); } + fsnotify_oldname_free(old_name); + return error; } -static inline int do_rename(const char * oldname, const char * newname) +static int do_rename(int olddfd, const char *oldname, + int newdfd, const char *newname) { int error = 0; struct dentry * old_dir, * new_dir; @@ -2155,11 +2498,11 @@ static inline int do_rename(const char * oldname, const char * newname) struct dentry * trap; struct nameidata oldnd, newnd; - error = path_lookup(oldname, LOOKUP_PARENT, &oldnd); + error = do_path_lookup(olddfd, oldname, LOOKUP_PARENT, &oldnd); if (error) goto exit; - error = path_lookup(newname, LOOKUP_PARENT, &newnd); + error = do_path_lookup(newdfd, newname, LOOKUP_PARENT, &newnd); if (error) goto exit1; @@ -2178,7 +2521,7 @@ static inline int do_rename(const char * oldname, const char * newname) trap = lock_rename(new_dir, old_dir); - old_dentry = lookup_hash(&oldnd.last, old_dir); + old_dentry = lookup_hash(&oldnd); error = PTR_ERR(old_dentry); if (IS_ERR(old_dentry)) goto exit3; @@ -2198,7 +2541,10 @@ static inline int do_rename(const char * oldname, const char * newname) error = -EINVAL; if (old_dentry == trap) goto exit4; - new_dentry = lookup_hash(&newnd.last, new_dir); + error = -EROFS; + if (MNT_IS_RDONLY(newnd.mnt)) + goto exit4; + new_dentry = lookup_hash(&newnd); error = PTR_ERR(new_dentry); if (IS_ERR(new_dentry)) goto exit4; @@ -2223,7 +2569,8 @@ exit: return error; } -asmlinkage long sys_rename(const char __user * oldname, const char __user * newname) +asmlinkage long sys_renameat(int olddfd, const char __user *oldname, + int newdfd, const char __user *newname) { int error; char * from; @@ -2235,13 +2582,18 @@ asmlinkage long sys_rename(const char __user * oldname, const char __user * newn to = getname(newname); error = PTR_ERR(to); if (!IS_ERR(to)) { - error = do_rename(from,to); + error = do_rename(olddfd, from, newdfd, to); putname(to); } putname(from); return error; } +asmlinkage long sys_rename(const char __user *oldname, const char __user *newname) +{ + return sys_renameat(AT_FDCWD, oldname, AT_FDCWD, newname); +} + int vfs_readlink(struct dentry *dentry, char __user *buffer, int buflen, const char *link) { int len; @@ -2267,15 +2619,17 @@ out: int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen) { struct nameidata nd; - int res; + void *cookie; + nd.depth = 0; - res = dentry->d_inode->i_op->follow_link(dentry, &nd); - if (!res) { - res = vfs_readlink(dentry, buffer, buflen, nd_get_link(&nd)); + cookie = dentry->d_inode->i_op->follow_link(dentry, &nd); + if (!IS_ERR(cookie)) { + int res = vfs_readlink(dentry, buffer, buflen, nd_get_link(&nd)); if (dentry->d_inode->i_op->put_link) - dentry->d_inode->i_op->put_link(dentry, &nd); + dentry->d_inode->i_op->put_link(dentry, &nd, cookie); + cookie = ERR_PTR(res); } - return res; + return PTR_ERR(cookie); } int vfs_follow_link(struct nameidata *nd, const char *link) @@ -2283,6 +2637,7 @@ int vfs_follow_link(struct nameidata *nd, const char *link) return __vfs_follow_link(nd, link); } + /* get the link contents into pagecache */ static char *page_getlink(struct dentry * dentry, struct page **ppage) { @@ -2318,54 +2673,52 @@ int page_readlink(struct dentry *dentry, char __user *buffer, int buflen) return res; } -int page_follow_link_light(struct dentry *dentry, struct nameidata *nd) +void *page_follow_link_light(struct dentry *dentry, struct nameidata *nd) { - struct page *page; + struct page *page = NULL; nd_set_link(nd, page_getlink(dentry, &page)); - return 0; + return page; } -void page_put_link(struct dentry *dentry, struct nameidata *nd) +void page_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie) { - if (!IS_ERR(nd_get_link(nd))) { - struct page *page; - page = find_get_page(dentry->d_inode->i_mapping, 0); - if (!page) - BUG(); - kunmap(page); - page_cache_release(page); - page_cache_release(page); - } -} + struct page *page = cookie; -int page_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - struct page *page = NULL; - char *s = page_getlink(dentry, &page); - int res = __vfs_follow_link(nd, s); if (page) { kunmap(page); page_cache_release(page); } - return res; } -int page_symlink(struct inode *inode, const char *symname, int len) +int __page_symlink(struct inode *inode, const char *symname, int len, + gfp_t gfp_mask) { struct address_space *mapping = inode->i_mapping; - struct page *page = grab_cache_page(mapping, 0); + struct page *page; int err = -ENOMEM; char *kaddr; +retry: + page = find_or_create_page(mapping, 0, gfp_mask); if (!page) goto fail; err = mapping->a_ops->prepare_write(NULL, page, 0, len-1); + if (err == AOP_TRUNCATED_PAGE) { + page_cache_release(page); + goto retry; + } if (err) goto fail_map; kaddr = kmap_atomic(page, KM_USER0); memcpy(kaddr, symname, len-1); kunmap_atomic(kaddr, KM_USER0); - mapping->a_ops->commit_write(NULL, page, 0, len-1); + err = mapping->a_ops->commit_write(NULL, page, 0, len-1); + if (err == AOP_TRUNCATED_PAGE) { + page_cache_release(page); + goto retry; + } + if (err) + goto fail_map; /* * Notice that we are _not_ going to block here - end of page is * unmapped, so this will only try to map the rest of page, see @@ -2375,7 +2728,8 @@ int page_symlink(struct inode *inode, const char *symname, int len) */ if (!PageUptodate(page)) { err = mapping->a_ops->readpage(NULL, page); - wait_on_page_locked(page); + if (err != AOP_TRUNCATED_PAGE) + wait_on_page_locked(page); } else { unlock_page(page); } @@ -2391,6 +2745,12 @@ fail: return err; } +int page_symlink(struct inode *inode, const char *symname, int len) +{ + return __page_symlink(inode, symname, len, + mapping_gfp_mask(inode->i_mapping)); +} + struct inode_operations page_symlink_inode_operations = { .readlink = generic_readlink, .follow_link = page_follow_link_light, @@ -2398,31 +2758,32 @@ struct inode_operations page_symlink_inode_operations = { }; EXPORT_SYMBOL(__user_walk); +EXPORT_SYMBOL(__user_walk_fd); EXPORT_SYMBOL(follow_down); EXPORT_SYMBOL(follow_up); EXPORT_SYMBOL(get_write_access); /* binfmt_aout */ EXPORT_SYMBOL(getname); EXPORT_SYMBOL(lock_rename); -EXPORT_SYMBOL(lookup_create); -EXPORT_SYMBOL(lookup_hash); EXPORT_SYMBOL(lookup_one_len); -EXPORT_SYMBOL(page_follow_link); EXPORT_SYMBOL(page_follow_link_light); EXPORT_SYMBOL(page_put_link); EXPORT_SYMBOL(page_readlink); +EXPORT_SYMBOL(__page_symlink); EXPORT_SYMBOL(page_symlink); EXPORT_SYMBOL(page_symlink_inode_operations); EXPORT_SYMBOL(path_lookup); EXPORT_SYMBOL(path_release); EXPORT_SYMBOL(path_walk); EXPORT_SYMBOL(permission); +EXPORT_SYMBOL(vfs_permission); +EXPORT_SYMBOL(file_permission); EXPORT_SYMBOL(unlock_rename); EXPORT_SYMBOL(vfs_create); EXPORT_SYMBOL(vfs_follow_link); EXPORT_SYMBOL(vfs_link); EXPORT_SYMBOL(vfs_mkdir); EXPORT_SYMBOL(vfs_mknod); -EXPORT_SYMBOL(vfs_permission); +EXPORT_SYMBOL(generic_permission); EXPORT_SYMBOL(vfs_readlink); EXPORT_SYMBOL(vfs_rename); EXPORT_SYMBOL(vfs_rmdir);