Re: [PATCH 1/2] vfs: make real_lookup do dentry revalidation with i_mutex held

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



> Latest here works OK.
> 
> I haven't finished checking yet but it looks like the patch below works 
> OK. I started with a 2.6.29 build with your two patches but it was a 
> little broken so I fell back to a Fedora 2.6.27 based kernel without the
> two revalidate pacthes to debug it. So I still need to test the result 
> against 2.6.29 again. I also don't have any real way to test for the three 
> process race we discussed where the revalidate isn't followed by a 
> ->lookup() but with both of your patches applied that shouldn't be a 
> problem (as we discussed).
> 
> I've not run checkpatch.pl against the patch either at this stage.

That's good news...
 
> There is a further issue and that is regarding the autofs module.
> 
> I can't see updating autofs for this being practical (although I haven't 
> actually looked yet). I suspect quite a bit of work would be needed. The 
> fact is that autofs isn't used much any more and it really should be 
> replaced with the autofs4 module at some point. But that's a fairly tricky 
> exercise and will likely cause some user space breakage. It will require 
> an updated module-init-tools to add "alais autofs4 autofs" for modprobe 
> backward compatibility and will break for any explicit checks for the 
> presence of the "autofs4" module.

Hmm.  Well, I assume autofs needs to work properly before this gets 
changed, though, right?  Should I see what I can do with it?  I took a 
quick look, and I don't think it will take too much to make it behave.  
It looks like the main thing is to make the lookup call to try_fill_dentry 
return any existing dentry in place of the one the vfs provides.

sage


