--- old/usr/src/uts/common/fs/smbsrv/smb_pathname.c	Mon Aug  5 17:26:37 2019
+++ new/usr/src/uts/common/fs/smbsrv/smb_pathname.c	Mon Aug  5 17:26:37 2019
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
  */
 
 #include <smbsrv/smb_kproto.h>
@@ -151,16 +151,17 @@
     char		*last_component)
 {
 	smb_node_t	*root_node;
-	pathname_t	ppn;
+	pathname_t	ppn, mnt_pn;
 	char		*usepath;
 	int		lookup_flags = FOLLOW;
 	int 		trailing_slash = 0;
 	int		err = 0;
 	int		len;
-	smb_node_t	*vss_cur_node;
-	smb_node_t	*vss_root_node;
+	smb_node_t	*vss_node;
 	smb_node_t	*local_cur_node;
 	smb_node_t	*local_root_node;
+	boolean_t 	chk_vss;
+	char		*gmttoken;
 
 	ASSERT(dir_node);
 	ASSERT(last_component);
@@ -167,8 +168,9 @@
 
 	*dir_node = NULL;
 	*last_component = '\0';
-	vss_cur_node = NULL;
-	vss_root_node = NULL;
+	vss_node = NULL;
+	gmttoken = NULL;
+	chk_vss = B_FALSE;
 
 	if (sr && sr->tid_tree) {
 		if (STYPE_ISIPC(sr->tid_tree->t_res_type))
@@ -224,24 +226,27 @@
 	}
 
 	if (sr != NULL) {
-		boolean_t chk_vss;
-		if (sr->session->dialect >= SMB_VERS_2_BASE)
+		if (sr->session->dialect >= SMB_VERS_2_BASE) {
 			chk_vss = sr->arg.open.create_timewarp;
-		else
+		} else {
 			chk_vss = (sr->smb_flg2 &
 			    SMB_FLAGS2_REPARSE_PATH) != 0;
-		if (chk_vss) {
-			err = smb_vss_lookup_nodes(sr, root_node, cur_node,
-			    usepath, &vss_cur_node, &vss_root_node);
-			if (err != 0) {
-				kmem_free(usepath, SMB_MAXPATHLEN);
-				return (err);
-			}
 
-			len = strlen(usepath);
-			local_cur_node = vss_cur_node;
-			local_root_node = vss_root_node;
+			if (chk_vss) {
+				gmttoken = kmem_alloc(SMB_VSS_GMT_SIZE,
+				    KM_SLEEP);
+				err = smb_vss_extract_gmttoken(usepath,
+				    gmttoken);
+				if (err != 0) {
+					kmem_free(usepath, SMB_MAXPATHLEN);
+					kmem_free(gmttoken, SMB_VSS_GMT_SIZE);
+					return (err);
+				}
+				len = strlen(usepath);
+			}
 		}
+		if (chk_vss)
+			(void) pn_alloc(&mnt_pn);
 	}
 
 	if (usepath[len - 1] == '/')
@@ -254,10 +259,10 @@
 	if ((err = pn_set(&ppn, usepath)) != 0) {
 		(void) pn_free(&ppn);
 		kmem_free(usepath, SMB_MAXPATHLEN);
-		if (vss_cur_node != NULL)
-			(void) smb_node_release(vss_cur_node);
-		if (vss_root_node != NULL)
-			(void) smb_node_release(vss_root_node);
+		if (chk_vss)
+			(void) pn_free(&mnt_pn);
+		if (gmttoken != NULL)
+			kmem_free(gmttoken, SMB_VSS_GMT_SIZE);
 		return (err);
 	}
 
@@ -266,14 +271,21 @@
 	 * last component.  (We only need to return an smb_node for
 	 * the second to last component; a name is returned for the
 	 * last component.)
+	 *
+	 * For VSS requests, the last component might be a filesystem of its
+	 * own, and we need to discover that before exiting this function,
+	 * so allow the lookup to happen on the last component.
+	 * We'll correct this later when we convert to the snapshot.
 	 */
 
-	if (trailing_slash) {
-		(void) strlcpy(last_component, ".", MAXNAMELEN);
-	} else {
-		(void) pn_setlast(&ppn);
-		(void) strlcpy(last_component, ppn.pn_path, MAXNAMELEN);
-		ppn.pn_path[0] = '\0';
+	if (!chk_vss) {
+		if (trailing_slash) {
+			(void) strlcpy(last_component, ".", MAXNAMELEN);
+		} else {
+			(void) pn_setlast(&ppn);
+			(void) strlcpy(last_component, ppn.pn_path, MAXNAMELEN);
+			ppn.pn_path[0] = '\0';
+		}
 	}
 
 	if ((strcmp(ppn.pn_buf, "/") == 0) || (ppn.pn_buf[0] == '\0')) {
@@ -281,7 +293,8 @@
 		*dir_node = local_cur_node;
 	} else {
 		err = smb_pathname(sr, ppn.pn_buf, lookup_flags,
-		    local_root_node, local_cur_node, NULL, dir_node, cred);
+		    local_root_node, local_cur_node, NULL, dir_node, cred,
+		    chk_vss ? &mnt_pn : NULL);
 	}
 
 	(void) pn_free(&ppn);
@@ -288,6 +301,55 @@
 	kmem_free(usepath, SMB_MAXPATHLEN);
 
 	/*
+	 * We need to try and convert to snapshots, even on error.
+	 * This is to handle the following cases:
+	 * - We're on the lowest level filesystem, but a directory got renamed
+	 *   on the live version. We'll get ENOENT, but can still find it in
+	 *   the snapshot.
+	 * - The last component was actually a file. We need to leave the last
+	 *   component in in case it is, itself, a mountpoint, but that means
+	 *   we might get ENOTDIR if it's not actually a directory.
+	 *
+	 * Note that if you change the share-relative name of a mountpoint,
+	 * you won't be able to access previous versions of files under it.
+	 */
+	if (chk_vss && *dir_node != NULL) {
+		if ((err = smb_vss_lookup_nodes(sr, *dir_node, &vss_node,
+		    gmttoken)) == 0) {
+			char *p = mnt_pn.pn_path;
+			size_t pathleft;
+
+			smb_node_release(*dir_node);
+			*dir_node = NULL;
+			pathleft = pn_pathleft(&mnt_pn);
+
+			if (pathleft == 0 || trailing_slash) {
+				(void) strlcpy(last_component, ".", MAXNAMELEN);
+			} else {
+				(void) pn_setlast(&mnt_pn);
+				(void) strlcpy(last_component, mnt_pn.pn_path,
+				    MAXNAMELEN);
+				mnt_pn.pn_path[0] = '\0';
+				pathleft -= strlen(last_component);
+			}
+
+			if (pathleft != 0) {
+				err = smb_pathname(sr, p, lookup_flags,
+				    vss_node, vss_node, NULL, dir_node, cred,
+				    NULL);
+			} else {
+				*dir_node = vss_node;
+				vss_node = NULL;
+			}
+		}
+	}
+
+	if (chk_vss)
+		(void) pn_free(&mnt_pn);
+	if (gmttoken != NULL)
+		kmem_free(gmttoken, SMB_VSS_GMT_SIZE);
+
+	/*
 	 * Prevent traversal to another file system if mount point
 	 * traversal is disabled.
 	 *
@@ -318,11 +380,8 @@
 		*last_component = 0;
 	}
 
-	if (vss_cur_node != NULL)
-		(void) smb_node_release(vss_cur_node);
-	if (vss_root_node != NULL)
-		(void) smb_node_release(vss_root_node);
-
+	if (vss_node != NULL)
+		(void) smb_node_release(vss_node);
 	return (err);
 }
 
@@ -357,11 +416,11 @@
 int
 smb_pathname(smb_request_t *sr, char *path, int flags,
     smb_node_t *root_node, smb_node_t *cur_node, smb_node_t **dir_node,
-    smb_node_t **ret_node, cred_t *cred)
+    smb_node_t **ret_node, cred_t *cred, pathname_t *mnt_pn)
 {
 	char		*component, *real_name, *namep;
 	pathname_t	pn, rpn, upn, link_pn;
-	smb_node_t	*dnode, *fnode;
+	smb_node_t	*dnode, *fnode, *mnt_node;
 	smb_attr_t	attr;
 	vnode_t		*rootvp, *vp;
 	size_t		pathleft;
@@ -370,6 +429,7 @@
 	int		local_flags;
 	uint32_t	abe_flag = 0;
 	char		namebuf[MAXNAMELEN];
+	vnode_t *fsrootvp = NULL;
 
 	if (path == NULL)
 		return (EINVAL);
@@ -390,6 +450,11 @@
 		return (err);
 	}
 
+	if (mnt_pn != NULL && (err = pn_set(mnt_pn, path) != 0)) {
+		(void) pn_free(&upn);
+		return (err);
+	}
+
 	if (SMB_TREE_SUPPORTS_ABE(sr))
 		abe_flag = SMB_ABE;
 
@@ -399,6 +464,11 @@
 	component = kmem_alloc(MAXNAMELEN, KM_SLEEP);
 	real_name = kmem_alloc(MAXNAMELEN, KM_SLEEP);
 
+	if (mnt_pn != NULL) {
+		mnt_node = cur_node;
+		smb_node_ref(cur_node);
+	} else
+		mnt_node = NULL;
 	fnode = NULL;
 	dnode = cur_node;
 	smb_node_ref(dnode);
@@ -423,6 +493,10 @@
 		if ((err = pn_set(&pn, namep)) != 0)
 			break;
 
+		/* We want the DOS attributes. */
+		bzero(&attr, sizeof (attr));
+		attr.sa_mask = SMB_AT_DOSATTR;
+
 		local_flags = flags & FIGNORECASE;
 		err = smb_pathname_lookup(&pn, &rpn, local_flags,
 		    &vp, rootvp, dnode->vp, &attr, cred);
@@ -527,18 +601,52 @@
 			upn.pn_pathlen--;
 		}
 
+		/*
+		 * If the node we looked up is the root of a filesystem,
+		 * snapshot the lookup so we can replay this after discovering
+		 * the lowest mounted filesystem.
+		 */
+		if (mnt_pn != NULL &&
+		    (err = VFS_ROOT(fnode->vp->v_vfsp, &fsrootvp)) == 0) {
+			if (fsrootvp == fnode->vp) {
+				mnt_pn->pn_pathlen = pn_pathleft(&upn);
+				mnt_pn->pn_path = mnt_pn->pn_buf +
+				    ((ptrdiff_t)upn.pn_path -
+				    (ptrdiff_t)upn.pn_buf);
+
+				smb_node_ref(fnode);
+				if (mnt_node != NULL)
+					smb_node_release(mnt_node);
+				mnt_node = fnode;
+
+			}
+			VN_RELE(fsrootvp);
+		}
 	}
 
 	if ((pathleft) && (err == ENOENT))
 		err = ENOTDIR;
 
