X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=fs%2Fnamei.c;h=3b993db26cee2c2df753f883239e6e917039bd38;hb=74af0baad4fd44cc4412cc210d6d9b6fdf7be8da;hp=21d85f1ac8395f82376e2dd5073494d568080e78;hpb=e31e14ec356f36b131576be5bc31d8fef7e95483;p=safe%2Fjmp%2Flinux-2.6 diff --git a/fs/namei.c b/fs/namei.c index 21d85f1..3b993db 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -22,12 +22,14 @@ #include #include #include -#include #include #include #include #include #include +#include +#include +#include #include #include @@ -100,10 +102,12 @@ */ /* * [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... */ +static int fastcall link_path_walk(const char *name, struct nameidata *nd); + /* 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.. @@ -111,7 +115,7 @@ * 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; @@ -155,7 +159,7 @@ char * getname(const char __user * filename) #ifdef CONFIG_AUDITSYSCALL void putname(const char *name) { - if (unlikely(current->audit_context)) + if (unlikely(!audit_dummy_context())) audit_putname(name); else __putname(name); @@ -224,6 +228,10 @@ int generic_permission(struct inode *inode, int mask, int permission(struct inode *inode, int mask, struct nameidata *nd) { int retval, submask; + struct vfsmount *mnt = NULL; + + if (nd) + mnt = nd->mnt; if (mask & MAY_WRITE) { umode_t mode = inode->i_mode; @@ -242,19 +250,72 @@ int permission(struct inode *inode, int mask, struct nameidata *nd) return -EACCES; } + if ((mask & MAY_EXEC) && S_ISREG(inode->i_mode)) { + /* + * MAY_EXEC on regular files is denied if the fs is mounted + * with the "noexec" flag. + */ + if (mnt && (mnt->mnt_flags & MNT_NOEXEC)) + return -EACCES; + } /* Ordinary permission routines do not understand MAY_APPEND. */ submask = mask & ~MAY_APPEND; - if (inode->i_op && inode->i_op->permission) + if (inode->i_op && inode->i_op->permission) { retval = inode->i_op->permission(inode, submask, nd); - else + if (!retval) { + /* + * Exec permission on a regular file is denied if none + * of the execute bits are set. + * + * This check should be done by the ->permission() + * method. + */ + if ((mask & MAY_EXEC) && S_ISREG(inode->i_mode) && + !(inode->i_mode & S_IXUGO)) + return -EACCES; + } + } else { 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_path.dentry->d_inode, mask, NULL); +} + /* * get_write_access() gets write permission for a file. * put_write_access() releases this write permission. @@ -288,7 +349,7 @@ int get_write_access(struct inode * inode) int deny_write_access(struct file * file) { - struct inode *inode = file->f_dentry->d_inode; + struct inode *inode = file->f_path.dentry->d_inode; spin_lock(&inode->i_lock); if (atomic_read(&inode->i_writecount) > 0) { @@ -317,6 +378,42 @@ void path_release_on_umount(struct nameidata *nd) 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_path.dentry == NULL) + put_filp(nd->intent.open.file); + else + fput(nd->intent.open.file); +} + +static inline struct dentry * +do_revalidate(struct dentry *dentry, struct nameidata *nd) +{ + int status = dentry->d_op->d_revalidate(dentry, nd); + if (unlikely(status <= 0)) { + /* + * The dentry failed validation. + * If d_revalidate returned 0 attempt to invalidate + * the dentry otherwise d_revalidate is asking us + * to return a fail status. + */ + if (!status) { + if (!d_invalidate(dentry)) { + dput(dentry); + dentry = NULL; + } + } else { + dput(dentry); + dentry = ERR_PTR(status); + } + } + return dentry; +} + /* * Internal lookup() using the new generic dcache. * SMP-safe @@ -331,12 +428,9 @@ static struct dentry * cached_lookup(struct dentry * parent, struct qstr * name, if (!dentry) dentry = d_lookup(parent, name); - if (dentry && dentry->d_op && dentry->d_op->d_revalidate) { - if (!dentry->d_op->d_revalidate(dentry, nd) && !d_invalidate(dentry)) { - dput(dentry); - dentry = NULL; - } - } + if (dentry && dentry->d_op && dentry->d_op->d_revalidate) + dentry = do_revalidate(dentry, nd); + return dentry; } @@ -350,7 +444,7 @@ static struct dentry * cached_lookup(struct dentry * parent, struct qstr * name, * 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; @@ -393,7 +487,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.. @@ -419,7 +513,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; } @@ -427,12 +521,11 @@ 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); + result = do_revalidate(result, nd); + if (!result) result = ERR_PTR(-ENOENT); - } } return result; } @@ -440,25 +533,27 @@ 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); - 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); + struct fs_struct *fs = current->fs; + + read_lock(&fs->lock); + if (fs->altroot && !(nd->flags & LOOKUP_NOALT)) { + nd->mnt = mntget(fs->altrootmnt); + nd->dentry = dget(fs->altroot); + read_unlock(&fs->lock); if (__emul_lookup_dentry(name,nd)) return 0; - read_lock(¤t->fs->lock); + read_lock(&fs->lock); } - nd->mnt = mntget(current->fs->rootmnt); - nd->dentry = dget(current->fs->root); - read_unlock(¤t->fs->lock); + nd->mnt = mntget(fs->rootmnt); + nd->dentry = dget(fs->root); + read_unlock(&fs->lock); 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; @@ -493,12 +588,23 @@ 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 int __do_follow_link(struct path *path, struct nameidata *nd) +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; @@ -507,8 +613,11 @@ static inline int __do_follow_link(struct path *path, struct nameidata *nd) touch_atime(path->mnt, dentry); nd_set_link(nd, NULL); - if (path->mnt == nd->mnt) - mntget(path->mnt); + 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)) { @@ -525,22 +634,6 @@ static inline int __do_follow_link(struct path *path, struct nameidata *nd) return error; } -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; -} - /* * This limits recursive symlink follows to 8, while * limiting consecutive symlinks to 40. @@ -644,19 +737,21 @@ int follow_down(struct vfsmount **mnt, struct dentry **dentry) return 0; } -static inline void follow_dotdot(struct nameidata *nd) +static __always_inline void follow_dotdot(struct nameidata *nd) { + struct fs_struct *fs = current->fs; + while(1) { struct vfsmount *parent; struct dentry *old = nd->dentry; - read_lock(¤t->fs->lock); - if (nd->dentry == current->fs->root && - nd->mnt == current->fs->rootmnt) { - read_unlock(¤t->fs->lock); + read_lock(&fs->lock); + if (nd->dentry == fs->root && + nd->mnt == fs->rootmnt) { + read_unlock(&fs->lock); break; } - read_unlock(¤t->fs->lock); + read_unlock(&fs->lock); spin_lock(&dcache_lock); if (nd->dentry != nd->mnt->mnt_root) { nd->dentry = dget(nd->dentry->d_parent); @@ -709,12 +804,12 @@ need_lookup: goto done; need_revalidate: - if (dentry->d_op->d_revalidate(dentry, nd)) - goto done; - if (d_invalidate(dentry)) - goto done; - dput(dentry); - goto need_lookup; + dentry = do_revalidate(dentry, nd); + if (!dentry) + goto need_lookup; + if (IS_ERR(dentry)) + goto fail; + goto done; fail: return PTR_ERR(dentry); @@ -742,7 +837,7 @@ static fastcall int __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(;;) { @@ -750,10 +845,10 @@ static fastcall int __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; @@ -802,7 +897,6 @@ static fastcall int __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); if (err) @@ -838,7 +932,8 @@ static fastcall int __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) { @@ -920,7 +1015,7 @@ return_err: * 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) +static int fastcall link_path_walk(const char *name, struct nameidata *nd) { struct nameidata save = *nd; int result; @@ -944,7 +1039,7 @@ int fastcall link_path_walk(const char *name, struct nameidata *nd) return result; } -int fastcall path_walk(const char * name, struct nameidata *nd) +static int fastcall path_walk(const char * name, struct nameidata *nd) { current->total_link_count = 0; return link_path_walk(name, nd); @@ -964,15 +1059,17 @@ static int __emul_lookup_dentry(const char *name, struct nameidata *nd) struct vfsmount *old_mnt = nd->mnt; struct qstr last = nd->last; int last_type = nd->last_type; + struct fs_struct *fs = current->fs; + /* - * NAME was not found in alternate root or it's a directory. Try to find - * it in the normal root: + * NAME was not found in alternate root or it's a directory. + * Try to find it in the normal root: */ nd->last_type = LAST_ROOT; - read_lock(¤t->fs->lock); - nd->mnt = mntget(current->fs->rootmnt); - nd->dentry = dget(current->fs->root); - read_unlock(¤t->fs->lock); + read_lock(&fs->lock); + nd->mnt = mntget(fs->rootmnt); + nd->dentry = dget(fs->root); + read_unlock(&fs->lock); if (path_walk(name, nd) == 0) { if (nd->dentry->d_inode) { dput(old_dentry); @@ -996,6 +1093,7 @@ void set_fs_altroot(void) struct vfsmount *mnt = NULL, *oldmnt; struct dentry *dentry = NULL, *olddentry; int err; + struct fs_struct *fs = current->fs; if (!emul) goto set_it; @@ -1005,12 +1103,12 @@ void set_fs_altroot(void) dentry = nd.dentry; } set_it: - write_lock(¤t->fs->lock); - oldmnt = current->fs->altrootmnt; - olddentry = current->fs->altroot; - current->fs->altrootmnt = mnt; - current->fs->altroot = dentry; - write_unlock(¤t->fs->lock); + write_lock(&fs->lock); + oldmnt = fs->altrootmnt; + olddentry = fs->altroot; + fs->altrootmnt = mnt; + fs->altroot = dentry; + write_unlock(&fs->lock); if (olddentry) { dput(olddentry); mntput(oldmnt); @@ -1018,56 +1116,186 @@ set_it: } /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */ -int fastcall path_lookup(const char *name, unsigned int flags, struct nameidata *nd) +static int fastcall do_path_lookup(int dfd, const char *name, + unsigned int flags, struct nameidata *nd) { int retval = 0; + int fput_needed; + struct file *file; + struct fs_struct *fs = current->fs; nd->last_type = LAST_ROOT; /* if there are only slashes... */ nd->flags = flags; nd->depth = 0; - read_lock(¤t->fs->lock); if (*name=='/') { - 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); + read_lock(&fs->lock); + if (fs->altroot && !(nd->flags & LOOKUP_NOALT)) { + nd->mnt = mntget(fs->altrootmnt); + nd->dentry = dget(fs->altroot); + read_unlock(&fs->lock); if (__emul_lookup_dentry(name,nd)) goto out; /* found in altroot */ - read_lock(¤t->fs->lock); + read_lock(&fs->lock); } - nd->mnt = mntget(current->fs->rootmnt); - nd->dentry = dget(current->fs->root); + nd->mnt = mntget(fs->rootmnt); + nd->dentry = dget(fs->root); + read_unlock(&fs->lock); + } else if (dfd == AT_FDCWD) { + read_lock(&fs->lock); + nd->mnt = mntget(fs->pwdmnt); + nd->dentry = dget(fs->pwd); + read_unlock(&fs->lock); } else { - nd->mnt = mntget(current->fs->pwdmnt); - nd->dentry = dget(current->fs->pwd); + struct dentry *dentry; + + file = fget_light(dfd, &fput_needed); + retval = -EBADF; + if (!file) + goto out_fail; + + dentry = file->f_path.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_path.mnt); + 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); + + retval = path_walk(name, nd); out: - if (unlikely(current->audit_context - && nd && nd->dentry && nd->dentry->d_inode)) - audit_inode(name, nd->dentry->d_inode); + if (unlikely(!retval && !audit_dummy_context() && nd->dentry && + nd->dentry->d_inode)) + audit_inode(name, nd->dentry); +out_fail: return retval; + +fput_fail: + fput_light(file, fput_needed); + goto out_fail; } -/* - * Restricted form of lookup. Doesn't follow links, single-component only, - * needs parent already locked. Doesn't follow mounts. - * SMP-safe. +int fastcall path_lookup(const char *name, unsigned int flags, + struct nameidata *nd) +{ + return do_path_lookup(AT_FDCWD, name, flags, nd); +} + +/** + * vfs_path_lookup - lookup a file path relative to a dentry-vfsmount pair + * @dentry: pointer to dentry of the base directory + * @mnt: pointer to vfs mount of the base directory + * @name: pointer to file name + * @flags: lookup flags + * @nd: pointer to nameidata */ -static struct dentry * __lookup_hash(struct qstr *name, struct dentry * base, struct nameidata *nd) +int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt, + const char *name, unsigned int flags, + struct nameidata *nd) { - struct dentry * dentry; + int retval; + + /* same as do_path_lookup */ + nd->last_type = LAST_ROOT; + nd->flags = flags; + nd->depth = 0; + + nd->mnt = mntget(mnt); + nd->dentry = dget(dentry); + + retval = path_walk(name, nd); + if (unlikely(!retval && !audit_dummy_context() && nd->dentry && + nd->dentry->d_inode)) + audit_inode(name, nd->dentry); + + return retval; + +} + +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; +} + +static struct dentry *__lookup_hash(struct qstr *name, + struct dentry *base, struct nameidata *nd) +{ + struct dentry *dentry; struct inode *inode; int err; inode = base->d_inode; - err = permission(inode, MAY_EXEC, nd); - dentry = ERR_PTR(err); - if (err) - goto out; /* * See if the low-level filesystem might want @@ -1096,60 +1324,109 @@ out: return dentry; } -struct dentry * lookup_hash(struct qstr *name, struct dentry * base) +/* + * Restricted form of lookup. Doesn't follow links, single-component only, + * needs parent already locked. Doesn't follow mounts. + * SMP-safe. + */ +static struct dentry *lookup_hash(struct nameidata *nd) { - return __lookup_hash(name, base, NULL); + int err; + + err = permission(nd->dentry->d_inode, MAY_EXEC, nd); + if (err) + return ERR_PTR(err); + return __lookup_hash(&nd->last, nd->dentry, nd); } -/* SMP-safe */ -struct dentry * lookup_one_len(const char * name, struct dentry * base, int len) +static int __lookup_one_len(const char *name, struct qstr *this, + struct dentry *base, int len) { unsigned long hash; - struct qstr this; unsigned int c; - this.name = name; - this.len = len; + this->name = name; + this->len = len; if (!len) - goto access; + return -EACCES; hash = init_name_hash(); while (len--) { c = *(const unsigned char *)name++; if (c == '/' || c == '\0') - goto access; + return -EACCES; hash = partial_name_hash(c, hash); } - this.hash = end_name_hash(hash); + this->hash = end_name_hash(hash); + return 0; +} - return lookup_hash(&this, base); -access: - return ERR_PTR(-EACCES); +/** + * lookup_one_len: filesystem helper to lookup single pathname component + * @name: pathname component to lookup + * @base: base directory to lookup from + * @len: maximum length @len should be interpreted to + * + * Note that this routine is purely a helper for filesystem useage and should + * not be called by generic code. Also note that by using this function to + * nameidata argument is passed to the filesystem methods and a filesystem + * using this helper needs to be prepared for that. + */ +struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) +{ + int err; + struct qstr this; + + err = __lookup_one_len(name, &this, base, len); + if (err) + return ERR_PTR(err); + + err = permission(base->d_inode, MAY_EXEC, NULL); + if (err) + return ERR_PTR(err); + return __lookup_hash(&this, base, NULL); } -/* - * namei() +/** + * lookup_one_noperm - bad hack for sysfs + * @name: pathname component to lookup + * @base: base directory to lookup from * - * is used by most simple commands to get the inode of a specified name. - * Open, link etc use their own routines, but this is enough for things - * like 'chmod' etc. + * This is a variant of lookup_one_len that doesn't perform any permission + * checks. It's a horrible hack to work around the braindead sysfs + * architecture and should not be used anywhere else. * - * namei exists in two versions: namei/lnamei. The only difference is - * that namei follows links, while lnamei does not. - * SMP-safe + * DON'T USE THIS FUNCTION EVER, thanks. */ -int fastcall __user_walk(const char __user *name, unsigned flags, struct nameidata *nd) +struct dentry *lookup_one_noperm(const char *name, struct dentry *base) +{ + int err; + struct qstr this; + + err = __lookup_one_len(name, &this, base, strlen(name)); + if (err) + return ERR_PTR(err); + return __lookup_hash(&this, base, NULL); +} + +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. @@ -1184,7 +1461,7 @@ 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) { int error; @@ -1192,6 +1469,7 @@ 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, dir); error = permission(dir,MAY_WRITE | MAY_EXEC, NULL); if (error) @@ -1234,9 +1512,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) @@ -1246,9 +1521,6 @@ 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; @@ -1263,39 +1535,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_nested(&p1->d_inode->i_mutex, I_MUTEX_PARENT); 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_nested(&p2->d_inode->i_mutex, I_MUTEX_PARENT); + mutex_lock_nested(&p1->d_inode->i_mutex, I_MUTEX_CHILD); 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_nested(&p1->d_inode->i_mutex, I_MUTEX_PARENT); + mutex_lock_nested(&p2->d_inode->i_mutex, I_MUTEX_CHILD); return p; } } - down(&p1->d_inode->i_sem); - down(&p2->d_inode->i_sem); + mutex_lock_nested(&p1->d_inode->i_mutex, I_MUTEX_PARENT); + mutex_lock_nested(&p2->d_inode->i_mutex, I_MUTEX_CHILD); 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); } } @@ -1317,7 +1589,7 @@ int vfs_create(struct inode *dir, struct dentry *dentry, int mode, DQUOT_INIT(dir); error = dir->i_op->create(dir, dentry, mode, nd); if (!error) - fsnotify_create(dir, dentry->d_name.name); + fsnotify_create(dir, dentry); return error; } @@ -1336,10 +1608,6 @@ 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); - if (error) - return error; - /* * FIFO's, sockets and device files are special: they don't * actually live on the filesystem itself, and as such you @@ -1354,6 +1622,10 @@ int may_open(struct nameidata *nd, int acc_mode, int flag) flag &= ~O_TRUNC; } else if (IS_RDONLY(inode) && (flag & FMODE_WRITE)) return -EROFS; + + error = vfs_permission(nd, acc_mode); + if (error) + return error; /* * An append-only file must be opened in append mode for writing. */ @@ -1366,7 +1638,7 @@ int may_open(struct nameidata *nd, int acc_mode, int flag) /* O_NOATIME can only be set by the owner or superuser */ if (flag & O_NOATIME) - if (current->fsuid != inode->i_uid && !capable(CAP_FOWNER)) + if (!is_owner_or_cap(inode)) return -EPERM; /* @@ -1387,8 +1659,10 @@ int may_open(struct nameidata *nd, int acc_mode, int flag) error = locks_verify_locked(inode); if (!error) { DQUOT_INIT(inode); - - error = do_truncate(dentry, 0); + + error = do_truncate(dentry, 0, + ATTR_MTIME|ATTR_CTIME|ATTR_OPEN, + NULL); } put_write_access(inode); if (error) @@ -1400,6 +1674,24 @@ int may_open(struct nameidata *nd, int acc_mode, int flag) return 0; } +static int open_namei_create(struct nameidata *nd, struct path *path, + int flag, int mode) +{ + int error; + struct dentry *dir = nd->dentry; + + if (!IS_POSIXACL(dir->d_inode)) + mode &= ~current->fs->umask; + error = vfs_create(dir->d_inode, path->dentry, mode, nd); + mutex_unlock(&dir->d_inode->i_mutex); + dput(nd->dentry); + nd->dentry = path->dentry; + if (error) + return error; + /* Don't check for write permission, don't truncate */ + return may_open(nd, 0, flag & ~O_TRUNC); +} + /* * open_namei() * @@ -1414,29 +1706,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; + 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; @@ -1445,7 +1739,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; @@ -1460,37 +1754,36 @@ 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); - path.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(path.dentry); if (IS_ERR(path.dentry)) { - up(&dir->d_inode->i_sem); + 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 (!path.dentry->d_inode) { - if (!IS_POSIXACL(dir->d_inode)) - mode &= ~current->fs->umask; - error = vfs_create(dir->d_inode, path.dentry, mode, nd); - up(&dir->d_inode->i_sem); - dput(nd->dentry); - nd->dentry = path.dentry; + error = open_namei_create(nd, &path, flag, mode); if (error) goto exit; - /* Don't check for write permission, don't truncate */ - acc_mode = 0; - flag &= ~O_TRUNC; - goto ok; + return 0; } /* * It already exists. */ - up(&dir->d_inode->i_sem); + mutex_unlock(&dir->d_inode->i_mutex); + audit_inode(pathname, path.dentry); error = -EEXIST; if (flag & O_EXCL) @@ -1501,6 +1794,7 @@ do_last: if (flag & O_NOFOLLOW) goto exit_dput; } + error = -ENOENT; if (!path.dentry->d_inode) goto exit_dput; @@ -1520,6 +1814,8 @@ ok: exit_dput: dput_path(&path, nd); exit: + if (!IS_ERR(nd->intent.open.file)) + release_open_intent(nd); path_release(nd); return error; @@ -1542,8 +1838,14 @@ do_link: if (error) goto exit_dput; error = __do_follow_link(&path, nd); - if (error) + if (error) { + /* Does someone understand code flow here? Or it is only + * me so stupid? Anathema to whoever designed this non-sense + * with "intent.open". + */ + release_open_intent(nd); return error; + } nd->flags &= ~LOOKUP_PARENT; if (nd->last_type == LAST_BIND) goto ok; @@ -1551,19 +1853,19 @@ do_link: 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); - path.dentry = __lookup_hash(&nd->last, nd->dentry, nd); + mutex_lock(&dir->d_inode->i_mutex); + path.dentry = lookup_hash(nd); path.mnt = nd->mnt; - putname(nd->last.name); + __putname(nd->last.name); goto do_last; } @@ -1575,13 +1877,13 @@ 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_sem locked. + * Returns with nd->dentry->d_inode->i_mutex locked. */ struct dentry *lookup_create(struct nameidata *nd, int is_dir) { struct dentry *dentry = ERR_PTR(-EEXIST); - down(&nd->dentry->d_inode->i_sem); + mutex_lock_nested(&nd->dentry->d_inode->i_mutex, I_MUTEX_PARENT); /* * Yucky last component or no last component at all? * (foo/., foo/.., /////) @@ -1589,11 +1891,13 @@ struct dentry *lookup_create(struct nameidata *nd, int is_dir) if (nd->last_type != LAST_NORM) goto fail; nd->flags &= ~LOOKUP_PARENT; + nd->flags |= LOOKUP_CREATE; + nd->intent.open.flags = O_EXCL; /* * Do the final lookup. */ - dentry = lookup_hash(&nd->last, nd->dentry); + dentry = lookup_hash(nd); if (IS_ERR(dentry)) goto fail; @@ -1634,11 +1938,12 @@ 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) - fsnotify_create(dir, dentry->d_name.name); + 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; @@ -1651,7 +1956,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); @@ -1679,7 +1984,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); @@ -1687,6 +1992,11 @@ out: return error; } +asmlinkage long sys_mknod(const char __user *filename, int mode, unsigned dev) +{ + return sys_mknodat(AT_FDCWD, filename, mode, dev); +} + int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) { int error = may_create(dir, dentry, NULL); @@ -1705,41 +2015,48 @@ int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) DQUOT_INIT(dir); error = dir->i_op->mkdir(dir, dentry, mode); if (!error) - fsnotify_mkdir(dir, dentry->d_name.name); + 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; + struct dentry *dentry; + struct nameidata nd; tmp = getname(pathname); error = PTR_ERR(tmp); - if (!IS_ERR(tmp)) { - struct dentry *dentry; - struct nameidata nd; + if (IS_ERR(tmp)) + goto out_err; - error = path_lookup(tmp, LOOKUP_PARENT, &nd); - if (error) - goto out; - dentry = lookup_create(&nd, 1); - error = PTR_ERR(dentry); - if (!IS_ERR(dentry)) { - if (!IS_POSIXACL(nd.dentry->d_inode)) - mode &= ~current->fs->umask; - error = vfs_mkdir(nd.dentry->d_inode, dentry, mode); - dput(dentry); - } - up(&nd.dentry->d_inode->i_sem); - path_release(&nd); -out: - putname(tmp); - } + error = do_path_lookup(dfd, tmp, LOOKUP_PARENT, &nd); + if (error) + goto out; + dentry = lookup_create(&nd, 1); + error = PTR_ERR(dentry); + if (IS_ERR(dentry)) + goto out_unlock; + if (!IS_POSIXACL(nd.dentry->d_inode)) + mode &= ~current->fs->umask; + error = vfs_mkdir(nd.dentry->d_inode, dentry, mode); + dput(dentry); +out_unlock: + mutex_unlock(&nd.dentry->d_inode->i_mutex); + path_release(&nd); +out: + putname(tmp); +out_err: 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 @@ -1758,8 +2075,7 @@ out: void dentry_unhash(struct dentry *dentry) { dget(dentry); - if (atomic_read(&dentry->d_count)) - shrink_dcache_parent(dentry); + shrink_dcache_parent(dentry); spin_lock(&dcache_lock); spin_lock(&dentry->d_lock); if (atomic_read(&dentry->d_count) == 2) @@ -1780,7 +2096,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; @@ -1792,7 +2108,7 @@ 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) { d_delete(dentry); } @@ -1801,7 +2117,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; @@ -1812,7 +2128,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; @@ -1827,14 +2143,15 @@ 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_nested(&nd.dentry->d_inode->i_mutex, I_MUTEX_PARENT); + dentry = lookup_hash(&nd); error = PTR_ERR(dentry); - if (!IS_ERR(dentry)) { - error = vfs_rmdir(nd.dentry->d_inode, dentry); - dput(dentry); - } - up(&nd.dentry->d_inode->i_sem); + if (IS_ERR(dentry)) + goto exit2; + error = vfs_rmdir(nd.dentry->d_inode, dentry); + dput(dentry); +exit2: + mutex_unlock(&nd.dentry->d_inode->i_mutex); exit1: path_release(&nd); exit: @@ -1842,6 +2159,11 @@ exit: return error; } +asmlinkage long sys_rmdir(const char __user *pathname) +{ + return do_rmdir(AT_FDCWD, pathname); +} + int vfs_unlink(struct inode *dir, struct dentry *dentry) { int error = may_delete(dir, dentry, 0); @@ -1854,7 +2176,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 { @@ -1862,7 +2184,7 @@ 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)) { @@ -1874,11 +2196,11 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry) /* * 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; @@ -1890,14 +2212,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_nested(&nd.dentry->d_inode->i_mutex, I_MUTEX_PARENT); + dentry = lookup_hash(&nd); error = PTR_ERR(dentry); if (!IS_ERR(dentry)) { /* Why not before? Because we want correct error value */ @@ -1910,7 +2232,7 @@ asmlinkage long sys_unlink(const char __user * pathname) 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: @@ -1925,6 +2247,22 @@ slashes: goto exit2; } +asmlinkage long sys_unlinkat(int dfd, const char __user *pathname, int flag) +{ + 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) { int error = may_create(dir, dentry, NULL); @@ -1942,43 +2280,52 @@ 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) - fsnotify_create(dir, dentry->d_name.name); + 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; char * to; + struct dentry *dentry; + struct nameidata nd; from = getname(oldname); if(IS_ERR(from)) return PTR_ERR(from); to = getname(newname); error = PTR_ERR(to); - if (!IS_ERR(to)) { - struct dentry *dentry; - struct nameidata nd; + if (IS_ERR(to)) + goto out_putname; - error = path_lookup(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); - dput(dentry); - } - up(&nd.dentry->d_inode->i_sem); - path_release(&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)) + goto out_unlock; + + error = vfs_symlink(nd.dentry->d_inode, dentry, from, S_IALLUGO); + dput(dentry); +out_unlock: + mutex_unlock(&nd.dentry->d_inode->i_mutex); + path_release(&nd); out: - putname(to); - } + putname(to); +out_putname: putname(from); return error; } +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 inode *inode = old_dentry->d_inode; @@ -2008,12 +2355,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); + mutex_unlock(&old_dentry->d_inode->i_mutex); if (!error) - fsnotify_create(dir, new_dentry->d_name.name); + fsnotify_create(dir, new_dentry); return error; } @@ -2026,21 +2373,28 @@ 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 & ~AT_SYMLINK_FOLLOW) != 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, + flags & AT_SYMLINK_FOLLOW ? LOOKUP_FOLLOW : 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; @@ -2048,11 +2402,12 @@ asmlinkage long sys_link(const char __user * oldname, const char __user * newnam goto out_release; 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); - dput(new_dentry); - } - up(&nd.dentry->d_inode->i_sem); + if (IS_ERR(new_dentry)) + goto out_unlock; + error = vfs_link(old_nd.dentry, nd.dentry->d_inode, new_dentry); + dput(new_dentry); +out_unlock: + mutex_unlock(&nd.dentry->d_inode->i_mutex); out_release: path_release(&nd); out: @@ -2063,6 +2418,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... @@ -2070,17 +2430,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. @@ -2090,9 +2450,9 @@ 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]. */ static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry, @@ -2117,7 +2477,7 @@ static 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)) @@ -2127,13 +2487,14 @@ static 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) - d_move(old_dentry,new_dentry); + if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) + d_move(old_dentry,new_dentry); return error; } @@ -2150,18 +2511,17 @@ static 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 error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry); if (!error) { - /* The following d_move() should become unconditional */ - if (!(old_dir->i_sb->s_type->fs_flags & FS_ODD_RENAME)) + if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) d_move(old_dentry, new_dentry); } if (target) - up(&target->i_sem); + mutex_unlock(&target->i_mutex); dput(new_dentry); return error; } @@ -2202,14 +2562,15 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, if (!error) { 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); + new_dentry->d_inode, old_dentry); } 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; @@ -2217,11 +2578,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; @@ -2240,7 +2601,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; @@ -2260,7 +2621,7 @@ 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); + new_dentry = lookup_hash(&newnd); error = PTR_ERR(new_dentry); if (IS_ERR(new_dentry)) goto exit4; @@ -2285,7 +2646,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; @@ -2297,13 +2659,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; @@ -2352,22 +2719,11 @@ static char *page_getlink(struct dentry * dentry, struct page **ppage) { struct page * page; struct address_space *mapping = dentry->d_inode->i_mapping; - page = read_cache_page(mapping, 0, (filler_t *)mapping->a_ops->readpage, - NULL); + page = read_mapping_page(mapping, 0, NULL); if (IS_ERR(page)) - goto sync_fail; - wait_on_page_locked(page); - if (!PageUptodate(page)) - goto async_fail; + return (char*)page; *ppage = page; return kmap(page); - -async_fail: - page_cache_release(page); - return ERR_PTR(-EIO); - -sync_fail: - return (char*)page; } int page_readlink(struct dentry *dentry, char __user *buffer, int buflen) @@ -2399,70 +2755,70 @@ void page_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie) } } -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); - int err = -ENOMEM; + struct page *page; + void *fsdata; + int err; char *kaddr; - if (!page) - goto fail; - err = mapping->a_ops->prepare_write(NULL, page, 0, len-1); +retry: + err = pagecache_write_begin(NULL, mapping, 0, len-1, + AOP_FLAG_UNINTERRUPTIBLE, &page, &fsdata); if (err) - goto fail_map; + goto fail; + 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); - /* - * 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 - * that it is unmapped (typically even will not look into inode - - * ->i_size will be enough for everything) and zero it out. - * OTOH it's obviously correct and should make the page up-to-date. - */ - if (!PageUptodate(page)) { - err = mapping->a_ops->readpage(NULL, page); - wait_on_page_locked(page); - } else { - unlock_page(page); - } - page_cache_release(page); + + err = pagecache_write_end(NULL, mapping, 0, len-1, len-1, + page, fsdata); if (err < 0) goto fail; + if (err < len-1) + goto retry; + mark_inode_dirty(inode); return 0; -fail_map: - unlock_page(page); - page_cache_release(page); fail: return err; } -struct inode_operations page_symlink_inode_operations = { +int page_symlink(struct inode *inode, const char *symname, int len) +{ + return __page_symlink(inode, symname, len, + mapping_gfp_mask(inode->i_mapping)); +} + +const struct inode_operations page_symlink_inode_operations = { .readlink = generic_readlink, .follow_link = page_follow_link_light, .put_link = page_put_link, }; 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_hash); EXPORT_SYMBOL(lookup_one_len); 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(vfs_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);