From: David Howells <dhowells@redhat.com>
To: Al Viro <viro@ZenIV.linux.org.uk>
Cc: dhowells@redhat.com, raven@themaw.net, npiggin@kernel.dk,
autofs@linux.kernel.org, linux-fsdevel@vger.kernel.org
Subject: [PATCH 19/18] Unexport do_add_mount() and add in follow_automount(), not ->d_automount()
Date: Fri, 14 Jan 2011 17:26:09 +0000 [thread overview]
Message-ID: <6672.1295025969@redhat.com> (raw)
In-Reply-To: <20110114154652.GD19804@ZenIV.linux.org.uk>
Unexport do_add_mount() and make ->d_automount() return the vfsmount to be
added rather than calling do_add_mount() itself. follow_automount() will then
do the addition.
This slightly complicates things as ->d_automount() normally wants to add the
new vfsmount to an expiration list and start an expiration timer. The problem
with that is that the vfsmount will be deleted if it has a refcount of 1 and
the timer will not repeat if the expiration list is empty.
To this end, we require the vfsmount to be returned from d_automount() with a
refcount of (at least) 2. One of these refs will be dropped unconditionally.
In addition, follow_automount() must get a 3rd ref around the call to
do_add_mount() lest it eat a ref and return an error, leaving the mount we
have open to being expired as we would otherwise have only 1 ref on it. This
would mean the currently upstream code is buggy for AFS, CIFS and NFS.
d_automount() should also add the the vfsmount to the expiration list (by
calling mnt_set_expiry()) and start the expiration timer before returning, if
this mechanism is to be used. The vfsmount will be unlinked from the
expiration list by follow_automount() if do_add_mount() fails.
This patch also fixes the call to do_add_mount() for AFS and CIFS to propagate
the mount flags from the parent vfsmount.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/filesystems/vfs.txt | 23 ++++++++++++--------
fs/afs/mntpt.c | 25 +++++-----------------
fs/cifs/cifs_dfs_ref.c | 26 +++++------------------
fs/internal.h | 2 ++
fs/namei.c | 42 +++++++++++++++++++++++++++++++------
fs/namespace.c | 41 +++++++++++++++++++++++++++++-------
fs/nfs/namespace.c | 24 ++++-----------------
include/linux/mount.h | 7 +-----
8 files changed, 101 insertions(+), 89 deletions(-)
diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt
index 3c4b2f1..94cf97b 100644
--- a/Documentation/filesystems/vfs.txt
+++ b/Documentation/filesystems/vfs.txt
@@ -933,15 +933,20 @@ struct dentry_operations {
dynamic_dname() helper function is provided to take care of this.
d_automount: called when an automount dentry is to be traversed (optional).
- This should create a new VFS mount record, mount it on the directory
- and return the record to the caller. The caller is supplied with a
- path parameter giving the automount directory to describe the automount
- target and the parent VFS mount record to provide inheritable mount
- parameters. NULL should be returned if someone else managed to make
- the automount first. If the automount failed, then an error code
- should be returned. If -EISDIR is returned, then the directory will
- be treated as an ordinary directory and returned to pathwalk to
- continue walking.
+ This should create a new VFS mount record and return the record to the
+ caller. The caller is supplied with a path parameter giving the
+ automount directory to describe the automount target and the parent
+ VFS mount record to provide inheritable mount parameters. NULL should
+ be returned if someone else managed to make the automount first. If
+ the vfsmount creation failed, then an error code should be returned.
+ If -EISDIR is returned, then the directory will be treated as an
+ ordinary directory and returned to pathwalk to continue walking.
+
+ If a vfsmount is returned, the caller will attempt to mount it on the
+ mountpoint and will remove the vfsmount from its expiration list in
+ the case of failure. The vfsmount should be returned with 2 refs on
+ it to prevent automatic expiration - the caller will clean up the
+ additional ref.
This function is only used if DCACHE_NEED_AUTOMOUNT is set on the
dentry. This is set by __d_instantiate() if S_AUTOMOUNT is set on the
diff --git a/fs/afs/mntpt.c b/fs/afs/mntpt.c
index 0f7dd7a..0d74c2c 100644
--- a/fs/afs/mntpt.c
+++ b/fs/afs/mntpt.c
@@ -241,7 +241,6 @@ error_no_devname:
struct vfsmount *afs_d_automount(struct path *path)
{
struct vfsmount *newmnt;
- int err;
_enter("{%s,%s}", path->mnt->mnt_devname, path->dentry->d_name.name);
@@ -249,24 +248,12 @@ struct vfsmount *afs_d_automount(struct path *path)
if (IS_ERR(newmnt))
return newmnt;
- mntget(newmnt);
- err = do_add_mount(newmnt, path, MNT_SHRINKABLE, &afs_vfsmounts);
- switch (err) {
- case 0:
- schedule_delayed_work(&afs_mntpt_expiry_timer,
- afs_mntpt_expiry_timeout * HZ);
- _leave(" = %p {%s}", newmnt, newmnt->mnt_devname);
- return newmnt;
- case -EBUSY:
- /* someone else made a mount here whilst we were busy */
- mntput(newmnt);
- _leave(" = NULL [EBUSY]");
- return NULL;
- default:
- mntput(newmnt);
- _leave(" = %d", err);
- return ERR_PTR(err);
- }
+ mntget(newmnt); /* prevent immediate expiration */
+ mnt_set_expiry(newmnt, &afs_vfsmounts);
+ schedule_delayed_work(&afs_mntpt_expiry_timer,
+ afs_mntpt_expiry_timeout * HZ);
+ _leave(" = %p {%s}", newmnt, newmnt->mnt_devname);
+ return newmnt;
}
/*
diff --git a/fs/cifs/cifs_dfs_ref.c b/fs/cifs/cifs_dfs_ref.c
index ddd0b3e..7ed3653 100644
--- a/fs/cifs/cifs_dfs_ref.c
+++ b/fs/cifs/cifs_dfs_ref.c
@@ -351,7 +351,6 @@ free_xid:
struct vfsmount *cifs_dfs_d_automount(struct path *path)
{
struct vfsmount *newmnt;
- int err;
cFYI(1, "in %s", __func__);
@@ -361,25 +360,12 @@ struct vfsmount *cifs_dfs_d_automount(struct path *path)
return newmnt;
}
- mntget(newmnt);
- err = do_add_mount(newmnt, path, MNT_SHRINKABLE,
- &cifs_dfs_automount_list);
- switch (err) {
- case 0:
- schedule_delayed_work(&cifs_dfs_automount_task,
- cifs_dfs_mountpoint_expiry_timeout);
- cFYI(1, "leaving %s [ok]" , __func__);
- return newmnt;
- case -EBUSY:
- /* someone else made a mount here whilst we were busy */
- mntput(newmnt);
- cFYI(1, "leaving %s [EBUSY]" , __func__);
- return NULL;
- default:
- mntput(newmnt);
- cFYI(1, "leaving %s [error %d]" , __func__, err);
- return ERR_PTR(err);
- }
+ mntget(newmnt); /* prevent immediate expiration */
+ mnt_set_expiry(newmnt, &cifs_dfs_automount_list);
+ schedule_delayed_work(&cifs_dfs_automount_task,
+ cifs_dfs_mountpoint_expiry_timeout);
+ cFYI(1, "leaving %s [ok]" , __func__);
+ return newmnt;
}
const struct inode_operations cifs_dfs_referral_inode_operations = {
diff --git a/fs/internal.h b/fs/internal.h
index 9687c2e..4931060 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -70,6 +70,8 @@ extern void mnt_set_mountpoint(struct vfsmount *, struct dentry *,
extern void release_mounts(struct list_head *);
extern void umount_tree(struct vfsmount *, int, struct list_head *);
extern struct vfsmount *copy_tree(struct vfsmount *, struct dentry *, int);
+extern int do_add_mount(struct vfsmount *, struct path *, int);
+extern void mnt_clear_expiry(struct vfsmount *);
extern void __init mnt_init(void);
diff --git a/fs/namei.c b/fs/namei.c
index b099541..cd7b7e4 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -898,6 +898,7 @@ static int follow_automount(struct path *path, unsigned flags,
bool *need_mntput)
{
struct vfsmount *mnt;
+ int err;
if (!path->dentry->d_op || !path->dentry->d_op->d_automount)
return -EREMOTE;
@@ -940,22 +941,49 @@ static int follow_automount(struct path *path, unsigned flags,
return -EREMOTE;
return PTR_ERR(mnt);
}
+
if (!mnt) /* mount collision */
return 0;
+ /* The new mount record should have at least 2 refs to prevent it being
+ * expired before we get a chance to add it
+ */
+ BUG_ON(mnt_get_count(mnt) < 2);
+
if (mnt->mnt_sb == path->mnt->mnt_sb &&
mnt->mnt_root == path->dentry) {
+ mnt_clear_expiry(mnt);
+ mntput(mnt);
mntput(mnt);
return -ELOOP;
}
- dput(path->dentry);
- if (*need_mntput)
- mntput(path->mnt);
- path->mnt = mnt;
- path->dentry = dget(mnt->mnt_root);
- *need_mntput = true;
- return 0;
+ /* We need to add the mountpoint to the parent. The filesystem may
+ * have placed it on an expiry list, and so we need to make sure it
+ * won't be expired under us if do_add_mount() fails (do_add_mount()
+ * will eat a reference unconditionally).
+ */
+ mntget(mnt);
+ err = do_add_mount(mnt, path, path->mnt->mnt_flags | MNT_SHRINKABLE);
+ switch (err) {
+ case -EBUSY:
+ /* Someone else made a mount here whilst we were busy */
+ err = 0;
+ default:
+ mnt_clear_expiry(mnt);
+ mntput(mnt);
+ mntput(mnt);
+ return err;
+ case 0:
+ mntput(mnt);
+ dput(path->dentry);
+ if (*need_mntput)
+ mntput(path->mnt);
+ path->mnt = mnt;
+ path->dentry = dget(mnt->mnt_root);
+ *need_mntput = true;
+ return 0;
+ }
}
/*
diff --git a/fs/namespace.c b/fs/namespace.c
index d94ccd6..bfcb701 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -1925,15 +1925,14 @@ static int do_new_mount(struct path *path, char *type, int flags,
if (IS_ERR(mnt))
return PTR_ERR(mnt);
- return do_add_mount(mnt, path, mnt_flags, NULL);
+ return do_add_mount(mnt, path, mnt_flags);
}
/*
* add a mount into a namespace's mount tree
- * - provide the option of adding the new mount to an expiration list
+ * - this unconditionally eats one of the caller's references to newmnt.
*/
-int do_add_mount(struct vfsmount *newmnt, struct path *path,
- int mnt_flags, struct list_head *fslist)
+int do_add_mount(struct vfsmount *newmnt, struct path *path, int mnt_flags)
{
int err;
@@ -1963,9 +1962,6 @@ int do_add_mount(struct vfsmount *newmnt, struct path *path,
if ((err = graft_tree(newmnt, path)))
goto unlock;
- if (fslist) /* add to the specified expiration list */
- list_add_tail(&newmnt->mnt_expire, fslist);
-
up_write(&namespace_sem);
return 0;
@@ -1975,7 +1971,36 @@ unlock:
return err;
}
-EXPORT_SYMBOL_GPL(do_add_mount);
+/**
+ * mnt_set_expiry - Put a mount on an expiration list
+ * @mnt: The mount to list.
+ * @expiry_list: The list to add the mount to.
+ */
+void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list)
+{
+ down_write(&namespace_sem);
+ br_write_lock(vfsmount_lock);
+
+ list_add_tail(&mnt->mnt_expire, expiry_list);
+
+ br_write_unlock(vfsmount_lock);
+ up_write(&namespace_sem);
+}
+EXPORT_SYMBOL(mnt_set_expiry);
+
+/*
+ * Remove a vfsmount from any expiration list it may be on
+ */
+void mnt_clear_expiry(struct vfsmount *mnt)
+{
+ if (!list_empty(&mnt->mnt_expire)) {
+ down_write(&namespace_sem);
+ br_write_lock(vfsmount_lock);
+ list_del_init(&mnt->mnt_expire);
+ br_write_unlock(vfsmount_lock);
+ up_write(&namespace_sem);
+ }
+}
/*
* process a list of expirable mountpoints with the intent of discarding any
diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c
index f3fbb1b..f32b860 100644
--- a/fs/nfs/namespace.c
+++ b/fs/nfs/namespace.c
@@ -149,26 +149,10 @@ struct vfsmount *nfs_d_automount(struct path *path)
if (IS_ERR(mnt))
goto out;
- mntget(mnt);
- err = do_add_mount(mnt, path, path->mnt->mnt_flags | MNT_SHRINKABLE,
- &nfs_automount_list);
- switch (err) {
- case 0:
- dprintk("%s: done, success\n", __func__);
- schedule_delayed_work(&nfs_automount_task, nfs_mountpoint_expiry_timeout);
- break;
- case -EBUSY:
- /* someone else made a mount here whilst we were busy */
- mntput(mnt);
- dprintk("%s: done, collision\n", __func__);
- mnt = NULL;
- break;
- default:
- mntput(mnt);
- dprintk("%s: done, error %d\n", __func__, err);
- mnt = ERR_PTR(err);
- break;
- }
+ dprintk("%s: done, success\n", __func__);
+ mntget(mnt); /* prevent immediate expiration */
+ mnt_set_expiry(mnt, &nfs_automount_list);
+ schedule_delayed_work(&nfs_automount_task, nfs_mountpoint_expiry_timeout);
out:
nfs_free_fattr(fattr);
diff --git a/include/linux/mount.h b/include/linux/mount.h
index 1869ea2..af4765e 100644
--- a/include/linux/mount.h
+++ b/include/linux/mount.h
@@ -110,12 +110,7 @@ extern struct vfsmount *vfs_kern_mount(struct file_system_type *type,
int flags, const char *name,
void *data);
-struct nameidata;
-
-struct path;
-extern int do_add_mount(struct vfsmount *newmnt, struct path *path,
- int mnt_flags, struct list_head *fslist);
-
+extern void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list);
extern void mark_mounts_for_expiry(struct list_head *mounts);
extern dev_t name_to_dev_t(char *name);
next prev parent reply other threads:[~2011-01-14 17:26 UTC|newest]
Thread overview: 45+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-01-13 21:53 [PATCH 00/18] Introduce automount support in the VFS [ver #4] David Howells
2011-01-13 21:54 ` [PATCH 01/18] Add a dentry op to handle automounting rather than abusing follow_link() " David Howells
2011-01-16 0:09 ` Al Viro
2011-01-16 1:17 ` Al Viro
2011-01-16 18:12 ` David Howells
2011-01-13 21:54 ` [PATCH 02/18] Add a dentry op to allow processes to be held during pathwalk transit " David Howells
2011-01-13 21:54 ` [PATCH 03/18] From: David Howells <dhowells@redhat.com> " David Howells
2011-01-13 21:54 ` [PATCH 04/18] AFS: Use d_automount() rather than abusing follow_link() " David Howells
2011-01-13 21:54 ` [PATCH 05/18] NFS: " David Howells
2011-01-13 21:54 ` [PATCH 06/18] CIFS: " David Howells
2011-01-13 21:54 ` [PATCH 07/18] Remove the automount through follow_link() kludge code from pathwalk " David Howells
2011-01-13 21:54 ` [PATCH 08/18] autofs4: Add d_automount() dentry operation " David Howells
2011-01-13 21:54 ` [PATCH 09/18] autofs4: Add d_manage() " David Howells
2011-01-14 13:51 ` Ian Kent
2011-01-14 14:37 ` Nick Piggin
2011-01-14 15:47 ` Nick Piggin
2011-01-14 15:35 ` David Howells
2011-01-14 15:46 ` Nick Piggin
2011-01-13 21:54 ` [PATCH 10/18] autofs4: Remove unused code " David Howells
2011-01-13 21:54 ` [PATCH 11/18] autofs4: Clean up inode operations " David Howells
2011-01-13 21:55 ` [PATCH 12/18] autofs4: Clean up dentry " David Howells
2011-01-13 21:55 ` [PATCH 13/18] autofs4: Clean up autofs4_free_ino() " David Howells
2011-01-14 16:03 ` Al Viro
2011-01-13 21:55 ` [PATCH 14/18] autofs4: Fix wait validation " David Howells
2011-01-13 21:55 ` [PATCH 15/18] autofs4: Add v4 pseudo direct mount support " David Howells
2011-01-13 21:55 ` [PATCH 16/18] autofs4: Bump version " David Howells
2011-01-13 21:55 ` [PATCH 17/18] Remove a further kludge from __do_follow_link() " David Howells
2011-01-13 21:55 ` [PATCH 18/18] Allow d_manage() to be used in RCU-walk mode " David Howells
2011-01-14 7:02 ` [PATCH 00/18] Introduce automount support in the VFS " Al Viro
2011-01-14 7:05 ` Al Viro
2011-01-14 11:20 ` David Howells
2011-01-14 11:43 ` David Howells
2011-01-14 15:46 ` Al Viro
2011-01-14 17:26 ` David Howells [this message]
2011-01-14 17:43 ` [PATCH 19/18] Unexport do_add_mount() and add in follow_automount(), not ->d_automount() Al Viro
2011-01-14 17:56 ` Al Viro
2011-01-14 18:06 ` Al Viro
2011-01-14 22:07 ` Nick Piggin
2011-01-15 13:30 ` Al Viro
2011-01-15 18:33 ` Nick Piggin
2011-01-16 0:24 ` Al Viro
2011-01-16 1:21 ` Nick Piggin
2011-01-15 18:46 ` Nick Piggin
2011-01-14 17:30 ` David Howells
2011-01-14 11:54 ` [PATCH 00/18] Introduce automount support in the VFS [ver #4] David Howells
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=6672.1295025969@redhat.com \
--to=dhowells@redhat.com \
--cc=autofs@linux.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=npiggin@kernel.dk \
--cc=raven@themaw.net \
--cc=viro@ZenIV.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).