-	if (err) {
+	if (mnt_node == NULL)
+		mnt_pn = NULL;
+
+	/*
+	 * We always want to return a node when we're doing VSS
+	 * (mnt_pn != NULL)
+	 */
+	if (mnt_pn == NULL && err != 0) {
 		if (fnode)
 			smb_node_release(fnode);
 		if (dnode)
 			smb_node_release(dnode);
 	} else {
-		*ret_node = fnode;
+		if (mnt_pn != NULL) {
+			*ret_node = mnt_node;
+			if (fnode != NULL)
+				smb_node_release(fnode);
+		} else {
+			*ret_node = fnode;
+		}
 
 		if (dir_node)
 			*dir_node = dnode;
@@ -907,10 +1015,10 @@
 		return (B_FALSE);
 	}
 
-	/* If fname is "." -> INVALID_OBJECT_NAME */
+	/* If fname is "." -> OBJECT_NAME_INVALID */
 	if (pn->pn_fname && (strcmp(pn->pn_fname, ".") == 0)) {
 		smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
-		    ERRDOS, ERROR_PATH_NOT_FOUND);
+		    ERRDOS, ERROR_INVALID_NAME);
 		return (B_FALSE);
 	}
 
@@ -984,7 +1092,8 @@
  *
  * smb_stream_parse_name should only be called for a path that
  * contains a valid named stream.  Path validation should have
