Commit ea5b778a8b98c85a87d66bf844904f9c3802b869
Committed by
Al Viro
1 parent
ab90911ff9
Exists in
master
and in
39 other branches
Unexport do_add_mount() and add in follow_automount(), not ->d_automount()
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. 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 to propagate the mount flags from the parent vfsmount. Signed-off-by: David Howells <dhowells@redhat.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Showing 8 changed files with 101 additions and 89 deletions Side-by-side Diff
Documentation/filesystems/vfs.txt
... | ... | @@ -933,15 +933,20 @@ |
933 | 933 | dynamic_dname() helper function is provided to take care of this. |
934 | 934 | |
935 | 935 | d_automount: called when an automount dentry is to be traversed (optional). |
936 | - This should create a new VFS mount record, mount it on the directory | |
937 | - and return the record to the caller. The caller is supplied with a | |
938 | - path parameter giving the automount directory to describe the automount | |
939 | - target and the parent VFS mount record to provide inheritable mount | |
940 | - parameters. NULL should be returned if someone else managed to make | |
941 | - the automount first. If the automount failed, then an error code | |
942 | - should be returned. If -EISDIR is returned, then the directory will | |
943 | - be treated as an ordinary directory and returned to pathwalk to | |
944 | - continue walking. | |
936 | + This should create a new VFS mount record and return the record to the | |
937 | + caller. The caller is supplied with a path parameter giving the | |
938 | + automount directory to describe the automount target and the parent | |
939 | + VFS mount record to provide inheritable mount parameters. NULL should | |
940 | + be returned if someone else managed to make the automount first. If | |
941 | + the vfsmount creation failed, then an error code should be returned. | |
942 | + If -EISDIR is returned, then the directory will be treated as an | |
943 | + ordinary directory and returned to pathwalk to continue walking. | |
944 | + | |
945 | + If a vfsmount is returned, the caller will attempt to mount it on the | |
946 | + mountpoint and will remove the vfsmount from its expiration list in | |
947 | + the case of failure. The vfsmount should be returned with 2 refs on | |
948 | + it to prevent automatic expiration - the caller will clean up the | |
949 | + additional ref. | |
945 | 950 | |
946 | 951 | This function is only used if DCACHE_NEED_AUTOMOUNT is set on the |
947 | 952 | dentry. This is set by __d_instantiate() if S_AUTOMOUNT is set on the |
fs/afs/mntpt.c
... | ... | @@ -241,7 +241,6 @@ |
241 | 241 | struct vfsmount *afs_d_automount(struct path *path) |
242 | 242 | { |
243 | 243 | struct vfsmount *newmnt; |
244 | - int err; | |
245 | 244 | |
246 | 245 | _enter("{%s,%s}", path->mnt->mnt_devname, path->dentry->d_name.name); |
247 | 246 | |
... | ... | @@ -249,24 +248,12 @@ |
249 | 248 | if (IS_ERR(newmnt)) |
250 | 249 | return newmnt; |
251 | 250 | |
252 | - mntget(newmnt); | |
253 | - err = do_add_mount(newmnt, path, MNT_SHRINKABLE, &afs_vfsmounts); | |
254 | - switch (err) { | |
255 | - case 0: | |
256 | - queue_delayed_work(afs_wq, &afs_mntpt_expiry_timer, | |
257 | - afs_mntpt_expiry_timeout * HZ); | |
258 | - _leave(" = %p {%s}", newmnt, newmnt->mnt_devname); | |
259 | - return newmnt; | |
260 | - case -EBUSY: | |
261 | - /* someone else made a mount here whilst we were busy */ | |
262 | - mntput(newmnt); | |
263 | - _leave(" = NULL [EBUSY]"); | |
264 | - return NULL; | |
265 | - default: | |
266 | - mntput(newmnt); | |
267 | - _leave(" = %d", err); | |
268 | - return ERR_PTR(err); | |
269 | - } | |
251 | + mntget(newmnt); /* prevent immediate expiration */ | |
252 | + mnt_set_expiry(newmnt, &afs_vfsmounts); | |
253 | + queue_delayed_work(afs_wq, &afs_mntpt_expiry_timer, | |
254 | + afs_mntpt_expiry_timeout * HZ); | |
255 | + _leave(" = %p {%s}", newmnt, newmnt->mnt_devname); | |
256 | + return newmnt; | |
270 | 257 | } |
271 | 258 | |
272 | 259 | /* |
fs/cifs/cifs_dfs_ref.c
... | ... | @@ -351,7 +351,6 @@ |
351 | 351 | struct vfsmount *cifs_dfs_d_automount(struct path *path) |
352 | 352 | { |
353 | 353 | struct vfsmount *newmnt; |
354 | - int err; | |
355 | 354 | |
356 | 355 | cFYI(1, "in %s", __func__); |
357 | 356 | |
... | ... | @@ -361,25 +360,12 @@ |
361 | 360 | return newmnt; |
362 | 361 | } |
363 | 362 | |
364 | - mntget(newmnt); | |
365 | - err = do_add_mount(newmnt, path, path->mnt->mnt_flags | MNT_SHRINKABLE, | |
366 | - &cifs_dfs_automount_list); | |
367 | - switch (err) { | |
368 | - case 0: | |
369 | - schedule_delayed_work(&cifs_dfs_automount_task, | |
370 | - cifs_dfs_mountpoint_expiry_timeout); | |
371 | - cFYI(1, "leaving %s [ok]" , __func__); | |
372 | - return newmnt; | |
373 | - case -EBUSY: | |
374 | - /* someone else made a mount here whilst we were busy */ | |
375 | - mntput(newmnt); | |
376 | - cFYI(1, "leaving %s [EBUSY]" , __func__); | |
377 | - return NULL; | |
378 | - default: | |
379 | - mntput(newmnt); | |
380 | - cFYI(1, "leaving %s [error %d]" , __func__, err); | |
381 | - return ERR_PTR(err); | |
382 | - } | |
363 | + mntget(newmnt); /* prevent immediate expiration */ | |
364 | + mnt_set_expiry(newmnt, &cifs_dfs_automount_list); | |
365 | + schedule_delayed_work(&cifs_dfs_automount_task, | |
366 | + cifs_dfs_mountpoint_expiry_timeout); | |
367 | + cFYI(1, "leaving %s [ok]" , __func__); | |
368 | + return newmnt; | |
383 | 369 | } |
384 | 370 | |
385 | 371 | const struct inode_operations cifs_dfs_referral_inode_operations = { |
fs/internal.h
... | ... | @@ -70,6 +70,8 @@ |
70 | 70 | extern void release_mounts(struct list_head *); |
71 | 71 | extern void umount_tree(struct vfsmount *, int, struct list_head *); |
72 | 72 | extern struct vfsmount *copy_tree(struct vfsmount *, struct dentry *, int); |
73 | +extern int do_add_mount(struct vfsmount *, struct path *, int); | |
74 | +extern void mnt_clear_expiry(struct vfsmount *); | |
73 | 75 | |
74 | 76 | extern void __init mnt_init(void); |
75 | 77 |
fs/namei.c
... | ... | @@ -900,6 +900,7 @@ |
900 | 900 | bool *need_mntput) |
901 | 901 | { |
902 | 902 | struct vfsmount *mnt; |
903 | + int err; | |
903 | 904 | |
904 | 905 | if (!path->dentry->d_op || !path->dentry->d_op->d_automount) |
905 | 906 | return -EREMOTE; |
906 | 907 | |
907 | 908 | |
908 | 909 | |
909 | 910 | |
... | ... | @@ -942,22 +943,49 @@ |
942 | 943 | return -EREMOTE; |
943 | 944 | return PTR_ERR(mnt); |
944 | 945 | } |
946 | + | |
945 | 947 | if (!mnt) /* mount collision */ |
946 | 948 | return 0; |
947 | 949 | |
950 | + /* The new mount record should have at least 2 refs to prevent it being | |
951 | + * expired before we get a chance to add it | |
952 | + */ | |
953 | + BUG_ON(mnt_get_count(mnt) < 2); | |
954 | + | |
948 | 955 | if (mnt->mnt_sb == path->mnt->mnt_sb && |
949 | 956 | mnt->mnt_root == path->dentry) { |
957 | + mnt_clear_expiry(mnt); | |
950 | 958 | mntput(mnt); |
959 | + mntput(mnt); | |
951 | 960 | return -ELOOP; |
952 | 961 | } |
953 | 962 | |
954 | - dput(path->dentry); | |
955 | - if (*need_mntput) | |
956 | - mntput(path->mnt); | |
957 | - path->mnt = mnt; | |
958 | - path->dentry = dget(mnt->mnt_root); | |
959 | - *need_mntput = true; | |
960 | - return 0; | |
963 | + /* We need to add the mountpoint to the parent. The filesystem may | |
964 | + * have placed it on an expiry list, and so we need to make sure it | |
965 | + * won't be expired under us if do_add_mount() fails (do_add_mount() | |
966 | + * will eat a reference unconditionally). | |
967 | + */ | |
968 | + mntget(mnt); | |
969 | + err = do_add_mount(mnt, path, path->mnt->mnt_flags | MNT_SHRINKABLE); | |
970 | + switch (err) { | |
971 | + case -EBUSY: | |
972 | + /* Someone else made a mount here whilst we were busy */ | |
973 | + err = 0; | |
974 | + default: | |
975 | + mnt_clear_expiry(mnt); | |
976 | + mntput(mnt); | |
977 | + mntput(mnt); | |
978 | + return err; | |
979 | + case 0: | |
980 | + mntput(mnt); | |
981 | + dput(path->dentry); | |
982 | + if (*need_mntput) | |
983 | + mntput(path->mnt); | |
984 | + path->mnt = mnt; | |
985 | + path->dentry = dget(mnt->mnt_root); | |
986 | + *need_mntput = true; | |
987 | + return 0; | |
988 | + } | |
961 | 989 | } |
962 | 990 | |
963 | 991 | /* |
fs/namespace.c
... | ... | @@ -1925,15 +1925,14 @@ |
1925 | 1925 | if (IS_ERR(mnt)) |
1926 | 1926 | return PTR_ERR(mnt); |
1927 | 1927 | |
1928 | - return do_add_mount(mnt, path, mnt_flags, NULL); | |
1928 | + return do_add_mount(mnt, path, mnt_flags); | |
1929 | 1929 | } |
1930 | 1930 | |
1931 | 1931 | /* |
1932 | 1932 | * add a mount into a namespace's mount tree |
1933 | - * - provide the option of adding the new mount to an expiration list | |
1933 | + * - this unconditionally eats one of the caller's references to newmnt. | |
1934 | 1934 | */ |
1935 | -int do_add_mount(struct vfsmount *newmnt, struct path *path, | |
1936 | - int mnt_flags, struct list_head *fslist) | |
1935 | +int do_add_mount(struct vfsmount *newmnt, struct path *path, int mnt_flags) | |
1937 | 1936 | { |
1938 | 1937 | int err; |
1939 | 1938 | |
... | ... | @@ -1963,9 +1962,6 @@ |
1963 | 1962 | if ((err = graft_tree(newmnt, path))) |
1964 | 1963 | goto unlock; |
1965 | 1964 | |
1966 | - if (fslist) /* add to the specified expiration list */ | |
1967 | - list_add_tail(&newmnt->mnt_expire, fslist); | |
1968 | - | |
1969 | 1965 | up_write(&namespace_sem); |
1970 | 1966 | return 0; |
1971 | 1967 | |
... | ... | @@ -1975,7 +1971,36 @@ |
1975 | 1971 | return err; |
1976 | 1972 | } |
1977 | 1973 | |
1978 | -EXPORT_SYMBOL_GPL(do_add_mount); | |
1974 | +/** | |
1975 | + * mnt_set_expiry - Put a mount on an expiration list | |
1976 | + * @mnt: The mount to list. | |
1977 | + * @expiry_list: The list to add the mount to. | |
1978 | + */ | |
1979 | +void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list) | |
1980 | +{ | |
1981 | + down_write(&namespace_sem); | |
1982 | + br_write_lock(vfsmount_lock); | |
1983 | + | |
1984 | + list_add_tail(&mnt->mnt_expire, expiry_list); | |
1985 | + | |
1986 | + br_write_unlock(vfsmount_lock); | |
1987 | + up_write(&namespace_sem); | |
1988 | +} | |
1989 | +EXPORT_SYMBOL(mnt_set_expiry); | |
1990 | + | |
1991 | +/* | |
1992 | + * Remove a vfsmount from any expiration list it may be on | |
1993 | + */ | |
1994 | +void mnt_clear_expiry(struct vfsmount *mnt) | |
1995 | +{ | |
1996 | + if (!list_empty(&mnt->mnt_expire)) { | |
1997 | + down_write(&namespace_sem); | |
1998 | + br_write_lock(vfsmount_lock); | |
1999 | + list_del_init(&mnt->mnt_expire); | |
2000 | + br_write_unlock(vfsmount_lock); | |
2001 | + up_write(&namespace_sem); | |
2002 | + } | |
2003 | +} | |
1979 | 2004 | |
1980 | 2005 | /* |
1981 | 2006 | * process a list of expirable mountpoints with the intent of discarding any |
fs/nfs/namespace.c
... | ... | @@ -149,26 +149,10 @@ |
149 | 149 | if (IS_ERR(mnt)) |
150 | 150 | goto out; |
151 | 151 | |
152 | - mntget(mnt); | |
153 | - err = do_add_mount(mnt, path, path->mnt->mnt_flags | MNT_SHRINKABLE, | |
154 | - &nfs_automount_list); | |
155 | - switch (err) { | |
156 | - case 0: | |
157 | - dprintk("%s: done, success\n", __func__); | |
158 | - schedule_delayed_work(&nfs_automount_task, nfs_mountpoint_expiry_timeout); | |
159 | - break; | |
160 | - case -EBUSY: | |
161 | - /* someone else made a mount here whilst we were busy */ | |
162 | - mntput(mnt); | |
163 | - dprintk("%s: done, collision\n", __func__); | |
164 | - mnt = NULL; | |
165 | - break; | |
166 | - default: | |
167 | - mntput(mnt); | |
168 | - dprintk("%s: done, error %d\n", __func__, err); | |
169 | - mnt = ERR_PTR(err); | |
170 | - break; | |
171 | - } | |
152 | + dprintk("%s: done, success\n", __func__); | |
153 | + mntget(mnt); /* prevent immediate expiration */ | |
154 | + mnt_set_expiry(mnt, &nfs_automount_list); | |
155 | + schedule_delayed_work(&nfs_automount_task, nfs_mountpoint_expiry_timeout); | |
172 | 156 | |
173 | 157 | out: |
174 | 158 | nfs_free_fattr(fattr); |
include/linux/mount.h
... | ... | @@ -110,12 +110,7 @@ |
110 | 110 | int flags, const char *name, |
111 | 111 | void *data); |
112 | 112 | |
113 | -struct nameidata; | |
114 | - | |
115 | -struct path; | |
116 | -extern int do_add_mount(struct vfsmount *newmnt, struct path *path, | |
117 | - int mnt_flags, struct list_head *fslist); | |
118 | - | |
113 | +extern void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list); | |
119 | 114 | extern void mark_mounts_for_expiry(struct list_head *mounts); |
120 | 115 | |
121 | 116 | extern dev_t name_to_dev_t(char *name); |