summaryrefslogtreecommitdiffstats
path: root/fs/cifs
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2020-12-17 17:41:37 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2020-12-17 17:41:37 -0800
commite13300bdaa68f5487000e66baed1ff69bcb510bf (patch)
tree6eb13723d607e46416e10dfc077acc22a8b12419 /fs/cifs
parentd64c6f96ba86bd8b97ed8d6762a8c8cc1770d214 (diff)
parentafee4410bc6c50e1422c5a45d633ad0e478ea960 (diff)
Merge tag '5.11-rc-smb3' of git://git.samba.org/sfrench/cifs-2.6
Pull cifs updates from Steve French: "The largest part are for support of the newer mount API which has been needed for cifs/smb3 mounts for a long time due to the new API's better handling of remount, and better error reporting. There are three additional small cleanup patches for this being tested, that are not included yet. This series also includes addition of support for the SMB3 witness protocol which can provide important notifications from the server to client on server address or export or network changes. This can be useful for example in order to be notified before the failure - when a server's IP address changes (in the future it will allow us to support server notifications of when a share is moved). It also includes three patches for stable e.g. some that better handle some confusing error messages during session establishment" * tag '5.11-rc-smb3' of git://git.samba.org/sfrench/cifs-2.6: (55 commits) cifs: update internal module version number cifs: Fix support for remount when not changing rsize/wsize cifs: handle "guest" mount parameter cifs: correct four aliased mount parms to allow use of previous names cifs: Tracepoints and logs for tracing credit changes. cifs: fix use after free in cifs_smb3_do_mount() cifs: fix rsize/wsize to be negotiated values cifs: Fix some error pointers handling detected by static checker smb3: remind users that witness protocol is experimental cifs: update super_operations to show_devname cifs: fix uninitialized variable in smb3_fs_context_parse_param cifs: update mnt_cifs_flags during reconfigure cifs: move update of flags into a separate function cifs: remove ctx argument from cifs_setup_cifs_sb cifs: do not allow changing posix_paths during remount cifs: uncomplicate printing the iocharset parameter cifs: don't create a temp nls in cifs_setup_ipc cifs: simplify handling of cifs_sb/ctx->local_nls cifs: we do not allow changing username/password/unc/... during remount cifs: add initial reconfigure support ...
Diffstat (limited to 'fs/cifs')
-rw-r--r--fs/cifs/Kconfig25
-rw-r--r--fs/cifs/Makefile4
-rw-r--r--fs/cifs/cache.c24
-rw-r--r--fs/cifs/cifs_debug.c13
-rw-r--r--fs/cifs/cifs_dfs_ref.c19
-rw-r--r--fs/cifs/cifs_fs_sb.h12
-rw-r--r--fs/cifs/cifs_swn.c694
-rw-r--r--fs/cifs/cifs_swn.h25
-rw-r--r--fs/cifs/cifsacl.c238
-rw-r--r--fs/cifs/cifsacl.h4
-rw-r--r--fs/cifs/cifsencrypt.c5
-rw-r--r--fs/cifs/cifsfs.c174
-rw-r--r--fs/cifs/cifsfs.h6
-rw-r--r--fs/cifs/cifsglob.h106
-rw-r--r--fs/cifs/cifspdu.h2
-rw-r--r--fs/cifs/cifsproto.h39
-rw-r--r--fs/cifs/connect.c2125
-rw-r--r--fs/cifs/dfs_cache.c115
-rw-r--r--fs/cifs/dfs_cache.h2
-rw-r--r--fs/cifs/dir.c7
-rw-r--r--fs/cifs/file.c26
-rw-r--r--fs/cifs/fs_context.c1529
-rw-r--r--fs/cifs/fs_context.h225
-rw-r--r--fs/cifs/fscache.c1
-rw-r--r--fs/cifs/fscache.h1
-rw-r--r--fs/cifs/inode.c74
-rw-r--r--fs/cifs/misc.c5
-rw-r--r--fs/cifs/netlink.c89
-rw-r--r--fs/cifs/netlink.h16
-rw-r--r--fs/cifs/readdir.c19
-rw-r--r--fs/cifs/sess.c71
-rw-r--r--fs/cifs/smb1ops.c13
-rw-r--r--fs/cifs/smb2misc.c31
-rw-r--r--fs/cifs/smb2ops.c63
-rw-r--r--fs/cifs/smb2pdu.c7
-rw-r--r--fs/cifs/smb2pdu.h14
-rw-r--r--fs/cifs/smbdirect.c1
-rw-r--r--fs/cifs/trace.h4
-rw-r--r--fs/cifs/transport.c15
-rw-r--r--fs/cifs/unc.c71
40 files changed, 3560 insertions, 2354 deletions
diff --git a/fs/cifs/Kconfig b/fs/cifs/Kconfig
index 604f65f4b6c5..fe03cbdae959 100644
--- a/fs/cifs/Kconfig
+++ b/fs/cifs/Kconfig
@@ -60,9 +60,9 @@ config CIFS_STATS2
Enabling this option will allow more detailed statistics on SMB
request timing to be displayed in /proc/fs/cifs/DebugData and also
allow optional logging of slow responses to dmesg (depending on the
- value of /proc/fs/cifs/cifsFYI, see fs/cifs/README for more details).
- These additional statistics may have a minor effect on performance
- and memory utilization.
+ value of /proc/fs/cifs/cifsFYI). See Documentation/admin-guide/cifs/usage.rst
+ for more details. These additional statistics may have a minor effect
+ on performance and memory utilization.
Unless you are a developer or are doing network performance analysis
or tuning, say N.
@@ -102,10 +102,10 @@ config CIFS_WEAK_PW_HASH
is enabled in the kernel build, LANMAN authentication will not be
used automatically. At runtime LANMAN mounts are disabled but
can be set to required (or optional) either in
- /proc/fs/cifs (see fs/cifs/README for more detail) or via an
- option on the mount command. This support is disabled by
- default in order to reduce the possibility of a downgrade
- attack.
+ /proc/fs/cifs (see Documentation/admin-guide/cifs/usage.rst for
+ more detail) or via an option on the mount command. This support
+ is disabled by default in order to reduce the possibility of a
+ downgrade attack.
If unsure, say N.
@@ -190,6 +190,17 @@ config CIFS_DFS_UPCALL
servers if their addresses change or for implicit mounts of
DFS junction points. If unsure, say Y.
+config CIFS_SWN_UPCALL
+ bool "SWN feature support"
+ depends on CIFS
+ help
+ The Service Witness Protocol (SWN) is used to get notifications
+ from a highly available server of resource state changes. This
+ feature enables an upcall mechanism for CIFS which contacts a
+ userspace daemon to establish the DCE/RPC connection to retrieve
+ the cluster available interfaces and resource change notifications.
+ If unsure, say Y.
+
config CIFS_NFSD_EXPORT
bool "Allow nfsd to export CIFS file system"
depends on CIFS && BROKEN
diff --git a/fs/cifs/Makefile b/fs/cifs/Makefile
index cd17d0e50f2a..5213b20843b5 100644
--- a/fs/cifs/Makefile
+++ b/fs/cifs/Makefile
@@ -8,7 +8,7 @@ obj-$(CONFIG_CIFS) += cifs.o
cifs-y := trace.o cifsfs.o cifssmb.o cifs_debug.o connect.o dir.o file.o \
inode.o link.o misc.o netmisc.o smbencrypt.o transport.o asn1.o \
cifs_unicode.o nterr.o cifsencrypt.o \
- readdir.o ioctl.o sess.o export.o smb1ops.o winucase.o \
+ readdir.o ioctl.o sess.o export.o smb1ops.o unc.o winucase.o \
smb2ops.o smb2maperror.o smb2transport.o \
smb2misc.o smb2pdu.o smb2inode.o smb2file.o cifsacl.o fs_context.o
@@ -18,6 +18,8 @@ cifs-$(CONFIG_CIFS_UPCALL) += cifs_spnego.o
cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o dfs_cache.o
+cifs-$(CONFIG_CIFS_SWN_UPCALL) += netlink.o cifs_swn.o
+
cifs-$(CONFIG_CIFS_FSCACHE) += fscache.o cache.o
cifs-$(CONFIG_CIFS_SMB_DIRECT) += smbdirect.o
diff --git a/fs/cifs/cache.c b/fs/cifs/cache.c
index 0f2adecb94f2..488fe0ffc1ef 100644
--- a/fs/cifs/cache.c
+++ b/fs/cifs/cache.c
@@ -53,30 +53,6 @@ const struct fscache_cookie_def cifs_fscache_server_index_def = {
.type = FSCACHE_COOKIE_TYPE_INDEX,
};
-char *extract_sharename(const char *treename)
-{
- const char *src;
- char *delim, *dst;
- int len;
-
- /* skip double chars at the beginning */
- src = treename + 2;
-
- /* share name is always preceded by '\\' now */
- delim = strchr(src, '\\');
- if (!delim)
- return ERR_PTR(-EINVAL);
- delim++;
- len = strlen(delim);
-
- /* caller has to free the memory */
- dst = kstrndup(delim, len, GFP_KERNEL);
- if (!dst)
- return ERR_PTR(-ENOMEM);
-
- return dst;
-}
-
static enum
fscache_checkaux cifs_fscache_super_check_aux(void *cookie_netfs_data,
const void *data,
diff --git a/fs/cifs/cifs_debug.c b/fs/cifs/cifs_debug.c
index 53588d7517b4..b231dcf1d1f9 100644
--- a/fs/cifs/cifs_debug.c
+++ b/fs/cifs/cifs_debug.c
@@ -23,6 +23,9 @@
#ifdef CONFIG_CIFS_SMB_DIRECT
#include "smbdirect.h"
#endif
+#ifdef CONFIG_CIFS_SWN_UPCALL
+#include "cifs_swn.h"
+#endif
void
cifs_dump_mem(char *label, void *data, int length)
@@ -115,6 +118,10 @@ static void cifs_debug_tcon(struct seq_file *m, struct cifs_tcon *tcon)
seq_printf(m, " POSIX Extensions");
if (tcon->ses->server->ops->dump_share_caps)
tcon->ses->server->ops->dump_share_caps(m, tcon);
+#ifdef CONFIG_CIFS_SWN_UPCALL
+ if (tcon->use_witness)
+ seq_puts(m, " Witness");
+#endif
if (tcon->need_reconnect)
seq_puts(m, "\tDISCONNECTED ");
@@ -262,6 +269,9 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
seq_printf(m, ",XATTR");
#endif
seq_printf(m, ",ACL");
+#ifdef CONFIG_CIFS_SWN_UPCALL
+ seq_puts(m, ",WITNESS");
+#endif
seq_putc(m, '\n');
seq_printf(m, "CIFSMaxBufSize: %d\n", CIFSMaxBufSize);
seq_printf(m, "Active VFS Requests: %d\n", GlobalTotalActiveXid);
@@ -462,6 +472,9 @@ skip_rdma:
spin_unlock(&cifs_tcp_ses_lock);
seq_putc(m, '\n');
+#ifdef CONFIG_CIFS_SWN_UPCALL
+ cifs_swn_dump(m);
+#endif
/* BB add code to dump additional info such as TCP session info now */
return 0;
}
diff --git a/fs/cifs/cifs_dfs_ref.c b/fs/cifs/cifs_dfs_ref.c
index cc3ada12848d..e4c6ae47a796 100644
--- a/fs/cifs/cifs_dfs_ref.c
+++ b/fs/cifs/cifs_dfs_ref.c
@@ -23,6 +23,7 @@
#include "cifs_debug.h"
#include "cifs_unicode.h"
#include "dfs_cache.h"
+#include "fs_context.h"
static LIST_HEAD(cifs_dfs_automount_list);
@@ -124,7 +125,6 @@ cifs_build_devname(char *nodename, const char *prepath)
* @sb_mountdata: parent/root DFS mount options (template)
* @fullpath: full path in UNC format
* @ref: optional server's referral
- * @devname: optional 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.
@@ -134,8 +134,7 @@ cifs_build_devname(char *nodename, const char *prepath)
*/
char *cifs_compose_mount_options(const char *sb_mountdata,
const char *fullpath,
- const struct dfs_info3_param *ref,
- char **devname)
+ const struct dfs_info3_param *ref)
{
int rc;
char *name;
@@ -232,10 +231,7 @@ char *cifs_compose_mount_options(const char *sb_mountdata,
strcat(mountdata, "ip=");
strcat(mountdata, srvIP);
- if (devname)
- *devname = name;
- else
- kfree(name);
+ kfree(name);
/*cifs_dbg(FYI, "%s: parent mountdata: %s\n", __func__, sb_mountdata);*/
/*cifs_dbg(FYI, "%s: submount mountdata: %s\n", __func__, mountdata );*/
@@ -258,6 +254,7 @@ compose_mount_options_err:
* to perform failover in case we failed to connect to the first target in the
* referral.
*
+ * @mntpt: directory entry for the path we are trying to automount
* @cifs_sb: parent/root superblock
* @fullpath: full path in UNC format
*/
@@ -275,9 +272,13 @@ static struct vfsmount *cifs_dfs_do_mount(struct dentry *mntpt,
convert_delimiter(devname, '/');
+ /* TODO: change to call fs_context_for_mount(), fill in context directly, call fc_mount */
+
+ /* See afs_mntpt_do_automount in fs/afs/mntpt.c for an example */
+
/* strip first '\' from fullpath */
- mountdata = cifs_compose_mount_options(cifs_sb->mountdata,
- fullpath + 1, NULL, NULL);
+ mountdata = cifs_compose_mount_options(cifs_sb->ctx->mount_options,
+ fullpath + 1, NULL);
if (IS_ERR(mountdata)) {
kfree(devname);
return (struct vfsmount *)mountdata;
diff --git a/fs/cifs/cifs_fs_sb.h b/fs/cifs/cifs_fs_sb.h
index 6e7c4427369d..aa77edc12212 100644
--- a/fs/cifs/cifs_fs_sb.h
+++ b/fs/cifs/cifs_fs_sb.h
@@ -61,19 +61,9 @@ struct cifs_sb_info {
spinlock_t tlink_tree_lock;
struct tcon_link *master_tlink;
struct nls_table *local_nls;
- unsigned int bsize;
- unsigned int rsize;
- unsigned int wsize;
- unsigned long actimeo; /* attribute cache timeout (jiffies) */
+ struct smb3_fs_context *ctx;
atomic_t active;
- kuid_t mnt_uid;
- kgid_t mnt_gid;
- kuid_t mnt_backupuid;
- kgid_t mnt_backupgid;
- umode_t mnt_file_mode;
- umode_t mnt_dir_mode;
unsigned int mnt_cifs_flags;
- char *mountdata; /* options received at mount time or via DFS refs */
struct delayed_work prune_tlinks;
struct rcu_head rcu;
diff --git a/fs/cifs/cifs_swn.c b/fs/cifs/cifs_swn.c
new file mode 100644
index 000000000000..c594e588a8b5
--- /dev/null
+++ b/fs/cifs/cifs_swn.c
@@ -0,0 +1,694 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Witness Service client for CIFS
+ *
+ * Copyright (c) 2020 Samuel Cabrero <scabrero@suse.de>
+ */
+
+#include <linux/kref.h>
+#include <net/genetlink.h>
+#include <uapi/linux/cifs/cifs_netlink.h>
+
+#include "cifs_swn.h"
+#include "cifsglob.h"
+#include "cifsproto.h"
+#include "fscache.h"
+#include "cifs_debug.h"
+#include "netlink.h"
+
+static DEFINE_IDR(cifs_swnreg_idr);
+static DEFINE_MUTEX(cifs_swnreg_idr_mutex);
+
+struct cifs_swn_reg {
+ int id;
+ struct kref ref_count;
+
+ const char *net_name;
+ const char *share_name;
+ bool net_name_notify;
+ bool share_name_notify;
+ bool ip_notify;
+
+ struct cifs_tcon *tcon;
+};
+
+static int cifs_swn_auth_info_krb(struct cifs_tcon *tcon, struct sk_buff *skb)
+{
+ int ret;
+
+ ret = nla_put_flag(skb, CIFS_GENL_ATTR_SWN_KRB_AUTH);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int cifs_swn_auth_info_ntlm(struct cifs_tcon *tcon, struct sk_buff *skb)
+{
+ int ret;
+
+ if (tcon->ses->user_name != NULL) {
+ ret = nla_put_string(skb, CIFS_GENL_ATTR_SWN_USER_NAME, tcon->ses->user_name);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (tcon->ses->password != NULL) {
+ ret = nla_put_string(skb, CIFS_GENL_ATTR_SWN_PASSWORD, tcon->ses->password);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (tcon->ses->domainName != NULL) {
+ ret = nla_put_string(skb, CIFS_GENL_ATTR_SWN_DOMAIN_NAME, tcon->ses->domainName);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Sends a register message to the userspace daemon based on the registration.
+ * The authentication information to connect to the witness service is bundled
+ * into the message.
+ */
+static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)
+{
+ struct sk_buff *skb;
+ struct genlmsghdr *hdr;
+ enum securityEnum authtype;
+ struct sockaddr_storage *addr;
+ int ret;
+
+ skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (skb == NULL) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ hdr = genlmsg_put(skb, 0, 0, &cifs_genl_family, 0, CIFS_GENL_CMD_SWN_REGISTER);
+ if (hdr == NULL) {
+ ret = -ENOMEM;
+ goto nlmsg_fail;
+ }
+
+ ret = nla_put_u32(skb, CIFS_GENL_ATTR_SWN_REGISTRATION_ID, swnreg->id);
+ if (ret < 0)
+ goto nlmsg_fail;
+
+ ret = nla_put_string(skb, CIFS_GENL_ATTR_SWN_NET_NAME, swnreg->net_name);
+ if (ret < 0)
+ goto nlmsg_fail;
+
+ ret = nla_put_string(skb, CIFS_GENL_ATTR_SWN_SHARE_NAME, swnreg->share_name);
+ if (ret < 0)
+ goto nlmsg_fail;
+
+ /*
+ * If there is an address stored use it instead of the server address, because we are
+ * in the process of reconnecting to it after a share has been moved or we have been
+ * told to switch to it (client move message). In these cases we unregister from the
+ * server address and register to the new address when we receive the notification.
+ */
+ if (swnreg->tcon->ses->server->use_swn_dstaddr)
+ addr = &swnreg->tcon->ses->server->swn_dstaddr;
+ else
+ addr = &swnreg->tcon->ses->server->dstaddr;
+
+ ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage), addr);
+ if (ret < 0)
+ goto nlmsg_fail;
+
+ if (swnreg->net_name_notify) {
+ ret = nla_put_flag(skb, CIFS_GENL_ATTR_SWN_NET_NAME_NOTIFY);
+ if (ret < 0)
+ goto nlmsg_fail;
+ }
+
+ if (swnreg->share_name_notify) {
+ ret = nla_put_flag(skb, CIFS_GENL_ATTR_SWN_SHARE_NAME_NOTIFY);
+ if (ret < 0)
+ goto nlmsg_fail;
+ }
+
+ if (swnreg->ip_notify) {
+ ret = nla_put_flag(skb, CIFS_GENL_ATTR_SWN_IP_NOTIFY);
+ if (ret < 0)
+ goto nlmsg_fail;
+ }
+
+ authtype = cifs_select_sectype(swnreg->tcon->ses->server, swnreg->tcon->ses->sectype);
+ switch (authtype) {
+ case Kerberos:
+ ret = cifs_swn_auth_info_krb(swnreg->tcon, skb);
+ if (ret < 0) {
+ cifs_dbg(VFS, "%s: Failed to get kerberos auth info: %d\n", __func__, ret);
+ goto nlmsg_fail;
+ }
+ break;
+ case LANMAN:
+ case NTLM:
+ case NTLMv2:
+ case RawNTLMSSP:
+ ret = cifs_swn_auth_info_ntlm(swnreg->tcon, skb);
+ if (ret < 0) {
+ cifs_dbg(VFS, "%s: Failed to get NTLM auth info: %d\n", __func__, ret);
+ goto nlmsg_fail;
+ }
+ break;
+ default:
+ cifs_dbg(VFS, "%s: secType %d not supported!\n", __func__, authtype);
+ ret = -EINVAL;
+ goto nlmsg_fail;
+ }
+
+ genlmsg_end(skb, hdr);
+ genlmsg_multicast(&cifs_genl_family, skb, 0, CIFS_GENL_MCGRP_SWN, GFP_ATOMIC);
+
+ cifs_dbg(FYI, "%s: Message to register for network name %s with id %d sent\n", __func__,
+ swnreg->net_name, swnreg->id);
+
+ return 0;
+
+nlmsg_fail:
+ genlmsg_cancel(skb, hdr);
+ nlmsg_free(skb);
+fail:
+ return ret;
+}
+
+/*
+ * Sends an uregister message to the userspace daemon based on the registration
+ */
+static int cifs_swn_send_unregister_message(struct cifs_swn_reg *swnreg)
+{
+ struct sk_buff *skb;
+ struct genlmsghdr *hdr;
+ int ret;
+
+ skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (skb == NULL)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(skb, 0, 0, &cifs_genl_family, 0, CIFS_GENL_CMD_SWN_UNREGISTER);
+ if (hdr == NULL) {
+ ret = -ENOMEM;
+ goto nlmsg_fail;
+ }
+
+ ret = nla_put_u32(skb, CIFS_GENL_ATTR_SWN_REGISTRATION_ID, swnreg->id);
+ if (ret < 0)
+ goto nlmsg_fail;
+
+ ret = nla_put_string(skb, CIFS_GENL_ATTR_SWN_NET_NAME, swnreg->net_name);
+ if (ret < 0)
+ goto nlmsg_fail;
+
+ ret = nla_put_string(skb, CIFS_GENL_ATTR_SWN_SHARE_NAME, swnreg->share_name);
+ if (ret < 0)
+ goto nlmsg_fail;
+
+ ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage),
+ &swnreg->tcon->ses->server->dstaddr);
+ if (ret < 0)
+ goto nlmsg_fail;
+
+ if (swnreg->net_name_notify) {
+ ret = nla_put_flag(skb, CIFS_GENL_ATTR_SWN_NET_NAME_NOTIFY);
+ if (ret < 0)
+ goto nlmsg_fail;
+ }
+
+ if (swnreg->share_name_notify) {
+ ret = nla_put_flag(skb, CIFS_GENL_ATTR_SWN_SHARE_NAME_NOTIFY);
+ if (ret < 0)
+ goto nlmsg_fail;
+ }
+
+ if (swnreg->ip_notify) {
+ ret = nla_put_flag(skb, CIFS_GENL_ATTR_SWN_IP_NOTIFY);
+ if (ret < 0)
+ goto nlmsg_fail;
+ }
+
+ genlmsg_end(skb, hdr);
+ genlmsg_multicast(&cifs_genl_family, skb, 0, CIFS_GENL_MCGRP_SWN, GFP_ATOMIC);
+
+ cifs_dbg(FYI, "%s: Message to unregister for network name %s with id %d sent\n", __func__,
+ swnreg->net_name, swnreg->id);
+
+ return 0;
+
+nlmsg_fail:
+ genlmsg_cancel(skb, hdr);
+ nlmsg_free(skb);
+ return ret;
+}
+
+/*
+ * Try to find a matching registration for the tcon's server name and share name.
+ * Calls to this funciton must be protected by cifs_swnreg_idr_mutex.
+ * TODO Try to avoid memory allocations
+ */
+static struct cifs_swn_reg *cifs_find_swn_reg(struct cifs_tcon *tcon)
+{
+ struct cifs_swn_reg *swnreg;
+ int id;
+ const char *share_name;
+ const char *net_name;
+
+ net_name = extract_hostname(tcon->treeName);
+ if (IS_ERR(net_name)) {
+ int ret;
+
+ ret = PTR_ERR(net_name);
+ cifs_dbg(VFS, "%s: failed to extract host name from target '%s': %d\n",
+ __func__, tcon->treeName, ret);
+ return ERR_PTR(-EINVAL);
+ }
+
+ share_name = extract_sharename(tcon->treeName);
+ if (IS_ERR(share_name)) {
+ int ret;
+
+ ret = PTR_ERR(net_name);
+ cifs_dbg(VFS, "%s: failed to extract share name from target '%s': %d\n",
+ __func__, tcon->treeName, ret);
+ kfree(net_name);
+ return ERR_PTR(-EINVAL);
+ }
+
+ idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) {
+ if (strcasecmp(swnreg->net_name, net_name) != 0
+ || strcasecmp(swnreg->share_name, share_name) != 0) {
+ continue;
+ }
+
+ mutex_unlock(&cifs_swnreg_idr_mutex);
+
+ cifs_dbg(FYI, "Existing swn registration for %s:%s found\n", swnreg->net_name,
+ swnreg->share_name);
+
+ kfree(net_name);
+ kfree(share_name);
+
+ return swnreg;
+ }
+
+ kfree(net_name);
+ kfree(share_name);
+
+ return ERR_PTR(-EEXIST);
+}
+
+/*
+ * Get a registration for the tcon's server and share name, allocating a new one if it does not
+ * exists
+ */
+static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon)
+{
+ struct cifs_swn_reg *reg = NULL;
+ int ret;
+
+ mutex_lock(&cifs_swnreg_idr_mutex);
+
+ /* Check if we are already registered for this network and share names */
+ reg = cifs_find_swn_reg(tcon);
+ if (!IS_ERR(reg)) {
+ kref_get(&reg->ref_count);
+ mutex_unlock(&cifs_swnreg_idr_mutex);
+ return reg;
+ } else if (PTR_ERR(reg) != -EEXIST) {
+ mutex_unlock(&cifs_swnreg_idr_mutex);
+ return reg;
+ }
+
+ reg = kmalloc(sizeof(struct cifs_swn_reg), GFP_ATOMIC);
+ if (reg == NULL) {
+ mutex_unlock(&cifs_swnreg_idr_mutex);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ kref_init(&reg->ref_count);
+
+ reg->id = idr_alloc(&cifs_swnreg_idr, reg, 1, 0, GFP_ATOMIC);
+ if (reg->id < 0) {
+ cifs_dbg(FYI, "%s: failed to allocate registration id\n", __func__);
+ ret = reg->id;
+ goto fail;
+ }
+
+ reg->net_name = extract_hostname(tcon->treeName);
+ if (IS_ERR(reg->net_name)) {
+ ret = PTR_ERR(reg->net_name);
+ cifs_dbg(VFS, "%s: failed to extract host name from target: %d\n", __func__, ret);
+ goto fail_idr;
+ }
+
+ reg->share_name = extract_sharename(tcon->treeName);
+ if (IS_ERR(reg->share_name)) {
+ ret = PTR_ERR(reg->share_name);
+ cifs_dbg(VFS, "%s: failed to extract share name from target: %d\n", __func__, ret);
+ goto fail_net_name;
+ }
+
+ reg->net_name_notify = true;
+ reg->share_name_notify = true;
+ reg->ip_notify = (tcon->capabilities & SMB2_SHARE_CAP_SCALEOUT);
+
+ reg->tcon = tcon;
+
+ mutex_unlock(&cifs_swnreg_idr_mutex);
+
+ return reg;
+
+fail_net_name:
+ kfree(reg->net_name);
+fail_idr:
+ idr_remove(&cifs_swnreg_idr, reg->id);
+fail:
+ kfree(reg);
+ mutex_unlock(&cifs_swnreg_idr_mutex);
+ return ERR_PTR(ret);
+}
+
+static void cifs_swn_reg_release(struct kref *ref)
+{
+ struct cifs_swn_reg *swnreg = container_of(ref, struct cifs_swn_reg, ref_count);
+ int ret;
+
+ ret = cifs_swn_send_unregister_message(swnreg);
+ if (ret < 0)
+ cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n", __func__, ret);
+
+ idr_remove(&cifs_swnreg_idr, swnreg->id);
+ kfree(swnreg->net_name);
+ kfree(swnreg->share_name);
+ kfree(swnreg);
+}
+
+static void cifs_put_swn_reg(struct cifs_swn_reg *swnreg)
+{
+ mutex_lock(&cifs_swnreg_idr_mutex);
+ kref_put(&swnreg->ref_count, cifs_swn_reg_release);
+ mutex_unlock(&cifs_swnreg_idr_mutex);
+}
+
+static int cifs_swn_resource_state_changed(struct cifs_swn_reg *swnreg, const char *name, int state)
+{
+ int i;
+
+ switch (state) {
+ case CIFS_SWN_RESOURCE_STATE_UNAVAILABLE:
+ cifs_dbg(FYI, "%s: resource name '%s' become unavailable\n", __func__, name);
+ for (i = 0; i < swnreg->tcon->ses->chan_count; i++) {
+ spin_lock(&GlobalMid_Lock);
+ if (swnreg->tcon->ses->chans[i].server->tcpStatus != CifsExiting)
+ swnreg->tcon->ses->chans[i].server->tcpStatus = CifsNeedReconnect;
+ spin_unlock(&GlobalMid_Lock);
+ }
+ break;
+ case CIFS_SWN_RESOURCE_STATE_AVAILABLE:
+ cifs_dbg(FYI, "%s: resource name '%s' become available\n", __func__, name);
+ for (i = 0; i < swnreg->tcon->ses->chan_count; i++) {
+ spin_lock(&GlobalMid_Lock);
+ if (swnreg->tcon->ses->chans[i].server->tcpStatus != CifsExiting)
+ swnreg->tcon->ses->chans[i].server->tcpStatus = CifsNeedReconnect;
+ spin_unlock(&GlobalMid_Lock);
+ }
+ break;
+ case CIFS_SWN_RESOURCE_STATE_UNKNOWN:
+ cifs_dbg(FYI, "%s: resource name '%s' changed to unknown state\n", __func__, name);
+ break;
+ }
+ return 0;
+}
+
+static bool cifs_sockaddr_equal(struct sockaddr_storage *addr1, struct sockaddr_storage *addr2)
+{
+ if (addr1->ss_family != addr2->ss_family)
+ return false;
+
+ if (addr1->ss_family == AF_INET) {
+ return (memcmp(&((const struct sockaddr_in *)addr1)->sin_addr,
+ &((const struct sockaddr_in *)addr2)->sin_addr,
+ sizeof(struct in_addr)) == 0);
+ }
+
+ if (addr1->ss_family == AF_INET6) {
+ return (memcmp(&((const struct sockaddr_in6 *)addr1)->sin6_addr,
+ &((const struct sockaddr_in6 *)addr2)->sin6_addr,
+ sizeof(struct in6_addr)) == 0);
+ }
+
+ return false;
+}
+
+static int cifs_swn_store_swn_addr(const struct sockaddr_storage *new,
+ const struct sockaddr_storage *old,
+ struct sockaddr_storage *dst)
+{
+ __be16 port;
+
+ if (old->ss_family == AF_INET) {
+ struct sockaddr_in *ipv4 = (struct sockaddr_in *)old;
+
+ port = ipv4->sin_port;
+ }
+
+ if (old->ss_family == AF_INET6) {
+ struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)old;
+
+ port = ipv6->sin6_port;
+ }
+
+ if (new->ss_family == AF_INET) {
+ struct sockaddr_in *ipv4 = (struct sockaddr_in *)new;
+
+ ipv4->sin_port = port;
+ }
+
+ if (new->ss_family == AF_INET6) {
+ struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)new;
+
+ ipv6->sin6_port = port;
+ }
+
+ *dst = *new;
+
+ return 0;
+}
+
+static int cifs_swn_reconnect(struct cifs_tcon *tcon, struct sockaddr_storage *addr)
+{
+ /* Store the reconnect address */
+ mutex_lock(&tcon->ses->server->srv_mutex);
+ if (!cifs_sockaddr_equal(&tcon->ses->server->dstaddr, addr)) {
+ int ret;
+
+ ret = cifs_swn_store_swn_addr(addr, &tcon->ses->server->dstaddr,
+ &tcon->ses->server->swn_dstaddr);
+ if (ret < 0) {
+ cifs_dbg(VFS, "%s: failed to store address: %d\n", __func__, ret);
+ return ret;
+ }
+ tcon->ses->server->use_swn_dstaddr = true;
+
+ /*
+ * Unregister to stop receiving notifications for the old IP address.
+ */
+ ret = cifs_swn_unregister(tcon);
+ if (ret < 0) {
+ cifs_dbg(VFS, "%s: Failed to unregister for witness notifications: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /*
+ * And register to receive notifications for the new IP address now that we have
+ * stored the new address.
+ */
+ ret = cifs_swn_register(tcon);
+ if (ret < 0) {
+ cifs_dbg(VFS, "%s: Failed to register for witness notifications: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ spin_lock(&GlobalMid_Lock);
+ if (tcon->ses->server->tcpStatus != CifsExiting)
+ tcon->ses->server->tcpStatus = CifsNeedReconnect;
+ spin_unlock(&GlobalMid_Lock);
+ }
+ mutex_unlock(&tcon->ses->server->srv_mutex);
+
+ return 0;
+}
+
+static int cifs_swn_client_move(struct cifs_swn_reg *swnreg, struct sockaddr_storage *addr)
+{
+ struct sockaddr_in *ipv4 = (struct sockaddr_in *)addr;
+ struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)addr;
+
+ if (addr->ss_family == AF_INET)
+ cifs_dbg(FYI, "%s: move to %pI4\n", __func__, &ipv4->sin_addr);
+ else if (addr->ss_family == AF_I