- * been performed before this function is called.
+ * been performed before this function is called, typically by
+ * calling smb_is_stream_name() just before this.
  *
  * Find the last component of path and split it into filename
  * and stream name.
@@ -991,12 +1100,15 @@
  *
  * On return the named stream type will be present.  The stream
  * type defaults to ":$DATA", if it has not been defined
- * For exmaple, 'stream' contains :<sname>:$DATA
+ * For example, 'stream' contains :<sname>:$DATA
+ *
+ * Output args: filename, stream both MAXNAMELEN
  */
 void
 smb_stream_parse_name(char *path, char *filename, char *stream)
 {
 	char *fname, *sname, *stype;
+	size_t flen, slen;
 
 	ASSERT(path);
 	ASSERT(filename);
@@ -1004,12 +1116,24 @@
 
 	fname = strrchr(path, '\\');
 	fname = (fname == NULL) ? path : fname + 1;
-	(void) strlcpy(filename, fname, MAXNAMELEN);
+	sname = strchr(fname, ':');
+	/* Caller makes sure there is a ':' in path. */
+	VERIFY(sname != NULL);
+	/* LINTED: possible ptrdiff_t overflow */
+	flen = sname - fname;
+	slen = strlen(sname);
 
-	sname = strchr(filename, ':');
-	(void) strlcpy(stream, sname, MAXNAMELEN);
-	*sname = '\0';
+	if (flen > (MAXNAMELEN-1))
+		flen = (MAXNAMELEN-1);
+	(void) strncpy(filename, fname, flen);
+	filename[flen] = '\0';
 
+	if (slen > (MAXNAMELEN-1))
+		slen = (MAXNAMELEN-1);
+	(void) strncpy(stream, sname, slen);
+	stream[slen] = '\0';
+
+	/* Add a "stream type" if there isn't one. */
 	stype = strchr(stream + 1, ':');
 	if (stype == NULL)
 		(void) strlcat(stream, ":$DATA", MAXNAMELEN);
@@ -1050,6 +1174,27 @@
 }
 
 /*
+ * Is this stream node a "restricted" type?
+ */
+boolean_t
+smb_strname_restricted(char *strname)
+{
+	char *stype;
+
+	stype = strrchr(strname, ':');
+	if (stype == NULL)
+		return (B_FALSE);
+
+	/*
+	 * Only ":$CA" is restricted (for now).
+	 */
+	if (strcmp(stype, ":$CA") == 0)
+		return (B_TRUE);
+
+	return (B_FALSE);
+}
+
+/*
  * smb_validate_stream_name
  *
  * B_FALSE will be returned, and the error status ser in the sr, if:
@@ -1063,6 +1208,7 @@
 smb_validate_stream_name(smb_request_t *sr, smb_pathname_t *pn)
 {
 	static char *strmtype[] = {
+		"$CA",
 		"$DATA",
 		"$INDEX_ALLOCATION"
 	};
