From: "Q (Igor Mammedov)" <qwerty0987654321@mail.ru>
To: Christoph Hellwig <hch@infradead.org>, Steve French <smfrench@gmail.com>
Cc: linux-fsdevel <linux-fsdevel@vger.kernel.org>,
linux-cifs-client@lists.samba.org
Subject: [PATCH 1/4] [CIFS] Provides DFS shrinkable submounts functionality
Date: Thu, 17 Jan 2008 22:08:18 +0300 [thread overview]
Message-ID: <478FA7A2.9060200@mail.ru> (raw)
Christoph, thanks for your review. Here is the dfs patch 1/4 I rewrote taking
into account your comments.
Patch still depends on patch 1/3 that is to be fixed yet.
Signed-off-by: Igor Mammedov <niallain@gmail.com>
---
fs/cifs/Makefile | 2 +-
fs/cifs/cifs_dfs_ref.c | 376 ++++++++++++++++++++++++++++++++++++++++++++++++
fs/cifs/cifs_dfs_ref.h | 28 ++++
fs/cifs/cifsfs.c | 3 +
4 files changed, 408 insertions(+), 1 deletions(-)
create mode 100644 fs/cifs/cifs_dfs_ref.c
create mode 100644 fs/cifs/cifs_dfs_ref.h
diff --git a/fs/cifs/Makefile b/fs/cifs/Makefile
index 09898b8..6ba43fb 100644
--- a/fs/cifs/Makefile
+++ b/fs/cifs/Makefile
@@ -10,4 +10,4 @@ cifs-y := cifsfs.o cifssmb.o cifs_debug.o connect.o dir.o file.o inode.o \
cifs-$(CONFIG_CIFS_UPCALL) += cifs_spnego.o
-cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o
+cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o
diff --git a/fs/cifs/cifs_dfs_ref.c b/fs/cifs/cifs_dfs_ref.c
new file mode 100644
index 0000000..740d99d
--- /dev/null
+++ b/fs/cifs/cifs_dfs_ref.c
@@ -0,0 +1,376 @@
+/*
+ * Contains the CIFS DFS referral mounting routines used for handling
+ * traversal via DFS junction point
+ *
+ * Copyright (c) 2007 Igor Mammedov
+ * Author(s): Igor Mammedov (niallain@gmail.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/dcache.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/vfs.h>
+#include <linux/fs.h>
+#include "cifsglob.h"
+#include "cifsproto.h"
+#include "cifsfs.h"
+#include "dns_resolve.h"
+#include "cifs_dfs_ref.h"
+#include "cifs_debug.h"
+
+LIST_HEAD(cifs_dfs_automount_list);
+
+/*
+ * DFS functions
+*/
+
+void dfs_shrink_umount_helper(struct vfsmount *vfsmnt)
+{
+ mark_mounts_for_expiry(&cifs_dfs_automount_list);
+ mark_mounts_for_expiry(&cifs_dfs_automount_list);
+ shrink_submounts(vfsmnt, &cifs_dfs_automount_list);
+}
+
+/**
+ * cifs_get_share_name - extracts share name from UNC
+ * @node_name: pointer to UNC string
+ *
+ * Extracts sharename form full UNC.
+ * i.e. strips from UNC trailing path that is not part of share
+ * name and fixup missing '\' in the begining of DFS node refferal
+ * if neccessary.
+ * Returns pointer to share name on success or NULL on error.
+ * Caller is responcible for freeing returned string.
+ */
+static char *cifs_get_share_name(const char *node_name)
+{
+ int len;
+ char *UNC;
+ char *pSep;
+
+ len = strlen(node_name);
+ UNC = kmalloc(len+2 /*for term null and additional \ if it's missed */,
+ GFP_KERNEL);
+ if (!UNC)
+ return NULL;
+
+ /* get share name and server name */
+ if (node_name[1] != '\\') {
+ UNC[0] = '\\';
+ strncpy(UNC+1, node_name, len);
+ len++;
+ UNC[len] = 0;
+ } else {
+ strncpy(UNC, node_name, len);
+ UNC[len] = 0;
+ }
+
+ /* find server name end */
+ pSep = memchr(UNC+2, '\\', len-2);
+ if (!pSep) {
+ cERROR(1, ("%s: no server name end in node name: %s",
+ __FUNCTION__, node_name));
+ kfree(UNC);
+ return NULL;
+ }
+
+ /* find sharename end */
+ pSep++;
+ pSep = memchr(UNC+(pSep-UNC), '\\', len-(pSep-UNC));
+ if (!pSep) {
+ cERROR(1, ("%s:2 cant find share name in node name: %s",
+ __FUNCTION__, node_name));
+ kfree(UNC);
+ return NULL;
+ }
+ /* trim path up to sharename end
+ * * now we have share name in UNC */
+ *pSep = 0;
+
+ return UNC;
+}
+
+
+/**
+ * compose_mount_options - creates mount options for refferral
+ * @sb_mountdata: parent/root DFS mount options (template)
+ * @ref_unc: refferral server UNC
+ * @devname: pointer for saving device name
+ *
+ * creates mount options for submount based on template options sb_mountdata
+ * and replacing unc,ip,prefixpath options with ones we've got form ref_unc.
+ *
+ * Returns: pointer to new mount options or ERR_PTR.
+ * Caller is responcible for freeing retunrned value if it is not error.
+ */
+char *compose_mount_options(const char *sb_mountdata, const char *ref_unc,
+ char **devname)
+{
+ int rc;
+ char *mountdata;
+ int md_len;
+ char *tkn_e;
+ char *srvIP = NULL;
+ char sep = ',';
+ int off, noff;
+
+ if (sb_mountdata == NULL)
+ return ERR_PTR(-EINVAL);
+
+ *devname = cifs_get_share_name(ref_unc);
+ rc = dns_resolve_server_name_to_ip(*devname, &srvIP);
+ if (rc != 0) {
+ cERROR(1, ("%s: Failed to resolve server part of %s to IP",
+ __FUNCTION__, *devname));
+ mountdata = ERR_PTR(rc);
+ goto compose_mount_options_out;
+ }
+ md_len = strlen(sb_mountdata) + strlen(srvIP) + strlen(ref_unc) + 3;
+ mountdata = kzalloc(md_len+1, GFP_KERNEL);
+ if (mountdata == NULL) {
+ mountdata = ERR_PTR(-ENOMEM);
+ goto compose_mount_options_out;
+ }
+
+ /* copy all options except of unc,ip,prefixpath */
+ off = 0;
+ if (strncmp(sb_mountdata, "sep=", 4) == 0) {
+ sep = sb_mountdata[4];
+ strncpy(mountdata, sb_mountdata, 5);
+ off += 5;
+ }
+ while ((tkn_e = strchr(sb_mountdata+off, sep))) {
+ noff = (tkn_e - (sb_mountdata+off)) + 1;
+ if (strnicmp(sb_mountdata+off, "unc=", 4) == 0) {
+ off += noff;
+ continue;
+ }
+ if (strnicmp(sb_mountdata+off, "ip=", 3) == 0) {
+ off += noff;
+ continue;
+ }
+ if (strnicmp(sb_mountdata+off, "prefixpath=", 3) == 0) {
+ off += noff;
+ continue;
+ }
+ strncat(mountdata, sb_mountdata+off, noff);
+ off += noff;
+ }
+ strcat(mountdata, sb_mountdata+off);
+ mountdata[md_len] = '\0';
+
+ /* copy new IP and ref share name */
+ strcat(mountdata, ",ip=");
+ strcat(mountdata, srvIP);
+ strcat(mountdata, ",unc=");
+ strcat(mountdata, *devname);
+
+ /* find & copy prefixpath */
+ tkn_e = strchr(ref_unc+2, '\\');
+ if (tkn_e) {
+ tkn_e = strchr(tkn_e+1, '\\');
+ if (tkn_e) {
+ strcat(mountdata, ",prefixpath=");
+ strcat(mountdata, tkn_e);
+ }
+ }
+
+ /*cFYI(1,("%s: parent mountdata: %s", __FUNCTION__,sb_mountdata));*/
+ /*cFYI(1, ("%s: submount mountdata: %s", __FUNCTION__, mountdata ));*/
+
+compose_mount_options_out:
+ kfree(srvIP);
+ return mountdata;
+}
+
+
+struct vfsmount *cifs_dfs_do_refmount(const struct vfsmount *mnt_parent,
+ struct dentry *dentry, char *ref_unc)
+{
+ struct cifs_sb_info *cifs_sb;
+ struct vfsmount *mnt;
+ char *mountdata;
+ char *devname;
+
+ cifs_sb = CIFS_SB(dentry->d_inode->i_sb);
+ mountdata = compose_mount_options(cifs_sb->mountdata,
+ ref_unc, &devname);
+
+ if (IS_ERR(mountdata))
+ return (struct vfsmount *)mountdata;
+
+ mnt = vfs_kern_mount(&cifs_fs_type, 0, devname, mountdata);
+ kfree(mountdata);
+ kfree(devname);
+ return mnt;
+
+}
+
+static char *build_full_dfs_path_from_dentry(struct dentry *dentry)
+{
+ char *full_path = NULL;
+ char *search_path;
+ char *tmp_path;
+ size_t l_max_len;
+ struct cifs_sb_info *cifs_sb;
+
+ if (dentry->d_inode == NULL)
+ return NULL;
+
+ cifs_sb = CIFS_SB(dentry->d_inode->i_sb);
+
+ if (cifs_sb->tcon == NULL)
+ return NULL;
+
+ search_path = build_path_from_dentry(dentry);
+ if (search_path == NULL)
+ return NULL;
+
+ if (cifs_sb->tcon->Flags & SMB_SHARE_IS_IN_DFS) {
+ /* we should use full path name to correct working with DFS */
+ l_max_len = strnlen(cifs_sb->tcon->treeName, MAX_TREE_SIZE+1) +
+ strnlen(search_path, MAX_PATHCONF) + 1;
+ tmp_path = kmalloc(l_max_len, GFP_KERNEL);
+ if (tmp_path == NULL) {
+ kfree(search_path);
+ return NULL;
+ }
+ strncpy(tmp_path, cifs_sb->tcon->treeName, l_max_len);
+ strcat(tmp_path, search_path);
+ tmp_path[l_max_len-1] = 0;
+ full_path = tmp_path;
+ kfree(search_path);
+ } else {
+ full_path = search_path;
+ }
+ return full_path;
+}
+
+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, nd->mnt->mnt_flags, mntlist);
+ switch (err) {
+ case 0:
+ dput(nd->dentry);
+ mntput(nd->mnt);
+ nd->mnt = newmnt;
+ nd->dentry = dget(newmnt->mnt_root);
+ break;
+ case -EBUSY:
+ /* someone else made a mount here whilst we were busy */
+ while (d_mountpoint(nd->dentry) &&
+ follow_down(&nd->mnt, &nd->dentry))
+ ;
+ err = 0;
+ default:
+ mntput(newmnt);
+ break;
+ }
+ return err;
+}
+
+void dump_referral(const struct dfs_info3_param *ref)
+{
+ cFYI(1, ("DFS: ref path: %s", ref->path_name));
+ cFYI(1, ("DFS: node path: %s", ref->node_name));
+ cFYI(1, ("DFS: fl: %hd, srv_type: %hd", ref->flags, ref->server_type));
+ cFYI(1, ("DFS: ref_flags: %hd, path_consumed: %hd", ref->ref_flag,
+ ref->PathConsumed));
+}
+
+
+static void*
+cifs_dfs_follow_mountpoint(struct dentry *dentry, struct nameidata *nd)
+{
+ struct dfs_info3_param *referrals = NULL;
+ unsigned int num_referrals = 0;
+ struct cifs_sb_info *cifs_sb;
+ struct cifsSesInfo *ses;
+ char *full_path = NULL;
+ int xid, i;
+ int rc = 0;
+ struct vfsmount *mnt = ERR_PTR(-ENOENT);
+
+ cFYI(1, ("in %s", __FUNCTION__));
+ BUG_ON(IS_ROOT(dentry));
+
+ xid = GetXid();
+
+ dput(nd->dentry);
+ nd->dentry = dget(dentry);
+
+ cifs_sb = CIFS_SB(dentry->d_inode->i_sb);
+ ses = cifs_sb->tcon->ses;
+
+ if (!ses) {
+ rc = -EINVAL;
+ goto out_err;
+ }
+
+ full_path = build_full_dfs_path_from_dentry(dentry);
+ if (full_path == NULL) {
+ rc = -ENOMEM;
+ goto out_err;
+ }
+
+ rc = get_dfs_path(xid, ses , full_path, cifs_sb->local_nls,
+ &num_referrals, &referrals,
+ cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
+
+ for (i = 0; i < num_referrals; i++) {
+ dump_referral(referrals+i);
+ /* connect to a storage node */
+ if (referrals[i].flags & DFSREF_STORAGE_SERVER) {
+ int len;
+ len = strlen(referrals[i].node_name);
+ if (len < 2) {
+ cERROR(1, ("%s: Net Address path too short: %s",
+ __FUNCTION__, referrals[i].node_name));
+ rc = -EINVAL;
+ goto out_err;
+ }
+ mnt = cifs_dfs_do_refmount(nd->mnt, nd->dentry,
+ referrals[i].node_name);
+ cFYI(1, ("%s: cifs_dfs_do_refmount:%s , mnt:%p",
+ __FUNCTION__,
+ referrals[i].node_name, mnt));
+
+ /* complete mount procedure if we accured submount */
+ if (!IS_ERR(mnt))
+ break;
+ }
+ }
+
+ /* we need it cause for() above could exit without valid submount */
+ rc = PTR_ERR(mnt);
+ if (IS_ERR(mnt))
+ goto out_err;
+
+ nd->mnt->mnt_flags |= MNT_SHRINKABLE;
+ rc = add_mount_helper(mnt, nd, &cifs_dfs_automount_list);
+
+out:
+ FreeXid(xid);
+ free_dfs_info_array(referrals, num_referrals);
+ kfree(full_path);
+ cFYI(1, ("leaving %s" , __FUNCTION__));
+ return ERR_PTR(rc);
+out_err:
+ path_release(nd);
+ goto out;
+}
+
+struct inode_operations cifs_dfs_referral_inode_operations = {
+ .follow_link = cifs_dfs_follow_mountpoint,
+};
+
diff --git a/fs/cifs/cifs_dfs_ref.h b/fs/cifs/cifs_dfs_ref.h
new file mode 100644
index 0000000..794c6e4
--- /dev/null
+++ b/fs/cifs/cifs_dfs_ref.h
@@ -0,0 +1,28 @@
+/*
+ * Contains the CIFS DFS refferral mounting routines used for handling
+ * traversal via DFS junction point
+ *
+ * Copyright (c) 2007 Igor Mammedov
+ * Author(s): Igor Mammedov (niallain@gmail.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _CIFS_DFS_REF_H
+#define _CIFS_DFS_REF_H
+
+extern struct list_head cifs_dfs_automount_list;
+extern struct inode_operations cifs_dfs_referral_inode_operations;
+#ifdef CONFIG_CIFS_DFS_UPCALL
+void dfs_shrink_umount_helper(struct vfsmount *vfsmnt);
+
+#else /* stubs section */
+
+#define dfs_shrink_umount_helper(vfsmnt)
+
+#endif /* CONFIG_CIFS_DFS_UPCALL */
+
+#endif /* _CIFS_DFS_REF_H */
diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
index 93e1078..1c6e5e9 100644
--- a/fs/cifs/cifsfs.c
+++ b/fs/cifs/cifsfs.c
@@ -46,6 +46,7 @@
#include <linux/key-type.h>
#include "dns_resolve.h"
#include "cifs_spnego.h"
+#include "cifs_dfs_ref.h"
#define CIFS_MAGIC_NUMBER 0xFF534D42 /* the first four bytes of SMB PDUs */
#ifdef CONFIG_CIFS_QUOTA
@@ -471,6 +472,8 @@ static void cifs_umount_begin(struct vfsmount *vfsmnt, int flags)
struct cifs_sb_info *cifs_sb;
struct cifsTconInfo *tcon;
+ dfs_shrink_umount_helper(vfsmnt);
+
if (!(flags & MNT_FORCE))
return;
cifs_sb = CIFS_SB(vfsmnt->mnt_sb);
--
1.5.3.7
reply other threads:[~2008-01-17 19:08 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=478FA7A2.9060200@mail.ru \
--to=qwerty0987654321@mail.ru \
--cc=hch@infradead.org \
--cc=linux-cifs-client@lists.samba.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=smfrench@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.