> Suddenly this fairly simple change gets bigger than Ben Hur, sorry!
> Thoughts?
> 
> Ian
> 
> autofs4 - always use lookup for lookup
> 
> From: Ian Kent <raven@xxxxxxxxxx>
> 
> We need to be able to hold the directory mutex during revalidate in
> some cases. To do this autofs needs to send ->d_revalidate() to lookup
> if the mutex is held since it cannot tell if it is the lock owner
> or if the lock is held by another path of execution.
> ---
> 
>  fs/autofs4/autofs_i.h |   15 ---
>  fs/autofs4/root.c     |  229 ++++++++++++++++++++++++++++---------------------
>  2 files changed, 131 insertions(+), 113 deletions(-)
> 
> 
> diff --git a/fs/autofs4/autofs_i.h b/fs/autofs4/autofs_i.h
> index b7ff33c..1fd9183 100644
> --- a/fs/autofs4/autofs_i.h
> +++ b/fs/autofs4/autofs_i.h
> @@ -95,6 +95,7 @@ struct autofs_info {
>  
>  #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_REVALIDATE	(1<<2) /* revalidate sent this mount request */
>  
>  struct autofs_wait_queue {
>  	wait_queue_head_t queue;
> @@ -156,20 +157,6 @@ static inline int autofs4_oz_mode(struct autofs_sb_info *sbi) {
>  	return sbi->catatonic || task_pgrp_nr(current) == sbi->oz_pgrp;
>  }
>  
> -/* Does a dentry have some pending activity? */
> -static inline int autofs4_ispending(struct dentry *dentry)
> -{
> -	struct autofs_info *inf = autofs4_dentry_ino(dentry);
> -
> -	if (dentry->d_flags & DCACHE_AUTOFS_PENDING)
> -		return 1;
> -
> -	if (inf->flags & AUTOFS_INF_EXPIRING)
> -		return 1;
> -
> -	return 0;
> -}
> -
>  static inline void autofs4_copy_atime(struct file *src, struct file *dst)
>  {
>  	dst->f_path.dentry->d_inode->i_atime =
> diff --git a/fs/autofs4/root.c b/fs/autofs4/root.c
> index e8c55d2..90c1c87 100644
> --- a/fs/autofs4/root.c
> +++ b/fs/autofs4/root.c
> @@ -72,6 +72,14 @@ const struct inode_operations autofs4_dir_inode_operations = {
>  	.rmdir		= autofs4_dir_rmdir,
>  };
>  
> +static unsigned int autofs4_need_lookup(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)
>  {
>  	struct dentry *dentry = file->f_path.dentry;
> @@ -103,7 +111,7 @@ out:
>  	return dcache_dir_open(inode, file);
>  }
>  
> -static int try_to_fill_dentry(struct dentry *dentry, int flags)
> +static int try_to_fill_dentry(struct dentry *dentry)
>  {
>  	struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
>  	struct autofs_info *ino = autofs4_dentry_ino(dentry);
> @@ -116,55 +124,18 @@ static int try_to_fill_dentry(struct dentry *dentry, int flags)
>  	 * 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(&dentry->d_lock);
> -			dentry->d_flags &= ~DCACHE_AUTOFS_PENDING;
> -			spin_unlock(&dentry->d_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 (dentry->d_flags & DCACHE_AUTOFS_PENDING ||
> -			flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) ||
> -			current->link_count) {
> -		DPRINTK("waiting for mount name=%.*s",
> -			dentry->d_name.len, dentry->d_name.name);
> +	DPRINTK("waiting for mount name=%.*s",
> +		 dentry->d_name.len, dentry->d_name.name);
>  
> -		spin_lock(&dentry->d_lock);
> -		dentry->d_flags |= DCACHE_AUTOFS_PENDING;
> -		spin_unlock(&dentry->d_lock);
> -		status = autofs4_wait(sbi, dentry, NFY_MOUNT);
> +	status = autofs4_wait(sbi, dentry, NFY_MOUNT);
>  
> -		DPRINTK("mount done status=%d", status);
> -
> -		if (status) {
> -			spin_lock(&dentry->d_lock);
> -			dentry->d_flags &= ~DCACHE_AUTOFS_PENDING;
> -			spin_unlock(&dentry->d_lock);
> -			return status;
> -		}
> -	}
> +	DPRINTK("mount done status=%d", status);
>  
>  	/* Initialize expiry counter after successful mount */
> -	if (ino)
> +	if (ino && !status)
>  		ino->last_used = jiffies;
>  
> -	spin_lock(&dentry->d_lock);
> -	dentry->d_flags &= ~DCACHE_AUTOFS_PENDING;
> -	spin_unlock(&dentry->d_lock);
> -
> -	return 0;
> +	return status;
>  }
>  
>  /* For autofs direct mounts the follow link triggers the mount */
> @@ -216,7 +187,16 @@ static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd)
>  	    (!d_mountpoint(dentry) && __simple_empty(dentry))) {
>  		spin_unlock(&dcache_lock);
>  
> -		status = try_to_fill_dentry(dentry, 0);
> +		spin_lock(&dentry->d_lock);
> +		dentry->d_flags |= DCACHE_AUTOFS_PENDING;
> +		spin_unlock(&dentry->d_lock);
> +
> +		status = try_to_fill_dentry(dentry);
> +
> +		spin_lock(&dentry->d_lock);
> +		dentry->d_flags &= ~DCACHE_AUTOFS_PENDING;
> +		spin_unlock(&dentry->d_lock);
> +
>  		if (status)
>  			goto out_error;
>  
> @@ -255,42 +235,55 @@ static int autofs4_revalidate(struct dentry *dentry, struct nameidata *nd)
>  {
>  	struct inode *dir = dentry->d_parent->d_inode;
>  	struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb);
> -	int oz_mode = autofs4_oz_mode(sbi);
> -	int flags = nd ? nd->flags : 0;
> -	int status = 1;
> -
> -	/* Pending dentry */
> -	spin_lock(&sbi->fs_lock);
> -	if (autofs4_ispending(dentry)) {
> -		/* The daemon never causes a mount to trigger */
> -		spin_unlock(&sbi->fs_lock);
> +	unsigned int flags = nd ? nd->flags : 0;
> +	int status;
>  
> -		if (oz_mode)
> -			return 1;
> +	DPRINTK("name = %.*s oz_mode = %d flags = %d",
> +		dentry->d_name.len, dentry->d_name.name,
> +		autofs4_oz_mode(sbi), nd ? nd->flags : 0);
>  
> -		/*
> -		 * 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;
> +	/* The daemon never causes a mount to trigger */
> +	if (autofs4_oz_mode(sbi))
> +		return 1;
>  
> +	spin_lock(&dentry->d_lock);
> +	if (dentry->d_inode == NULL || dentry->d_flags & DCACHE_AUTOFS_PENDING) {
> +		unsigned int lock_held = mutex_is_locked(&dir->i_mutex);
> +		unsigned int pending = dentry->d_flags & DCACHE_AUTOFS_PENDING;
> +		spin_unlock(&dentry->d_lock);
>  		/*
> -		 * A zero status is success otherwise we have a
> -		 * negative error code.
> +		 * If the directory mutex lock is held we must hide the
> +		 * dentry and proceed to ->lookup() to wait for mount
> +		 * completion since we cannot know whether the lock was
> +		 * taken by this code path or is held by another.
> +		 * Otherwise we can just wait for mount completion.
>  		 */
> -		status = try_to_fill_dentry(dentry, flags);
> -		if (status == 0)
> -			return 1;
> +		status = 0;
> +		if (lock_held) {
> +			/* Already pending, send to ->lookup() */
> +			d_drop(dentry);
> +		} else {
> +			/*
> +			 * The mutex isn't held so if there's a pending
> +			 * mount we wait. If not then we are on the tail
> +			 * end of a mount fail, invalidate the dentry and
> +			 * return true so we don't go to lookup again.
> +			 */
> +			if (pending) {
> +				if (autofs4_expire_wait(dentry) != -EAGAIN) {
> +					status = try_to_fill_dentry(dentry);
> +					if (!status)
> +						status = 1;
> +				}
> +			} else {
> +				d_invalidate(dentry);
> +				status = 1;
> +			}
> +		}
>  
>  		return status;
>  	}
> -	spin_unlock(&sbi->fs_lock);
> -
> -	/* Negative dentry.. invalidate if "old" */
> -	if (dentry->d_inode == NULL)
> -		return 0;
> +	spin_unlock(&dentry->d_lock);
>  
>  	/* Check for a non-mountpoint directory with no contents */
>  	spin_lock(&dcache_lock);
> @@ -299,19 +292,24 @@ static int autofs4_revalidate(struct dentry *dentry, struct nameidata *nd)
>  	    __simple_empty(dentry)) {
>  		DPRINTK("dentry=%p %.*s, emptydir",
>  			 dentry, dentry->d_name.len, dentry->d_name.name);
> -		spin_unlock(&dcache_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;
> +		/* Always send new mount requests to lookup */
> +		status = 1;
> +		spin_lock(&sbi->fs_lock);
> +		if (autofs4_need_lookup(flags) || current->link_count) {
> +			struct autofs_info *ino = autofs4_dentry_ino(dentry);
> +			ino->flags |= AUTOFS_INF_REVALIDATE;
> +			spin_lock(&sbi->lookup_lock);
> +			if (list_empty(&ino->active))
> +				list_add(&ino->active, &sbi->active_list);
> +			spin_unlock(&sbi->lookup_lock);
> +			spin_lock(&dentry->d_lock);
> +			__d_drop(dentry);
> +			spin_unlock(&dentry->d_lock);
> +			status = 0;
> +		}
> +		spin_unlock(&sbi->fs_lock);
> +		spin_unlock(&dcache_lock);
>  
>  		return status;
>  	}
> @@ -471,6 +469,7 @@ static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, s
>  	struct autofs_info *ino;
>  	struct dentry *expiring, *unhashed;
>  	int oz_mode;
> +	int status = 0;
>  
>  	DPRINTK("name = %.*s",
>  		dentry->d_name.len, dentry->d_name.name);
> @@ -486,9 +485,18 @@ static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, s
>  		 current->pid, task_pgrp_nr(current), sbi->catatonic, oz_mode);
>  
>  	unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name);
> -	if (unhashed)
> +	if (unhashed) {
>  		dentry = unhashed;
> -	else {
> +		/*
> +		 * For requests from revalidate we need to rehash the dentry
> +		 * here as we don't end up calling mkdir().
> +		 */
> +		spin_lock(&sbi->fs_lock);
> +		ino = autofs4_dentry_ino(dentry);
> +		if (ino->flags & AUTOFS_INF_REVALIDATE)
> +			d_rehash(dentry);
> +		spin_unlock(&sbi->fs_lock);
> +	} else {
>  		/*
>  		 * Mark the dentry incomplete but don't hash it. We do this
>  		 * to serialize our inode creation operations (symlink and
> @@ -522,6 +530,10 @@ static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, s
>  	}
>  
>  	if (!oz_mode) {
> +		spin_lock(&dentry->d_lock);
> +		dentry->d_flags |= DCACHE_AUTOFS_PENDING;
> +		spin_unlock(&dentry->d_lock);
> +
>  		mutex_unlock(&dir->i_mutex);
>  		expiring = autofs4_lookup_expiring(sbi,
>  						   dentry->d_parent,
> @@ -541,19 +553,33 @@ static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, s
>  			dput(expiring);
>  		}
>  
> +		status = try_to_fill_dentry(dentry);
> +
> +		mutex_lock(&dir->i_mutex);
> +
>  		spin_lock(&dentry->d_lock);
> -		dentry->d_flags |= DCACHE_AUTOFS_PENDING;
> +		dentry->d_flags &= ~DCACHE_AUTOFS_PENDING;
>  		spin_unlock(&dentry->d_lock);
> -		if (dentry->d_op && dentry->d_op->d_revalidate)
> -			(dentry->d_op->d_revalidate)(dentry, nd);
> -		mutex_lock(&dir->i_mutex);
> +
> +		/*
> +		 * If we got a new lookup request from revaidate, remove
> +		 * it from the active queue and rehash the dentry.
> +		 */
> +		spin_lock(&sbi->fs_lock);
> +		if (ino->flags & AUTOFS_INF_REVALIDATE) {
> +			spin_lock(&sbi->lookup_lock);
> +			if (!list_empty(&ino->active))
> +				list_del_init(&ino->active);
> +			spin_unlock(&sbi->lookup_lock);
> +		}
> +		spin_unlock(&sbi->fs_lock);
>  	}
>  
>  	/*
> -	 * If we are still pending, check if we had to handle
> +	 * If we had a mount fail, check if we had to handle
>  	 * a signal. If so we can force a restart..
>  	 */
> -	if (dentry->d_flags & DCACHE_AUTOFS_PENDING) {
> +	if (status) {
>  		/* See if we were interrupted */
>  		if (signal_pending(current)) {
>  			sigset_t *sigset = &current->pending.signal;
> @@ -565,11 +591,6 @@ static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, s
>  			    return ERR_PTR(-ERESTARTNOINTR);
>  			}
>  		}
> -		if (!oz_mode) {
> -			spin_lock(&dentry->d_lock);
> -			dentry->d_flags &= ~DCACHE_AUTOFS_PENDING;
> -			spin_unlock(&dentry->d_lock);
> -		}
>  	}
>  
>  	/*
> @@ -599,7 +620,17 @@ static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, s
>  		return dentry;
>  	}
>  
> -	if (unhashed)
> +	/*
> +	 * If we had a mount failure, return status to user space.
> +	 * If the mount succeeded and we used a dentry from the
> +	 * active queue return it.
> +	 */
> +	if (status) {
> +		dentry = ERR_PTR(status);
> +		if (unhashed)
> +			dput(unhashed);
> +		return dentry;
> +	} else if (unhashed)
>  		return unhashed;
>  
>  	return NULL;
> --
> To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]
  Powered by Linux