Commit f8206b92 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs-2.6

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs-2.6: (23 commits)
  sanitize vfsmount refcounting changes
  fix old umount_tree() breakage
  autofs4: Merge the remaining dentry ops tables
  Unexport do_add_mount() and add in follow_automount(), not ->d_automount()
  Allow d_manage() to be used in RCU-walk mode
  Remove a further kludge from __do_follow_link()
  autofs4: Bump version
  autofs4: Add v4 pseudo direct mount support
  autofs4: Fix wait validation
  autofs4: Clean up autofs4_free_ino()
  autofs4: Clean up dentry operations
  autofs4: Clean up inode operations
  autofs4: Remove unused code
  autofs4: Add d_manage() dentry operation
  autofs4: Add d_automount() dentry operation
  Remove the automount through follow_link() kludge code from pathwalk
  CIFS: Use d_automount() rather than abusing follow_link()
  NFS: Use d_automount() rather than abusing follow_link()
  AFS: Use d_automount() rather than abusing follow_link()
  Add an AT_NO_AUTOMOUNT flag to suppress terminal automount
  ...
parents 1b59be2a f03c6599
...@@ -19,6 +19,8 @@ prototypes: ...@@ -19,6 +19,8 @@ prototypes:
void (*d_release)(struct dentry *); void (*d_release)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *); void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)((struct dentry *dentry, char *buffer, int buflen); char *(*d_dname)((struct dentry *dentry, char *buffer, int buflen);
struct vfsmount *(*d_automount)(struct path *path);
int (*d_manage)(struct dentry *, bool);
locking rules: locking rules:
rename_lock ->d_lock may block rcu-walk rename_lock ->d_lock may block rcu-walk
...@@ -29,6 +31,8 @@ d_delete: no yes no no ...@@ -29,6 +31,8 @@ d_delete: no yes no no
d_release: no no yes no d_release: no no yes no
d_iput: no no yes no d_iput: no no yes no
d_dname: no no no no d_dname: no no no no
d_automount: no no yes no
d_manage: no no yes (ref-walk) maybe
--------------------------- inode_operations --------------------------- --------------------------- inode_operations ---------------------------
prototypes: prototypes:
......
...@@ -864,6 +864,8 @@ struct dentry_operations { ...@@ -864,6 +864,8 @@ struct dentry_operations {
void (*d_release)(struct dentry *); void (*d_release)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *); void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)(struct dentry *, char *, int); char *(*d_dname)(struct dentry *, char *, int);
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(struct dentry *, bool, bool);
}; };
d_revalidate: called when the VFS needs to revalidate a dentry. This d_revalidate: called when the VFS needs to revalidate a dentry. This
...@@ -930,6 +932,47 @@ struct dentry_operations { ...@@ -930,6 +932,47 @@ struct dentry_operations {
at the end of the buffer, and returns a pointer to the first char. at the end of the buffer, and returns a pointer to the first char.
dynamic_dname() helper function is provided to take care of this. 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 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
inode being added.
d_manage: called to allow the filesystem to manage the transition from a
dentry (optional). This allows autofs, for example, to hold up clients
waiting to explore behind a 'mountpoint' whilst letting the daemon go
past and construct the subtree there. 0 should be returned to let the
calling process continue. -EISDIR can be returned to tell pathwalk to
use this directory as an ordinary directory and to ignore anything
mounted on it and not to check the automount flag. Any other error
code will abort pathwalk completely.
If the 'mounting_here' parameter is true, then namespace_sem is being
held by the caller and the function should not initiate any mounts or
unmounts that it will then wait for.
If the 'rcu_walk' parameter is true, then the caller is doing a
pathwalk in RCU-walk mode. Sleeping is not permitted in this mode,
and the caller can be asked to leave it and call again by returing
-ECHILD.
This function is only used if DCACHE_MANAGE_TRANSIT is set on the
dentry being transited from.
Example : Example :
static char *pipefs_dname(struct dentry *dent, char *buffer, int buflen) static char *pipefs_dname(struct dentry *dent, char *buffer, int buflen)
......
...@@ -1201,7 +1201,7 @@ static int __init init_mtdchar(void) ...@@ -1201,7 +1201,7 @@ static int __init init_mtdchar(void)
static void __exit cleanup_mtdchar(void) static void __exit cleanup_mtdchar(void)
{ {
unregister_mtd_user(&mtdchar_notifier); unregister_mtd_user(&mtdchar_notifier);
mntput_long(mtd_inode_mnt); mntput(mtd_inode_mnt);
unregister_filesystem(&mtd_inodefs_type); unregister_filesystem(&mtd_inodefs_type);
__unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd"); __unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd");
} }
......
...@@ -88,14 +88,13 @@ struct autofs_dir_ent *autofs_expire(struct super_block *sb, ...@@ -88,14 +88,13 @@ struct autofs_dir_ent *autofs_expire(struct super_block *sb,
} }
path.mnt = mnt; path.mnt = mnt;
path_get(&path); path_get(&path);
if (!follow_down(&path)) { if (!follow_down_one(&path)) {
path_put(&path); path_put(&path);
DPRINTK(("autofs: not expirable\ DPRINTK(("autofs: not expirable\
(not a mounted directory): %s\n", ent->name)); (not a mounted directory): %s\n", ent->name));
continue; continue;
} }
while (d_mountpoint(path.dentry) && follow_down(&path)) follow_down(&path, false); // TODO: need to check error
;
umount_ok = may_umount(path.mnt); umount_ok = may_umount(path.mnt);
path_put(&path); path_put(&path);
......
...@@ -66,6 +66,7 @@ const struct dentry_operations afs_fs_dentry_operations = { ...@@ -66,6 +66,7 @@ const struct dentry_operations afs_fs_dentry_operations = {
.d_revalidate = afs_d_revalidate, .d_revalidate = afs_d_revalidate,
.d_delete = afs_d_delete, .d_delete = afs_d_delete,
.d_release = afs_d_release, .d_release = afs_d_release,
.d_automount = afs_d_automount,
}; };
#define AFS_DIR_HASHTBL_SIZE 128 #define AFS_DIR_HASHTBL_SIZE 128
......
...@@ -184,7 +184,8 @@ struct inode *afs_iget_autocell(struct inode *dir, const char *dev_name, ...@@ -184,7 +184,8 @@ struct inode *afs_iget_autocell(struct inode *dir, const char *dev_name,
inode->i_generation = 0; inode->i_generation = 0;
set_bit(AFS_VNODE_PSEUDODIR, &vnode->flags); set_bit(AFS_VNODE_PSEUDODIR, &vnode->flags);
inode->i_flags |= S_NOATIME; set_bit(AFS_VNODE_MOUNTPOINT, &vnode->flags);
inode->i_flags |= S_AUTOMOUNT | S_NOATIME;
unlock_new_inode(inode); unlock_new_inode(inode);
_leave(" = %p", inode); _leave(" = %p", inode);
return inode; return inode;
......
...@@ -592,6 +592,7 @@ extern const struct inode_operations afs_mntpt_inode_operations; ...@@ -592,6 +592,7 @@ extern const struct inode_operations afs_mntpt_inode_operations;
extern const struct inode_operations afs_autocell_inode_operations; extern const struct inode_operations afs_autocell_inode_operations;
extern const struct file_operations afs_mntpt_file_operations; extern const struct file_operations afs_mntpt_file_operations;
extern struct vfsmount *afs_d_automount(struct path *);
extern int afs_mntpt_check_symlink(struct afs_vnode *, struct key *); extern int afs_mntpt_check_symlink(struct afs_vnode *, struct key *);
extern void afs_mntpt_kill_timer(void); extern void afs_mntpt_kill_timer(void);
......
...@@ -24,7 +24,6 @@ static struct dentry *afs_mntpt_lookup(struct inode *dir, ...@@ -24,7 +24,6 @@ static struct dentry *afs_mntpt_lookup(struct inode *dir,
struct dentry *dentry, struct dentry *dentry,
struct nameidata *nd); struct nameidata *nd);
static int afs_mntpt_open(struct inode *inode, struct file *file); static int afs_mntpt_open(struct inode *inode, struct file *file);
static void *afs_mntpt_follow_link(struct dentry *dentry, struct nameidata *nd);
static void afs_mntpt_expiry_timed_out(struct work_struct *work); static void afs_mntpt_expiry_timed_out(struct work_struct *work);
const struct file_operations afs_mntpt_file_operations = { const struct file_operations afs_mntpt_file_operations = {
...@@ -34,13 +33,11 @@ const struct file_operations afs_mntpt_file_operations = { ...@@ -34,13 +33,11 @@ const struct file_operations afs_mntpt_file_operations = {
const struct inode_operations afs_mntpt_inode_operations = { const struct inode_operations afs_mntpt_inode_operations = {
.lookup = afs_mntpt_lookup, .lookup = afs_mntpt_lookup,
.follow_link = afs_mntpt_follow_link,
.readlink = page_readlink, .readlink = page_readlink,
.getattr = afs_getattr, .getattr = afs_getattr,
}; };
const struct inode_operations afs_autocell_inode_operations = { const struct inode_operations afs_autocell_inode_operations = {
.follow_link = afs_mntpt_follow_link,
.getattr = afs_getattr, .getattr = afs_getattr,
}; };
...@@ -88,6 +85,7 @@ int afs_mntpt_check_symlink(struct afs_vnode *vnode, struct key *key) ...@@ -88,6 +85,7 @@ int afs_mntpt_check_symlink(struct afs_vnode *vnode, struct key *key)
_debug("symlink is a mountpoint"); _debug("symlink is a mountpoint");
spin_lock(&vnode->lock); spin_lock(&vnode->lock);
set_bit(AFS_VNODE_MOUNTPOINT, &vnode->flags); set_bit(AFS_VNODE_MOUNTPOINT, &vnode->flags);
vnode->vfs_inode.i_flags |= S_AUTOMOUNT;
spin_unlock(&vnode->lock); spin_unlock(&vnode->lock);
} }
...@@ -238,52 +236,24 @@ static struct vfsmount *afs_mntpt_do_automount(struct dentry *mntpt) ...@@ -238,52 +236,24 @@ static struct vfsmount *afs_mntpt_do_automount(struct dentry *mntpt)
} }
/* /*
* follow a link from a mountpoint directory, thus causing it to be mounted * handle an automount point
*/ */
static void *afs_mntpt_follow_link(struct dentry *dentry, struct nameidata *nd) struct vfsmount *afs_d_automount(struct path *path)
{ {
struct vfsmount *newmnt; struct vfsmount *newmnt;
int err;
_enter("%p{%s},{%s:%p{%s},}", _enter("{%s,%s}", path->mnt->mnt_devname, path->dentry->d_name.name);
dentry,
dentry->d_name.name,
nd->path.mnt->mnt_devname,
dentry,
nd->path.dentry->d_name.name);
dput(nd->path.dentry);
nd->path.dentry = dget(dentry);
newmnt = afs_mntpt_do_automount(nd->path.dentry); newmnt = afs_mntpt_do_automount(path->dentry);
if (IS_ERR(newmnt)) { if (IS_ERR(newmnt))
path_put(&nd->path); return newmnt;
return (void *)newmnt;
}
mntget(newmnt); mntget(newmnt); /* prevent immediate expiration */
err = do_add_mount(newmnt, &nd->path, MNT_SHRINKABLE, &afs_vfsmounts); mnt_set_expiry(newmnt, &afs_vfsmounts);
switch (err) {
case 0:
path_put(&nd->path);
nd->path.mnt = newmnt;
nd->path.dentry = dget(newmnt->mnt_root);
queue_delayed_work(afs_wq, &afs_mntpt_expiry_timer, queue_delayed_work(afs_wq, &afs_mntpt_expiry_timer,
afs_mntpt_expiry_timeout * HZ); afs_mntpt_expiry_timeout * HZ);
break; _leave(" = %p {%s}", newmnt, newmnt->mnt_devname);
case -EBUSY: return newmnt;
/* someone else made a mount here whilst we were busy */
while (d_mountpoint(nd->path.dentry) &&
follow_down(&nd->path))
;
err = 0;
default:
mntput(newmnt);
break;
}
_leave(" = %d", err);
return ERR_PTR(err);
} }
/* /*
......
...@@ -233,7 +233,7 @@ static int __init anon_inode_init(void) ...@@ -233,7 +233,7 @@ static int __init anon_inode_init(void)
return 0; return 0;
err_mntput: err_mntput:
mntput_long(anon_inode_mnt); mntput(anon_inode_mnt);
err_unregister_filesystem: err_unregister_filesystem:
unregister_filesystem(&anon_inode_fs_type); unregister_filesystem(&anon_inode_fs_type);
err_exit: err_exit:
......
...@@ -99,7 +99,6 @@ struct autofs_info { ...@@ -99,7 +99,6 @@ struct autofs_info {
}; };
#define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */
#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */
#define AUTOFS_INF_PENDING (1<<2) /* dentry pending mount */ #define AUTOFS_INF_PENDING (1<<2) /* dentry pending mount */
struct autofs_wait_queue { struct autofs_wait_queue {
...@@ -176,13 +175,6 @@ static inline int autofs4_ispending(struct dentry *dentry) ...@@ -176,13 +175,6 @@ static inline int autofs4_ispending(struct dentry *dentry)
return 0; return 0;
} }
static inline void autofs4_copy_atime(struct file *src, struct file *dst)
{
dst->f_path.dentry->d_inode->i_atime =
src->f_path.dentry->d_inode->i_atime;
return;
}
struct inode *autofs4_get_inode(struct super_block *, struct autofs_info *); struct inode *autofs4_get_inode(struct super_block *, struct autofs_info *);
void autofs4_free_ino(struct autofs_info *); void autofs4_free_ino(struct autofs_info *);
...@@ -212,11 +204,83 @@ void autofs_dev_ioctl_exit(void); ...@@ -212,11 +204,83 @@ void autofs_dev_ioctl_exit(void);
extern const struct inode_operations autofs4_symlink_inode_operations; extern const struct inode_operations autofs4_symlink_inode_operations;
extern const struct inode_operations autofs4_dir_inode_operations; extern const struct inode_operations autofs4_dir_inode_operations;
extern const struct inode_operations autofs4_root_inode_operations;
extern const struct inode_operations autofs4_indirect_root_inode_operations;
extern const struct inode_operations autofs4_direct_root_inode_operations;
extern const struct file_operations autofs4_dir_operations; extern const struct file_operations autofs4_dir_operations;
extern const struct file_operations autofs4_root_operations; extern const struct file_operations autofs4_root_operations;
extern const struct dentry_operations autofs4_dentry_operations;
/* VFS automount flags management functions */
static inline void __managed_dentry_set_automount(struct dentry *dentry)
{
dentry->d_flags |= DCACHE_NEED_AUTOMOUNT;
}
static inline void managed_dentry_set_automount(struct dentry *dentry)
{
spin_lock(&dentry->d_lock);
__managed_dentry_set_automount(dentry);
spin_unlock(&dentry->d_lock);
}
static inline void __managed_dentry_clear_automount(struct dentry *dentry)
{
dentry->d_flags &= ~DCACHE_NEED_AUTOMOUNT;
}
static inline void managed_dentry_clear_automount(struct dentry *dentry)
{
spin_lock(&dentry->d_lock);
__managed_dentry_clear_automount(dentry);
spin_unlock(&dentry->d_lock);
}
static inline void __managed_dentry_set_transit(struct dentry *dentry)
{
dentry->d_flags |= DCACHE_MANAGE_TRANSIT;
}
static inline void managed_dentry_set_transit(struct dentry *dentry)
{
spin_lock(&dentry->d_lock);
__managed_dentry_set_transit(dentry);
spin_unlock(&dentry->d_lock);
}
static inline void __managed_dentry_clear_transit(struct dentry *dentry)
{
dentry->d_flags &= ~DCACHE_MANAGE_TRANSIT;
}
static inline void managed_dentry_clear_transit(struct dentry *dentry)
{
spin_lock(&dentry->d_lock);
__managed_dentry_clear_transit(dentry);
spin_unlock(&dentry->d_lock);
}
static inline void __managed_dentry_set_managed(struct dentry *dentry)
{
dentry->d_flags |= (DCACHE_NEED_AUTOMOUNT|DCACHE_MANAGE_TRANSIT);
}
static inline void managed_dentry_set_managed(struct dentry *dentry)
{
spin_lock(&dentry->d_lock);
__managed_dentry_set_managed(dentry);
spin_unlock(&dentry->d_lock);
}
static inline void __managed_dentry_clear_managed(struct dentry *dentry)
{
dentry->d_flags &= ~(DCACHE_NEED_AUTOMOUNT|DCACHE_MANAGE_TRANSIT);
}
static inline void managed_dentry_clear_managed(struct dentry *dentry)
{
spin_lock(&dentry->d_lock);
__managed_dentry_clear_managed(dentry);
spin_unlock(&dentry->d_lock);
}
/* Initializing function */ /* Initializing function */
...@@ -229,19 +293,6 @@ int autofs4_wait(struct autofs_sb_info *,struct dentry *, enum autofs_notify); ...@@ -229,19 +293,6 @@ int autofs4_wait(struct autofs_sb_info *,struct dentry *, enum autofs_notify);
int autofs4_wait_release(struct autofs_sb_info *,autofs_wqt_t,int); int autofs4_wait_release(struct autofs_sb_info *,autofs_wqt_t,int);
void autofs4_catatonic_mode(struct autofs_sb_info *); void autofs4_catatonic_mode(struct autofs_sb_info *);
static inline int autofs4_follow_mount(struct path *path)
{
int res = 0;
while (d_mountpoint(path->dentry)) {
int followed = follow_down(path);
if (!followed)
break;
res = 1;
}
return res;
}
static inline u32 autofs4_get_dev(struct autofs_sb_info *sbi) static inline u32 autofs4_get_dev(struct autofs_sb_info *sbi)
{ {
return new_encode_dev(sbi->sb->s_dev); return new_encode_dev(sbi->sb->s_dev);
......
...@@ -551,7 +551,7 @@ static int autofs_dev_ioctl_ismountpoint(struct file *fp, ...@@ -551,7 +551,7 @@ static int autofs_dev_ioctl_ismountpoint(struct file *fp,
err = have_submounts(path.dentry); err = have_submounts(path.dentry);
if (follow_down(&path)) if (follow_down_one(&path))
magic = path.mnt->mnt_sb->s_magic; magic = path.mnt->mnt_sb->s_magic;
} }
......
...@@ -26,10 +26,6 @@ static inline int autofs4_can_expire(struct dentry *dentry, ...@@ -26,10 +26,6 @@ static inline int autofs4_can_expire(struct dentry *dentry,
if (ino == NULL) if (ino == NULL)
return 0; return 0;
/* No point expiring a pending mount */
if (ino->flags & AUTOFS_INF_PENDING)
return 0;
if (!do_now) { if (!do_now) {
/* Too young to die */ /* Too young to die */
if (!timeout || time_after(ino->last_used + timeout, now)) if (!timeout || time_after(ino->last_used + timeout, now))
...@@ -56,7 +52,7 @@ static int autofs4_mount_busy(struct vfsmount *mnt, struct dentry *dentry) ...@@ -56,7 +52,7 @@ static int autofs4_mount_busy(struct vfsmount *mnt, struct dentry *dentry)
path_get(&path); path_get(&path);
if (!follow_down(&path)) if (!follow_down_one(&path))
goto done; goto done;
if (is_autofs4_dentry(path.dentry)) { if (is_autofs4_dentry(path.dentry)) {
...@@ -283,6 +279,7 @@ struct dentry *autofs4_expire_direct(struct super_block *sb, ...@@ -283,6 +279,7 @@ struct dentry *autofs4_expire_direct(struct super_block *sb,
unsigned long timeout; unsigned long timeout;
struct dentry *root = dget(sb->s_root); struct dentry *root = dget(sb->s_root);
int do_now = how & AUTOFS_EXP_IMMEDIATE; int do_now = how & AUTOFS_EXP_IMMEDIATE;
struct autofs_info *ino;
if (!root) if (!root)
return NULL; return NULL;
...@@ -291,19 +288,21 @@ struct dentry *autofs4_expire_direct(struct super_block *sb, ...@@ -291,19 +288,21 @@ struct dentry *autofs4_expire_direct(struct super_block *sb,
timeout = sbi->exp_timeout; timeout = sbi->exp_timeout;
spin_lock(&sbi->fs_lock); spin_lock(&sbi->fs_lock);
ino = autofs4_dentry_ino(root);
/* No point expiring a pending mount */
if (ino->flags & AUTOFS_INF_PENDING) {
spin_unlock(&sbi->fs_lock);
return NULL;
}
managed_dentry_set_transit(root);
if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { if (!autofs4_direct_busy(mnt, root, timeout, do_now)) {
struct autofs_info *ino = autofs4_dentry_ino(root); struct autofs_info *ino = autofs4_dentry_ino(root);
if (d_mountpoint(root)) {
ino->flags |= AUTOFS_INF_MOUNTPOINT;
spin_lock(&root->d_lock);
root->d_flags &= ~DCACHE_MOUNTED;
spin_unlock(&root->d_lock);
}
ino->flags |= AUTOFS_INF_EXPIRING; ino->flags |= AUTOFS_INF_EXPIRING;
init_completion(&ino->expire_complete); init_completion(&ino->expire_complete);
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
return root; return root;
} }
managed_dentry_clear_transit(root);
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
dput(root); dput(root);
...@@ -340,6 +339,10 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb, ...@@ -340,6 +339,10 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
while ((dentry = get_next_positive_dentry(dentry, root))) { while ((dentry = get_next_positive_dentry(dentry, root))) {
spin_lock(&sbi->fs_lock); spin_lock(&sbi->fs_lock);
ino = autofs4_dentry_ino(dentry); ino = autofs4_dentry_ino(dentry);
/* No point expiring a pending mount */
if (ino->flags & AUTOFS_INF_PENDING)
goto cont;
managed_dentry_set_transit(dentry);
/* /*
* Case 1: (i) indirect mount or top level pseudo direct mount * Case 1: (i) indirect mount or top level pseudo direct mount
...@@ -399,6 +402,8 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb, ...@@ -399,6 +402,8 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
} }
} }
next: next:
managed_dentry_clear_transit(dentry);
cont:
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
} }
return NULL; return NULL;
...@@ -479,6 +484,8 @@ int autofs4_expire_run(struct super_block *sb, ...@@ -479,6 +484,8 @@ int autofs4_expire_run(struct super_block *sb,
spin_lock(&sbi->fs_lock); spin_lock(&sbi->fs_lock);
ino = autofs4_dentry_ino(dentry); ino = autofs4_dentry_ino(dentry);
ino->flags &= ~AUTOFS_INF_EXPIRING; ino->flags &= ~AUTOFS_INF_EXPIRING;
if (!d_unhashed(dentry))
managed_dentry_clear_transit(dentry);
complete_all(&ino->expire_complete); complete_all(&ino->expire_complete);
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
...@@ -504,18 +511,18 @@ int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ...@@ -504,18 +511,18 @@ int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt,
ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ret = autofs4_wait(sbi, dentry, NFY_EXPIRE);
spin_lock(&sbi->fs_lock); spin_lock(&sbi->fs_lock);
if (ino->flags & AUTOFS_INF_MOUNTPOINT) {
spin_lock(&sb->s_root->d_lock);
/*
* If we haven't been expired away, then reset
* mounted status.
*/
if (mnt->mnt_parent != mnt)
sb->s_root->d_flags |= DCACHE_MOUNTED;
spin_unlock(&sb->s_root->d_lock);
ino->flags &= ~AUTOFS_INF_MOUNTPOINT;
}
ino->flags &= ~AUTOFS_INF_EXPIRING; ino->flags &= ~AUTOFS_INF_EXPIRING;
spin_lock(&dentry->d_lock);
if (ret)
__managed_dentry_clear_transit(dentry);
else {
if ((IS_ROOT(dentry) ||
(autofs_type_indirect(sbi->type) &&
IS_ROOT(dentry->d_parent))) &&
!(dentry->d_flags & DCACHE_NEED_AUTOMOUNT))
__managed_dentry_set_automount(dentry);
}
spin_unlock(&dentry->d_lock);
complete_all(&ino->expire_complete); complete_all(&ino->expire_complete);
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
dput(dentry); dput(dentry);
......
...@@ -45,7 +45,6 @@ struct autofs_info *autofs4_init_ino(struct autofs_info *ino, ...@@ -45,7 +45,6 @@ struct autofs_info *autofs4_init_ino(struct autofs_info *ino,
if (!reinit) { if (!reinit) {
ino->flags = 0; ino->flags = 0;
ino->inode = NULL;
ino->dentry = NULL; ino->dentry = NULL;
ino->size = 0; ino->size = 0;
INIT_LIST_HEAD(&ino->active); INIT_LIST_HEAD(&ino->active);
...@@ -76,19 +75,8 @@ struct autofs_info *autofs4_init_ino(struct autofs_info *ino, ...@@ -76,19 +75,8 @@ struct autofs_info *autofs4_init_ino(struct autofs_info *ino,
void autofs4_free_ino(struct autofs_info *ino) void autofs4_free_ino(struct autofs_info *ino)
{ {
struct autofs_info *p_ino;
if (ino->dentry) { if (ino->dentry) {
ino->dentry->d_fsdata = NULL; ino->dentry->d_fsdata = NULL;
if (ino->dentry->d_inode) {
struct dentry *parent = ino->dentry->d_parent;
if (atomic_dec_and_test(&ino->count)) {
p_ino = autofs4_dentry_ino(parent);
if (p_ino && parent != ino->dentry)
atomic_dec(&p_ino->count);
}
dput(ino->dentry);
}
ino->dentry = NULL; ino->dentry = NULL;
} }
if (ino->free) if (ino->free)
...@@ -251,10 +239,6 @@ static struct autofs_info *autofs4_mkroot(struct autofs_sb_info *sbi) ...@@ -251,10 +239,6 @@ static struct autofs_info *autofs4_mkroot(struct autofs_sb_info *sbi)
return ino; return ino;
} }
static const struct dentry_operations autofs4_sb_dentry_operations = {
.d_release = autofs4_dentry_release,
};
int autofs4_fill_super(struct super_block *s, void *data, int silent) int autofs4_fill_super(struct super_block *s, void *data, int silent)
{ {
struct inode * root_inode; struct inode * root_inode;
...@@ -292,6 +276,7 @@ int autofs4_fill_super(struct super_block *s, void *data, int silent) ...@@ -292,6 +276,7 @@ int autofs4_fill_super(struct super_block *s, void *data, int silent)
s->s_blocksize_bits = 10; s->s_blocksize_bits = 10;
s->s_magic = AUTOFS_SUPER_MAGIC; s->s_magic = AUTOFS_SUPER_MAGIC;
s->s_op = &autofs4_sops; s->s_op = &autofs4_sops;
s->s_d_op = &autofs4_dentry_operations;
s->s_time_gran = 1; s->s_time_gran = 1;
/* /*
...@@ -309,7 +294,6 @@ int autofs4_fill_super(struct super_block *s, void *data, int silent) ...@@ -309,7 +294,6 @@ int autofs4_fill_super(struct super_block *s, void *data, int silent)
goto fail_iput; goto fail_iput;
pipe = NULL; pipe = NULL;
d_set_d_op(root, &autofs4_sb_dentry_operations);
root->d_fsdata = ino; root->d_fsdata = ino;
/* Can this call block? */ /* Can this call block? */
...@@ -320,10 +304,11 @@ int autofs4_fill_super(struct super_block *s, void *data, int silent) ...@@ -320,10 +304,11 @@ int autofs4_fill_super(struct super_block *s, void *data, int silent)
goto fail_dput; goto fail_dput;
} }
if (autofs_type_trigger(sbi->type))
__managed_dentry_set_managed(root);
root_inode->i_fop = &autofs4_root_operations; root_inode->i_fop = &autofs4_root_operations;
root_inode->i_op = autofs_type_trigger(sbi->type) ? root_inode->i_op = &autofs4_dir_inode_operations;
&autofs4_direct_root_inode_operations :
&autofs4_indirect_root_inode_operations;
/* Couldn't this be tested earlier? */ /* Couldn't this be tested earlier? */
if (sbi->max_proto < AUTOFS_MIN_PROTO_VERSION || if (sbi->max_proto < AUTOFS_MIN_PROTO_VERSION ||
...@@ -391,7 +376,6 @@ struct inode *autofs4_get_inode(struct super_block *sb, ...@@ -391,7 +376,6 @@ struct inode *autofs4_get_inode(struct super_block *sb,
if (inode == NULL) if (inode == NULL)
return NULL; return NULL;
inf->inode = inode;
inode->i_mode = inf->mode; inode->i_mode = inf->mode;
if (sb->s_root) { if (sb->s_root) {
inode->i_uid = sb->s_root->d_inode->i_uid; inode->i_uid = sb->s_root->d_inode->i_uid;
......
...@@ -35,10 +35,8 @@ static long autofs4_root_compat_ioctl(struct file *,unsigned int,unsigned long); ...@@ -35,10 +35,8 @@ static long autofs4_root_compat_ioctl(struct file *,unsigned int,unsigned long);
#endif #endif
static int autofs4_dir_open(struct inode *inode, struct file *file); static int autofs4_dir_open(struct inode *inode, struct file *file);
static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *);
static void *autofs4_follow_link(struct dentry *, struct nameidata *); static struct vfsmount *autofs4_d_automount(struct path *);
static int autofs4_d_manage(struct dentry *, bool, bool);
#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY)
#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE)
const struct file_operations autofs4_root_operations = { const struct file_operations autofs4_root_operations = {
.open = dcache_dir_open, .open = dcache_dir_open,
...@@ -60,7 +58,7 @@ const struct file_operations autofs4_dir_operations = { ...@@ -60,7 +58,7 @@ const struct file_operations autofs4_dir_operations = {
.llseek = dcache_dir_lseek, .llseek = dcache_dir_lseek,
}; };
const struct inode_operations autofs4_indirect_root_inode_operations = { const struct inode_operations autofs4_dir_inode_operations = {
.lookup = autofs4_lookup, .lookup = autofs4_lookup,
.unlink = autofs4_dir_unlink, .unlink = autofs4_dir_unlink,
.symlink = autofs4_dir_symlink, .symlink = autofs4_dir_symlink,
...@@ -68,20 +66,10 @@ const struct inode_operations autofs4_indirect_root_inode_operations = { ...@@ -68,20 +66,10 @@ const struct inode_operations autofs4_indirect_root_inode_operations = {
.rmdir = autofs4_dir_rmdir, .rmdir = autofs4_dir_rmdir,
}; };
const struct inode_operations autofs4_direct_root_inode_operations = { const struct dentry_operations autofs4_dentry_operations = {
.lookup = autofs4_lookup, .d_automount = autofs4_d_automount,
.unlink = autofs4_dir_unlink, .d_manage = autofs4_d_manage,
.mkdir = autofs4_dir_mkdir, .d_release = autofs4_dentry_release,
.rmdir = autofs4_dir_rmdir,
.follow_link = autofs4_follow_link,
};
const struct inode_operations autofs4_dir_inode_operations = {
.lookup = autofs4_lookup,
.unlink = autofs4_dir_unlink,
.symlink = autofs4_dir_symlink,
.mkdir = autofs4_dir_mkdir,
.rmdir = autofs4_dir_rmdir,
}; };
static void autofs4_add_active(struct dentry *dentry) static void autofs4_add_active(struct dentry *dentry)
...@@ -116,14 +104,6 @@ static void autofs4_del_active(struct dentry *dentry) ...@@ -116,14 +104,6 @@ static void autofs4_del_active(struct dentry *dentry)
return; return;
} }
static unsigned int autofs4_need_mount(unsigned int flags)
{
unsigned int res = 0;
if (flags & (TRIGGER_FLAGS | TRIGGER_INTENTS))
res = 1;
return res;
}
static int autofs4_dir_open(struct inode *inode, struct file *file) static int autofs4_dir_open(struct inode *inode, struct file *file)
{ {
struct dentry *dentry = file->f_path.dentry; struct dentry *dentry = file->f_path.dentry;
...@@ -158,239 +138,6 @@ static int autofs4_dir_open(struct inode *inode, struct file *file) ...@@ -158,239 +138,6 @@ static int autofs4_dir_open(struct inode *inode, struct file *file)
return dcache_dir_open(inode, file); return dcache_dir_open(inode, file);
} }
static int try_to_fill_dentry(struct dentry *dentry, int flags)
{
struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
struct autofs_info *ino = autofs4_dentry_ino(dentry);
int status;
DPRINTK("dentry=%p %.*s ino=%p",
dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode);
/*
* Wait for a pending mount, triggering one if there
* isn't one already
*/
if (dentry->d_inode == NULL) {
DPRINTK("waiting for mount name=%.*s",
dentry->d_name.len, dentry->d_name.name);
status = autofs4_wait(sbi, dentry, NFY_MOUNT);
DPRINTK("mount done status=%d", status);
/* Turn this into a real negative dentry? */
if (status == -ENOENT) {
spin_lock(&sbi->fs_lock);
ino->flags &= ~AUTOFS_INF_PENDING;
spin_unlock(&sbi->fs_lock);
return status;
} else if (status) {
/* Return a negative dentry, but leave it "pending" */
return status;
}
/* Trigger mount for path component or follow link */
} else if (ino->flags & AUTOFS_INF_PENDING ||
autofs4_need_mount(flags)) {
DPRINTK("waiting for mount name=%.*s",
dentry->d_name.len, dentry->d_name.name);
spin_lock(&sbi->fs_lock);
ino->flags |= AUTOFS_INF_PENDING;
spin_unlock(&sbi->fs_lock);
status = autofs4_wait(sbi, dentry, NFY_MOUNT);
DPRINTK("mount done status=%d", status);
if (status) {
spin_lock(&sbi->fs_lock);
ino->flags &= ~AUTOFS_INF_PENDING;
spin_unlock(&sbi->fs_lock);
return status;
}
}
/* Initialize expiry counter after successful mount */
ino->last_used = jiffies;
spin_lock(&sbi->fs_lock);
ino->flags &= ~AUTOFS_INF_PENDING;
spin_unlock(&sbi->fs_lock);
return 0;
}
/* For autofs direct mounts the follow link triggers the mount */
static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd)
{
struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
struct autofs_info *ino = autofs4_dentry_ino(dentry);
int oz_mode = autofs4_oz_mode(sbi);
unsigned int lookup_type;
int status;
DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d",
dentry, dentry->d_name.len, dentry->d_name.name, oz_mode,
nd->flags);
/*
* For an expire of a covered direct or offset mount we need
* to break out of follow_down() at the autofs mount trigger
* (d_mounted--), so we can see the expiring flag, and manage
* the blocking and following here until the expire is completed.
*/
if (oz_mode) {
spin_lock(&sbi->fs_lock);
if (ino->flags & AUTOFS_INF_EXPIRING) {
spin_unlock(&sbi->fs_lock);
/* Follow down to our covering mount. */
if (!follow_down(&nd->path))
goto done;
goto follow;
}
spin_unlock(&sbi->fs_lock);
goto done;
}
/* If an expire request is pending everyone must wait. */
autofs4_expire_wait(dentry);
/* We trigger a mount for almost all flags */
lookup_type = autofs4_need_mount(nd->flags);
spin_lock(&sbi->fs_lock);
spin_lock(&autofs4_lock);
spin_lock(&dentry->d_lock);
if (!(lookup_type || ino->flags & AUTOFS_INF_PENDING)) {
spin_unlock(&dentry->d_lock);
spin_unlock(&autofs4_lock);
spin_unlock(&sbi->fs_lock);
goto follow;
}
/*
* If the dentry contains directories then it is an autofs
* multi-mount with no root mount offset. So don't try to
* mount it again.
*/
if (ino->flags & AUTOFS_INF_PENDING ||
(!d_mountpoint(dentry) && list_empty(&dentry->d_subdirs))) {
spin_unlock(&dentry->d_lock);
spin_unlock(&autofs4_lock);
spin_unlock(&sbi->fs_lock);
status = try_to_fill_dentry(dentry, nd->flags);
if (status)
goto out_error;
goto follow;
}
spin_unlock(&dentry->d_lock);
spin_unlock(&autofs4_lock);
spin_unlock(&sbi->fs_lock);
follow:
/*
* If there is no root mount it must be an autofs
* multi-mount with no root offset so we don't need
* to follow it.
*/
if (d_mountpoint(dentry)) {
if (!autofs4_follow_mount(&nd->path)) {
status = -ENOENT;
goto out_error;
}
}
done:
return NULL;
out_error:
path_put(&nd->path);
return ERR_PTR(status);
}
/*
* Revalidate is called on every cache lookup. Some of those
* cache lookups may actually happen while the dentry is not
* yet completely filled in, and revalidate has to delay such
* lookups..
*/
static int autofs4_revalidate(struct dentry *dentry, struct nameidata *nd)
{
struct inode *dir;
struct autofs_sb_info *sbi;
int oz_mode;
int flags = nd ? nd->flags : 0;
int status = 1;
if (flags & LOOKUP_RCU)
return -ECHILD;
dir = dentry->d_parent->d_inode;
sbi = autofs4_sbi(dir->i_sb);
oz_mode = autofs4_oz_mode(sbi);
/* Pending dentry */
spin_lock(&sbi->fs_lock);
if (autofs4_ispending(dentry)) {
/* The daemon never causes a mount to trigger */
spin_unlock(&sbi->fs_lock);
if (oz_mode)
return 1;
/*
* If the directory has gone away due to an expire
* we have been called as ->d_revalidate() and so
* we need to return false and proceed to ->lookup().
*/
if (autofs4_expire_wait(dentry) == -EAGAIN)
return 0;
/*
* A zero status is success otherwise we have a
* negative error code.
*/
status = try_to_fill_dentry(dentry, flags);
if (status == 0)
return 1;
return status;
}
spin_unlock(&sbi->fs_lock);
/* Negative dentry.. invalidate if "old" */
if (dentry->d_inode == NULL)
return 0;
/* Check for a non-mountpoint directory with no contents */
spin_lock(&autofs4_lock);
spin_lock(&dentry->d_lock);
if (S_ISDIR(dentry->d_inode->i_mode) &&
!d_mountpoint(dentry) && list_empty(&dentry->d_subdirs)) {
DPRINTK("dentry=%p %.*s, emptydir",
dentry, dentry->d_name.len, dentry->d_name.name);
spin_unlock(&dentry->d_lock);
spin_unlock(&autofs4_lock);
/* The daemon never causes a mount to trigger */
if (oz_mode)
return 1;
/*
* A zero status is success otherwise we have a
* negative error code.
*/
status = try_to_fill_dentry(dentry, flags);
if (status == 0)
return 1;
return status;
}
spin_unlock(&dentry->d_lock);
spin_unlock(&autofs4_lock);
return 1;
}
void autofs4_dentry_release(struct dentry *de) void autofs4_dentry_release(struct dentry *de)
{ {
struct autofs_info *inf; struct autofs_info *inf;
...@@ -398,11 +145,8 @@ void autofs4_dentry_release(struct dentry *de) ...@@ -398,11 +145,8 @@ void autofs4_dentry_release(struct dentry *de)
DPRINTK("releasing %p", de); DPRINTK("releasing %p", de);
inf = autofs4_dentry_ino(de); inf = autofs4_dentry_ino(de);
de->d_fsdata = NULL;
if (inf) { if (inf) {
struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb);
if (sbi) { if (sbi) {
spin_lock(&sbi->lookup_lock); spin_lock(&sbi->lookup_lock);
if (!list_empty(&inf->active)) if (!list_empty(&inf->active))
...@@ -411,26 +155,10 @@ void autofs4_dentry_release(struct dentry *de) ...@@ -411,26 +155,10 @@ void autofs4_dentry_release(struct dentry *de)
list_del(&inf->expiring); list_del(&inf->expiring);
spin_unlock(&sbi->lookup_lock); spin_unlock(&sbi->lookup_lock);
} }
inf->dentry = NULL;
inf->inode = NULL;
autofs4_free_ino(inf); autofs4_free_ino(inf);
} }
} }
/* For dentries of directories in the root dir */
static const struct dentry_operations autofs4_root_dentry_operations = {
.d_revalidate = autofs4_revalidate,
.d_release = autofs4_dentry_release,
};
/* For other dentries */
static const struct dentry_operations autofs4_dentry_operations = {
.d_revalidate = autofs4_revalidate,
.d_release = autofs4_dentry_release,
};
static struct dentry *autofs4_lookup_active(struct dentry *dentry) static struct dentry *autofs4_lookup_active(struct dentry *dentry)
{ {
struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
...@@ -541,137 +269,255 @@ static struct dentry *autofs4_lookup_expiring(struct dentry *dentry) ...@@ -541,137 +269,255 @@ static struct dentry *autofs4_lookup_expiring(struct dentry *dentry)
return NULL; return NULL;
} }
/* Lookups in the root directory */ static int autofs4_mount_wait(struct dentry *dentry)
static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{ {
struct autofs_sb_info *sbi; struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
struct autofs_info *ino; struct autofs_info *ino = autofs4_dentry_ino(dentry);
struct dentry *expiring, *active; int status;
int oz_mode;
DPRINTK("name = %.*s", if (ino->flags & AUTOFS_INF_PENDING) {
DPRINTK("waiting for mount name=%.*s",
dentry->d_name.len, dentry->d_name.name); dentry->d_name.len, dentry->d_name.name);
status = autofs4_wait(sbi, dentry, NFY_MOUNT);
DPRINTK("mount wait done status=%d", status);
ino->last_used = jiffies;
return status;
}
return 0;
}
/* File name too long to exist */ static int do_expire_wait(struct dentry *dentry)
if (dentry->d_name.len > NAME_MAX) {
return ERR_PTR(-ENAMETOOLONG); struct dentry *expiring;
sbi = autofs4_sbi(dir->i_sb);
oz_mode = autofs4_oz_mode(sbi);
DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d",
current->pid, task_pgrp_nr(current), sbi->catatonic, oz_mode);
active = autofs4_lookup_active(dentry); expiring = autofs4_lookup_expiring(dentry);
if (active) { if (!expiring)
dentry = active; return autofs4_expire_wait(dentry);
ino = autofs4_dentry_ino(dentry); else {
} else {
/* /*
* Mark the dentry incomplete but don't hash it. We do this * If we are racing with expire the request might not
* to serialize our inode creation operations (symlink and * be quite complete, but the directory has been removed
* mkdir) which prevents deadlock during the callback to * so it must have been successful, just wait for it.
* the daemon. Subsequent user space lookups for the same
* dentry are placed on the wait queue while the daemon
* itself is allowed passage unresticted so the create
* operation itself can then hash the dentry. Finally,
* we check for the hashed dentry and return the newly
* hashed dentry.
*/ */
d_set_d_op(dentry, &autofs4_root_dentry_operations); autofs4_expire_wait(expiring);
autofs4_del_expiring(expiring);
dput(expiring);
}
return 0;
}
static struct dentry *autofs4_mountpoint_changed(struct path *path)
{
struct dentry *dentry = path->dentry;
struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
/* /*
* And we need to ensure that the same dentry is used for * If this is an indirect mount the dentry could have gone away
* all following lookup calls until it is hashed so that * as a result of an expire and a new one created.
* the dentry flags are persistent throughout the request.
*/ */
ino = autofs4_init_ino(NULL, sbi, 0555); if (autofs_type_indirect(sbi->type) && d_unhashed(dentry)) {
if (!ino) struct dentry *parent = dentry->d_parent;
return ERR_PTR(-ENOMEM); struct dentry *new = d_lookup(parent, &dentry->d_name);
if (!new)
return NULL;
dput(path->dentry);
path->dentry = new;
}
return path->dentry;
}
dentry->d_fsdata = ino; static struct vfsmount *autofs4_d_automount(struct path *path)
ino->dentry = dentry; {
struct dentry *dentry = path->dentry;
struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
struct autofs_info *ino = autofs4_dentry_ino(dentry);
int status;
autofs4_add_active(dentry); DPRINTK("dentry=%p %.*s",
dentry, dentry->d_name.len, dentry->d_name.name);
d_instantiate(dentry, NULL); /*
* Someone may have manually umounted this or it was a submount
* that has gone away.
*/
spin_lock(&dentry->d_lock);
if (!d_mountpoint(dentry) && list_empty(&dentry->d_subdirs)) {
if (!(dentry->d_flags & DCACHE_MANAGE_TRANSIT) &&
(dentry->d_flags & DCACHE_NEED_AUTOMOUNT))
__managed_dentry_set_transit(path->dentry);
} }
spin_unlock(&dentry->d_lock);
/* The daemon never triggers a mount. */
if (autofs4_oz_mode(sbi))
return NULL;
if (!oz_mode) {
mutex_unlock(&dir->i_mutex);
expiring = autofs4_lookup_expiring(dentry);
if (expiring) {
/* /*
* If we are racing with expire the request might not * If an expire request is pending everyone must wait.
* be quite complete but the directory has been removed * If the expire fails we're still mounted so continue
* so it must have been successful, so just wait for it. * the follow and return. A return of -EAGAIN (which only
* happens with indirect mounts) means the expire completed
* and the directory was removed, so just go ahead and try
* the mount.
*/ */
autofs4_expire_wait(expiring); status = do_expire_wait(dentry);
autofs4_del_expiring(expiring); if (status && status != -EAGAIN)
dput(expiring); return NULL;
}
/* Callback to the daemon to perform the mount or wait */
spin_lock(&sbi->fs_lock); spin_lock(&sbi->fs_lock);
ino->flags |= AUTOFS_INF_PENDING; if (ino->flags & AUTOFS_INF_PENDING) {
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
if (dentry->d_op && dentry->d_op->d_revalidate) status = autofs4_mount_wait(dentry);
(dentry->d_op->d_revalidate)(dentry, nd); if (status)
mutex_lock(&dir->i_mutex); return ERR_PTR(status);
spin_lock(&sbi->fs_lock);
goto done;
} }
/* /*
* If we are still pending, check if we had to handle * If the dentry is a symlink it's equivalent to a directory
* a signal. If so we can force a restart.. * having d_mountpoint() true, so there's no need to call back
* to the daemon.
*/ */
if (ino->flags & AUTOFS_INF_PENDING) { if (dentry->d_inode && S_ISLNK(dentry->d_inode->i_mode))
/* See if we were interrupted */ goto done;
if (signal_pending(current)) { if (!d_mountpoint(dentry)) {
sigset_t *sigset = &current->pending.signal; /*
if (sigismember (sigset, SIGKILL) || * It's possible that user space hasn't removed directories
sigismember (sigset, SIGQUIT) || * after umounting a rootless multi-mount, although it
sigismember (sigset, SIGINT)) { * should. For v5 have_submounts() is sufficient to handle
if (active) * this because the leaves of the directory tree under the
dput(active); * mount never trigger mounts themselves (they have an autofs
return ERR_PTR(-ERESTARTNOINTR); * trigger mount mounted on them). But v4 pseudo direct mounts
* do need the leaves to to trigger mounts. In this case we
* have no choice but to use the list_empty() check and
* require user space behave.
*/
if (sbi->version > 4) {
if (have_submounts(dentry))
goto done;
} else {
spin_lock(&dentry->d_lock);
if (!list_empty(&dentry->d_subdirs)) {
spin_unlock(&dentry->d_lock);
goto done;
} }
spin_unlock(&dentry->d_lock);
} }
if (!oz_mode) { ino->flags |= AUTOFS_INF_PENDING;
spin_unlock(&sbi->fs_lock);
status = autofs4_mount_wait(dentry);
if (status)
return ERR_PTR(status);
spin_lock(&sbi->fs_lock); spin_lock(&sbi->fs_lock);
ino->flags &= ~AUTOFS_INF_PENDING; ino->flags &= ~AUTOFS_INF_PENDING;
spin_unlock(&sbi->fs_lock);
}
} }
done:
if (!(ino->flags & AUTOFS_INF_EXPIRING)) {
/* /*
* If this dentry is unhashed, then we shouldn't honour this * Any needed mounting has been completed and the path updated
* lookup. Returning ENOENT here doesn't do the right thing * so turn this into a normal dentry so we don't continually
* for all system calls, but it should be OK for the operations * call ->d_automount() and ->d_manage().
* we permit from an autofs.
*/ */
if (!oz_mode && d_unhashed(dentry)) { spin_lock(&dentry->d_lock);
__managed_dentry_clear_transit(dentry);
/* /*
* A user space application can (and has done in the past) * Only clear DMANAGED_AUTOMOUNT for rootless multi-mounts and
* remove and re-create this directory during the callback. * symlinks as in all other cases the dentry will be covered by
* This can leave us with an unhashed dentry, but a * an actual mount so ->d_automount() won't be called during
* successful mount! So we need to perform another * the follow.
* cached lookup in case the dentry now exists.
*/ */
struct dentry *parent = dentry->d_parent; if ((!d_mountpoint(dentry) &&
struct dentry *new = d_lookup(parent, &dentry->d_name); !list_empty(&dentry->d_subdirs)) ||
if (new != NULL) (dentry->d_inode && S_ISLNK(dentry->d_inode->i_mode)))
dentry = new; __managed_dentry_clear_automount(dentry);
else spin_unlock(&dentry->d_lock);
dentry = ERR_PTR(-ENOENT); }
spin_unlock(&sbi->fs_lock);
if (active) /* Mount succeeded, check if we ended up with a new dentry */
dput(active); dentry = autofs4_mountpoint_changed(path);
if (!dentry)
return ERR_PTR(-ENOENT);
return dentry; return NULL;
}
int autofs4_d_manage(struct dentry *dentry, bool mounting_here, bool rcu_walk)
{
struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
DPRINTK("dentry=%p %.*s",
dentry, dentry->d_name.len, dentry->d_name.name);
/* The daemon never waits. */
if (autofs4_oz_mode(sbi) || mounting_here) {
if (!d_mountpoint(dentry))
return -EISDIR;
return 0;
} }
if (active) /* We need to sleep, so we need pathwalk to be in ref-mode */
if (rcu_walk)
return -ECHILD;
/* Wait for pending expires */
do_expire_wait(dentry);
/*
* This dentry may be under construction so wait on mount
* completion.
*/
return autofs4_mount_wait(dentry);
}
/* Lookups in the root directory */
static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{
struct autofs_sb_info *sbi;
struct autofs_info *ino;
struct dentry *active;
DPRINTK("name = %.*s", dentry->d_name.len, dentry->d_name.name);
/* File name too long to exist */
if (dentry->d_name.len > NAME_MAX)
return ERR_PTR(-ENAMETOOLONG);
sbi = autofs4_sbi(dir->i_sb);
DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d",
current->pid, task_pgrp_nr(current), sbi->catatonic, oz_mode);
active = autofs4_lookup_active(dentry);
if (active) {
return active; return active;
} else {
/*
* A dentry that is not within the root can never trigger a
* mount operation, unless the directory already exists, so we
* can return fail immediately. The daemon however does need
* to create directories within the file system.
*/
if (!autofs4_oz_mode(sbi) && !IS_ROOT(dentry->d_parent))
return ERR_PTR(-ENOENT);
/* Mark entries in the root as mount triggers */
if (autofs_type_indirect(sbi->type) && IS_ROOT(dentry->d_parent))
__managed_dentry_set_managed(dentry);
ino = autofs4_init_ino(NULL, sbi, 0555);
if (!ino)
return ERR_PTR(-ENOMEM);
dentry->d_fsdata = ino;
ino->dentry = dentry;
autofs4_add_active(dentry);
d_instantiate(dentry, NULL);
}
return NULL; return NULL;
} }
...@@ -716,18 +562,12 @@ static int autofs4_dir_symlink(struct inode *dir, ...@@ -716,18 +562,12 @@ static int autofs4_dir_symlink(struct inode *dir,
} }
d_add(dentry, inode); d_add(dentry, inode);
if (dir == dir->i_sb->s_root->d_inode)
d_set_d_op(dentry, &autofs4_root_dentry_operations);
else
d_set_d_op(dentry, &autofs4_dentry_operations);
dentry->d_fsdata = ino; dentry->d_fsdata = ino;
ino->dentry = dget(dentry); ino->dentry = dget(dentry);
atomic_inc(&ino->count); atomic_inc(&ino->count);
p_ino = autofs4_dentry_ino(dentry->d_parent); p_ino = autofs4_dentry_ino(dentry->d_parent);
if (p_ino && dentry->d_parent != dentry) if (p_ino && dentry->d_parent != dentry)
atomic_inc(&p_ino->count); atomic_inc(&p_ino->count);
ino->inode = inode;
ino->u.symlink = cp; ino->u.symlink = cp;
dir->i_mtime = CURRENT_TIME; dir->i_mtime = CURRENT_TIME;
...@@ -782,6 +622,58 @@ static int autofs4_dir_unlink(struct inode *dir, struct dentry *dentry) ...@@ -782,6 +622,58 @@ static int autofs4_dir_unlink(struct inode *dir, struct dentry *dentry)
return 0; return 0;
} }
/*
* Version 4 of autofs provides a pseudo direct mount implementation
* that relies on directories at the leaves of a directory tree under
* an indirect mount to trigger mounts. To allow for this we need to
* set the DMANAGED_AUTOMOUNT and DMANAGED_TRANSIT flags on the leaves
* of the directory tree. There is no need to clear the automount flag
* following a mount or restore it after an expire because these mounts
* are always covered. However, it is neccessary to ensure that these
* flags are clear on non-empty directories to avoid unnecessary calls
* during path walks.
*/
static void autofs_set_leaf_automount_flags(struct dentry *dentry)
{
struct dentry *parent;
/* root and dentrys in the root are already handled */
if (IS_ROOT(dentry->d_parent))
return;
managed_dentry_set_managed(dentry);
parent = dentry->d_parent;
/* only consider parents below dentrys in the root */
if (IS_ROOT(parent->d_parent))
return;
managed_dentry_clear_managed(parent);
return;
}
static void autofs_clear_leaf_automount_flags(struct dentry *dentry)
{
struct list_head *d_child;
struct dentry *parent;
/* flags for dentrys in the root are handled elsewhere */
if (IS_ROOT(dentry->d_parent))
return;
managed_dentry_clear_managed(dentry);
parent = dentry->d_parent;
/* only consider parents below dentrys in the root */
if (IS_ROOT(parent->d_parent))
return;
d_child = &dentry->d_u.d_child;
/* Set parent managed if it's becoming empty */
if (d_child->next == &parent->d_subdirs &&
d_child->prev == &parent->d_subdirs)
managed_dentry_set_managed(parent);
return;
}
static int autofs4_dir_rmdir(struct inode *dir, struct dentry *dentry) static int autofs4_dir_rmdir(struct inode *dir, struct dentry *dentry)
{ {
struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb); struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb);
...@@ -809,6 +701,9 @@ static int autofs4_dir_rmdir(struct inode *dir, struct dentry *dentry) ...@@ -809,6 +701,9 @@ static int autofs4_dir_rmdir(struct inode *dir, struct dentry *dentry)
spin_unlock(&dentry->d_lock); spin_unlock(&dentry->d_lock);
spin_unlock(&autofs4_lock); spin_unlock(&autofs4_lock);
if (sbi->version < 5)
autofs_clear_leaf_automount_flags(dentry);
if (atomic_dec_and_test(&ino->count)) { if (atomic_dec_and_test(&ino->count)) {
p_ino = autofs4_dentry_ino(dentry->d_parent); p_ino = autofs4_dentry_ino(dentry->d_parent);
if (p_ino && dentry->d_parent != dentry) if (p_ino && dentry->d_parent != dentry)
...@@ -851,10 +746,8 @@ static int autofs4_dir_mkdir(struct inode *dir, struct dentry *dentry, int mode) ...@@ -851,10 +746,8 @@ static int autofs4_dir_mkdir(struct inode *dir, struct dentry *dentry, int mode)
} }
d_add(dentry, inode); d_add(dentry, inode);
if (dir == dir->i_sb->s_root->d_inode) if (sbi->version < 5)
d_set_d_op(dentry, &autofs4_root_dentry_operations); autofs_set_leaf_automount_flags(dentry);
else
d_set_d_op(dentry, &autofs4_dentry_operations);
dentry->d_fsdata = ino; dentry->d_fsdata = ino;
ino->dentry = dget(dentry); ino->dentry = dget(dentry);
...@@ -862,7 +755,6 @@ static int autofs4_dir_mkdir(struct inode *dir, struct dentry *dentry, int mode) ...@@ -862,7 +755,6 @@ static int autofs4_dir_mkdir(struct inode *dir, struct dentry *dentry, int mode)
p_ino = autofs4_dentry_ino(dentry->d_parent); p_ino = autofs4_dentry_ino(dentry->d_parent);
if (p_ino && dentry->d_parent != dentry) if (p_ino && dentry->d_parent != dentry)
atomic_inc(&p_ino->count); atomic_inc(&p_ino->count);
ino->inode = inode;
inc_nlink(dir); inc_nlink(dir);
dir->i_mtime = CURRENT_TIME; dir->i_mtime = CURRENT_TIME;
...@@ -944,8 +836,7 @@ static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) ...@@ -944,8 +836,7 @@ static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p)
int is_autofs4_dentry(struct dentry *dentry) int is_autofs4_dentry(struct dentry *dentry)
{ {
return dentry && dentry->d_inode && return dentry && dentry->d_inode &&
(dentry->d_op == &autofs4_root_dentry_operations || dentry->d_op == &autofs4_dentry_operations &&
dentry->d_op == &autofs4_dentry_operations) &&
dentry->d_fsdata != NULL; dentry->d_fsdata != NULL;
} }
......
...@@ -309,6 +309,9 @@ static int validate_request(struct autofs_wait_queue **wait, ...@@ -309,6 +309,9 @@ static int validate_request(struct autofs_wait_queue **wait,
* completed while we waited on the mutex ... * completed while we waited on the mutex ...
*/ */
if (notify == NFY_MOUNT) { if (notify == NFY_MOUNT) {
struct dentry *new = NULL;
int valid = 1;
/* /*
* If the dentry was successfully mounted while we slept * If the dentry was successfully mounted while we slept
* on the wait queue mutex we can return success. If it * on the wait queue mutex we can return success. If it
...@@ -316,8 +319,20 @@ static int validate_request(struct autofs_wait_queue **wait, ...@@ -316,8 +319,20 @@ static int validate_request(struct autofs_wait_queue **wait,
* a multi-mount with no mount at it's base) we can * a multi-mount with no mount at it's base) we can
* continue on and create a new request. * continue on and create a new request.
*/ */
if (!IS_ROOT(dentry)) {
if (dentry->d_inode && d_unhashed(dentry)) {
struct dentry *parent = dentry->d_parent;
new = d_lookup(parent, &dentry->d_name);
if (new)
dentry = new;
}
}
if (have_submounts(dentry)) if (have_submounts(dentry))
return 0; valid = 0;
if (new)
dput(new);
return valid;
} }
return 1; return 1;
......
...@@ -255,35 +255,6 @@ static struct vfsmount *cifs_dfs_do_refmount(struct cifs_sb_info *cifs_sb, ...@@ -255,35 +255,6 @@ static struct vfsmount *cifs_dfs_do_refmount(struct cifs_sb_info *cifs_sb,
} }
static int add_mount_helper(struct vfsmount *newmnt, struct nameidata *nd,
struct list_head *mntlist)
{
/* stolen from afs code */
int err;
mntget(newmnt);
err = do_add_mount(newmnt, &nd->path, nd->path.mnt->mnt_flags | MNT_SHRINKABLE, mntlist);
switch (err) {
case 0:
path_put(&nd->path);
nd->path.mnt = newmnt;
nd->path.dentry = dget(newmnt->mnt_root);
schedule_delayed_work(&cifs_dfs_automount_task,
cifs_dfs_mountpoint_expiry_timeout);
break;
case -EBUSY:
/* someone else made a mount here whilst we were busy */
while (d_mountpoint(nd->path.dentry) &&
follow_down(&nd->path))
;
err = 0;
default:
mntput(newmnt);
break;
}
return err;
}
static void dump_referral(const struct dfs_info3_param *ref) static void dump_referral(const struct dfs_info3_param *ref)
{ {
cFYI(1, "DFS: ref path: %s", ref->path_name); cFYI(1, "DFS: ref path: %s", ref->path_name);
...@@ -293,45 +264,43 @@ static void dump_referral(const struct dfs_info3_param *ref) ...@@ -293,45 +264,43 @@ static void dump_referral(const struct dfs_info3_param *ref)
ref->path_consumed); ref->path_consumed);
} }
/*
static void* * Create a vfsmount that we can automount
cifs_dfs_follow_mountpoint(struct dentry *dentry, struct nameidata *nd) */
static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
{ {
struct dfs_info3_param *referrals = NULL; struct dfs_info3_param *referrals = NULL;
unsigned int num_referrals = 0; unsigned int num_referrals = 0;
struct cifs_sb_info *cifs_sb; struct cifs_sb_info *cifs_sb;
struct cifsSesInfo *ses; struct cifsSesInfo *ses;
char *full_path = NULL; char *full_path;
int xid, i; int xid, i;
int rc = 0; int rc;
struct vfsmount *mnt = ERR_PTR(-ENOENT); struct vfsmount *mnt;
struct tcon_link *tlink; struct tcon_link *tlink;
cFYI(1, "in %s", __func__); cFYI(1, "in %s", __func__);
BUG_ON(IS_ROOT(dentry)); BUG_ON(IS_ROOT(mntpt));
xid = GetXid(); xid = GetXid();
dput(nd->path.dentry);
nd->path.dentry = dget(dentry);
/* /*
* The MSDFS spec states that paths in DFS referral requests and * The MSDFS spec states that paths in DFS referral requests and
* responses must be prefixed by a single '\' character instead of * responses must be prefixed by a single '\' character instead of
* the double backslashes usually used in the UNC. This function * the double backslashes usually used in the UNC. This function
* gives us the latter, so we must adjust the result. * gives us the latter, so we must adjust the result.
*/ */
full_path = build_path_from_dentry(dentry); mnt = ERR_PTR(-ENOMEM);
if (full_path == NULL) { full_path = build_path_from_dentry(mntpt);
rc = -ENOMEM; if (full_path == NULL)
goto out_err; goto free_xid;
}
cifs_sb = CIFS_SB(dentry->d_inode->i_sb); cifs_sb = CIFS_SB(mntpt->d_inode->i_sb);
tlink = cifs_sb_tlink(cifs_sb); tlink = cifs_sb_tlink(cifs_sb);
mnt = ERR_PTR(-EINVAL);
if (IS_ERR(tlink)) { if (IS_ERR(tlink)) {
rc = PTR_ERR(tlink); mnt = ERR_CAST(tlink);
goto out_err; goto free_full_path;
} }
ses = tlink_tcon(tlink)->ses; ses = tlink_tcon(tlink)->ses;
...@@ -341,46 +310,63 @@ cifs_dfs_follow_mountpoint(struct dentry *dentry, struct nameidata *nd) ...@@ -341,46 +310,63 @@ cifs_dfs_follow_mountpoint(struct dentry *dentry, struct nameidata *nd)
cifs_put_tlink(tlink); cifs_put_tlink(tlink);
mnt = ERR_PTR(-ENOENT);
for (i = 0; i < num_referrals; i++) { for (i = 0; i < num_referrals; i++) {
int len; int len;
dump_referral(referrals+i); dump_referral(referrals + i);
/* connect to a node */ /* connect to a node */
len = strlen(referrals[i].node_name); len = strlen(referrals[i].node_name);
if (len < 2) { if (len < 2) {
cERROR(1, "%s: Net Address path too short: %s", cERROR(1, "%s: Net Address path too short: %s",
__func__, referrals[i].node_name); __func__, referrals[i].node_name);
rc = -EINVAL; mnt = ERR_PTR(-EINVAL);
goto out_err; break;
} }
mnt = cifs_dfs_do_refmount(cifs_sb, mnt = cifs_dfs_do_refmount(cifs_sb,
full_path, referrals + i); full_path, referrals + i);
cFYI(1, "%s: cifs_dfs_do_refmount:%s , mnt:%p", __func__, cFYI(1, "%s: cifs_dfs_do_refmount:%s , mnt:%p", __func__,
referrals[i].node_name, mnt); referrals[i].node_name, mnt);
/* complete mount procedure if we accured submount */
if (!IS_ERR(mnt)) if (!IS_ERR(mnt))
break; goto success;
} }
/* we need it cause for() above could exit without valid submount */ /* no valid submounts were found; return error from get_dfs_path() by
rc = PTR_ERR(mnt); * preference */
if (IS_ERR(mnt)) if (rc != 0)
goto out_err; mnt = ERR_PTR(rc);
rc = add_mount_helper(mnt, nd, &cifs_dfs_automount_list);
out: success:
FreeXid(xid);
free_dfs_info_array(referrals, num_referrals); free_dfs_info_array(referrals, num_referrals);
free_full_path:
kfree(full_path); kfree(full_path);
free_xid:
FreeXid(xid);
cFYI(1, "leaving %s" , __func__); cFYI(1, "leaving %s" , __func__);
return ERR_PTR(rc); return mnt;
out_err: }
path_put(&nd->path);
goto out; /*
* Attempt to automount the referral
*/
struct vfsmount *cifs_dfs_d_automount(struct path *path)
{
struct vfsmount *newmnt;
cFYI(1, "in %s", __func__);
newmnt = cifs_dfs_do_automount(path->dentry);
if (IS_ERR(newmnt)) {
cFYI(1, "leaving %s [automount failed]" , __func__);
return newmnt;
}
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 = { const struct inode_operations cifs_dfs_referral_inode_operations = {
.follow_link = cifs_dfs_follow_mountpoint,
}; };
...@@ -93,6 +93,12 @@ extern int cifs_readdir(struct file *file, void *direntry, filldir_t filldir); ...@@ -93,6 +93,12 @@ extern int cifs_readdir(struct file *file, void *direntry, filldir_t filldir);
extern const struct dentry_operations cifs_dentry_ops; extern const struct dentry_operations cifs_dentry_ops;
extern const struct dentry_operations cifs_ci_dentry_ops; extern const struct dentry_operations cifs_ci_dentry_ops;
#ifdef CONFIG_CIFS_DFS_UPCALL
extern struct vfsmount *cifs_dfs_d_automount(struct path *path);
#else
#define cifs_dfs_d_automount NULL
#endif
/* Functions related to symlinks */ /* Functions related to symlinks */
extern void *cifs_follow_link(struct dentry *direntry, struct nameidata *nd); extern void *cifs_follow_link(struct dentry *direntry, struct nameidata *nd);
extern void cifs_put_link(struct dentry *direntry, extern void cifs_put_link(struct dentry *direntry,
......
...@@ -675,6 +675,7 @@ cifs_d_revalidate(struct dentry *direntry, struct nameidata *nd) ...@@ -675,6 +675,7 @@ cifs_d_revalidate(struct dentry *direntry, struct nameidata *nd)
const struct dentry_operations cifs_dentry_ops = { const struct dentry_operations cifs_dentry_ops = {
.d_revalidate = cifs_d_revalidate, .d_revalidate = cifs_d_revalidate,
.d_automount = cifs_dfs_d_automount,
/* d_delete: cifs_d_delete, */ /* not needed except for debugging */ /* d_delete: cifs_d_delete, */ /* not needed except for debugging */
}; };
...@@ -711,4 +712,5 @@ const struct dentry_operations cifs_ci_dentry_ops = { ...@@ -711,4 +712,5 @@ const struct dentry_operations cifs_ci_dentry_ops = {
.d_revalidate = cifs_d_revalidate, .d_revalidate = cifs_d_revalidate,
.d_hash = cifs_ci_hash, .d_hash = cifs_ci_hash,
.d_compare = cifs_ci_compare, .d_compare = cifs_ci_compare,
.d_automount = cifs_dfs_d_automount,
}; };
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
#include "fscache.h" #include "fscache.h"
static void cifs_set_ops(struct inode *inode, const bool is_dfs_referral) static void cifs_set_ops(struct inode *inode)
{ {
struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
...@@ -60,7 +60,7 @@ static void cifs_set_ops(struct inode *inode, const bool is_dfs_referral) ...@@ -60,7 +60,7 @@ static void cifs_set_ops(struct inode *inode, const bool is_dfs_referral)
break; break;
case S_IFDIR: case S_IFDIR:
#ifdef CONFIG_CIFS_DFS_UPCALL #ifdef CONFIG_CIFS_DFS_UPCALL
if (is_dfs_referral) { if (IS_AUTOMOUNT(inode)) {
inode->i_op = &cifs_dfs_referral_inode_operations; inode->i_op = &cifs_dfs_referral_inode_operations;
} else { } else {
#else /* NO DFS support, treat as a directory */ #else /* NO DFS support, treat as a directory */
...@@ -167,7 +167,9 @@ cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr) ...@@ -167,7 +167,9 @@ cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr)
} }
spin_unlock(&inode->i_lock); spin_unlock(&inode->i_lock);
cifs_set_ops(inode, fattr->cf_flags & CIFS_FATTR_DFS_REFERRAL); if (fattr->cf_flags & CIFS_FATTR_DFS_REFERRAL)
inode->i_flags |= S_AUTOMOUNT;
cifs_set_ops(inode);
} }
void void
......
...@@ -1380,8 +1380,11 @@ EXPORT_SYMBOL(d_set_d_op); ...@@ -1380,8 +1380,11 @@ EXPORT_SYMBOL(d_set_d_op);
static void __d_instantiate(struct dentry *dentry, struct inode *inode) static void __d_instantiate(struct dentry *dentry, struct inode *inode)
{ {
spin_lock(&dentry->d_lock); spin_lock(&dentry->d_lock);
if (inode) if (inode) {
if (unlikely(IS_AUTOMOUNT(inode)))
dentry->d_flags |= DCACHE_NEED_AUTOMOUNT;
list_add(&dentry->d_alias, &inode->i_dentry); list_add(&dentry->d_alias, &inode->i_dentry);
}
dentry->d_inode = inode; dentry->d_inode = inode;
dentry_rcuwalk_barrier(dentry); dentry_rcuwalk_barrier(dentry);
spin_unlock(&dentry->d_lock); spin_unlock(&dentry->d_lock);
......
...@@ -4,6 +4,19 @@ ...@@ -4,6 +4,19 @@
#include <linux/path.h> #include <linux/path.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/fs_struct.h> #include <linux/fs_struct.h>
#include "internal.h"
static inline void path_get_longterm(struct path *path)
{
path_get(path);
mnt_make_longterm(path->mnt);
}
static inline void path_put_longterm(struct path *path)
{
mnt_make_shortterm(path->mnt);
path_put(path);
}
/* /*
* Replace the fs->{rootmnt,root} with {mnt,dentry}. Put the old values. * Replace the fs->{rootmnt,root} with {mnt,dentry}. Put the old values.
...@@ -17,11 +30,11 @@ void set_fs_root(struct fs_struct *fs, struct path *path) ...@@ -17,11 +30,11 @@ void set_fs_root(struct fs_struct *fs, struct path *path)
write_seqcount_begin(&fs->seq); write_seqcount_begin(&fs->seq);
old_root = fs->root; old_root = fs->root;
fs->root = *path; fs->root = *path;
path_get_long(path); path_get_longterm(path);
write_seqcount_end(&fs->seq); write_seqcount_end(&fs->seq);
spin_unlock(&fs->lock); spin_unlock(&fs->lock);
if (old_root.dentry) if (old_root.dentry)
path_put_long(&old_root); path_put_longterm(&old_root);
} }
/* /*
...@@ -36,12 +49,12 @@ void set_fs_pwd(struct fs_struct *fs, struct path *path) ...@@ -36,12 +49,12 @@ void set_fs_pwd(struct fs_struct *fs, struct path *path)
write_seqcount_begin(&fs->seq); write_seqcount_begin(&fs->seq);
old_pwd = fs->pwd; old_pwd = fs->pwd;
fs->pwd = *path; fs->pwd = *path;
path_get_long(path); path_get_longterm(path);
write_seqcount_end(&fs->seq); write_seqcount_end(&fs->seq);
spin_unlock(&fs->lock); spin_unlock(&fs->lock);
if (old_pwd.dentry) if (old_pwd.dentry)
path_put_long(&old_pwd); path_put_longterm(&old_pwd);
} }
void chroot_fs_refs(struct path *old_root, struct path *new_root) void chroot_fs_refs(struct path *old_root, struct path *new_root)
...@@ -59,13 +72,13 @@ void chroot_fs_refs(struct path *old_root, struct path *new_root) ...@@ -59,13 +72,13 @@ void chroot_fs_refs(struct path *old_root, struct path *new_root)
write_seqcount_begin(&fs->seq); write_seqcount_begin(&fs->seq);
if (fs->root.dentry == old_root->dentry if (fs->root.dentry == old_root->dentry
&& fs->root.mnt == old_root->mnt) { && fs->root.mnt == old_root->mnt) {
path_get_long(new_root); path_get_longterm(new_root);
fs->root = *new_root; fs->root = *new_root;
count++; count++;
} }
if (fs->pwd.dentry == old_root->dentry if (fs->pwd.dentry == old_root->dentry
&& fs->pwd.mnt == old_root->mnt) { && fs->pwd.mnt == old_root->mnt) {
path_get_long(new_root); path_get_longterm(new_root);
fs->pwd = *new_root; fs->pwd = *new_root;
count++; count++;
} }
...@@ -76,13 +89,13 @@ void chroot_fs_refs(struct path *old_root, struct path *new_root) ...@@ -76,13 +89,13 @@ void chroot_fs_refs(struct path *old_root, struct path *new_root)
} while_each_thread(g, p); } while_each_thread(g, p);
read_unlock(&tasklist_lock); read_unlock(&tasklist_lock);
while (count--) while (count--)
path_put_long(old_root); path_put_longterm(old_root);
} }
void free_fs_struct(struct fs_struct *fs) void free_fs_struct(struct fs_struct *fs)
{ {
path_put_long(&fs->root); path_put_longterm(&fs->root);
path_put_long(&fs->pwd); path_put_longterm(&fs->pwd);
kmem_cache_free(fs_cachep, fs); kmem_cache_free(fs_cachep, fs);
} }
...@@ -118,9 +131,9 @@ struct fs_struct *copy_fs_struct(struct fs_struct *old) ...@@ -118,9 +131,9 @@ struct fs_struct *copy_fs_struct(struct fs_struct *old)
spin_lock(&old->lock); spin_lock(&old->lock);
fs->root = old->root; fs->root = old->root;
path_get_long(&fs->root); path_get_longterm(&fs->root);
fs->pwd = old->pwd; fs->pwd = old->pwd;
path_get_long(&fs->pwd); path_get_longterm(&fs->pwd);
spin_unlock(&old->lock); spin_unlock(&old->lock);
} }
return fs; return fs;
......
...@@ -70,6 +70,11 @@ extern void mnt_set_mountpoint(struct vfsmount *, struct dentry *, ...@@ -70,6 +70,11 @@ extern void mnt_set_mountpoint(struct vfsmount *, struct dentry *,
extern void release_mounts(struct list_head *); extern void release_mounts(struct list_head *);
extern void umount_tree(struct vfsmount *, int, struct list_head *); extern void umount_tree(struct vfsmount *, int, struct list_head *);
extern struct vfsmount *copy_tree(struct vfsmount *, struct dentry *, int); 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 mnt_make_longterm(struct vfsmount *);
extern void mnt_make_shortterm(struct vfsmount *);
extern void __init mnt_init(void); extern void __init mnt_init(void);
......
...@@ -367,18 +367,6 @@ void path_get(struct path *path) ...@@ -367,18 +367,6 @@ void path_get(struct path *path)
} }
EXPORT_SYMBOL(path_get); EXPORT_SYMBOL(path_get);
/**
* path_get_long - get a long reference to a path
* @path: path to get the reference to
*
* Given a path increment the reference count to the dentry and the vfsmount.
*/
void path_get_long(struct path *path)
{
mntget_long(path->mnt);
dget(path->dentry);
}
/** /**
* path_put - put a reference to a path * path_put - put a reference to a path
* @path: path to put the reference to * @path: path to put the reference to
...@@ -392,18 +380,6 @@ void path_put(struct path *path) ...@@ -392,18 +380,6 @@ void path_put(struct path *path)
} }
EXPORT_SYMBOL(path_put); EXPORT_SYMBOL(path_put);
/**
* path_put_long - put a long reference to a path
* @path: path to put the reference to
*
* Given a path decrement the reference count to the dentry and the vfsmount.
*/
void path_put_long(struct path *path)
{
dput(path->dentry);
mntput_long(path->mnt);
}
/** /**
* nameidata_drop_rcu - drop this nameidata out of rcu-walk * nameidata_drop_rcu - drop this nameidata out of rcu-walk
* @nd: nameidata pathwalk data to drop * @nd: nameidata pathwalk data to drop
...@@ -800,11 +776,7 @@ __do_follow_link(const struct path *link, struct nameidata *nd, void **p) ...@@ -800,11 +776,7 @@ __do_follow_link(const struct path *link, struct nameidata *nd, void **p)
touch_atime(link->mnt, dentry); touch_atime(link->mnt, dentry);
nd_set_link(nd, NULL); nd_set_link(nd, NULL);
if (link->mnt != nd->path.mnt) { if (link->mnt == nd->path.mnt)
path_to_nameidata(link, nd);
nd->inode = nd->path.dentry->d_inode;
dget(dentry);
}
mntget(link->mnt); mntget(link->mnt);
nd->last_type = LAST_BIND; nd->last_type = LAST_BIND;
...@@ -896,54 +868,169 @@ int follow_up(struct path *path) ...@@ -896,54 +868,169 @@ int follow_up(struct path *path)
} }
/* /*
* serialization is taken care of in namespace.c * Perform an automount
* - return -EISDIR to tell follow_managed() to stop and return the path we
* were called with.
*/ */
static void __follow_mount_rcu(struct nameidata *nd, struct path *path, static int follow_automount(struct path *path, unsigned flags,
struct inode **inode) bool *need_mntput)
{ {
while (d_mountpoint(path->dentry)) { struct vfsmount *mnt;
struct vfsmount *mounted; int err;
mounted = __lookup_mnt(path->mnt, path->dentry, 1);
if (!mounted) if (!path->dentry->d_op || !path->dentry->d_op->d_automount)
return; return -EREMOTE;
path->mnt = mounted;
path->dentry = mounted->mnt_root; /* We don't want to mount if someone supplied AT_NO_AUTOMOUNT
nd->seq = read_seqcount_begin(&path->dentry->d_seq); * and this is the terminal part of the path.
*inode = path->dentry->d_inode; */
if ((flags & LOOKUP_NO_AUTOMOUNT) && !(flags & LOOKUP_CONTINUE))
return -EISDIR; /* we actually want to stop here */
/* We want to mount if someone is trying to open/create a file of any
* type under the mountpoint, wants to traverse through the mountpoint
* or wants to open the mounted directory.
*
* We don't want to mount if someone's just doing a stat and they've
* set AT_SYMLINK_NOFOLLOW - unless they're stat'ing a directory and
* appended a '/' to the name.
*/
if (!(flags & LOOKUP_FOLLOW) &&
!(flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY |
LOOKUP_OPEN | LOOKUP_CREATE)))
return -EISDIR;
current->total_link_count++;
if (current->total_link_count >= 40)
return -ELOOP;
mnt = path->dentry->d_op->d_automount(path);
if (IS_ERR(mnt)) {
/*
* The filesystem is allowed to return -EISDIR here to indicate
* it doesn't want to automount. For instance, autofs would do
* this so that its userspace daemon can mount on this dentry.
*
* However, we can only permit this if it's a terminal point in
* the path being looked up; if it wasn't then the remainder of
* the path is inaccessible and we should say so.
*/
if (PTR_ERR(mnt) == -EISDIR && (flags & LOOKUP_CONTINUE))
return -EREMOTE;
return PTR_ERR(mnt);
} }
}
static int __follow_mount(struct path *path) if (!mnt) /* mount collision */
{ return 0;
int res = 0;
while (d_mountpoint(path->dentry)) { /* The new mount record should have at least 2 refs to prevent it being
struct vfsmount *mounted = lookup_mnt(path); * expired before we get a chance to add it
if (!mounted) */
break; 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;
}
/* 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); dput(path->dentry);
if (res) if (*need_mntput)
mntput(path->mnt); mntput(path->mnt);
path->mnt = mounted; path->mnt = mnt;
path->dentry = dget(mounted->mnt_root); path->dentry = dget(mnt->mnt_root);
res = 1; *need_mntput = true;
return 0;
} }
return res;
} }
static void follow_mount(struct path *path) /*
* Handle a dentry that is managed in some way.
* - Flagged for transit management (autofs)
* - Flagged as mountpoint
* - Flagged as automount point
*
* This may only be called in refwalk mode.
*
* Serialization is taken care of in namespace.c
*/
static int follow_managed(struct path *path, unsigned flags)
{ {
while (d_mountpoint(path->dentry)) { unsigned managed;
bool need_mntput = false;
int ret;
/* Given that we're not holding a lock here, we retain the value in a
* local variable for each dentry as we look at it so that we don't see
* the components of that value change under us */
while (managed = ACCESS_ONCE(path->dentry->d_flags),
managed &= DCACHE_MANAGED_DENTRY,
unlikely(managed != 0)) {
/* Allow the filesystem to manage the transit without i_mutex
* being held. */
if (managed & DCACHE_MANAGE_TRANSIT) {
BUG_ON(!path->dentry->d_op);
BUG_ON(!path->dentry->d_op->d_manage);
ret = path->dentry->d_op->d_manage(path->dentry,
false, false);
if (ret < 0)
return ret == -EISDIR ? 0 : ret;
}
/* Transit to a mounted filesystem. */
if (managed & DCACHE_MOUNTED) {
struct vfsmount *mounted = lookup_mnt(path); struct vfsmount *mounted = lookup_mnt(path);
if (!mounted) if (mounted) {
break;
dput(path->dentry); dput(path->dentry);
if (need_mntput)
mntput(path->mnt); mntput(path->mnt);
path->mnt = mounted; path->mnt = mounted;
path->dentry = dget(mounted->mnt_root); path->dentry = dget(mounted->mnt_root);
need_mntput = true;
continue;
}
/* Something is mounted on this dentry in another
* namespace and/or whatever was mounted there in this
* namespace got unmounted before we managed to get the
* vfsmount_lock */
}
/* Handle an automount point */
if (managed & DCACHE_NEED_AUTOMOUNT) {
ret = follow_automount(path, flags, &need_mntput);
if (ret < 0)
return ret == -EISDIR ? 0 : ret;
continue;
}
/* We didn't change the current path point */
break;
} }
return 0;
} }
int follow_down(struct path *path) int follow_down_one(struct path *path)
{ {
struct vfsmount *mounted; struct vfsmount *mounted;
...@@ -958,13 +1045,41 @@ int follow_down(struct path *path) ...@@ -958,13 +1045,41 @@ int follow_down(struct path *path)
return 0; return 0;
} }
/*
* Skip to top of mountpoint pile in rcuwalk mode. We abort the rcu-walk if we
* meet a managed dentry and we're not walking to "..". True is returned to
* continue, false to abort.
*/
static bool __follow_mount_rcu(struct nameidata *nd, struct path *path,
struct inode **inode, bool reverse_transit)
{
while (d_mountpoint(path->dentry)) {
struct vfsmount *mounted;
if (unlikely(path->dentry->d_flags & DCACHE_MANAGE_TRANSIT) &&
!reverse_transit &&
path->dentry->d_op->d_manage(path->dentry, false, true) < 0)
return false;
mounted = __lookup_mnt(path->mnt, path->dentry, 1);
if (!mounted)
break;
path->mnt = mounted;
path->dentry = mounted->mnt_root;
nd->seq = read_seqcount_begin(&path->dentry->d_seq);
*inode = path->dentry->d_inode;
}
if (unlikely(path->dentry->d_flags & DCACHE_NEED_AUTOMOUNT))
return reverse_transit;
return true;
}
static int follow_dotdot_rcu(struct nameidata *nd) static int follow_dotdot_rcu(struct nameidata *nd)
{ {
struct inode *inode = nd->inode; struct inode *inode = nd->inode;
set_root_rcu(nd); set_root_rcu(nd);
while(1) { while (1) {
if (nd->path.dentry == nd->root.dentry && if (nd->path.dentry == nd->root.dentry &&
nd->path.mnt == nd->root.mnt) { nd->path.mnt == nd->root.mnt) {
break; break;
...@@ -987,12 +1102,80 @@ static int follow_dotdot_rcu(struct nameidata *nd) ...@@ -987,12 +1102,80 @@ static int follow_dotdot_rcu(struct nameidata *nd)
nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq); nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);
inode = nd->path.dentry->d_inode; inode = nd->path.dentry->d_inode;
} }
__follow_mount_rcu(nd, &nd->path, &inode); __follow_mount_rcu(nd, &nd->path, &inode, true);
nd->inode = inode; nd->inode = inode;
return 0; return 0;
} }
/*
* Follow down to the covering mount currently visible to userspace. At each
* point, the filesystem owning that dentry may be queried as to whether the
* caller is permitted to proceed or not.
*
* Care must be taken as namespace_sem may be held (indicated by mounting_here
* being true).
*/
int follow_down(struct path *path, bool mounting_here)
{
unsigned managed;
int ret;
while (managed = ACCESS_ONCE(path->dentry->d_flags),
unlikely(managed & DCACHE_MANAGED_DENTRY)) {
/* Allow the filesystem to manage the transit without i_mutex
* being held.
*
* We indicate to the filesystem if someone is trying to mount
* something here. This gives autofs the chance to deny anyone
* other than its daemon the right to mount on its
* superstructure.
*
* The filesystem may sleep at this point.
*/
if (managed & DCACHE_MANAGE_TRANSIT) {
BUG_ON(!path->dentry->d_op);
BUG_ON(!path->dentry->d_op->d_manage);
ret = path->dentry->d_op->d_manage(
path->dentry, mounting_here, false);
if (ret < 0)
return ret == -EISDIR ? 0 : ret;
}
/* Transit to a mounted filesystem. */
if (managed & DCACHE_MOUNTED) {
struct vfsmount *mounted = lookup_mnt(path);
if (!mounted)
break;
dput(path->dentry);
mntput(path->mnt);
path->mnt = mounted;
path->dentry = dget(mounted->mnt_root);
continue;
}
/* Don't handle automount points here */
break;
}
return 0;
}
/*
* Skip to top of mountpoint pile in refwalk mode for follow_dotdot()
*/
static void follow_mount(struct path *path)
{
while (d_mountpoint(path->dentry)) {
struct vfsmount *mounted = lookup_mnt(path);
if (!mounted)
break;
dput(path->dentry);
mntput(path->mnt);
path->mnt = mounted;
path->dentry = dget(mounted->mnt_root);
}
}
static void follow_dotdot(struct nameidata *nd) static void follow_dotdot(struct nameidata *nd)
{ {
set_root(nd); set_root(nd);
...@@ -1057,12 +1240,14 @@ static int do_lookup(struct nameidata *nd, struct qstr *name, ...@@ -1057,12 +1240,14 @@ static int do_lookup(struct nameidata *nd, struct qstr *name,
struct vfsmount *mnt = nd->path.mnt; struct vfsmount *mnt = nd->path.mnt;
struct dentry *dentry, *parent = nd->path.dentry; struct dentry *dentry, *parent = nd->path.dentry;
struct inode *dir; struct inode *dir;
int err;
/* /*
* See if the low-level filesystem might want * See if the low-level filesystem might want
* to use its own hash.. * to use its own hash..
*/ */
if (unlikely(parent->d_flags & DCACHE_OP_HASH)) { if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {
int err = parent->d_op->d_hash(parent, nd->inode, name); err = parent->d_op->d_hash(parent, nd->inode, name);
if (err < 0) if (err < 0)
return err; return err;
} }
...@@ -1089,10 +1274,15 @@ static int do_lookup(struct nameidata *nd, struct qstr *name, ...@@ -1089,10 +1274,15 @@ static int do_lookup(struct nameidata *nd, struct qstr *name,
nd->seq = seq; nd->seq = seq;
if (dentry->d_flags & DCACHE_OP_REVALIDATE) if (dentry->d_flags & DCACHE_OP_REVALIDATE)
goto need_revalidate; goto need_revalidate;
done2:
path->mnt = mnt; path->mnt = mnt;
path->dentry = dentry; path->dentry = dentry;
__follow_mount_rcu(nd, path, inode); if (likely(__follow_mount_rcu(nd, path, inode, false)))
} else { return 0;
if (nameidata_drop_rcu(nd))
return -ECHILD;
/* fallthru */
}
dentry = __d_lookup(parent, name); dentry = __d_lookup(parent, name);
if (!dentry) if (!dentry)
goto need_lookup; goto need_lookup;
...@@ -1102,9 +1292,10 @@ static int do_lookup(struct nameidata *nd, struct qstr *name, ...@@ -1102,9 +1292,10 @@ static int do_lookup(struct nameidata *nd, struct qstr *name,
done: done:
path->mnt = mnt; path->mnt = mnt;
path->dentry = dentry; path->dentry = dentry;
__follow_mount(path); err = follow_managed(path, nd->flags);
if (unlikely(err < 0))
return err;
*inode = path->dentry->d_inode; *inode = path->dentry->d_inode;
}
return 0; return 0;
need_lookup: need_lookup:
...@@ -1143,23 +1334,14 @@ static int do_lookup(struct nameidata *nd, struct qstr *name, ...@@ -1143,23 +1334,14 @@ static int do_lookup(struct nameidata *nd, struct qstr *name,
goto need_lookup; goto need_lookup;
if (IS_ERR(dentry)) if (IS_ERR(dentry))
goto fail; goto fail;
if (nd->flags & LOOKUP_RCU)
goto done2;
goto done; goto done;
fail: fail:
return PTR_ERR(dentry); return PTR_ERR(dentry);
} }
/*
* This is a temporary kludge to deal with "automount" symlinks; proper
* solution is to trigger them on follow_mount(), so that do_lookup()
* would DTRT. To be killed before 2.6.34-final.
*/
static inline int follow_on_final(struct inode *inode, unsigned lookup_flags)
{
return inode && unlikely(inode->i_op->follow_link) &&
((lookup_flags & LOOKUP_FOLLOW) || S_ISDIR(inode->i_mode));
}
/* /*
* Name resolution. * Name resolution.
* This is the basic name resolution function, turning a pathname into * This is the basic name resolution function, turning a pathname into
...@@ -1298,7 +1480,8 @@ static int link_path_walk(const char *name, struct nameidata *nd) ...@@ -1298,7 +1480,8 @@ static int link_path_walk(const char *name, struct nameidata *nd)
err = do_lookup(nd, &this, &next, &inode); err = do_lookup(nd, &this, &next, &inode);
if (err) if (err)
break; break;
if (follow_on_final(inode, lookup_flags)) { if (inode && unlikely(inode->i_op->follow_link) &&
(lookup_flags & LOOKUP_FOLLOW)) {
if (nameidata_dentry_drop_rcu_maybe(nd, next.dentry)) if (nameidata_dentry_drop_rcu_maybe(nd, next.dentry))
return -ECHILD; return -ECHILD;
BUG_ON(inode != next.dentry->d_inode); BUG_ON(inode != next.dentry->d_inode);
...@@ -2200,11 +2383,9 @@ static struct file *do_last(struct nameidata *nd, struct path *path, ...@@ -2200,11 +2383,9 @@ static struct file *do_last(struct nameidata *nd, struct path *path,
if (open_flag & O_EXCL) if (open_flag & O_EXCL)
goto exit_dput; goto exit_dput;
if (__follow_mount(path)) { error = follow_managed(path, nd->flags);
error = -ELOOP; if (error < 0)
if (open_flag & O_NOFOLLOW)
goto exit_dput; goto exit_dput;
}
error = -ENOENT; error = -ENOENT;
if (!path->dentry->d_inode) if (!path->dentry->d_inode)
...@@ -2353,8 +2534,7 @@ struct file *do_filp_open(int dfd, const char *pathname, ...@@ -2353,8 +2534,7 @@ struct file *do_filp_open(int dfd, const char *pathname,
struct inode *linki = link.dentry->d_inode; struct inode *linki = link.dentry->d_inode;
void *cookie; void *cookie;
error = -ELOOP; error = -ELOOP;
/* S_ISDIR part is a temporary automount kludge */ if (!(nd.flags & LOOKUP_FOLLOW))
if (!(nd.flags & LOOKUP_FOLLOW) && !S_ISDIR(linki->i_mode))
goto exit_dput; goto exit_dput;
if (count++ == 32) if (count++ == 32)
goto exit_dput; goto exit_dput;
...@@ -3413,6 +3593,7 @@ const struct inode_operations page_symlink_inode_operations = { ...@@ -3413,6 +3593,7 @@ const struct inode_operations page_symlink_inode_operations = {
}; };
EXPORT_SYMBOL(user_path_at); EXPORT_SYMBOL(user_path_at);
EXPORT_SYMBOL(follow_down_one);
EXPORT_SYMBOL(follow_down); EXPORT_SYMBOL(follow_down);
EXPORT_SYMBOL(follow_up); EXPORT_SYMBOL(follow_up);
EXPORT_SYMBOL(get_write_access); /* binfmt_aout */ EXPORT_SYMBOL(get_write_access); /* binfmt_aout */
......
...@@ -183,7 +183,7 @@ static inline void mnt_dec_count(struct vfsmount *mnt) ...@@ -183,7 +183,7 @@ static inline void mnt_dec_count(struct vfsmount *mnt)
unsigned int mnt_get_count(struct vfsmount *mnt) unsigned int mnt_get_count(struct vfsmount *mnt)
{ {
#ifdef CONFIG_SMP #ifdef CONFIG_SMP
unsigned int count = atomic_read(&mnt->mnt_longrefs); unsigned int count = 0;
int cpu; int cpu;
for_each_possible_cpu(cpu) { for_each_possible_cpu(cpu) {
...@@ -217,7 +217,7 @@ struct vfsmount *alloc_vfsmnt(const char *name) ...@@ -217,7 +217,7 @@ struct vfsmount *alloc_vfsmnt(const char *name)
if (!mnt->mnt_pcp) if (!mnt->mnt_pcp)
goto out_free_devname; goto out_free_devname;
atomic_set(&mnt->mnt_longrefs, 1); this_cpu_add(mnt->mnt_pcp->mnt_count, 1);
#else #else
mnt->mnt_count = 1; mnt->mnt_count = 1;
mnt->mnt_writers = 0; mnt->mnt_writers = 0;
...@@ -624,8 +624,11 @@ static void commit_tree(struct vfsmount *mnt) ...@@ -624,8 +624,11 @@ static void commit_tree(struct vfsmount *mnt)
BUG_ON(parent == mnt); BUG_ON(parent == mnt);
list_add_tail(&head, &mnt->mnt_list); list_add_tail(&head, &mnt->mnt_list);
list_for_each_entry(m, &head, mnt_list) list_for_each_entry(m, &head, mnt_list) {
m->mnt_ns = n; m->mnt_ns = n;
atomic_inc(&m->mnt_longterm);
}
list_splice(&head, n->list.prev); list_splice(&head, n->list.prev);
list_add_tail(&mnt->mnt_hash, mount_hashtable + list_add_tail(&mnt->mnt_hash, mount_hashtable +
...@@ -734,51 +737,30 @@ static inline void mntfree(struct vfsmount *mnt) ...@@ -734,51 +737,30 @@ static inline void mntfree(struct vfsmount *mnt)
deactivate_super(sb); deactivate_super(sb);
} }
#ifdef CONFIG_SMP static void mntput_no_expire(struct vfsmount *mnt)
static inline void __mntput(struct vfsmount *mnt, int longrefs)
{ {
if (!longrefs) {
put_again: put_again:
#ifdef CONFIG_SMP
br_read_lock(vfsmount_lock); br_read_lock(vfsmount_lock);
if (likely(atomic_read(&mnt->mnt_longrefs))) { if (likely(atomic_read(&mnt->mnt_longterm))) {
mnt_dec_count(mnt); mnt_dec_count(mnt);
br_read_unlock(vfsmount_lock); br_read_unlock(vfsmount_lock);
return; return;
} }
br_read_unlock(vfsmount_lock); br_read_unlock(vfsmount_lock);
} else {
BUG_ON(!atomic_read(&mnt->mnt_longrefs));
if (atomic_add_unless(&mnt->mnt_longrefs, -1, 1))
return;
}
br_write_lock(vfsmount_lock); br_write_lock(vfsmount_lock);
if (!longrefs)
mnt_dec_count(mnt); mnt_dec_count(mnt);
else
atomic_dec(&mnt->mnt_longrefs);
if (mnt_get_count(mnt)) { if (mnt_get_count(mnt)) {
br_write_unlock(vfsmount_lock); br_write_unlock(vfsmount_lock);
return; return;
} }
if (unlikely(mnt->mnt_pinned)) {
mnt_add_count(mnt, mnt->mnt_pinned + 1);
mnt->mnt_pinned = 0;
br_write_unlock(vfsmount_lock);
acct_auto_close_mnt(mnt);
goto put_again;
}
br_write_unlock(vfsmount_lock);
mntfree(mnt);
}
#else #else
static inline void __mntput(struct vfsmount *mnt, int longrefs)
{
put_again:
mnt_dec_count(mnt); mnt_dec_count(mnt);
if (likely(mnt_get_count(mnt))) if (likely(mnt_get_count(mnt)))
return; return;
br_write_lock(vfsmount_lock); br_write_lock(vfsmount_lock);
#endif
if (unlikely(mnt->mnt_pinned)) { if (unlikely(mnt->mnt_pinned)) {
mnt_add_count(mnt, mnt->mnt_pinned + 1); mnt_add_count(mnt, mnt->mnt_pinned + 1);
mnt->mnt_pinned = 0; mnt->mnt_pinned = 0;
...@@ -789,12 +771,6 @@ static inline void __mntput(struct vfsmount *mnt, int longrefs) ...@@ -789,12 +771,6 @@ static inline void __mntput(struct vfsmount *mnt, int longrefs)
br_write_unlock(vfsmount_lock); br_write_unlock(vfsmount_lock);
mntfree(mnt); mntfree(mnt);
} }
#endif
static void mntput_no_expire(struct vfsmount *mnt)
{
__mntput(mnt, 0);
}
void mntput(struct vfsmount *mnt) void mntput(struct vfsmount *mnt)
{ {
...@@ -802,7 +778,7 @@ void mntput(struct vfsmount *mnt) ...@@ -802,7 +778,7 @@ void mntput(struct vfsmount *mnt)
/* avoid cacheline pingpong, hope gcc doesn't get "smart" */ /* avoid cacheline pingpong, hope gcc doesn't get "smart" */
if (unlikely(mnt->mnt_expiry_mark)) if (unlikely(mnt->mnt_expiry_mark))
mnt->mnt_expiry_mark = 0; mnt->mnt_expiry_mark = 0;
__mntput(mnt, 0); mntput_no_expire(mnt);
} }
} }
EXPORT_SYMBOL(mntput); EXPORT_SYMBOL(mntput);
...@@ -815,33 +791,6 @@ struct vfsmount *mntget(struct vfsmount *mnt) ...@@ -815,33 +791,6 @@ struct vfsmount *mntget(struct vfsmount *mnt)
} }
EXPORT_SYMBOL(mntget); EXPORT_SYMBOL(mntget);
void mntput_long(struct vfsmount *mnt)
{
#ifdef CONFIG_SMP
if (mnt) {
/* avoid cacheline pingpong, hope gcc doesn't get "smart" */
if (unlikely(mnt->mnt_expiry_mark))
mnt->mnt_expiry_mark = 0;
__mntput(mnt, 1);
}
#else
mntput(mnt);
#endif
}
EXPORT_SYMBOL(mntput_long);
struct vfsmount *mntget_long(struct vfsmount *mnt)
{
#ifdef CONFIG_SMP
if (mnt)
atomic_inc(&mnt->mnt_longrefs);
return mnt;
#else
return mntget(mnt);
#endif
}
EXPORT_SYMBOL(mntget_long);
void mnt_pin(struct vfsmount *mnt) void mnt_pin(struct vfsmount *mnt)
{ {
br_write_lock(vfsmount_lock); br_write_lock(vfsmount_lock);
...@@ -1216,7 +1165,7 @@ void release_mounts(struct list_head *head) ...@@ -1216,7 +1165,7 @@ void release_mounts(struct list_head *head)
dput(dentry); dput(dentry);
mntput(m); mntput(m);
} }
mntput_long(mnt); mntput(mnt);
} }
} }
...@@ -1226,19 +1175,21 @@ void release_mounts(struct list_head *head) ...@@ -1226,19 +1175,21 @@ void release_mounts(struct list_head *head)
*/ */
void umount_tree(struct vfsmount *mnt, int propagate, struct list_head *kill) void umount_tree(struct vfsmount *mnt, int propagate, struct list_head *kill)
{ {
LIST_HEAD(tmp_list);
struct vfsmount *p; struct vfsmount *p;
for (p = mnt; p; p = next_mnt(p, mnt)) for (p = mnt; p; p = next_mnt(p, mnt))
list_move(&p->mnt_hash, kill); list_move(&p->mnt_hash, &tmp_list);
if (propagate) if (propagate)
propagate_umount(kill); propagate_umount(&tmp_list);
list_for_each_entry(p, kill, mnt_hash) { list_for_each_entry(p, &tmp_list, mnt_hash) {
list_del_init(&p->mnt_expire); list_del_init(&p->mnt_expire);
list_del_init(&p->mnt_list); list_del_init(&p->mnt_list);
__touch_mnt_namespace(p->mnt_ns); __touch_mnt_namespace(p->mnt_ns);
p->mnt_ns = NULL; p->mnt_ns = NULL;
atomic_dec(&p->mnt_longterm);
list_del_init(&p->mnt_child); list_del_init(&p->mnt_child);
if (p->mnt_parent != p) { if (p->mnt_parent != p) {
p->mnt_parent->mnt_ghosts++; p->mnt_parent->mnt_ghosts++;
...@@ -1246,6 +1197,7 @@ void umount_tree(struct vfsmount *mnt, int propagate, struct list_head *kill) ...@@ -1246,6 +1197,7 @@ void umount_tree(struct vfsmount *mnt, int propagate, struct list_head *kill)
} }
change_mnt_propagation(p, MS_PRIVATE); change_mnt_propagation(p, MS_PRIVATE);
} }
list_splice(&tmp_list, kill);
} }
static void shrink_submounts(struct vfsmount *mnt, struct list_head *umounts); static void shrink_submounts(struct vfsmount *mnt, struct list_head *umounts);
...@@ -1844,9 +1796,10 @@ static int do_move_mount(struct path *path, char *old_name) ...@@ -1844,9 +1796,10 @@ static int do_move_mount(struct path *path, char *old_name)
return err; return err;
down_write(&namespace_sem); down_write(&namespace_sem);
while (d_mountpoint(path->dentry) && err = follow_down(path, true);
follow_down(path)) if (err < 0)
; goto out;
err = -EINVAL; err = -EINVAL;
if (!check_mnt(path->mnt) || !check_mnt(old_path.mnt)) if (!check_mnt(path->mnt) || !check_mnt(old_path.mnt))
goto out; goto out;
...@@ -1924,15 +1877,14 @@ static int do_new_mount(struct path *path, char *type, int flags, ...@@ -1924,15 +1877,14 @@ static int do_new_mount(struct path *path, char *type, int flags,
if (IS_ERR(mnt)) if (IS_ERR(mnt))
return PTR_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 * 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 do_add_mount(struct vfsmount *newmnt, struct path *path, int mnt_flags)
int mnt_flags, struct list_head *fslist)
{ {
int err; int err;
...@@ -1940,9 +1892,10 @@ int do_add_mount(struct vfsmount *newmnt, struct path *path, ...@@ -1940,9 +1892,10 @@ int do_add_mount(struct vfsmount *newmnt, struct path *path,
down_write(&namespace_sem); down_write(&namespace_sem);
/* Something was mounted here while we slept */ /* Something was mounted here while we slept */
while (d_mountpoint(path->dentry) && err = follow_down(path, true);
follow_down(path)) if (err < 0)
; goto unlock;
err = -EINVAL; err = -EINVAL;
if (!(mnt_flags & MNT_SHRINKABLE) && !check_mnt(path->mnt)) if (!(mnt_flags & MNT_SHRINKABLE) && !check_mnt(path->mnt))
goto unlock; goto unlock;
...@@ -1961,19 +1914,45 @@ int do_add_mount(struct vfsmount *newmnt, struct path *path, ...@@ -1961,19 +1914,45 @@ int do_add_mount(struct vfsmount *newmnt, struct path *path,
if ((err = graft_tree(newmnt, path))) if ((err = graft_tree(newmnt, path)))
goto unlock; goto unlock;
if (fslist) /* add to the specified expiration list */
list_add_tail(&newmnt->mnt_expire, fslist);
up_write(&namespace_sem); up_write(&namespace_sem);
return 0; return 0;
unlock: unlock:
up_write(&namespace_sem); up_write(&namespace_sem);
mntput_long(newmnt); mntput(newmnt);
return err; 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 * process a list of expirable mountpoints with the intent of discarding any
...@@ -2262,6 +2241,20 @@ static struct mnt_namespace *alloc_mnt_ns(void) ...@@ -2262,6 +2241,20 @@ static struct mnt_namespace *alloc_mnt_ns(void)
return new_ns; return new_ns;
} }
void mnt_make_longterm(struct vfsmount *mnt)
{
atomic_inc(&mnt->mnt_longterm);
}
void mnt_make_shortterm(struct vfsmount *mnt)
{
if (atomic_add_unless(&mnt->mnt_longterm, -1, 1))
return;
br_write_lock(vfsmount_lock);
atomic_dec(&mnt->mnt_longterm);
br_write_unlock(vfsmount_lock);
}
/* /*
* Allocate a new namespace structure and populate it with contents * Allocate a new namespace structure and populate it with contents
* copied from the namespace of the passed in task structure. * copied from the namespace of the passed in task structure.
...@@ -2299,14 +2292,19 @@ static struct mnt_namespace *dup_mnt_ns(struct mnt_namespace *mnt_ns, ...@@ -2299,14 +2292,19 @@ static struct mnt_namespace *dup_mnt_ns(struct mnt_namespace *mnt_ns,
q = new_ns->root; q = new_ns->root;
while (p) { while (p) {
q->mnt_ns = new_ns; q->mnt_ns = new_ns;
atomic_inc(&q->mnt_longterm);
if (fs) { if (fs) {
if (p == fs->root.mnt) { if (p == fs->root.mnt) {
fs->root.mnt = mntget(q);
atomic_inc(&q->mnt_longterm);
mnt_make_shortterm(p);
rootmnt = p; rootmnt = p;
fs->root.mnt = mntget_long(q);
} }
if (p == fs->pwd.mnt) { if (p == fs->pwd.mnt) {
fs->pwd.mnt = mntget(q);
atomic_inc(&q->mnt_longterm);
mnt_make_shortterm(p);
pwdmnt = p; pwdmnt = p;
fs->pwd.mnt = mntget_long(q);
} }
} }
p = next_mnt(p, mnt_ns->root); p = next_mnt(p, mnt_ns->root);
...@@ -2315,9 +2313,9 @@ static struct mnt_namespace *dup_mnt_ns(struct mnt_namespace *mnt_ns, ...@@ -2315,9 +2313,9 @@ static struct mnt_namespace *dup_mnt_ns(struct mnt_namespace *mnt_ns,
up_write(&namespace_sem); up_write(&namespace_sem);
if (rootmnt) if (rootmnt)
mntput_long(rootmnt); mntput(rootmnt);
if (pwdmnt) if (pwdmnt)
mntput_long(pwdmnt); mntput(pwdmnt);
return new_ns; return new_ns;
} }
...@@ -2350,6 +2348,7 @@ struct mnt_namespace *create_mnt_ns(struct vfsmount *mnt) ...@@ -2350,6 +2348,7 @@ struct mnt_namespace *create_mnt_ns(struct vfsmount *mnt)
new_ns = alloc_mnt_ns(); new_ns = alloc_mnt_ns();
if (!IS_ERR(new_ns)) { if (!IS_ERR(new_ns)) {
mnt->mnt_ns = new_ns; mnt->mnt_ns = new_ns;
atomic_inc(&mnt->mnt_longterm);
new_ns->root = mnt; new_ns->root = mnt;
list_add(&new_ns->list, &new_ns->root->mnt_list); list_add(&new_ns->list, &new_ns->root->mnt_list);
} }
......
...@@ -970,7 +970,7 @@ int nfs_lookup_verify_inode(struct inode *inode, struct nameidata *nd) ...@@ -970,7 +970,7 @@ int nfs_lookup_verify_inode(struct inode *inode, struct nameidata *nd)
{ {
struct nfs_server *server = NFS_SERVER(inode); struct nfs_server *server = NFS_SERVER(inode);
if (test_bit(NFS_INO_MOUNTPOINT, &NFS_I(inode)->flags)) if (IS_AUTOMOUNT(inode))
return 0; return 0;
if (nd != NULL) { if (nd != NULL) {
/* VFS wants an on-the-wire revalidation */ /* VFS wants an on-the-wire revalidation */
...@@ -1173,6 +1173,7 @@ const struct dentry_operations nfs_dentry_operations = { ...@@ -1173,6 +1173,7 @@ const struct dentry_operations nfs_dentry_operations = {
.d_revalidate = nfs_lookup_revalidate, .d_revalidate = nfs_lookup_revalidate,
.d_delete = nfs_dentry_delete, .d_delete = nfs_dentry_delete,
.d_iput = nfs_dentry_iput, .d_iput = nfs_dentry_iput,
.d_automount = nfs_d_automount,
}; };
static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, struct nameidata *nd) static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, struct nameidata *nd)
...@@ -1246,6 +1247,7 @@ const struct dentry_operations nfs4_dentry_operations = { ...@@ -1246,6 +1247,7 @@ const struct dentry_operations nfs4_dentry_operations = {
.d_revalidate = nfs_open_revalidate, .d_revalidate = nfs_open_revalidate,
.d_delete = nfs_dentry_delete, .d_delete = nfs_dentry_delete,
.d_iput = nfs_dentry_iput, .d_iput = nfs_dentry_iput,
.d_automount = nfs_d_automount,
}; };
/* /*
......
...@@ -300,7 +300,7 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr) ...@@ -300,7 +300,7 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)
else else
inode->i_op = &nfs_mountpoint_inode_operations; inode->i_op = &nfs_mountpoint_inode_operations;
inode->i_fop = NULL; inode->i_fop = NULL;
set_bit(NFS_INO_MOUNTPOINT, &nfsi->flags); inode->i_flags |= S_AUTOMOUNT;
} }
} else if (S_ISLNK(inode->i_mode)) } else if (S_ISLNK(inode->i_mode))
inode->i_op = &nfs_symlink_inode_operations; inode->i_op = &nfs_symlink_inode_operations;
...@@ -1208,7 +1208,7 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr) ...@@ -1208,7 +1208,7 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)
/* Update the fsid? */ /* Update the fsid? */
if (S_ISDIR(inode->i_mode) && (fattr->valid & NFS_ATTR_FATTR_FSID) && if (S_ISDIR(inode->i_mode) && (fattr->valid & NFS_ATTR_FATTR_FSID) &&
!nfs_fsid_equal(&server->fsid, &fattr->fsid) && !nfs_fsid_equal(&server->fsid, &fattr->fsid) &&
!test_bit(NFS_INO_MOUNTPOINT, &nfsi->flags)) !IS_AUTOMOUNT(inode))
server->fsid = fattr->fsid; server->fsid = fattr->fsid;
/* /*
......
...@@ -252,6 +252,7 @@ extern char *nfs_path(const char *base, ...@@ -252,6 +252,7 @@ extern char *nfs_path(const char *base,
const struct dentry *droot, const struct dentry *droot,
const struct dentry *dentry, const struct dentry *dentry,
char *buffer, ssize_t buflen); char *buffer, ssize_t buflen);
extern struct vfsmount *nfs_d_automount(struct path *path);
/* getroot.c */ /* getroot.c */
extern struct dentry *nfs_get_root(struct super_block *, struct nfs_fh *); extern struct dentry *nfs_get_root(struct super_block *, struct nfs_fh *);
......
...@@ -97,9 +97,8 @@ char *nfs_path(const char *base, ...@@ -97,9 +97,8 @@ char *nfs_path(const char *base,
} }
/* /*
* nfs_follow_mountpoint - handle crossing a mountpoint on the server * nfs_d_automount - Handle crossing a mountpoint on the server
* @dentry - dentry of mountpoint * @path - The mountpoint
* @nd - nameidata info
* *
* When we encounter a mountpoint on the server, we want to set up * When we encounter a mountpoint on the server, we want to set up
* a mountpoint on the client too, to prevent inode numbers from * a mountpoint on the client too, to prevent inode numbers from
...@@ -109,87 +108,65 @@ char *nfs_path(const char *base, ...@@ -109,87 +108,65 @@ char *nfs_path(const char *base,
* situation, and that different filesystems may want to use * situation, and that different filesystems may want to use
* different security flavours. * different security flavours.
*/ */
static void * nfs_follow_mountpoint(struct dentry *dentry, struct nameidata *nd) struct vfsmount *nfs_d_automount(struct path *path)
{ {
struct vfsmount *mnt; struct vfsmount *mnt;
struct nfs_server *server = NFS_SERVER(dentry->d_inode); struct nfs_server *server = NFS_SERVER(path->dentry->d_inode);
struct dentry *parent; struct dentry *parent;
struct nfs_fh *fh = NULL; struct nfs_fh *fh = NULL;
struct nfs_fattr *fattr = NULL; struct nfs_fattr *fattr = NULL;
int err; int err;
dprintk("--> nfs_follow_mountpoint()\n"); dprintk("--> nfs_d_automount()\n");
err = -ESTALE; mnt = ERR_PTR(-ESTALE);
if (IS_ROOT(dentry)) if (IS_ROOT(path->dentry))
goto out_err; goto out_nofree;
err = -ENOMEM; mnt = ERR_PTR(-ENOMEM);
fh = nfs_alloc_fhandle(); fh = nfs_alloc_fhandle();
fattr = nfs_alloc_fattr(); fattr = nfs_alloc_fattr();
if (fh == NULL || fattr == NULL) if (fh == NULL || fattr == NULL)
goto out_err; goto out;
dprintk("%s: enter\n", __func__); dprintk("%s: enter\n", __func__);
dput(nd->path.dentry);
nd->path.dentry = dget(dentry);
/* Look it up again */ /* Look it up again to get its attributes */
parent = dget_parent(nd->path.dentry); parent = dget_parent(path->dentry);
err = server->nfs_client->rpc_ops->lookup(parent->d_inode, err = server->nfs_client->rpc_ops->lookup(parent->d_inode,
&nd->path.dentry->d_name, &path->dentry->d_name,
fh, fattr); fh, fattr);
dput(parent); dput(parent);
if (err != 0) if (err != 0) {
goto out_err; mnt = ERR_PTR(err);
goto out;
}
if (fattr->valid & NFS_ATTR_FATTR_V4_REFERRAL) if (fattr->valid & NFS_ATTR_FATTR_V4_REFERRAL)
mnt = nfs_do_refmount(nd->path.mnt, nd->path.dentry); mnt = nfs_do_refmount(path->mnt, path->dentry);
else else
mnt = nfs_do_submount(nd->path.mnt, nd->path.dentry, fh, mnt = nfs_do_submount(path->mnt, path->dentry, fh, fattr);
fattr);
err = PTR_ERR(mnt);
if (IS_ERR(mnt)) if (IS_ERR(mnt))
goto out_err; goto out;
mntget(mnt); dprintk("%s: done, success\n", __func__);
err = do_add_mount(mnt, &nd->path, nd->path.mnt->mnt_flags|MNT_SHRINKABLE, mntget(mnt); /* prevent immediate expiration */
&nfs_automount_list); mnt_set_expiry(mnt, &nfs_automount_list);
if (err < 0) {
mntput(mnt);
if (err == -EBUSY)
goto out_follow;
goto out_err;
}
path_put(&nd->path);
nd->path.mnt = mnt;
nd->path.dentry = dget(mnt->mnt_root);
schedule_delayed_work(&nfs_automount_task, nfs_mountpoint_expiry_timeout); schedule_delayed_work(&nfs_automount_task, nfs_mountpoint_expiry_timeout);
out: out:
nfs_free_fattr(fattr); nfs_free_fattr(fattr);
nfs_free_fhandle(fh); nfs_free_fhandle(fh);
dprintk("%s: done, returned %d\n", __func__, err); out_nofree:
dprintk("<-- nfs_follow_mountpoint() = %p\n", mnt);
dprintk("<-- nfs_follow_mountpoint() = %d\n", err); return mnt;
return ERR_PTR(err);
out_err:
path_put(&nd->path);
goto out;
out_follow:
while (d_mountpoint(nd->path.dentry) &&
follow_down(&nd->path))
;
err = 0;
goto out;
} }
const struct inode_operations nfs_mountpoint_inode_operations = { const struct inode_operations nfs_mountpoint_inode_operations = {
.follow_link = nfs_follow_mountpoint,
.getattr = nfs_getattr, .getattr = nfs_getattr,
}; };
const struct inode_operations nfs_referral_inode_operations = { const struct inode_operations nfs_referral_inode_operations = {
.follow_link = nfs_follow_mountpoint,
}; };
static void nfs_expire_automounts(struct work_struct *work) static void nfs_expire_automounts(struct work_struct *work)
......
...@@ -87,8 +87,9 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp, ...@@ -87,8 +87,9 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp,
.dentry = dget(dentry)}; .dentry = dget(dentry)};
int err = 0; int err = 0;
while (d_mountpoint(path.dentry) && follow_down(&path)) err = follow_down(&path, false);
; if (err < 0)
goto out;
exp2 = rqst_exp_get_by_name(rqstp, &path); exp2 = rqst_exp_get_by_name(rqstp, &path);
if (IS_ERR(exp2)) { if (IS_ERR(exp2)) {
......
...@@ -1292,7 +1292,7 @@ static int __init init_pipe_fs(void) ...@@ -1292,7 +1292,7 @@ static int __init init_pipe_fs(void)
static void __exit exit_pipe_fs(void) static void __exit exit_pipe_fs(void)
{ {
unregister_filesystem(&pipe_fs_type); unregister_filesystem(&pipe_fs_type);
mntput_long(pipe_mnt); mntput(pipe_mnt);
} }
fs_initcall(init_pipe_fs); fs_initcall(init_pipe_fs);
......
...@@ -75,11 +75,13 @@ int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat, ...@@ -75,11 +75,13 @@ int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat,
int error = -EINVAL; int error = -EINVAL;
int lookup_flags = 0; int lookup_flags = 0;
if ((flag & ~AT_SYMLINK_NOFOLLOW) != 0) if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT)) != 0)
goto out; goto out;
if (!(flag & AT_SYMLINK_NOFOLLOW)) if (!(flag & AT_SYMLINK_NOFOLLOW))
lookup_flags |= LOOKUP_FOLLOW; lookup_flags |= LOOKUP_FOLLOW;
if (flag & AT_NO_AUTOMOUNT)
lookup_flags |= LOOKUP_NO_AUTOMOUNT;
error = user_path_at(dfd, filename, lookup_flags, &path); error = user_path_at(dfd, filename, lookup_flags, &path);
if (error) if (error)
......
...@@ -1141,7 +1141,7 @@ static struct vfsmount *fs_set_subtype(struct vfsmount *mnt, const char *fstype) ...@@ -1141,7 +1141,7 @@ static struct vfsmount *fs_set_subtype(struct vfsmount *mnt, const char *fstype)
return mnt; return mnt;
err: err:
mntput_long(mnt); mntput(mnt);
return ERR_PTR(err); return ERR_PTR(err);
} }
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
#define AUTOFS_MIN_PROTO_VERSION 3 #define AUTOFS_MIN_PROTO_VERSION 3
#define AUTOFS_MAX_PROTO_VERSION 5 #define AUTOFS_MAX_PROTO_VERSION 5
#define AUTOFS_PROTO_SUBVERSION 1 #define AUTOFS_PROTO_SUBVERSION 2
/* Mask for expire behaviour */ /* Mask for expire behaviour */
#define AUTOFS_EXP_IMMEDIATE 1 #define AUTOFS_EXP_IMMEDIATE 1
......
...@@ -167,6 +167,8 @@ struct dentry_operations { ...@@ -167,6 +167,8 @@ struct dentry_operations {
void (*d_release)(struct dentry *); void (*d_release)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *); void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)(struct dentry *, char *, int); char *(*d_dname)(struct dentry *, char *, int);
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(struct dentry *, bool, bool);
} ____cacheline_aligned; } ____cacheline_aligned;
/* /*
...@@ -205,13 +207,18 @@ struct dentry_operations { ...@@ -205,13 +207,18 @@ struct dentry_operations {
#define DCACHE_CANT_MOUNT 0x0100 #define DCACHE_CANT_MOUNT 0x0100
#define DCACHE_GENOCIDE 0x0200 #define DCACHE_GENOCIDE 0x0200
#define DCACHE_MOUNTED 0x0400 /* is a mountpoint */
#define DCACHE_OP_HASH 0x1000 #define DCACHE_OP_HASH 0x1000
#define DCACHE_OP_COMPARE 0x2000 #define DCACHE_OP_COMPARE 0x2000
#define DCACHE_OP_REVALIDATE 0x4000 #define DCACHE_OP_REVALIDATE 0x4000
#define DCACHE_OP_DELETE 0x8000 #define DCACHE_OP_DELETE 0x8000
#define DCACHE_MOUNTED 0x10000 /* is a mountpoint */
#define DCACHE_NEED_AUTOMOUNT 0x20000 /* handle automount on this dir */
#define DCACHE_MANAGE_TRANSIT 0x40000 /* manage transit from this dirent */
#define DCACHE_MANAGED_DENTRY \
(DCACHE_MOUNTED|DCACHE_NEED_AUTOMOUNT|DCACHE_MANAGE_TRANSIT)
extern seqlock_t rename_lock; extern seqlock_t rename_lock;
static inline int dname_external(struct dentry *dentry) static inline int dname_external(struct dentry *dentry)
...@@ -399,7 +406,12 @@ static inline void dont_mount(struct dentry *dentry) ...@@ -399,7 +406,12 @@ static inline void dont_mount(struct dentry *dentry)
extern void dput(struct dentry *); extern void dput(struct dentry *);
static inline int d_mountpoint(struct dentry *dentry) static inline bool d_managed(struct dentry *dentry)
{
return dentry->d_flags & DCACHE_MANAGED_DENTRY;
}
static inline bool d_mountpoint(struct dentry *dentry)
{ {
return dentry->d_flags & DCACHE_MOUNTED; return dentry->d_flags & DCACHE_MOUNTED;
} }
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
#define AT_REMOVEDIR 0x200 /* Remove directory instead of #define AT_REMOVEDIR 0x200 /* Remove directory instead of
unlinking file. */ unlinking file. */
#define AT_SYMLINK_FOLLOW 0x400 /* Follow symbolic links. */ #define AT_SYMLINK_FOLLOW 0x400 /* Follow symbolic links. */
#define AT_NO_AUTOMOUNT 0x800 /* Suppress terminal automount traversal */
#ifdef __KERNEL__ #ifdef __KERNEL__
......
...@@ -242,6 +242,7 @@ struct inodes_stat_t { ...@@ -242,6 +242,7 @@ struct inodes_stat_t {
#define S_SWAPFILE 256 /* Do not truncate: swapon got its bmaps */ #define S_SWAPFILE 256 /* Do not truncate: swapon got its bmaps */
#define S_PRIVATE 512 /* Inode is fs-internal */ #define S_PRIVATE 512 /* Inode is fs-internal */
#define S_IMA 1024 /* Inode has an associated IMA struct */ #define S_IMA 1024 /* Inode has an associated IMA struct */
#define S_AUTOMOUNT 2048 /* Automount/referral quasi-directory */
/* /*
* Note that nosuid etc flags are inode-specific: setting some file-system * Note that nosuid etc flags are inode-specific: setting some file-system
...@@ -277,6 +278,7 @@ struct inodes_stat_t { ...@@ -277,6 +278,7 @@ struct inodes_stat_t {
#define IS_SWAPFILE(inode) ((inode)->i_flags & S_SWAPFILE) #define IS_SWAPFILE(inode) ((inode)->i_flags & S_SWAPFILE)
#define IS_PRIVATE(inode) ((inode)->i_flags & S_PRIVATE) #define IS_PRIVATE(inode) ((inode)->i_flags & S_PRIVATE)
#define IS_IMA(inode) ((inode)->i_flags & S_IMA) #define IS_IMA(inode) ((inode)->i_flags & S_IMA)
#define IS_AUTOMOUNT(inode) ((inode)->i_flags & S_AUTOMOUNT)
/* the read-only stuff doesn't really belong here, but any other place is /* the read-only stuff doesn't really belong here, but any other place is
probably as bad and I don't want to create yet another include file. */ probably as bad and I don't want to create yet another include file. */
......
...@@ -60,7 +60,7 @@ struct vfsmount { ...@@ -60,7 +60,7 @@ struct vfsmount {
struct super_block *mnt_sb; /* pointer to superblock */ struct super_block *mnt_sb; /* pointer to superblock */
#ifdef CONFIG_SMP #ifdef CONFIG_SMP
struct mnt_pcp __percpu *mnt_pcp; struct mnt_pcp __percpu *mnt_pcp;
atomic_t mnt_longrefs; atomic_t mnt_longterm; /* how many of the refs are longterm */
#else #else
int mnt_count; int mnt_count;
int mnt_writers; int mnt_writers;
...@@ -96,8 +96,6 @@ extern int mnt_clone_write(struct vfsmount *mnt); ...@@ -96,8 +96,6 @@ extern int mnt_clone_write(struct vfsmount *mnt);
extern void mnt_drop_write(struct vfsmount *mnt); extern void mnt_drop_write(struct vfsmount *mnt);
extern void mntput(struct vfsmount *mnt); extern void mntput(struct vfsmount *mnt);
extern struct vfsmount *mntget(struct vfsmount *mnt); extern struct vfsmount *mntget(struct vfsmount *mnt);
extern void mntput_long(struct vfsmount *mnt);
extern struct vfsmount *mntget_long(struct vfsmount *mnt);
extern void mnt_pin(struct vfsmount *mnt); extern void mnt_pin(struct vfsmount *mnt);
extern void mnt_unpin(struct vfsmount *mnt); extern void mnt_unpin(struct vfsmount *mnt);
extern int __mnt_is_readonly(struct vfsmount *mnt); extern int __mnt_is_readonly(struct vfsmount *mnt);
...@@ -110,12 +108,7 @@ extern struct vfsmount *vfs_kern_mount(struct file_system_type *type, ...@@ -110,12 +108,7 @@ extern struct vfsmount *vfs_kern_mount(struct file_system_type *type,
int flags, const char *name, int flags, const char *name,
void *data); void *data);
struct nameidata; extern void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list);
struct path;
extern int do_add_mount(struct vfsmount *newmnt, struct path *path,
int mnt_flags, struct list_head *fslist);
extern void mark_mounts_for_expiry(struct list_head *mounts); extern void mark_mounts_for_expiry(struct list_head *mounts);
extern dev_t name_to_dev_t(char *name); extern dev_t name_to_dev_t(char *name);
......
...@@ -45,6 +45,7 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND}; ...@@ -45,6 +45,7 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND};
* - ending slashes ok even for nonexistent files * - ending slashes ok even for nonexistent files
* - internal "there are more path components" flag * - internal "there are more path components" flag
* - dentry cache is untrusted; force a real lookup * - dentry cache is untrusted; force a real lookup
* - suppress terminal automount
*/ */
#define LOOKUP_FOLLOW 0x0001 #define LOOKUP_FOLLOW 0x0001
#define LOOKUP_DIRECTORY 0x0002 #define LOOKUP_DIRECTORY 0x0002
...@@ -53,6 +54,7 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND}; ...@@ -53,6 +54,7 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND};
#define LOOKUP_PARENT 0x0010 #define LOOKUP_PARENT 0x0010
#define LOOKUP_REVAL 0x0020 #define LOOKUP_REVAL 0x0020
#define LOOKUP_RCU 0x0040 #define LOOKUP_RCU 0x0040
#define LOOKUP_NO_AUTOMOUNT 0x0080
/* /*
* Intent data * Intent data
*/ */
...@@ -79,7 +81,8 @@ extern struct file *lookup_instantiate_filp(struct nameidata *nd, struct dentry ...@@ -79,7 +81,8 @@ extern struct file *lookup_instantiate_filp(struct nameidata *nd, struct dentry
extern struct dentry *lookup_one_len(const char *, struct dentry *, int); extern struct dentry *lookup_one_len(const char *, struct dentry *, int);
extern int follow_down(struct path *); extern int follow_down_one(struct path *);
extern int follow_down(struct path *, bool);
extern int follow_up(struct path *); extern int follow_up(struct path *);
extern struct dentry *lock_rename(struct dentry *, struct dentry *); extern struct dentry *lock_rename(struct dentry *, struct dentry *);
......
...@@ -215,7 +215,6 @@ struct nfs_inode { ...@@ -215,7 +215,6 @@ struct nfs_inode {
#define NFS_INO_ADVISE_RDPLUS (0) /* advise readdirplus */ #define NFS_INO_ADVISE_RDPLUS (0) /* advise readdirplus */
#define NFS_INO_STALE (1) /* possible stale inode */ #define NFS_INO_STALE (1) /* possible stale inode */
#define NFS_INO_ACL_LRU_SET (2) /* Inode is on the LRU list */ #define NFS_INO_ACL_LRU_SET (2) /* Inode is on the LRU list */
#define NFS_INO_MOUNTPOINT (3) /* inode is remote mountpoint */
#define NFS_INO_FLUSHING (4) /* inode is flushing out data */ #define NFS_INO_FLUSHING (4) /* inode is flushing out data */
#define NFS_INO_FSCACHE (5) /* inode can be cached by FS-Cache */ #define NFS_INO_FSCACHE (5) /* inode can be cached by FS-Cache */
#define NFS_INO_FSCACHE_LOCK (6) /* FS-Cache cookie management lock */ #define NFS_INO_FSCACHE_LOCK (6) /* FS-Cache cookie management lock */
......
...@@ -10,9 +10,7 @@ struct path { ...@@ -10,9 +10,7 @@ struct path {
}; };
extern void path_get(struct path *); extern void path_get(struct path *);
extern void path_get_long(struct path *);
extern void path_put(struct path *); extern void path_put(struct path *);
extern void path_put_long(struct path *);
static inline int path_equal(const struct path *path1, const struct path *path2) static inline int path_equal(const struct path *path1, const struct path *path2)
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment