diff mbox

[RFC] cifs: revalidate directories instiantiated via FIND_* in order to handle DFS referrals

Message ID 1358276491-4835-1-git-send-email-jlayton@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jeff Layton Jan. 15, 2013, 7:01 p.m. UTC
We've had a long-standing problem with DFS referral points. CIFS servers
generally try to make them look like directories in FIND_FIRST/NEXT
responses. When you go to try to do a FIND_FIRST on them though, the
server will then (correctly) return STATUS_PATH_NOT_COVERED. Mostly this
manifests as spurious EREMOTE errors back to userland.

This patch attempts to fix this by marking directories that are
discovered via FIND_FIRST/NEXT for revaldiation. When the lookup code
runs across them again, we'll reissue a QPathInfo against them and that
will make it chase the referral properly.

There is some performance penalty involved here and no I haven't
measured it -- it'll be highly dependent upon the workload and contents
of the mounted share. To try and mitigate that though, the code only
marks the inode for revalidation when it's possible to run across a DFS
referral. i.e.: when the kernel has DFS support built in and the share
is "in DFS".

Signed-off-by: Jeff Layton <jlayton@redhat.com>
---
 fs/cifs/readdir.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

Comments

Steve French Jan. 15, 2013, 7:34 p.m. UTC | #1
On Tue, Jan 15, 2013 at 1:01 PM, Jeff Layton <jlayton@redhat.com> wrote:
> We've had a long-standing problem with DFS referral points. CIFS servers
> generally try to make them look like directories in FIND_FIRST/NEXT
> responses. When you go to try to do a FIND_FIRST on them though, the
> server will then (correctly) return STATUS_PATH_NOT_COVERED. Mostly this
> manifests as spurious EREMOTE errors back to userland.

Why would we ever return STATUS_PATH_NOT_COVERED (converted to
EREMOTE)?  If we do a find first on a directory and get status path
not covered - why don't we simply chase the DFS referral then rather
than greatly slow down findfirst?
Jeff Layton Jan. 15, 2013, 7:47 p.m. UTC | #2
On Tue, 15 Jan 2013 13:34:07 -0600
Steve French <smfrench@gmail.com> wrote:

> On Tue, Jan 15, 2013 at 1:01 PM, Jeff Layton <jlayton@redhat.com> wrote:
> > We've had a long-standing problem with DFS referral points. CIFS servers
> > generally try to make them look like directories in FIND_FIRST/NEXT
> > responses. When you go to try to do a FIND_FIRST on them though, the
> > server will then (correctly) return STATUS_PATH_NOT_COVERED. Mostly this
> > manifests as spurious EREMOTE errors back to userland.
> 
> Why would we ever return STATUS_PATH_NOT_COVERED (converted to
> EREMOTE)?  If we do a find first on a directory and get status path
> not covered - why don't we simply chase the DFS referral then rather
> than greatly slow down findfirst?
> 

Note that I marked this RFC since I'm not 100% sold on the approach...

This doesn't slow down FIND_FIRST, it just means that we revalidate the
inode before trying to use it. The scenario goes something like this:

Suppose we have a share like this:

    //server/share

...and under the top level of that share is a DFS referral "dfslink".
Now, we go and do a readdir on the top level directory and that causes
a FIND_FIRST to go out on the wire. The response has "dfslink" in it,
but it looks just like a directory. So, we instantiate a new dentry for
it with a directory inode, based on the info in the FIND_FIRST response.

Now, we go to try and do a readdir() on "dfslink". We do an opendir()
which doesn't cause any sort of call to go out onto the wire because we
just instantiated the dentry. So, we end up doing a FIND_FIRST
against //server/share/dfslink, and get back STATUS_PATH_NOT_COVERED.

This patch fixes that by forcing the client to revalidate the dentry at
lookup time (i.e. when the directory is opened in the above example).
The lookup will be redone (via QPathInfo) and we'll end up chasing the
referral as expected.
Sachin Prabhu Feb. 7, 2013, 5:42 p.m. UTC | #3
We had a problem reported by a user which I found is fixed by this
patch.

