Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b05430fc authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull vfs pile 3 (of many) from Al Viro:
 "Waiman's conversion of d_path() and bits related to it,
  kern_path_mountpoint(), several cleanups and fixes (exportfs
  one is -stable fodder, IMO).

  There definitely will be more...  ;-/"

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  split read_seqretry_or_unlock(), convert d_walk() to resulting primitives
  dcache: Translating dentry into pathname without taking rename_lock
  autofs4 - fix device ioctl mount lookup
  introduce kern_path_mountpoint()
  rename user_path_umountat() to user_path_mountpoint_at()
  take unlazy_walk() into umount_lookup_last()
  Kill indirect include of file.h from eventfd.h, use fdget() in cgroup.c
  prune_super(): sb->s_op is never NULL
  exportfs: don't assume that ->iterate() won't feed us too long entries
  afs: get rid of redundant ->d_name.len checks
parents d0d27277 48f5ec21
Loading
Loading
Loading
Loading
+0 −24
Original line number Diff line number Diff line
@@ -751,10 +751,6 @@ static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
	_enter("{%x:%u},{%s},%ho",
	       dvnode->fid.vid, dvnode->fid.vnode, dentry->d_name.name, mode);

	ret = -ENAMETOOLONG;
	if (dentry->d_name.len >= AFSNAMEMAX)
		goto error;

	key = afs_request_key(dvnode->volume->cell);
	if (IS_ERR(key)) {
		ret = PTR_ERR(key);
@@ -816,10 +812,6 @@ static int afs_rmdir(struct inode *dir, struct dentry *dentry)
	_enter("{%x:%u},{%s}",
	       dvnode->fid.vid, dvnode->fid.vnode, dentry->d_name.name);

	ret = -ENAMETOOLONG;
	if (dentry->d_name.len >= AFSNAMEMAX)
		goto error;

	key = afs_request_key(dvnode->volume->cell);
	if (IS_ERR(key)) {
		ret = PTR_ERR(key);
@@ -936,10 +928,6 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
	_enter("{%x:%u},{%s},%ho,",
	       dvnode->fid.vid, dvnode->fid.vnode, dentry->d_name.name, mode);

	ret = -ENAMETOOLONG;
	if (dentry->d_name.len >= AFSNAMEMAX)
		goto error;

	key = afs_request_key(dvnode->volume->cell);
	if (IS_ERR(key)) {
		ret = PTR_ERR(key);
@@ -1005,10 +993,6 @@ static int afs_link(struct dentry *from, struct inode *dir,
	       dvnode->fid.vid, dvnode->fid.vnode,
	       dentry->d_name.name);

	ret = -ENAMETOOLONG;
	if (dentry->d_name.len >= AFSNAMEMAX)
		goto error;

	key = afs_request_key(dvnode->volume->cell);
	if (IS_ERR(key)) {
		ret = PTR_ERR(key);
@@ -1053,10 +1037,6 @@ static int afs_symlink(struct inode *dir, struct dentry *dentry,
	       dvnode->fid.vid, dvnode->fid.vnode, dentry->d_name.name,
	       content);

	ret = -ENAMETOOLONG;
	if (dentry->d_name.len >= AFSNAMEMAX)
		goto error;

	ret = -EINVAL;
	if (strlen(content) >= AFSPATHMAX)
		goto error;
@@ -1127,10 +1107,6 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
	       new_dvnode->fid.vid, new_dvnode->fid.vnode,
	       new_dentry->d_name.name);

	ret = -ENAMETOOLONG;
	if (new_dentry->d_name.len >= AFSNAMEMAX)
		goto error;

	key = afs_request_key(orig_dvnode->volume->cell);
	if (IS_ERR(key)) {
		ret = PTR_ERR(key);
+12 −11
Original line number Diff line number Diff line
@@ -183,13 +183,14 @@ static int autofs_dev_ioctl_protosubver(struct file *fp,
	return 0;
}

/* Find the topmost mount satisfying test() */
static int find_autofs_mount(const char *pathname,
			     struct path *res,
			     int test(struct path *path, void *data),
			     void *data)
{
	struct path path;
	int err = kern_path(pathname, 0, &path);
	int err = kern_path_mountpoint(AT_FDCWD, pathname, &path, 0);
	if (err)
		return err;
	err = -ENOENT;
@@ -197,10 +198,9 @@ static int find_autofs_mount(const char *pathname,
		if (path.dentry->d_sb->s_magic == AUTOFS_SUPER_MAGIC) {
			if (test(&path, data)) {
				path_get(&path);
				if (!err) /* already found some */
					path_put(res);
				*res = path;
				err = 0;
				break;
			}
		}
		if (!follow_up(&path))
@@ -486,12 +486,11 @@ static int autofs_dev_ioctl_askumount(struct file *fp,
 * mount if there is one or 0 if it isn't a mountpoint.
 *
 * If we aren't supplied with a file descriptor then we
 * lookup the nameidata of the path and check if it is the
 * root of a mount. If a type is given we are looking for
 * a particular autofs mount and if we don't find a match
 * we return fail. If the located nameidata path is the
 * root of a mount we return 1 along with the super magic
 * of the mount or 0 otherwise.
 * lookup the path and check if it is the root of a mount.
 * If a type is given we are looking for a particular autofs
 * mount and if we don't find a match we return fail. If the
 * located path is the root of a mount we return 1 along with
 * the super magic of the mount or 0 otherwise.
 *
 * In both cases the the device number (as returned by
 * new_encode_dev()) is also returned.
@@ -519,9 +518,11 @@ static int autofs_dev_ioctl_ismountpoint(struct file *fp,

	if (!fp || param->ioctlfd == -1) {
		if (autofs_type_any(type))
			err = kern_path(name, LOOKUP_FOLLOW, &path);
			err = kern_path_mountpoint(AT_FDCWD,
						   name, &path, LOOKUP_FOLLOW);
		else
			err = find_autofs_mount(name, &path, test_by_type, &type);
			err = find_autofs_mount(name, &path,
						test_by_type, &type);
		if (err)
			goto out;
		devid = new_encode_dev(path.dentry->d_sb->s_dev);
+144 −76
Original line number Diff line number Diff line
@@ -88,6 +88,35 @@ EXPORT_SYMBOL(rename_lock);

static struct kmem_cache *dentry_cache __read_mostly;

/**
 * read_seqbegin_or_lock - begin a sequence number check or locking block
 * lock: sequence lock
 * seq : sequence number to be checked
 *
 * First try it once optimistically without taking the lock. If that fails,
 * take the lock. The sequence number is also used as a marker for deciding
 * whether to be a reader (even) or writer (odd).
 * N.B. seq must be initialized to an even number to begin with.
 */
static inline void read_seqbegin_or_lock(seqlock_t *lock, int *seq)
{
	if (!(*seq & 1))	/* Even */
		*seq = read_seqbegin(lock);
	else			/* Odd */
		write_seqlock(lock);
}

static inline int need_seqretry(seqlock_t *lock, int seq)
{
	return !(seq & 1) && read_seqretry(lock, seq);
}

static inline void done_seqretry(seqlock_t *lock, int seq)
{
	if (seq & 1)
		write_sequnlock(lock);
}

/*
 * This is the single most critical data structure when it comes
 * to the dcache: the hashtable for lookups. Somebody should try
@@ -1012,7 +1041,7 @@ void shrink_dcache_for_umount(struct super_block *sb)
 * the parenthood after dropping the lock and check
 * that the sequence number still matches.
 */
static struct dentry *try_to_ascend(struct dentry *old, int locked, unsigned seq)
static struct dentry *try_to_ascend(struct dentry *old, unsigned seq)
{
	struct dentry *new = old->d_parent;

@@ -1026,7 +1055,7 @@ static struct dentry *try_to_ascend(struct dentry *old, int locked, unsigned seq
	 */
	if (new != old->d_parent ||
		 (old->d_flags & DCACHE_DENTRY_KILLED) ||
		 (!locked && read_seqretry(&rename_lock, seq))) {
		 need_seqretry(&rename_lock, seq)) {
		spin_unlock(&new->d_lock);
		new = NULL;
	}
@@ -1063,13 +1092,12 @@ static void d_walk(struct dentry *parent, void *data,
{
	struct dentry *this_parent;
	struct list_head *next;
	unsigned seq;
	int locked = 0;
	unsigned seq = 0;
	enum d_walk_ret ret;
	bool retry = true;

	seq = read_seqbegin(&rename_lock);
again:
	read_seqbegin_or_lock(&rename_lock, &seq);
	this_parent = parent;
	spin_lock(&this_parent->d_lock);

@@ -1123,13 +1151,13 @@ static void d_walk(struct dentry *parent, void *data,
	 */
	if (this_parent != parent) {
		struct dentry *child = this_parent;
		this_parent = try_to_ascend(this_parent, locked, seq);
		this_parent = try_to_ascend(this_parent, seq);
		if (!this_parent)
			goto rename_retry;
		next = child->d_u.d_child.next;
		goto resume;
	}
	if (!locked && read_seqretry(&rename_lock, seq)) {
	if (need_seqretry(&rename_lock, seq)) {
		spin_unlock(&this_parent->d_lock);
		goto rename_retry;
	}
@@ -1138,17 +1166,13 @@ static void d_walk(struct dentry *parent, void *data,

out_unlock:
	spin_unlock(&this_parent->d_lock);
	if (locked)
		write_sequnlock(&rename_lock);
	done_seqretry(&rename_lock, seq);
	return;

rename_retry:
	if (!retry)
		return;
	if (locked)
		goto again;
	locked = 1;
	write_seqlock(&rename_lock);
	seq = 1;
	goto again;
}

@@ -2647,9 +2671,39 @@ static int prepend(char **buffer, int *buflen, const char *str, int namelen)
	return 0;
}

/**
 * prepend_name - prepend a pathname in front of current buffer pointer
 * buffer: buffer pointer
 * buflen: allocated length of the buffer
 * name:   name string and length qstr structure
 *
 * With RCU path tracing, it may race with d_move(). Use ACCESS_ONCE() to
 * make sure that either the old or the new name pointer and length are
 * fetched. However, there may be mismatch between length and pointer.
 * The length cannot be trusted, we need to copy it byte-by-byte until
 * the length is reached or a null byte is found. It also prepends "/" at
 * the beginning of the name. The sequence number check at the caller will
 * retry it again when a d_move() does happen. So any garbage in the buffer
 * due to mismatched pointer and length will be discarded.
 */
static int prepend_name(char **buffer, int *buflen, struct qstr *name)
{
	return prepend(buffer, buflen, name->name, name->len);
	const char *dname = ACCESS_ONCE(name->name);
	u32 dlen = ACCESS_ONCE(name->len);
	char *p;

	if (*buflen < dlen + 1)
		return -ENAMETOOLONG;
	*buflen -= dlen + 1;
	p = *buffer -= dlen + 1;
	*p++ = '/';
	while (dlen--) {
		char c = *dname++;
		if (!c)
			break;
		*p++ = c;
	}
	return 0;
}

/**
@@ -2659,7 +2713,14 @@ static int prepend_name(char **buffer, int *buflen, struct qstr *name)
 * @buffer: pointer to the end of the buffer
 * @buflen: pointer to buffer length
 *
 * Caller holds the rename_lock.
 * The function tries to write out the pathname without taking any lock other
 * than the RCU read lock to make sure that dentries won't go away. It only
 * checks the sequence number of the global rename_lock as any change in the
 * dentry's d_seq will be preceded by changes in the rename_lock sequence
 * number. If the sequence number had been change, it will restart the whole
 * pathname back-tracing sequence again. It performs a total of 3 trials of
 * lockless back-tracing sequences before falling back to take the
 * rename_lock.
 */
static int prepend_path(const struct path *path,
			const struct path *root,
@@ -2668,54 +2729,66 @@ static int prepend_path(const struct path *path,
	struct dentry *dentry = path->dentry;
	struct vfsmount *vfsmnt = path->mnt;
	struct mount *mnt = real_mount(vfsmnt);
	bool slash = false;
	int error = 0;
	unsigned seq = 0;
	char *bptr;
	int blen;

	rcu_read_lock();
restart:
	bptr = *buffer;
	blen = *buflen;
	read_seqbegin_or_lock(&rename_lock, &seq);
	while (dentry != root->dentry || vfsmnt != root->mnt) {
		struct dentry * parent;

		if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
			/* Global root? */
			if (!mnt_has_parent(mnt))
				goto global_root;
			if (mnt_has_parent(mnt)) {
				dentry = mnt->mnt_mountpoint;
				mnt = mnt->mnt_parent;
				vfsmnt = &mnt->mnt;
				continue;
			}
		parent = dentry->d_parent;
		prefetch(parent);
		spin_lock(&dentry->d_lock);
		error = prepend_name(buffer, buflen, &dentry->d_name);
		spin_unlock(&dentry->d_lock);
		if (!error)
			error = prepend(buffer, buflen, "/", 1);
		if (error)
			break;

		slash = true;
		dentry = parent;
	}

	if (!error && !slash)
		error = prepend(buffer, buflen, "/", 1);

	return error;

global_root:
			/*
			 * Filesystems needing to implement special "root names"
			 * should do so with ->d_dname()
			 */
			if (IS_ROOT(dentry) &&
	    (dentry->d_name.len != 1 || dentry->d_name.name[0] != '/')) {
			   (dentry->d_name.len != 1 ||
			    dentry->d_name.name[0] != '/')) {
				WARN(1, "Root dentry has weird name <%.*s>\n",
		     (int) dentry->d_name.len, dentry->d_name.name);
				     (int) dentry->d_name.len,
				     dentry->d_name.name);
			}
	if (!slash)
		error = prepend(buffer, buflen, "/", 1);
			if (!error)
				error = is_mounted(vfsmnt) ? 1 : 2;
			break;
		}
		parent = dentry->d_parent;
		prefetch(parent);
		error = prepend_name(&bptr, &blen, &dentry->d_name);
		if (error)
			break;

		dentry = parent;
	}
	if (!(seq & 1))
		rcu_read_unlock();
	if (need_seqretry(&rename_lock, seq)) {
		seq = 1;
		goto restart;
	}
	done_seqretry(&rename_lock, seq);

	if (error >= 0 && bptr == *buffer) {
		if (--blen < 0)
			error = -ENAMETOOLONG;
		else
			*--bptr = '/';
	}
	*buffer = bptr;
	*buflen = blen;
	return error;
}

@@ -2744,9 +2817,7 @@ char *__d_path(const struct path *path,

	prepend(&res, &buflen, "\0", 1);
	br_read_lock(&vfsmount_lock);
	write_seqlock(&rename_lock);
	error = prepend_path(path, root, &res, &buflen);
	write_sequnlock(&rename_lock);
	br_read_unlock(&vfsmount_lock);

	if (error < 0)
@@ -2765,9 +2836,7 @@ char *d_absolute_path(const struct path *path,

	prepend(&res, &buflen, "\0", 1);
	br_read_lock(&vfsmount_lock);
	write_seqlock(&rename_lock);
	error = prepend_path(path, &root, &res, &buflen);
	write_sequnlock(&rename_lock);
	br_read_unlock(&vfsmount_lock);

	if (error > 1)
@@ -2833,9 +2902,7 @@ char *d_path(const struct path *path, char *buf, int buflen)

	get_fs_root(current->fs, &root);
	br_read_lock(&vfsmount_lock);
	write_seqlock(&rename_lock);
	error = path_with_deleted(path, &root, &res, &buflen);
	write_sequnlock(&rename_lock);
	br_read_unlock(&vfsmount_lock);
	if (error < 0)
		res = ERR_PTR(error);
@@ -2870,7 +2937,7 @@ char *simple_dname(struct dentry *dentry, char *buffer, int buflen)
	char *end = buffer + buflen;
	/* these dentries are never renamed, so d_lock is not needed */
	if (prepend(&end, &buflen, " (deleted)", 11) ||
	    prepend_name(&end, &buflen, &dentry->d_name) ||
	    prepend(&end, &buflen, dentry->d_name.name, dentry->d_name.len) ||
	    prepend(&end, &buflen, "/", 1))  
		end = ERR_PTR(-ENAMETOOLONG);
	return end;
@@ -2881,30 +2948,42 @@ char *simple_dname(struct dentry *dentry, char *buffer, int buflen)
 */
static char *__dentry_path(struct dentry *dentry, char *buf, int buflen)
{
	char *end = buf + buflen;
	char *retval;
	char *end, *retval;
	int len, seq = 0;
	int error = 0;

	prepend(&end, &buflen, "\0", 1);
	rcu_read_lock();
restart:
	end = buf + buflen;
	len = buflen;
	prepend(&end, &len, "\0", 1);
	if (buflen < 1)
		goto Elong;
	/* Get '/' right */
	retval = end-1;
	*retval = '/';

	read_seqbegin_or_lock(&rename_lock, &seq);
	while (!IS_ROOT(dentry)) {
		struct dentry *parent = dentry->d_parent;
		int error;

		prefetch(parent);
		spin_lock(&dentry->d_lock);
		error = prepend_name(&end, &buflen, &dentry->d_name);
		spin_unlock(&dentry->d_lock);
		if (error != 0 || prepend(&end, &buflen, "/", 1) != 0)
			goto Elong;
		error = prepend_name(&end, &len, &dentry->d_name);
		if (error)
			break;

		retval = end;
		dentry = parent;
	}
	if (!(seq & 1))
		rcu_read_unlock();
	if (need_seqretry(&rename_lock, seq)) {
		seq = 1;
		goto restart;
	}
	done_seqretry(&rename_lock, seq);
	if (error)
		goto Elong;
	return retval;
Elong:
	return ERR_PTR(-ENAMETOOLONG);
@@ -2912,13 +2991,7 @@ static char *__dentry_path(struct dentry *dentry, char *buf, int buflen)

char *dentry_path_raw(struct dentry *dentry, char *buf, int buflen)
{
	char *retval;

	write_seqlock(&rename_lock);
	retval = __dentry_path(dentry, buf, buflen);
	write_sequnlock(&rename_lock);

	return retval;
	return __dentry_path(dentry, buf, buflen);
}
EXPORT_SYMBOL(dentry_path_raw);

@@ -2927,7 +3000,6 @@ char *dentry_path(struct dentry *dentry, char *buf, int buflen)
	char *p = NULL;
	char *retval;

	write_seqlock(&rename_lock);
	if (d_unlinked(dentry)) {
		p = buf + buflen;
		if (prepend(&p, &buflen, "//deleted", 10) != 0)
@@ -2935,7 +3007,6 @@ char *dentry_path(struct dentry *dentry, char *buf, int buflen)
		buflen++;
	}
	retval = __dentry_path(dentry, buf, buflen);
	write_sequnlock(&rename_lock);
	if (!IS_ERR(retval) && p)
		*p = '/';	/* restore '/' overriden with '\0' */
	return retval;
@@ -2974,7 +3045,6 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)

	error = -ENOENT;
	br_read_lock(&vfsmount_lock);
	write_seqlock(&rename_lock);
	if (!d_unlinked(pwd.dentry)) {
		unsigned long len;
		char *cwd = page + PAGE_SIZE;
@@ -2982,7 +3052,6 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)

		prepend(&cwd, &buflen, "\0", 1);
		error = prepend_path(&pwd, &root, &cwd, &buflen);
		write_sequnlock(&rename_lock);
		br_read_unlock(&vfsmount_lock);

		if (error < 0)
@@ -3003,7 +3072,6 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
				error = -EFAULT;
		}
	} else {
		write_sequnlock(&rename_lock);
		br_read_unlock(&vfsmount_lock);
	}

+1 −1
Original line number Diff line number Diff line
@@ -231,7 +231,7 @@ static int filldir_one(void * __buf, const char * name, int len,
	int result = 0;

	buf->sequence++;
	if (buf->ino == ino) {
	if (buf->ino == ino && len <= NAME_MAX) {
		memcpy(buf->name, name, len);
		buf->name[len] = '\0';
		buf->found = 1;
+3 −0
Original line number Diff line number Diff line
@@ -45,6 +45,9 @@ extern void __init chrdev_init(void);
 * namei.c
 */
extern int __inode_permission(struct inode *, int);
extern int user_path_mountpoint_at(int, const char __user *, unsigned int, struct path *);
extern int vfs_path_lookup(struct dentry *, struct vfsmount *,
			   const char *, unsigned int, struct path *);

/*
 * namespace.c
Loading