The customer reported a crash where they were able to cd into a DFS
share where the target doesn't exist. The machine eventually crashes
when the lookup code attempts to access the inode->i_ops->lookup which
is set to NULL for DFS shares.

I was able to recreate this problem and noticed that this also affects a
standard DFS share. To recreate the problem, simply mount the DFS share
on a mount point and run the following commands.

# mount -t cifs -o
username=user,password=mysecret //vm140-52/DFS_Public/ /mnt
# cd /mnt
# ls; cd n1/

Where n1 is a DFS share.

1) The first command 'ls', results in the readdir system call which ends
up calling FIND_FIRST SMB command to the server. 
2) An inode is initiated but no S_AUTOMOUNT flag is set at this stage.
3) The dentry initiated doesn't contain the DCACHE_NEED_AUTOMOUNT flag.
4) When we cd into this directory immediately, the dentry returned was
just created(ie. not yet timed out) and is valid.
5) We then called  for this dentry. Since we do not have the
DCACHE_NEED_AUTOMOUNT flag set in dentry->d_flags, we do not trigger the
automount. 
6) After we have done the chdir, By attempting a few lookups, we
eventually hit a problem because the inode functions in inode->i_ops are
all set to NULL.

I have tested with this patch and it does work since we force a
QUERY_PATH_LOOKUP on all directories returned in FIND_FIRST. The
downside ofcourse is increase in the QUERY_PATH_LOOKUPs.

Sachin Prabhu

On Tue, 2013-01-15 at 14:01 -0500, Jeff Layton wrote:
> We've had a long-standing problem with DFS referral points. CIFS servers
> generally try to make them look like directories in FIND_FIRST/NEXT
> responses. When you go to try to do a FIND_FIRST on them though, the
> server will then (correctly) return STATUS_PATH_NOT_COVERED. Mostly this
> manifests as spurious EREMOTE errors back to userland.
> 
> This patch attempts to fix this by marking directories that are
> discovered via FIND_FIRST/NEXT for revaldiation. When the lookup code
> runs across them again, we'll reissue a QPathInfo against them and that
> will make it chase the referral properly.
> 
> There is some performance penalty involved here and no I haven't
> measured it -- it'll be highly dependent upon the workload and contents
> of the mounted share. To try and mitigate that though, the code only
> marks the inode for revalidation when it's possible to run across a DFS
> referral. i.e.: when the kernel has DFS support built in and the share
> is "in DFS".
> 
> Signed-off-by: Jeff Layton <jlayton@redhat.com>
> ---
>  fs/cifs/readdir.c | 24 ++++++++++++++++++++++++
>  1 file changed, 24 insertions(+)
> 
> diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c
> index cdd6ff4..5fc9b4a 100644
> --- a/fs/cifs/readdir.c
> +++ b/fs/cifs/readdir.c
> @@ -128,6 +128,22 @@ out:
>  	dput(dentry);
>  }
>  
> +/*
> + * Is it possible that this directory might turn out to be a DFS referral
> + * once we go to try and use it?
> + */
> +static bool
> +cifs_dfs_is_possible(struct cifs_sb_info *cifs_sb)
> +{
> +#ifdef CONFIG_CIFS_DFS_UPCALL
> +	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
> +
> +	if (tcon->Flags & SMB_SHARE_IS_IN_DFS)
> +		return true;
> +#endif
> +	return false;
> +}
> +
>  static void
>  cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
>  {
> @@ -137,6 +153,14 @@ cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
>  	if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
>  		fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
>  		fattr->cf_dtype = DT_DIR;
> +		/*
> +		 * CIFS servers generally make DFS referrals look like
> +		 * directories in FIND_* responses. Since there's no way to
> +		 * tell the difference, we must revalidate directory inodes
> +		 * before trying to use them.
> +		 */
> +		if (cifs_dfs_is_possible(cifs_sb))
> +			fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
>  	} else {
>  		fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
>  		fattr->cf_dtype = DT_REG;



--
To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Martijn de Gouw April 23, 2013, 9:59 a.m. UTC | #4
Hi Jeff,

On 01/15/2013 08:01 PM, Jeff Layton wrote:
> We've had a long-standing problem with DFS referral points. CIFS servers
> generally try to make them look like directories in FIND_FIRST/NEXT
> responses. When you go to try to do a FIND_FIRST on them though, the
> server will then (correctly) return STATUS_PATH_NOT_COVERED. Mostly this
> manifests as spurious EREMOTE errors back to userland.
>
> This patch attempts to fix this by marking directories that are
> discovered via FIND_FIRST/NEXT for revaldiation. When the lookup code
> runs across them again, we'll reissue a QPathInfo against them and that
> will make it chase the referral properly.
>
> There is some performance penalty involved here and no I haven't
> measured it -- it'll be highly dependent upon the workload and contents
> of the mounted share. To try and mitigate that though, the code only
> marks the inode for revalidation when it's possible to run across a DFS
> referral. i.e.: when the kernel has DFS support built in and the share
> is "in DFS".

Are you planning to get this patch into mainline?

I've applied this patch to 3.7.7, otherwise we where not able to mount 
dfs shares. It's running for over 2 months now on our servers and we 
have not seen any problems whatsoever.

Regards, Martijn
Jeff Layton April 23, 2013, 11 a.m. UTC | #5
On Tue, 23 Apr 2013 11:59:45 +0200
Martijn de Gouw <martijn.de.gouw@prodrive.nl> wrote:

> Hi Jeff,
> 
> On 01/15/2013 08:01 PM, Jeff Layton wrote:
> > We've had a long-standing problem with DFS referral points. CIFS servers
> > generally try to make them look like directories in FIND_FIRST/NEXT
> > responses. When you go to try to do a FIND_FIRST on them though, the
> > server will then (correctly) return STATUS_PATH_NOT_COVERED. Mostly this
> > manifests as spurious EREMOTE errors back to userland.
> >
> > This patch attempts to fix this by marking directories that are
> > discovered via FIND_FIRST/NEXT for revaldiation. When the lookup code
> > runs across them again, we'll reissue a QPathInfo against them and that
> > will make it chase the referral properly.
> >
> > There is some performance penalty involved here and no I haven't
> > measured it -- it'll be highly dependent upon the workload and contents
> > of the mounted share. To try and mitigate that though, the code only
> > marks the inode for revalidation when it's possible to run across a DFS
> > referral. i.e.: when the kernel has DFS support built in and the share
> > is "in DFS".
> 
> Are you planning to get this patch into mainline?
> 
> I've applied this patch to 3.7.7, otherwise we where not able to mount 
> dfs shares. It's running for over 2 months now on our servers and we 
> have not seen any problems whatsoever.
> 
> Regards, Martijn
> 

I was hoping that it would go into the upcoming 3.10 merge window, but
Steve won't take it as he's concerned about the performance impact. The
current story is "maybe 3.11".

One possibility to mitigate the performance impact might be to switch
the default to "nodfs". IOW, make it so that you consciously have to
mount with "-o dfs" in order to get it. That's a departure from how it
works today though.
diff mbox

Patch

diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c
index cdd6ff4..5fc9b4a 100644
--- a/fs/cifs/readdir.c
+++ b/fs/cifs/readdir.c
@@ -128,6 +128,22 @@  out:
 	dput(dentry);
 }
 
+/*
+ * Is it possible that this directory might turn out to be a DFS referral
+ * once we go to try and use it?
+ */
+static bool
+cifs_dfs_is_possible(struct cifs_sb_info *cifs_sb)
+{
+#ifdef CONFIG_CIFS_DFS_UPCALL
+	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
+
+	if (tcon->Flags & SMB_SHARE_IS_IN_DFS)
+		return true;
+#endif
+	return false;
+}
+
 static void
 cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
 {
@@ -137,6 +153,14 @@  cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
 	if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
 		fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
 		fattr->cf_dtype = DT_DIR;
+		/*
+		 * CIFS servers generally make DFS referrals look like
+		 * directories in FIND_* responses. Since there's no way to
+		 * tell the difference, we must revalidate directory inodes
+		 * before trying to use them.
+		 */
+		if (cifs_dfs_is_possible(cifs_sb))
+			fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
 	} else {
 		fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
 		fattr->cf_dtype = DT_REG;