changeset 21490:04078b85898d

Merge pull request #496 from hadfl/upstream_merge/2019061101 Upstream merge/2019061101
author Tobias Oetiker <tobi@oetiker.ch>
date Wed, 12 Jun 2019 22:46:54 +0200
parents b75afc1a41da (current diff) 427dbbb22843 (diff)
children fd839de25b5f 6d4bf5c93bf4
files usr/src/cmd/mdb/sparc/v7/libmlsvc/Makefile usr/src/cmd/mdb/sparc/v9/libmlsvc/Makefile usr/src/cmd/smbsrv/testoplock/Run-cmd.sh usr/src/uts/common/smbsrv/smb_signing.h
diffstat 201 files changed, 16294 insertions(+), 4106 deletions(-) [+]
line wrap: on
line diff
--- a/exception_lists/check_rtime	Wed Jun 05 14:38:04 2019 +0200
+++ b/exception_lists/check_rtime	Wed Jun 12 22:46:54 2019 +0200
@@ -231,6 +231,7 @@
 FORBIDDEN_DEP usr/lib/MACH(smbsrv)/libfksmbsrv.so.1
 FORBIDDEN_DEP usr/lib/smbsrv/fksmbd
 FORBIDDEN_DEP usr/lib/smbsrv/test-msgbuf
+FORBIDDEN_DEP usr/lib/smbsrv/testoplock
 FORBIDDEN_DEP usr/sbin/amd64/zdb
 FORBIDDEN_DEP usr/sbin/i86/zdb
 FORBIDDEN_DEP usr/sbin/sparcv7/zdb
--- a/exception_lists/copyright	Wed Jun 05 14:38:04 2019 +0200
+++ b/exception_lists/copyright	Wed Jun 12 22:46:54 2019 +0200
@@ -84,6 +84,7 @@
 usr/src/cmd/krb5/ldap_util/kdb5_ldap_services.h
 usr/src/cmd/localedef/data/manual-input.UTF-8
 usr/src/cmd/smbsrv/smbd/eventlog.dll
+usr/src/cmd/smbsrv/testoplock/case*
 usr/src/cmd/terminfo/termcap.src
 usr/src/cmd/terminfo/terminfo.src
 usr/src/common/bzip2/LICENSE
--- a/exception_lists/packaging	Wed Jun 05 14:38:04 2019 +0200
+++ b/exception_lists/packaging	Wed Jun 12 22:46:54 2019 +0200
@@ -605,6 +605,7 @@
 usr/lib/smbsrv/libsmb.so
 usr/lib/smbsrv/libsmbns.so
 usr/lib/smbsrv/test-msgbuf
+usr/lib/smbsrv/testoplock
 #
 #
 # Private/Internal 64-bit libraries of smbsrv. Do not ship.
--- a/exception_lists/wscheck	Wed Jun 05 14:38:04 2019 +0200
+++ b/exception_lists/wscheck	Wed Jun 12 22:46:54 2019 +0200
@@ -17,6 +17,7 @@
 usr/src/data/hwdata/pci.ids
 usr/src/data/hwdata/usb.ids
 usr/src/data/perfmon/readme.txt
+usr/src/cmd/smbsrv/testoplock/case*.ref
 
 usr/src/data/ucode/amd/*
 usr/src/data/ucode/intel/*
--- a/usr/src/boot/sys/boot/common/tem.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/boot/sys/boot/common/tem.c	Wed Jun 12 22:46:54 2019 +0200
@@ -196,7 +196,7 @@
 {
 	struct tem_vt_state *tem = (struct tem_vt_state *)tem_arg;
 
-	if (!tem->tvs_initialized) {
+	if (tems.ts_initialized == 0 || tem->tvs_initialized == 0) {
 		return;
 	}
 
@@ -252,10 +252,9 @@
 {
 	struct tem_vt_state *ptem;
 
-	ptem = malloc(sizeof (struct tem_vt_state));
+	ptem = calloc(1, sizeof (struct tem_vt_state));
 	if (ptem == NULL)
 		return ((tem_vt_state_t)ptem);
-	bzero(ptem, sizeof (*ptem));
 
 	ptem->tvs_isactive = false;
 	ptem->tvs_fbmode = KD_TEXT;
@@ -591,6 +590,7 @@
 	tem_modechg_cb_arg_t cb_arg;
 	size_t height = 0;
 	size_t width = 0;
+	int state;
 
 	diff = tems_check_videomode(devinit);
 	if (diff == 0) {
@@ -625,10 +625,13 @@
 
 	plat_tem_get_prom_size(&height, &width);
 
+	state = tems.ts_initialized;
+	tems.ts_initialized = 0;	/* stop all output */
 	tems_setup_terminal(devinit, height, width);
 
 	tems_reset_colormap();
 	tems_get_initial_color(&tems.ts_init_color);
+	tems.ts_initialized = state;	/* restore state */
 
 	for (p = list_head(&tems.ts_list); p != NULL;
 	    p = list_next(&tems.ts_list, p)) {
@@ -790,7 +793,7 @@
 
 	/* clear */
 	width = tems.ts_font.vf_width;
-	ncols = (tems.ts_p_dimension.width + (width - 1))/ width;
+	ncols = (tems.ts_p_dimension.width + (width - 1)) / width;
 
 	tem_pix_cls_range(tem, 0, nrows, tems.ts_p_offset.y,
 	    0, ncols, 0, B_TRUE);
@@ -1147,16 +1150,13 @@
 		break;
 
 	case A_CSI:
-		{
-			int i;
-			tem->tvs_curparam = 0;
-			tem->tvs_paramval = 0;
-			tem->tvs_gotparam = B_FALSE;
-			/* clear the parameters */
-			for (i = 0; i < TEM_MAXPARAMS; i++)
-				tem->tvs_params[i] = -1;
-			tem->tvs_state = A_STATE_CSI;
-		}
+		tem->tvs_curparam = 0;
+		tem->tvs_paramval = 0;
+		tem->tvs_gotparam = B_FALSE;
+		/* clear the parameters */
+		for (int i = 0; i < TEM_MAXPARAMS; i++)
+			tem->tvs_params[i] = -1;
+		tem->tvs_state = A_STATE_CSI;
 		break;
 
 	case A_GS:
@@ -1628,7 +1628,7 @@
 static void
 tem_getparams(struct tem_vt_state *tem, uint8_t ch)
 {
-	if (ch >= '0' && ch <= '9') {
+	if (isdigit(ch)) {
 		tem->tvs_paramval = ((tem->tvs_paramval * 10) + (ch - '0'));
 		tem->tvs_gotparam = B_TRUE;  /* Remember got parameter */
 		return; /* Return immediately */
@@ -2359,7 +2359,7 @@
 	offset = tems.ts_p_offset.y % height;
 
 	nrows = tems.ts_p_offset.y / height;
-	ncols = (tems.ts_p_dimension.width + (width - 1))/ width;
+	ncols = (tems.ts_p_dimension.width + (width - 1)) / width;
 
 	if (nrows > 0)
 		tem_pix_cls_range(tem, 0, nrows, offset, 0, ncols, 0,
@@ -2894,7 +2894,7 @@
 		return;
 
 	width = tems.ts_c_dimension.width;
-	addr = tem->tvs_screen_buf +  (row * width + col);
+	addr = tem->tvs_screen_buf + (row * width + col);
 	for (i = 0; i < count; i++) {
 		*addr++ = string[i];
 	}
--- a/usr/src/cmd/fs.d/smbclnt/smbutil/smbutil.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/fs.d/smbclnt/smbutil/smbutil.c	Wed Jun 12 22:46:54 2019 +0200
@@ -34,7 +34,7 @@
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  *
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.
  */
 
 #include <sys/param.h>
@@ -190,17 +190,16 @@
 	printf("\n");
 	printf(gettext("usage: %s [-hv] subcommand [args]\n"), __progname);
 	printf(gettext("where subcommands are:\n"
-	" crypt		slightly obscure password\n"
-	" help		display help on specified subcommand\n"
-	/* " lc		display active connections\n" */
-	" info		display server type and version\n"
-	" login		login to specified host\n"
-	" logout	logout from specified host\n"
-	" logoutall	logout all users (requires privilege)\n"
-	" lookup	resolve NetBIOS name to IP address\n"
-	" print		print file to the specified remote printer\n"
-	" status	resolve IP address or DNS name to NetBIOS names\n"
-	" view		list resources on specified host\n"
+	" crypt          slightly obscure password\n"
+	" help           display help on specified subcommand\n"
+	" info           display server type and version\n"
+	" login          login to specified host\n"
+	" logout         logout from specified host\n"
+	" logoutall      logout all users (requires privilege)\n"
+	" lookup         resolve NetBIOS name to IP address\n"
+	" print          print file to the specified remote printer\n"
+	" status         resolve IP address or DNS name to NetBIOS names\n"
+	" view           list resources on specified host\n"
 	"\n"));
 	exit(1);
 }
--- a/usr/src/cmd/idmap/idmapd/idmap.xml	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/idmap/idmapd/idmap.xml	Wed Jun 12 22:46:54 2019 +0200
@@ -2,7 +2,7 @@
 <!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
 <!--
  Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
 
  CDDL HEADER START
 
@@ -41,12 +41,12 @@
 	<single_instance />
 
 	<dependency
-		name='rpcbind' 
-		grouping='require_all' 
-		restart_on='restart' 
-		type='service'> 
-		<service_fmri value='svc:/network/rpc/bind' /> 
-	</dependency> 
+		name='rpcbind'
+		grouping='require_all'
+		restart_on='restart'
+		type='service'>
+		<service_fmri value='svc:/network/rpc/bind' />
+	</dependency>
 
 	<dependency name='filesystem-minimal'
 		grouping='require_all'
@@ -94,6 +94,10 @@
 			type='count'
 			value='0' />
 		<propval
+			name='max_threads'
+			type='count'
+			value='40' />
+		<propval
 			name='value_authorization'
 			type='astring'
 			value='solaris.smf.value.idmap' />
@@ -114,6 +118,10 @@
 			type='count'
 			value='3600' />
 		<propval
+			name='site_name'
+			type='astring'
+			value='' />
+		<propval
 			name='use_ads'
 			type='boolean'
 			value='true' />
--- a/usr/src/cmd/idmap/idmapd/idmap_config.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/idmap/idmapd/idmap_config.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2019 Nexenta Systems, Inc.  All rights reserved.
  */
 
 
@@ -45,6 +45,7 @@
 #include <arpa/inet.h>
 #include <netdb.h>
 #include <note.h>
+#include <limits.h>
 #include "idmapd.h"
 #include "addisc.h"
 
@@ -77,6 +78,11 @@
  */
 #define	MIN_REDISCOVERY_INTERVAL	60
 
+/*
+ * Max number of concurrent door calls
+ */
+#define	MAX_THREADS_DEFAULT	40
+
 enum event_type {
 	EVENT_NOTHING,	/* Woke up for no good reason */
 	EVENT_TIMEOUT,	/* Timeout expired */
@@ -1481,7 +1487,8 @@
  */
 static
 int
-valid_ldap_attr(const char *attr) {
+valid_ldap_attr(const char *attr)
+{
 	for (; *attr; attr++) {
 		if (!isalnum(*attr) && *attr != '-' &&
 		    *attr != '_' && *attr != '.' && *attr != ';')
@@ -1601,6 +1608,15 @@
 	if (rc != 0)
 		(*errors)++;
 
+	rc = get_val_int(handles, "max_threads",
+	    &pgcfg->max_threads, SCF_TYPE_COUNT);
+	if (rc != 0)
+		(*errors)++;
+	if (pgcfg->max_threads == 0)
+		pgcfg->max_threads = MAX_THREADS_DEFAULT;
+	if (pgcfg->max_threads > UINT_MAX)
+		pgcfg->max_threads = UINT_MAX;
+
 	rc = get_val_int(handles, "id_cache_timeout",
 	    &pgcfg->id_cache_timeout, SCF_TYPE_COUNT);
 	if (rc != 0)
@@ -1632,9 +1648,10 @@
 			free(pgcfg->domain_name);
 			pgcfg->domain_name = NULL;
 		}
+		if (pgcfg->domain_name != NULL)
+			pgcfg->domain_name_auto_disc = B_FALSE;
 		(void) ad_disc_set_DomainName(handles->ad_ctx,
 		    pgcfg->domain_name);
-		pgcfg->domain_name_auto_disc = B_FALSE;
 	}
 
 	rc = get_val_astring(handles, "default_domain",
@@ -1727,16 +1744,30 @@
 	if (rc != 0)
 		(*errors)++;
 	else {
+		if (pgcfg->forest_name != NULL &&
+		    pgcfg->forest_name[0] == '\0') {
+			free(pgcfg->forest_name);
+			pgcfg->forest_name = NULL;
+		}
+		if (pgcfg->forest_name != NULL)
+			pgcfg->forest_name_auto_disc = B_FALSE;
 		(void) ad_disc_set_ForestName(handles->ad_ctx,
 		    pgcfg->forest_name);
-		pgcfg->forest_name_auto_disc = B_FALSE;
 	}
 
 	rc = get_val_astring(handles, "site_name", &pgcfg->site_name);
 	if (rc != 0)
 		(*errors)++;
-	else
+	else {
+		if (pgcfg->site_name != NULL &&
+		    pgcfg->site_name[0] == '\0') {
+			free(pgcfg->site_name);
+			pgcfg->site_name = NULL;
+		}
+		if (pgcfg->site_name != NULL)
+			pgcfg->site_name_auto_disc = B_FALSE;
 		(void) ad_disc_set_SiteName(handles->ad_ctx, pgcfg->site_name);
+	}
 
 	rc = get_val_ds(handles, "global_catalog", 3268,
 	    &pgcfg->global_catalog);
@@ -2198,6 +2229,9 @@
 	changed += update_uint64(&live_pgcfg->list_size_limit,
 	    &new_pgcfg.list_size_limit, "list_size_limit");
 
+	changed += update_uint64(&live_pgcfg->max_threads,
+	    &new_pgcfg.max_threads, "max_threads");
+
 	changed += update_uint64(&live_pgcfg->id_cache_timeout,
 	    &new_pgcfg.id_cache_timeout, "id_cache_timeout");
 
--- a/usr/src/cmd/idmap/idmapd/idmap_config.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/idmap/idmapd/idmap_config.h	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #ifndef _IDMAP_CONFIG_H
@@ -75,6 +75,7 @@
 
 typedef struct idmap_pg_config {
 	uint64_t	list_size_limit;
+	uint64_t	max_threads;
 	uint64_t	id_cache_timeout;
 	uint64_t	name_cache_timeout;
 	uint64_t	rediscovery_interval;
@@ -125,7 +126,7 @@
 } idmap_cfg_t;
 
 
-extern void 		idmap_cfg_unload(idmap_pg_config_t *);
+extern void		idmap_cfg_unload(idmap_pg_config_t *);
 extern int		idmap_cfg_load(idmap_cfg_t *, int);
 extern idmap_cfg_t	*idmap_cfg_init(void);
 extern int		idmap_cfg_fini(idmap_cfg_t *);
--- a/usr/src/cmd/idmap/idmapd/idmapd.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/idmap/idmapd/idmapd.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 
@@ -84,13 +84,14 @@
  * Server door thread start routine.
  *
  * Set a TSD value to the door thread. This enables the destructor to
- * be called when this thread exits.
+ * be called when this thread exits. Note that we need a non-NULL
+ * value for this or the TSD destructor is not called.
  */
 /*ARGSUSED*/
 static void *
 idmapd_door_thread_start(void *arg)
 {
-	static void *value = 0;
+	static void *value = "NON-NULL TSD";
 
 	/*
 	 * Disable cancellation to avoid memory leaks from not running
@@ -136,6 +137,8 @@
 {
 	int num;
 
+	/* set TSD to NULL so we don't loop infinitely */
+	(void) pthread_setspecific(create_threads_key, NULL);
 	num = atomic_dec_32_nv(&num_threads);
 	idmapdlog(LOG_DEBUG,
 	    "exiting thread ID %d - %d threads currently active",
@@ -379,6 +382,13 @@
 		exit(error < -2 ? SMF_EXIT_ERR_CONFIG : 1);
 	}
 
+	/*
+	 * This means max_threads can't be updated without restarting idmap.
+	 */
+	RDLOCK_CONFIG();
+	max_threads = _idmapdstate.cfg->pgcfg.max_threads;
+	UNLOCK_CONFIG();
+
 	(void) door_server_create(idmapd_door_thread_create);
 	if ((error = pthread_key_create(&create_threads_key,
 	    idmapd_door_thread_cleanup)) != 0) {
@@ -521,7 +531,8 @@
 
 /* printflike */
 void
-idmapdlog(int pri, const char *format, ...) {
+idmapdlog(int pri, const char *format, ...)
+{
 	static time_t prev_ts;
 	va_list args;
 	char cbuf[CBUFSIZ];
--- a/usr/src/cmd/idmap/idmapd/init.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/idmap/idmapd/init.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -357,6 +357,7 @@
 	pgcfg = &_idmapdstate.cfg->pgcfg;
 
 	idmapdlog(LOG_DEBUG, "list_size_limit=%llu", pgcfg->list_size_limit);
+	idmapdlog(LOG_DEBUG, "max_threads=%llu", pgcfg->max_threads);
 	idmapdlog(LOG_DEBUG, "default_domain=%s",
 	    CHECK_NULL(pgcfg->default_domain));
 	idmapdlog(LOG_DEBUG, "domain_name=%s", CHECK_NULL(pgcfg->domain_name));
--- a/usr/src/cmd/idmap/test-getdc/getdc_main.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/idmap/test-getdc/getdc_main.c	Wed Jun 12 22:46:54 2019 +0200
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2019 Nexenta Systems, Inc.  All rights reserved.
  */
 
 
@@ -23,6 +23,7 @@
 
 int debug;
 char *domainname = NULL;
+char *sitename = NULL;
 
 void print_ds(ad_disc_ds_t *);
 void mylogger(int pri, const char *format, ...);
@@ -48,7 +49,9 @@
 	}
 
 	if (optind < argc)
-		domainname = argv[optind];
+		domainname = argv[optind++];
+	if (optind < argc)
+		sitename = argv[optind++];
 
 	adutils_set_logger(mylogger);
 	adutils_set_debug(AD_DEBUG_ALL, debug);
@@ -58,6 +61,8 @@
 
 	if (domainname)
 		(void) ad_disc_set_DomainName(ad_ctx, domainname);
+	if (sitename)
+		(void) ad_disc_set_SiteName(ad_ctx, sitename);
 
 	ad_disc_refresh(ad_ctx);
 
--- a/usr/src/cmd/ldapcachemgr/cachemgr.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/ldapcachemgr/cachemgr.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,6 +21,7 @@
 /*
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
+ * Copyright 2019 Nexenta Systems, Inc.
  */
 
 /*
@@ -225,13 +226,13 @@
 static void *
 server_tsd_bind(void *arg)
 {
-	static void	*value = 0;
+	static void *value = "NON-NULL TSD";
 
 	/*
 	 * disable cancellation to prevent hangs when server
 	 * threads disappear
 	 */
-
+	(void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
 	(void) thr_setspecific(server_key, value);
 	(void) door_return(NULL, 0, NULL, 0);
 
@@ -268,6 +269,7 @@
 	(void) mutex_lock(&create_lock);
 	num_servers--;
 	(void) mutex_unlock(&create_lock);
+	(void) thr_setspecific(server_key, NULL);
 }
 
 static void		client_killserver();
@@ -1198,7 +1200,7 @@
 static void
 detachfromtty(char *pgm)
 {
-	int 	status;
+	int	status;
 	pid_t	pid, wret;
 
 	(void) close(0);
--- a/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2016 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
  */
 
 #include <mdb/mdb_modapi.h>
@@ -34,6 +34,7 @@
 #include <smbsrv/smb.h>
 #include <smbsrv/smb_ktypes.h>
 #include <smbsrv/smb_token.h>
+#include <smbsrv/smb_oplock.h>
 
 #ifndef _KMDB
 #include "smbsrv_pcap.h"
@@ -52,7 +53,7 @@
 #define	ACE_TYPE_ENTRY(_v_)	{_v_, #_v_}
 #define	SMB_COM_ENTRY(_v_, _x_)	{#_v_, _x_}
 
-#define	SMB_MDB_MAX_OPTS	9
+#define	SMB_MDB_MAX_OPTS	10
 
 #define	SMB_OPT_SERVER		0x00000001
 #define	SMB_OPT_SESSION		0x00000002
@@ -102,7 +103,7 @@
  */
 typedef struct {
 	uint_t		ex_mask;
-	int 		(*ex_offset)(void);
+	int		(*ex_offset)(void);
 	const char	*ex_dcmd;
 	const char	*ex_name;
 } smb_exp_t;
@@ -477,12 +478,16 @@
 	"smb2_invalid_cmd"
 };
 
+struct mdb_smb_oplock;
+
 static int smb_sid_print(uintptr_t);
 static int smb_dcmd_getopt(uint_t *, int, const mdb_arg_t *);
 static int smb_dcmd_setopt(uint_t, int, mdb_arg_t *);
 static int smb_obj_expand(uintptr_t, uint_t, const smb_exp_t *, ulong_t);
 static int smb_obj_list(const char *, uint_t, uint_t);
 static int smb_worker_findstack(uintptr_t);
+static int smb_node_get_oplock(uintptr_t, struct mdb_smb_oplock **);
+static int smb_node_oplock_cnt(struct mdb_smb_oplock *);
 static void smb_inaddr_ntop(smb_inaddr_t *, char *, size_t);
 static void get_enum(char *, size_t, const char *, int, const char *);
 
@@ -571,16 +576,38 @@
 typedef struct mdb_smb_server {
 	smb_server_state_t	sv_state;
 	zoneid_t		sv_zid;
+	smb_hash_t		*sv_persistid_ht;
 } mdb_smb_server_t;
 
 static int
+smb_server_exp_off_sv_list(void)
+{
+	int svl_off, ll_off;
+
+	/* OFFSETOF(smb_server_t, sv_session_list.ll_list); */
+	GET_OFFSET(svl_off, smb_server_t, sv_session_list);
+	GET_OFFSET(ll_off, smb_llist_t, ll_list);
+	return (svl_off + ll_off);
+}
+
+static int
 smb_server_exp_off_nbt_list(void)
 {
 	int svd_off, lds_off, ll_off;
 
 	/* OFFSETOF(smb_server_t, sv_nbt_daemon.ld_session_list.ll_list); */
 	GET_OFFSET(svd_off, smb_server_t, sv_nbt_daemon);
-	GET_OFFSET(lds_off, smb_listener_daemon_t, ld_session_list);
+	/*
+	 * We can't do OFFSETOF() because the member doesn't exist,
+	 * but we want backwards compatibility to old cores
+	 */
+	lds_off = mdb_ctf_offsetof_by_name("smb_listener_daemon_t",
+	    "ld_session_list");
+	if (lds_off < 0) {
+		mdb_warn("cannot lookup: "
+		    "smb_listener_daemon_t .ld_session_list");
+		return (-1);
+	}
 	GET_OFFSET(ll_off, smb_llist_t, ll_list);
 	return (svd_off + lds_off + ll_off);
 }
@@ -592,7 +619,17 @@
 
 	/* OFFSETOF(smb_server_t, sv_tcp_daemon.ld_session_list.ll_list); */
 	GET_OFFSET(svd_off, smb_server_t, sv_tcp_daemon);
-	GET_OFFSET(lds_off, smb_listener_daemon_t, ld_session_list);
+	/*
+	 * We can't do OFFSETOF() because the member doesn't exist,
+	 * but we want backwards compatibility to old cores
+	 */
+	lds_off = mdb_ctf_offsetof_by_name("smb_listener_daemon_t",
+	    "ld_session_list");
+	if (lds_off < 0) {
+		mdb_warn("cannot lookup: "
+		    "smb_listener_daemon_t .ld_session_list");
+		return (-1);
+	}
 	GET_OFFSET(ll_off, smb_llist_t, ll_list);
 	return (svd_off + lds_off + ll_off);
 }
@@ -603,6 +640,15 @@
 static const smb_exp_t smb_server_exp[] =
 {
 	{ SMB_OPT_ALL_OBJ,
+	    smb_server_exp_off_sv_list,
+	    "smbsess", "smb_session"},
+	{ 0, 0, NULL, NULL }
+};
+
+/* for backwards compatibility only */
+static const smb_exp_t smb_server_exp_old[] =
+{
+	{ SMB_OPT_ALL_OBJ,
 	    smb_server_exp_off_nbt_list,
 	    "smbsess", "smb_session"},
 	{ SMB_OPT_ALL_OBJ,
@@ -622,6 +668,9 @@
 {
 	uint_t		opts;
 	ulong_t		indent = 0;
+	const smb_exp_t	*sv_exp;
+	mdb_ctf_id_t id;
+	ulong_t off;
 
 	if (smb_dcmd_getopt(&opts, argc, argv))
 		return (DCMD_USAGE);
@@ -668,7 +717,18 @@
 			    addr, sv->sv_zid, state);
 		}
 	}
-	if (smb_obj_expand(addr, opts, smb_server_exp, indent))
+
+	/* if we can't look up the type name, just error out */
+	if (mdb_ctf_lookup_by_name("smb_server_t", &id) == -1)
+		return (DCMD_ERR);
+
+	if (mdb_ctf_offsetof(id, "sv_session_list", &off) == -1)
+		/* sv_session_list doesn't exist; old core */
+		sv_exp = smb_server_exp_old;
+	else
+		sv_exp = smb_server_exp;
+
+	if (smb_obj_expand(addr, opts, sv_exp, indent))
 		return (DCMD_ERR);
 	return (DCMD_OK);
 }
@@ -721,7 +781,7 @@
 	volatile uint32_t	s_file_cnt;
 	volatile uint32_t	s_dir_cnt;
 
-	char 			workstation[SMB_PI_MAX_HOST];
+	char			workstation[SMB_PI_MAX_HOST];
 } mdb_smb_session_t;
 
 static int
@@ -875,6 +935,7 @@
 	}
 	if (smb_obj_expand(addr, opts, smb_session_exp, indent))
 		return (DCMD_ERR);
+
 	return (DCMD_OK);
 }
 
@@ -892,8 +953,6 @@
 
 	unsigned char		first_smb_com;
 	unsigned char		smb_com;
-	uint16_t		smb2_cmd_code;
-	uint64_t		smb2_messageid;
 
 	uint16_t		smb_tid;
 	uint32_t		smb_pid;
@@ -901,6 +960,10 @@
 	uint16_t		smb_mid;
 	uint16_t		smb_fid;
 
+	uint16_t		smb2_cmd_code;
+	uint64_t		smb2_messageid;
+	uint64_t		smb2_ssnid;
+
 	struct smb_tree		*tid_tree;
 	struct smb_ofile	*fid_ofile;
 	smb_user_t		*uid_user;
@@ -990,15 +1053,21 @@
 			    "state: %u (%s)\n",
 			    sr->sr_state, state);
 
+			if (sr->smb2_ssnid != 0) {
+				mdb_printf(
+				    "SSNID(user): 0x%llx (%p)\n",
+				    sr->smb2_ssnid, sr->uid_user);
+			} else {
+				mdb_printf(
+				    "UID(user): %u (%p)\n",
+				    sr->smb_uid, sr->uid_user);
+			}
+
 			mdb_printf(
 			    "TID(tree): %u (%p)\n",
 			    sr->smb_tid, sr->tid_tree);
 
 			mdb_printf(
-			    "UID(user): %u (%p)\n",
-			    sr->smb_uid, sr->uid_user);
-
-			mdb_printf(
 			    "FID(file): %u (%p)\n",
 			    sr->smb_fid, sr->fid_ofile);
 
@@ -1284,6 +1353,7 @@
 	cred_t			*u_cred;
 	cred_t			*u_privcred;
 
+	uint64_t		u_ssnid;
 	uint32_t		u_refcnt;
 	uint32_t		u_flags;
 	uint32_t		u_privileges;
@@ -1387,6 +1457,7 @@
 			mdb_printf("%<b>%<u>SMB user information (%p):"
 			    "%</u>%</b>\n", addr);
 			mdb_printf("UID: %u\n", user->u_uid);
+			mdb_printf("SSNID: %llx\n", user->u_ssnid);
 			mdb_printf("State: %d (%s)\n", user->u_state, state);
 			mdb_printf("Flags: 0x%08x <%b>\n", user->u_flags,
 			    user->u_flags, user_flag_bits);
@@ -1401,11 +1472,12 @@
 				mdb_printf(
 				    "%<b>%<u>%?-s "
 				    "%-5s "
+				    "%-16s "
 				    "%-32s%</u>%</b>\n",
-				    "USER", "UID", "ACCOUNT");
-
-			mdb_printf("%-?p %-5u %-32s\n", addr, user->u_uid,
-			    account);
+				    "USER", "UID", "SSNID", "ACCOUNT");
+
+			mdb_printf("%-?p %-5u %-16llx %-32s\n",
+			    addr, user->u_uid, user->u_ssnid, account);
 		}
 	}
 	return (DCMD_OK);
@@ -1530,9 +1602,12 @@
 	{ "SPARSE",
 	    SMB_TREE_SPARSE,
 	    SMB_TREE_SPARSE },
-	{ "XMNT",
+	{ "XMOUNTS",
 	    SMB_TREE_TRAVERSE_MOUNTS,
 	    SMB_TREE_TRAVERSE_MOUNTS },
+	{ "FORCE_L2_OPLOCK",
+	    SMB_TREE_FORCE_L2_OPLOCK,
+	    SMB_TREE_FORCE_L2_OPLOCK },
 	{ NULL, 0, 0 }
 };
 
@@ -1717,13 +1792,13 @@
 	int			f_mode;
 	cred_t			*f_cr;
 	pid_t			f_pid;
+	uintptr_t		f_lease;
+	smb_dh_vers_t		dh_vers;
 } mdb_smb_ofile_t;
 
 static const mdb_bitmask_t
 ofile_flag_bits[] = {
-	{ "RO",
-	    SMB_OFLAGS_READONLY,
-	    SMB_OFLAGS_READONLY },
+	{ "RO", 1, 1 }, /* old SMB_OFLAGS_READONLY */
 	{ "EXEC",
 	    SMB_OFLAGS_EXECONLY,
 	    SMB_OFLAGS_EXECONLY },
@@ -1777,16 +1852,24 @@
 		}
 		if (opts & SMB_OPT_VERBOSE) {
 			char		state[40];
+			char		durable[40];
 
 			get_enum(state, sizeof (state),
 			    "smb_ofile_state_t", of->f_state,
 			    "SMB_OFILE_STATE_");
 
+			get_enum(durable, sizeof (durable),
+			    "smb_dh_vers_t", of->dh_vers,
+			    "SMB2_");
+
 			mdb_printf(
 			    "%<b>%<u>SMB ofile information (%p):%</u>%</b>\n\n",
 			    addr);
 			mdb_printf("FID: %u\n", of->f_fid);
 			mdb_printf("State: %d (%s)\n", of->f_state, state);
+			mdb_printf("DH Type: %d (%s)\n", of->dh_vers,
+			    durable);
+			mdb_printf("Lease: %p\n", of->f_lease);
 			mdb_printf("SMB Node: %p\n", of->f_node);
 			mdb_printf("LLF Offset: 0x%llx (%s)\n",
 			    of->f_llf_pos,
@@ -1809,13 +1892,293 @@
 				    "%<b>%<u>%-?s "
 				    "%-5s "
 				    "%-?s "
-				    "%-?s%</u>%</b>\n",
-				    "OFILE", "FID", "SMB NODE", "CRED");
-
-			mdb_printf("%?p %-5u %-p %p\n", addr,
-			    of->f_fid, of->f_node, of->f_cr);
+				    "%-?s "
+				    "%-?s "
+				    "%</u>%</b>\n",
+				    "OFILE",
+				    "FID",
+				    "NODE",
+				    "CRED",
+				    "LEASE");
+
+			mdb_printf("%?p %-5u %-p %-p %-p\n", addr,
+			    of->f_fid, of->f_node, of->f_cr, of->f_lease);
+		}
+	}
+	return (DCMD_OK);
+}
+
+static int
+smbdurable_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+	mdb_smb_server_t *sv;
+
+	if (!(flags & DCMD_ADDRSPEC)) {
+		mdb_printf("require address of an smb_server_t\n");
+		return (WALK_ERR);
+	}
+
+	sv = mdb_zalloc(sizeof (*sv), UM_SLEEP | UM_GC);
+	if (mdb_ctf_vread(sv, SMBSRV_SCOPE "smb_server_t",
+	    "mdb_smb_server_t", addr, 0) < 0) {
+		mdb_warn("failed to read smb_server at %p", addr);
+		return (DCMD_ERR);
+	}
+
+	if (mdb_pwalk_dcmd("smb_hash_walker", "smbofile",
+	    argc, argv, (uintptr_t)sv->sv_persistid_ht) == -1) {
+		mdb_warn("failed to walk 'smb_ofile'");
+		return (DCMD_ERR);
+	}
+	return (DCMD_OK);
+}
+
+static int
+smb_hash_walk_init(mdb_walk_state_t *wsp)
+{
+	smb_hash_t hash;
+	int ll_off, sll_off, i;
+	uintptr_t addr = wsp->walk_addr;
+
+	if (addr == NULL) {
+		mdb_printf("require address of an smb_hash_t\n");
+		return (WALK_ERR);
+	}
+
+	GET_OFFSET(sll_off, smb_bucket_t, b_list);
+	GET_OFFSET(ll_off, smb_llist_t, ll_list);
+
+	if (mdb_vread(&hash, sizeof (hash), addr) == -1) {
+		mdb_warn("failed to read smb_hash_t at %p", addr);
+		return (WALK_ERR);
+	}
+
+	for (i = 0; i < hash.num_buckets; i++) {
+		wsp->walk_addr = (uintptr_t)hash.buckets +
+		    (i * sizeof (smb_bucket_t)) + sll_off + ll_off;
+		if (mdb_layered_walk("list", wsp) == -1) {
+			mdb_warn("failed to walk 'list'");
+			return (WALK_ERR);
 		}
 	}
+
+	return (WALK_NEXT);
+}
+
+static int
+smb_hash_walk_step(mdb_walk_state_t *wsp)
+{
+	return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer,
+	    wsp->walk_cbdata));
+}
+
+static int
+smbhashstat_cb(uintptr_t addr, const void *data, void *varg)
+{
+	_NOTE(ARGUNUSED(varg))
+	const smb_bucket_t *bucket = data;
+
+	mdb_printf("%-?p ", addr);	/* smb_bucket_t */
+	mdb_printf("%-6u ", bucket->b_list.ll_count);
+	mdb_printf("%-16u", bucket->b_max_seen);
+	mdb_printf("%-u\n", (bucket->b_list.ll_wrop +
+	    bucket->b_list.ll_count) / 2);
+	return (WALK_NEXT);
+}
+
+static int
+smbhashstat_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+	_NOTE(ARGUNUSED(argc, argv))
+	if (!(flags & DCMD_ADDRSPEC)) {
+		mdb_printf("require address of an smb_hash_t\n");
+		return (DCMD_USAGE);
+	}
+
+	if (DCMD_HDRSPEC(flags)) {
+		mdb_printf(
+		    "%<b>%<u>"
+		    "%-?s "
+		    "%-6s "
+		    "%-16s"
+		    "%-s"
+		    "%</u>%</b>\n",
+		    "smb_bucket_t", "count", "largest seen", "inserts");
+	}
+
+	if (mdb_pwalk("smb_hashstat_walker", smbhashstat_cb,
+	    NULL, addr) == -1) {
+		mdb_warn("failed to walk 'smb_ofile'");
+		return (DCMD_ERR);
+	}
+	return (DCMD_OK);
+}
+
+typedef struct smb_hash_wd {
+	smb_bucket_t	*bucket;
+	smb_bucket_t	*end;
+} smb_hash_wd_t;
+
+static int
+smb_hashstat_walk_init(mdb_walk_state_t *wsp)
+{
+	int sll_off, ll_off;
+	smb_hash_t hash;
+	smb_bucket_t *buckets;
+	uintptr_t addr = wsp->walk_addr;
+	uint32_t arr_sz;
+	smb_hash_wd_t *wd;
+
+	if (addr == NULL) {
+		mdb_printf("require address of an smb_hash_t\n");
+		return (WALK_ERR);
+	}
+
+	GET_OFFSET(sll_off, smb_bucket_t, b_list);
+	GET_OFFSET(ll_off, smb_llist_t, ll_list);
+
+	if (mdb_vread(&hash, sizeof (hash), addr) == -1) {
+		mdb_warn("failed to read smb_hash_t at %p", addr);
+		return (WALK_ERR);
+	}
+
+	arr_sz = hash.num_buckets * sizeof (smb_bucket_t);
+	buckets = mdb_alloc(arr_sz, UM_SLEEP | UM_GC);
+	if (mdb_vread(buckets, arr_sz, (uintptr_t)hash.buckets) == -1) {
+		mdb_warn("failed to read smb_bucket_t array at %p",
+		    hash.buckets);
+		return (WALK_ERR);
+	}
+
+	wd = mdb_alloc(sizeof (*wd), UM_SLEEP | UM_GC);
+	wd->bucket = buckets;
+	wd->end = buckets + hash.num_buckets;
+
+	wsp->walk_addr = (uintptr_t)hash.buckets;
+	wsp->walk_data = wd;
+
+	return (WALK_NEXT);
+}
+
+static int
+smb_hashstat_walk_step(mdb_walk_state_t *wsp)
+{
+	int rc;
+	smb_hash_wd_t *wd = wsp->walk_data;
+
+	if (wd->bucket >= wd->end)
+		return (WALK_DONE);
+
+	rc = wsp->walk_callback(wsp->walk_addr, wd->bucket++,
+	    wsp->walk_cbdata);
+
+	wsp->walk_addr += sizeof (smb_bucket_t);
+	return (rc);
+}
+
+/*
+ * smbsrv_leases
+ */
+static int
+smbsrv_leases_dcmd(uintptr_t addr, uint_t flags, int argc,
+    const mdb_arg_t *argv)
+{
+	uint_t		opts;
+	int		ht_off;
+	uintptr_t	ht_addr;
+
+	if (smb_dcmd_getopt(&opts, argc, argv))
+		return (DCMD_USAGE);
+
+	if (!(flags & DCMD_ADDRSPEC)) {
+		mdb_printf("require address of an smb_server_t\n");
+		return (DCMD_USAGE);
+	}
+
+	ht_off = mdb_ctf_offsetof_by_name("smb_server_t", "sv_lease_ht");
+	if (ht_off < 0) {
+		mdb_warn("No .sv_lease_ht in server (old kernel?)");
+		return (DCMD_ERR);
+	}
+	addr += ht_off;
+
+	if (mdb_vread(&ht_addr, sizeof (ht_addr), addr) <= 0) {
+		mdb_warn("failed to read server .sv_lease_ht");
+		return (DCMD_ERR);
+	}
+
+	if (mdb_pwalk_dcmd("smb_hash_walker", "smblease",
+	    argc, argv, ht_addr) == -1) {
+		mdb_warn("failed to walk 'smb_lease'");
+		return (DCMD_ERR);
+	}
+	return (DCMD_OK);
+}
+
+typedef struct mdb_smb_lease {
+	struct smb_node		*ls_node;
+	uint32_t		ls_refcnt;
+	uint16_t		ls_epoch;
+	uint8_t			ls_key[SMB_LEASE_KEY_SZ];
+} mdb_smb_lease_t;
+
+static int
+smblease_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+	mdb_smb_lease_t *ls;
+	uint_t opts;
+	int i;
+
+	if (smb_dcmd_getopt(&opts, argc, argv))
+		return (DCMD_USAGE);
+
+	if (!(flags & DCMD_ADDRSPEC)) {
+		mdb_printf("require address of an smb_lease_t\n");
+		return (DCMD_USAGE);
+	}
+
+	if (((opts & SMB_OPT_WALK) && (opts & SMB_OPT_OFILE)) ||
+	    !(opts & SMB_OPT_WALK)) {
+
+		ls = mdb_zalloc(sizeof (*ls), UM_SLEEP | UM_GC);
+		if (mdb_ctf_vread(ls, SMBSRV_SCOPE "smb_lease_t",
+		    "mdb_smb_lease_t", addr, 0) < 0) {
+			mdb_warn("failed to read smb_lease_t at %p", addr);
+			return (DCMD_ERR);
+		}
+		if (opts & SMB_OPT_VERBOSE) {
+
+			mdb_printf(
+			    "%<b>%<u>SMB lease (%p):%</u>%</b>\n\n", addr);
+
+			mdb_printf("SMB Node: %p\n", ls->ls_node);
+			mdb_printf("Refcount: %u\n", ls->ls_refcnt);
+			mdb_printf("Epoch: %u\n", ls->ls_epoch);
+
+			mdb_printf("Key: [");
+			for (i = 0; i < SMB_LEASE_KEY_SZ; i++) {
+				mdb_printf(" %02x", ls->ls_key[i] & 0xFF);
+				if ((i & 3) == 3)
+					mdb_printf(" ");
+			}
+			mdb_printf(" ]\n");
+		} else {
+			if (DCMD_HDRSPEC(flags))
+				mdb_printf(
+				    "%<b>%<u>"
+				    "%-?s "
+				    "%-?s "
+				    "%-?s%</u>%</b>\n",
+				    "LEASE", "SMB NODE", "KEY");
+
+			mdb_printf("%?p %-p [", addr, ls->ls_node);
+			for (i = 0; i < 8; i++) {
+				mdb_printf(" %02x", ls->ls_key[i] & 0xFF);
+			}
+			mdb_printf(" ...]\n");
+		}
+	}
+
 	return (DCMD_OK);
 }
 
@@ -2097,7 +2460,6 @@
 	smb_llist_t		n_ofile_list;
 	smb_llist_t		n_lock_list;
 	volatile int		flags;
-	smb_oplock_t		n_oplock;
 	struct smb_node		*n_dnode;
 	struct smb_node		*n_unode;
 	char			od_name[MAXNAMELEN];
@@ -2115,7 +2477,6 @@
 	smb_llist_t		n_ofile_list;
 	smb_llist_t		n_lock_list;
 	volatile int		flags;
-	smb_oplock_t		n_oplock;
 	struct smb_node		*n_dnode;
 	struct smb_node		*n_unode;
 	char			od_name[MAXNAMELEN];
@@ -2153,10 +2514,12 @@
 	int		verbose = FALSE;
 	int		print_full_path = FALSE;
 	int		stack_trace = FALSE;
+	int		ol_cnt = 0;
 	vnode_t		vnode;
 	char		od_name[MAXNAMELEN];
 	char		path_name[1024];
-	uintptr_t	list_addr, oplock_addr;
+	uintptr_t	list_addr;
+	struct mdb_smb_oplock *node_oplock;
 
 	if (mdb_getopts(argc, argv,
 	    'v', MDB_OPT_SETBITS, TRUE, &verbose,
@@ -2209,11 +2572,17 @@
 			}
 		}
 	}
+
+	rc = smb_node_get_oplock(addr, &node_oplock);
+	if (rc != DCMD_OK)
+		return (rc);
+	ol_cnt = smb_node_oplock_cnt(node_oplock);
+
 	if (verbose) {
-		int nll_off, wll_off, nol_off, ll_off;
-
+		int nol_off, nll_off, wll_off, ll_off;
+
+		GET_OFFSET(nol_off, smb_node_t, n_ofile_list);
 		GET_OFFSET(nll_off, smb_node_t, n_lock_list);
-		GET_OFFSET(nol_off, smb_node_t, n_oplock);
 		GET_OFFSET(ll_off, smb_llist_t, ll_list);
 		/* This one is optional (for now). */
 		/* GET_OFFSET(wll_off, smb_node_t, n_wlock_list); */
@@ -2226,7 +2595,18 @@
 		mdb_printf("Name: %s\n", od_name);
 		if (print_full_path)
 			mdb_printf("V-node Path: %s\n", path_name);
+		mdb_printf("Reference Count: %u\n", node.n_refcnt);
 		mdb_printf("Ofiles: %u\n", node.n_ofile_list.ll_count);
+		if (node.n_ofile_list.ll_count != 0 && nol_off != -1) {
+			(void) mdb_inc_indent(SMB_DCMD_INDENT);
+			list_addr = addr + nol_off + ll_off;
+			if (mdb_pwalk_dcmd("list", "smbofile", 0,
+			    NULL, list_addr)) {
+				mdb_warn("failed to walk node's ofiles");
+			}
+			(void) mdb_dec_indent(SMB_DCMD_INDENT);
+		}
+
 		mdb_printf("Granted Locks: %u\n",
 		    node.n_lock_list.ll_count);
 		if (node.n_lock_list.ll_count != 0) {
@@ -2251,18 +2631,18 @@
 			}
 			(void) mdb_dec_indent(SMB_DCMD_INDENT);
 		}
-		if (node.n_oplock.ol_count == 0) {
-			mdb_printf("Opportunistic Locks: 0\n");
+		if (ol_cnt == 0) {
+			mdb_printf("Opportunistic Locks: (none)\n");
 		} else {
-			oplock_addr = addr + nol_off;
-			mdb_printf("Opportunistic Lock: %p\n",
-			    oplock_addr);
-			rc = mdb_call_dcmd("smboplock", oplock_addr,
+			mdb_printf("Opportunistic Locks:\n");
+			(void) mdb_inc_indent(SMB_DCMD_INDENT);
+			/* Takes node address */
+			rc = mdb_call_dcmd("smbnode_oplock", addr,
 			    flags, argc, argv);
+			(void) mdb_dec_indent(SMB_DCMD_INDENT);
 			if (rc != DCMD_OK)
 				return (rc);
 		}
-		mdb_printf("Reference Count: %u\n\n", node.n_refcnt);
 	} else {
 		if (DCMD_HDRSPEC(flags)) {
 			mdb_printf(
@@ -2281,7 +2661,7 @@
 		mdb_printf("%-?p %-?p %-18s %-6d %-6d %-8d %-8d %-6d ",
 		    addr, node.vp, od_name, node.n_ofile_list.ll_count,
 		    node.n_lock_list.ll_count, node.n_wlock_list.ll_count,
-		    node.n_oplock.ol_count, node.n_refcnt);
+		    ol_cnt, node.n_refcnt);
 
 		if (print_full_path)
 			mdb_printf("\t%s\n", path_name);
@@ -2518,53 +2898,139 @@
  */
 
 typedef struct mdb_smb_oplock_grant {
-	uint8_t			og_breaking;
-	uint8_t			og_level;
-	struct smb_ofile	*og_ofile;
+	uint32_t		og_state;	/* latest sent to client */
+	uint8_t			onlist_II;
+	uint8_t			onlist_R;
+	uint8_t			onlist_RH;
+	uint8_t			onlist_RHBQ;
+	uint8_t			BreakingToRead;
 } mdb_smb_oplock_grant_t;
 
+static const mdb_bitmask_t
+oplock_bits[] = {
+	{  "READ_CACHING",
+	    READ_CACHING,
+	    READ_CACHING },
+	{  "HANDLE_CACHING",
+	    HANDLE_CACHING,
+	    HANDLE_CACHING },
+	{  "WRITE_CACHING",
+	    WRITE_CACHING,
+	    WRITE_CACHING },
+	{  "EXCLUSIVE",
+	    EXCLUSIVE,
+	    EXCLUSIVE },
+	{  "MIXED_R_AND_RH",
+	    MIXED_R_AND_RH,
+	    MIXED_R_AND_RH },
+	{  "LEVEL_TWO_OPLOCK",
+	    LEVEL_TWO_OPLOCK,
+	    LEVEL_TWO_OPLOCK },
+	{  "LEVEL_ONE_OPLOCK",
+	    LEVEL_ONE_OPLOCK,
+	    LEVEL_ONE_OPLOCK },
+	{  "BATCH_OPLOCK",
+	    BATCH_OPLOCK,
+	    BATCH_OPLOCK },
+	{  "BREAK_TO_TWO",
+	    BREAK_TO_TWO,
+	    BREAK_TO_TWO },
+	{  "BREAK_TO_NONE",
+	    BREAK_TO_NONE,
+	    BREAK_TO_NONE },
+	{  "BREAK_TO_TWO_TO_NONE",
+	    BREAK_TO_TWO_TO_NONE,
+	    BREAK_TO_TWO_TO_NONE },
+	{  "BREAK_TO_READ_CACHING",
+	    BREAK_TO_READ_CACHING,
+	    BREAK_TO_READ_CACHING },
+	{  "BREAK_TO_HANDLE_CACHING",
+	    BREAK_TO_HANDLE_CACHING,
+	    BREAK_TO_HANDLE_CACHING },
+	{  "BREAK_TO_WRITE_CACHING",
+	    BREAK_TO_WRITE_CACHING,
+	    BREAK_TO_WRITE_CACHING },
+	{  "BREAK_TO_NO_CACHING",
+	    BREAK_TO_NO_CACHING,
+	    BREAK_TO_NO_CACHING },
+	{  "NO_OPLOCK",
+	    NO_OPLOCK,
+	    NO_OPLOCK },
+	{  NULL, 0, 0 }
+};
+
+/*
+ * Show smb_ofile_t oplock info
+ * address is the ofile
+ */
+
 /*ARGSUSED*/
 static int
-smboplockgrant_dcmd(uintptr_t addr, uint_t flags, int argc,
+smbofile_oplock_dcmd(uintptr_t addr, uint_t flags, int argc,
     const mdb_arg_t *argv)
 {
-	mdb_smb_oplock_grant_t	grant;
-	char			 *level;
+	mdb_smb_oplock_grant_t	og;
+	int verbose = FALSE;
+	static int og_off;
+
+	if (mdb_getopts(argc, argv,
+	    'v', MDB_OPT_SETBITS, TRUE, &verbose,
+	    NULL) != argc)
+		return (DCMD_USAGE);
 
 	if (!(flags & DCMD_ADDRSPEC))
 		return (DCMD_USAGE);
 
-	/*
-	 * If this is the first invocation of the command, print a nice
-	 * header line for the output that will follow.
-	 */
-	if (DCMD_HDRSPEC(flags)) {
-		mdb_printf("%<u>%-16s %-10s %-16s%</u>\n",
-		    "Grants:", "LEVEL", "OFILE");
+	if (og_off <= 0) {
+		og_off = mdb_ctf_offsetof_by_name(
+		    "smb_ofile_t", "f_oplock");
+		if (og_off < 0) {
+			mdb_warn("cannot lookup: smb_ofile_t .f_oplock");
+			return (DCMD_ERR);
+		}
 	}
 
-	if (mdb_ctf_vread(&grant, SMBSRV_SCOPE "smb_oplock_grant_t",
-	    "mdb_smb_oplock_grant_t", addr, 0) < 0) {
-		mdb_warn("failed to read oplock grant at %p", addr);
+	if (mdb_ctf_vread(&og, SMBSRV_SCOPE "smb_oplock_grant_t",
+	    "mdb_smb_oplock_grant_t", addr + og_off, 0) < 0) {
+		mdb_warn("failed to read oplock grant in ofile at %p", addr);
 		return (DCMD_ERR);
 	}
 
-	switch (grant.og_level) {
-	case SMB_OPLOCK_EXCLUSIVE:
-		level = "EXCLUSIVE";
-		break;
-	case SMB_OPLOCK_BATCH:
-		level = "BATCH";
-		break;
-	case SMB_OPLOCK_LEVEL_II:
-		level = "LEVEL_II";
-		break;
-	default:
-		level = "UNKNOWN";
-		break;
+	if (verbose) {
+		mdb_printf("%<b>%<u>SMB ofile (oplock_grant) "
+		    "(%p):%</u>%</b>\n", addr);
+		mdb_printf("State: 0x%x <%b>\n",
+		    og.og_state,
+		    og.og_state,
+		    oplock_bits);
+		mdb_printf("OnList_II: %d\n", og.onlist_II);
+		mdb_printf("OnList_R: %d\n", og.onlist_R);
+		mdb_printf("OnList_RH: %d\n", og.onlist_RH);
+		mdb_printf("OnList_RHBQ: %d\n", og.onlist_RHBQ);
+		mdb_printf("BrkToRead: %d\n", og.BreakingToRead);
+
+	} else {
+
+		if (DCMD_HDRSPEC(flags)) {
+			mdb_printf("%<u>%-16s %-10s %-16s%</u>\n",
+			    "OFILE", "STATE", "OnList...");
+		}
+
+		mdb_printf("%-16p", addr);
+		mdb_printf(" 0x%x", og.og_state);
+		if (og.onlist_II)
+			mdb_printf(" II");
+		if (og.onlist_R)
+			mdb_printf(" R");
+		if (og.onlist_RH)
+			mdb_printf(" RH");
+		if (og.onlist_RHBQ)
+			mdb_printf(" RHBQ");
+		if (og.BreakingToRead)
+			mdb_printf(" BrkToRd");
+		mdb_printf("\n");
 	}
 
-	mdb_printf("%-16p %-10s %-16p", addr, level, grant.og_ofile);
 	return (DCMD_OK);
 }
 
@@ -2575,51 +3041,130 @@
  */
 
 typedef struct mdb_smb_oplock {
-	uint8_t			ol_brk_pending;
-	uint8_t			ol_break;
-	uint32_t		ol_count;	/* number of grants */
-	list_t			ol_grants;	/* list of smb_oplock_grant_t */
+	struct smb_ofile	*excl_open;
+	uint32_t		ol_state;
+	int32_t			cnt_II;
+	int32_t			cnt_R;
+	int32_t			cnt_RH;
+	int32_t			cnt_RHBQ;
+	int32_t			waiters;
 } mdb_smb_oplock_t;
 
+/*
+ * Helpers for smbnode_dcmd and smbnode_oplock_dcmd
+ */
+
+/*
+ * Read the smb_oplock_t part of the node
+ * addr is the smb_node
+ */
+static int
+smb_node_get_oplock(uintptr_t addr, struct mdb_smb_oplock **ol_ret)
+{
+	mdb_smb_oplock_t *ol;
+	static int ol_off;
+
+	if (ol_off <= 0) {
+		ol_off = mdb_ctf_offsetof_by_name(
+		    "smb_node_t", "n_oplock");
+		if (ol_off < 0) {
+			mdb_warn("cannot lookup: smb_node_t .n_oplock");
+			return (DCMD_ERR);
+		}
+	}
+
+	ol = mdb_alloc(sizeof (*ol), UM_SLEEP | UM_GC);
+
+	if (mdb_ctf_vread(ol, SMBSRV_SCOPE "smb_oplock_t",
+	    "mdb_smb_oplock_t", addr + ol_off, 0) < 0) {
+		mdb_warn("failed to read smb_oplock in node at %p", addr);
+		return (DCMD_ERR);
+	}
+
+	*ol_ret = ol;
+	return (DCMD_OK);
+}
+
+/*
+ * Return the oplock count
+ */
+static int
+smb_node_oplock_cnt(struct mdb_smb_oplock *ol)
+{
+	int ol_cnt = 0;
+
+	/* Compute total oplock count. */
+	if (ol->excl_open != NULL)
+		ol_cnt++;
+	ol_cnt += ol->cnt_II;
+	ol_cnt += ol->cnt_R;
+	ol_cnt += ol->cnt_RH;
+
+	return (ol_cnt);
+}
+
+/*
+ * Show smb_node_t oplock info, and optionally the
+ * list of ofiles with oplocks on this node.
+ * Address is the smb_node_t.
+ */
+
 /*ARGSUSED*/
 static int
-smboplock_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+smbnode_oplock_dcmd(uintptr_t addr, uint_t flags, int argc,
+    const mdb_arg_t *argv)
 {
-	mdb_smb_oplock_t oplock;
-	uintptr_t	list_addr;
-	int		og_off;
+	mdb_smb_oplock_t *ol;
+	int verbose = FALSE;
+	int ol_cnt, rc;
+	int fl_off, ll_off;
+
+	if (mdb_getopts(argc, argv,
+	    'v', MDB_OPT_SETBITS, TRUE, &verbose,
+	    NULL) != argc)
+		return (DCMD_USAGE);
 
 	if (!(flags & DCMD_ADDRSPEC))
 		return (DCMD_USAGE);
 
-	if (mdb_ctf_vread(&oplock, SMBSRV_SCOPE "smb_oplock_t",
-	    "mdb_smb_oplock_t", addr, 0) < 0) {
-		mdb_warn("failed to read struct smb_oplock at %p", addr);
-		return (DCMD_ERR);
+	rc = smb_node_get_oplock(addr, &ol);
+	if (rc != DCMD_OK)
+		return (rc);
+	ol_cnt = smb_node_oplock_cnt(ol);
+
+	if (verbose) {
+		mdb_printf("%<b>%<u>SMB node (oplock) "
+		    "(%p):%</u>%</b>\n", addr);
+		mdb_printf("State: 0x%x <%b>\n",
+		    ol->ol_state,
+		    ol->ol_state,
+		    oplock_bits);
+		mdb_printf("Exclusive Open: %p\n", ol->excl_open);
+		mdb_printf("cnt_II: %d\n", ol->cnt_II);
+		mdb_printf("cnt_R: %d\n", ol->cnt_R);
+		mdb_printf("cnt_RH: %d\n", ol->cnt_RH);
+		mdb_printf("cnt_RHBQ: %d\n", ol->cnt_RHBQ);
+		mdb_printf("waiters: %d\n", ol->waiters);
+	} else {
+		if (DCMD_HDRSPEC(flags)) {
+			mdb_printf("%<u>%-16s %-10s %-16s%</u>\n",
+			    "NODE", "STATE", "OPLOCKS");
+		}
+		mdb_printf("%-16p 0x%x %d\n",
+		    addr, ol->ol_state, ol_cnt);
 	}
 
-	if (oplock.ol_count == 0)
+	if (ol_cnt == 0)
 		return (DCMD_OK);
 
+	GET_OFFSET(fl_off, smb_node_t, n_ofile_list);
+	GET_OFFSET(ll_off, smb_llist_t, ll_list);
+
 	(void) mdb_inc_indent(SMB_DCMD_INDENT);
-	switch (oplock.ol_break) {
-	case SMB_OPLOCK_BREAK_TO_NONE:
-		mdb_printf("Break Pending: BREAK_TO_NONE\n");
-		break;
-	case SMB_OPLOCK_BREAK_TO_LEVEL_II:
-		mdb_printf(
-		    "Break Pending: BREAK_TO_LEVEL_II\n");
-		break;
-	default:
-		break;
-	}
-
-	GET_OFFSET(og_off, smb_oplock_t, ol_grants);
-	list_addr = addr + og_off;
-
-	if (mdb_pwalk_dcmd("list", "smboplockgrant",
-	    argc, argv, list_addr)) {
-		mdb_warn("failed to walk oplock grants");
+
+	if (mdb_pwalk_dcmd("list", "smbofile_oplock",
+	    argc, argv, addr + fl_off + ll_off)) {
+		mdb_warn("failed to walk ofile oplocks");
 	}
 
 	(void) mdb_dec_indent(SMB_DCMD_INDENT);
@@ -3170,6 +3715,13 @@
 	while (x->ex_dcmd) {
 		if (x->ex_mask & opts) {
 			ex_off = (x->ex_offset)();
+			if (ex_off < 0) {
+				mdb_warn("failed to get the list offset for %s",
+				    x->ex_name);
+				rc = ex_off;
+				break;
+			}
+
 			rc = mdb_pwalk_dcmd("list", x->ex_dcmd, argc, argv,
 			    addr + ex_off);
 
@@ -3339,12 +3891,20 @@
 	    "[-v]",
 	    "print smb_file_t information",
 	    smbofile_dcmd },
-	{   "smboplock", NULL,
-	    "print smb_oplock_t information",
-	    smboplock_dcmd },
-	{   "smboplockgrant", NULL,
-	    "print smb_oplock_grant_t information",
-	    smboplockgrant_dcmd },
+	{   "smbsrv_leases",
+	    "[-v]",
+	    "print lease table for a server",
+	    smbsrv_leases_dcmd },
+	{   "smblease",
+	    "[-v]",
+	    "print smb_lease_t information",
+	    smblease_dcmd },
+	{   "smbnode_oplock", NULL,
+	    "print smb_node_t oplock information",
+	    smbnode_oplock_dcmd },
+	{   "smbofile_oplock", NULL,
+	    "print smb_ofile_t oplock information",
+	    smbofile_oplock_dcmd },
 	{   "smbace", "[-v]",
 	    "print smb_ace_t information",
 	    smbace_dcmd },
@@ -3363,6 +3923,15 @@
 	{   "smb_mbuf_dump", ":[max_len]",
 	    "print mbuf_t data",
 	    smb_mbuf_dump_dcmd },
+	{   "smbdurable",
+	    "[-v]",
+	    "list ofiles on sv->sv_persistid_ht",
+	    smbdurable_dcmd },
+	{   "smbhashstat",
+	    "[-v]",
+	    "list stats from an smb_hash_t structure",
+	    smbhashstat_dcmd },
+
 	{ NULL }
 };
 
@@ -3397,6 +3966,19 @@
 	    smb_mbuf_walk_step,
 	    NULL,
 	    NULL },
+	{   "smb_hash_walker",
+	    "walk an smb_hash_t structure",
+	    smb_hash_walk_init,
+	    smb_hash_walk_step,
+	    NULL,
+	    NULL },
+	{   "smb_hashstat_walker",
+	    "walk the buckets from an smb_hash_t structure",
+	    smb_hashstat_walk_init,
+	    smb_hashstat_walk_step,
+	    NULL,
+	    NULL },
+
 	{ NULL }
 };
 
--- a/usr/src/cmd/mdb/sparc/v7/libfksmbsrv/Makefile	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/mdb/sparc/v7/libfksmbsrv/Makefile	Wed Jun 12 22:46:54 2019 +0200
@@ -25,12 +25,13 @@
 #
 # Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
 # Copyright 2015 Gary Mills
+# Copyright 2019 Peter Tribble.
 #
 
 MODULE = libfksmbsrv.so
 MDBTGT = proc
 
-MODSRCS = smbsrv.c list.c
+MODSRCS = smbsrv.c smbsrv_pcap.c list.c
 
 include ../../../../Makefile.cmd
 include ../../Makefile.sparcv7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/mdb/sparc/v7/libmlsvc/Makefile	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,46 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License").  You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+# Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2019 Peter Tribble.
+#
+
+MODULE = libmlsvc.so
+MDBTGT = proc
+
+MODSRCS = mlsvc.c smb_ht.c
+
+include ../../../../Makefile.cmd
+include ../../Makefile.sparcv7
+include ../../../Makefile.module
+
+MODSRCS_DIR = ../../../common/modules/libmlsvc
+
+# CPPFLAGS += -I$(SRC)/lib/smbsrv/libmlsvc
+CPPFLAGS += -I$(SRC)/uts/common
+
+CSTD=	$(CSTD_GNU99)
+C99MODE=	-xc99=%all
+C99LMODE=	-Xc99=%all
--- a/usr/src/cmd/mdb/sparc/v9/libfksmbsrv/Makefile	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/mdb/sparc/v9/libfksmbsrv/Makefile	Wed Jun 12 22:46:54 2019 +0200
@@ -25,12 +25,13 @@
 #
 # Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
 # Copyright 2015 Gary Mills
+# Copyright 2019 Peter Tribble.
 #
 
 MODULE = libfksmbsrv.so
 MDBTGT = proc
 
-MODSRCS = smbsrv.c list.c
+MODSRCS = smbsrv.c smbsrv_pcap.c list.c
 
 include ../../../../Makefile.cmd
 include ../../../../Makefile.cmd.64
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/mdb/sparc/v9/libmlsvc/Makefile	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,47 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License").  You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+# Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2019 Peter Tribble.
+#
+
+MODULE = libmlsvc.so
+MDBTGT = proc
+
+MODSRCS = mlsvc.c smb_ht.c
+
+include ../../../../Makefile.cmd
+include ../../../../Makefile.cmd.64
+include ../../Makefile.sparcv9
+include ../../../Makefile.module
+
+MODSRCS_DIR = ../../../common/modules/libmlsvc
+
+# CPPFLAGS += -I$(SRC)/lib/smbsrv/libmlsvc
+CPPFLAGS += -I$(SRC)/uts/common
+
+CSTD=	$(CSTD_GNU99)
+C99MODE=	-xc99=%all
+C99LMODE=	-Xc99=%all
--- a/usr/src/cmd/nscd/nscd_frontend.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/nscd/nscd_frontend.c	Wed Jun 12 22:46:54 2019 +0200
@@ -23,6 +23,7 @@
  * Use is subject to license terms.
  * Copyright 2012 Milan Jurik. All rights reserved.
  * Copyright 2018 Joyent, Inc.
+ * Copyright 2019 Nexenta Systems, Inc.
  */
 
 #include <stdlib.h>
@@ -88,7 +89,7 @@
 static void *
 server_tsd_bind(void *arg)
 {
-	static void *value = 0;
+	static void *value = "NON-NULL TSD";
 
 	(void) thr_setname(thr_self(), "server_tsd_bind");
 
@@ -129,6 +130,7 @@
 	(void) mutex_lock(&create_lock);
 	num_servers--;
 	(void) mutex_unlock(&create_lock);
+	(void) thr_setspecific(server_key, NULL);
 }
 
 /*
--- a/usr/src/cmd/ptools/Makefile.bld	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/ptools/Makefile.bld	Wed Jun 12 22:46:54 2019 +0200
@@ -169,7 +169,7 @@
 	-$(LN) $(ISAEXEC) $(ROOTBINPROG)
 	-$(INSTALL_$(PTOOL_TYPE))
 
-$(ROOTBINLN):
+$(ROOTBINLN): $(ROOTISAPROG)
 	-$(RM) $@
 	-$(LN) $(ISAEXEC) $@
 
--- a/usr/src/cmd/smbsrv/Makefile	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/smbsrv/Makefile	Wed Jun 12 22:46:54 2019 +0200
@@ -22,11 +22,11 @@
 # Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-# Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
 #
 
 SUBDIRS = smbadm smbd smbstat dtrace fksmbd bind-helper \
-	test-msgbuf
+	test-msgbuf testoplock
 MSGSUBDIRS = smbadm smbstat
 
 include ../Makefile.cmd
--- a/usr/src/cmd/smbsrv/fksmbd/Makefile	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/smbsrv/fksmbd/Makefile	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
 #
 #
 # Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
-# Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 #
 
 
@@ -76,11 +76,11 @@
 CPPFLAGS += -D_LARGEFILE64_SOURCE=1
 CPPFLAGS += -DFKSMBD
 # Always debug here
-CPPFLAGS += -DDEBUG 
+CPPFLAGS += -DDEBUG
 CPPFLAGS += $(INCS)
 
 LDFLAGS += $(ZNOLAZYLOAD)
-LDFLAGS += -R/usr/lib/smbsrv
+LDFLAGS += '-R$$ORIGIN' '-R$$ORIGIN/..'
 LDLIBS += -L$(ROOT)/usr/lib/smbsrv
 LDLIBS += -lfksmbsrv -lfakekernel
 # prefer to keep libs ordered by dependence
--- a/usr/src/cmd/smbsrv/fksmbd/Run.sh	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/smbsrv/fksmbd/Run.sh	Wed Jun 12 22:46:54 2019 +0200
@@ -12,7 +12,7 @@
 #
 
 #
-# Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 #
 
 # Helper program to run fksmbd (user-space smbd for debugging)
@@ -55,6 +55,10 @@
 LD_LIBRARY_PATH=$ROOT/usr/lib/smbsrv:$ROOT/usr/lib:$ROOT/lib
 export LD_LIBRARY_PATH
 
+# Enable everything, for debugging
+export SMB_MAX_PROTOCOL=3
+export SMB_SIGNING=require
+
 # normally runs with cwd=/ but this is more careful
 cd /var/smb
 
--- a/usr/src/cmd/smbsrv/fksmbd/fksmbd_kmod.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/smbsrv/fksmbd/fksmbd_kmod.c	Wed Jun 12 22:46:54 2019 +0200
@@ -69,6 +69,24 @@
 	smbd_report("maxconnections=%d, maxworkers=%d",
 	    ioc->maxconnections, ioc->maxworkers);
 
+	if ((s = getenv("SMB_MAX_PROTOCOL")) != NULL) {
+		switch (s[0]) {
+		case '1':
+			ioc->max_protocol = SMB_VERS_1;
+			break;
+		case '2':
+			ioc->max_protocol = SMB_VERS_2_1;
+			break;
+		case '3':
+			ioc->max_protocol = SMB_VERS_3_0;
+			break;
+		default:
+			smbd_report("env SMB_MAX_PROTOCOL invalid");
+			break;
+		}
+	}
+	smbd_report("max_protocol=0x%x", ioc->max_protocol);
+
 	if ((s = getenv("SMB_SIGNING")) != NULL) {
 		ioc->signing_enable = 0;
 		ioc->signing_required = 0;
--- a/usr/src/cmd/smbsrv/smbd/server.xml	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/smbsrv/smbd/server.xml	Wed Jun 12 22:46:54 2019 +0200
@@ -184,7 +184,7 @@
 		<propval name='max_connections' type='integer'
 			value='100000' override='true'/>
 		<propval name='keep_alive' type='integer'
-			value='5400' override='true'/>
+			value='0' override='true'/>
 		<propval name='restrict_anonymous' type='boolean'
 			value='false' override='true'/>
 		<propval name='signing_enabled' type='boolean'
@@ -229,6 +229,8 @@
 			value='true' override='true'/>
 		<propval name='max_protocol' type='astring'
 			value='' override='true'/>
+		<propval name='encrypt' type='astring'
+			value='disabled' override='true'/>
 		<propval name='initial_credits' type='integer'
 			value='20' override='true'/>
 		<propval name='maximum_credits' type='integer'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/.dbxrc	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,23 @@
+
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
+#
+
+set -o emacs
+
+# testoplock is always 32-bit
+setenv LD_LIBRARY_PATH ${ROOT}/usr/lib/smbsrv:${ROOT}/usr/lib:${ROOT}/lib
+
+echo 'Do one of: attach ${PID}'
+echo 'or: debug ${ROOT}/usr/lib/smbsrv/testoplock'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/Makefile	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,121 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
+#
+
+
+PROG=	testoplock
+
+OBJS_LOCAL=	tol_main.o tol_misc.o
+OBJS_SMBSRV=	smb_cmn_oplock.o
+OBJS_LIBSMB=	smb_status_tbl.o
+
+OBJS=	${OBJS_LOCAL} ${OBJS_SMBSRV}  ${OBJS_LIBSMB}
+
+SMBSRV_SRCDIR=../../../uts/common/fs/smbsrv
+SRCS=	${OBJS_LOCAL:.o=.c} \
+	${OBJS_SMBSRV:%.o=${SMBSRV_SRCDIR}/%.c}
+
+include ../../Makefile.cmd
+include ../../Makefile.ctf
+
+# Note: need our sys includes _before_ ENVCPPFLAGS, proto etc.
+CPPFLAGS.first += -I.
+CPPFLAGS.first += -I../../../lib/libfakekernel/common
+CPPFLAGS.first += -I../../../lib/smbsrv/libfksmbsrv/common
+
+INCS +=	-I../../../uts/common
+
+CSTD=		$(CSTD_GNU99)
+
+CFLAGS += $(CCVERBOSE)
+CFLAGS64 += $(CCVERBOSE)
+
+CPPFLAGS.master=$(DTEXTDOM) $(DTS_ERRNO)
+
+#	CPPFLAGS is deliberatly set with a "=" and not a "+="...
+CPPFLAGS=	$(CPPFLAGS.first) $(CPPFLAGS.master)
+
+CPPFLAGS += -D_REENTRANT
+CPPFLAGS += -DTESTJIG
+CPPFLAGS += -Dsyslog=smb_syslog
+CPPFLAGS += -D_LARGEFILE64_SOURCE=1
+# Always debug here
+CPPFLAGS += -DDEBUG
+CPPFLAGS += $(INCS)
+
+LDFLAGS += $(ZNOLAZYLOAD)
+LDFLAGS += '-R$$ORIGIN/..'
+LDLIBS += -lfakekernel -lcmdutils
+
+LINTFLAGS += -xerroff=E_NAME_DEF_NOT_USED2
+LINTFLAGS += -xerroff=E_NAME_USED_NOT_DEF2
+LINTFLAGS += -xerroff=E_INCONS_ARG_DECL2
+LINTFLAGS += -xerroff=E_INCONS_VAL_TYPE_DECL2
+
+ROOTSMBDDIR = $(ROOTLIB)/smbsrv
+ROOTSMBDFILE = $(PROG:%=$(ROOTSMBDDIR)/%)
+
+.KEEP_STATE:
+
+all: $(PROG)
+
+$(PROG): $(OBJS)
+	$(LINK.c) -o $(PROG) $(OBJS) $(LDLIBS)
+	$(POST_PROCESS)
+
+clean:
+	-$(RM) $(OBJS)
+
+lint:	# lint_SRCS
+
+include ../../Makefile.targ
+
+install: all $(ROOTSMBDFILE)
+
+
+tol_main.o : tol_main.c
+	$(CC) $(CFLAGS) $(CPPFLAGS) -D_KMEMUSER -c tol_main.c
+	$(POST_PROCESS_O)
+
+tol_misc.o : tol_misc.c
+	$(CC) $(CFLAGS) $(CPPFLAGS) -D_FAKE_KERNEL \
+	  -I../../../uts/common/smbsrv \
+	  -I../../../common/smbsrv -c tol_misc.c
+	$(POST_PROCESS_O)
+
+# OBJS_SMBSRV
+%.o: ../../../uts/common/fs/smbsrv/%.c
+	$(CC) $(CFLAGS) $(CPPFLAGS) -D_FAKE_KERNEL \
+	  -I../../../uts/common/smbsrv \
+	  -I../../../common/smbsrv -c $<
+	$(POST_PROCESS_O)
+
+# OBJS_LIBSMB
+%.o: ../../../lib/smbsrv/libsmb/common/%.c
+	$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
+	$(POST_PROCESS_O)
+
+
+$(ROOTSMBDDIR)/%: %
+	$(INS.file)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/Run-cmd.sh	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
+#
+
+# Helper program to run fksmbd (user-space smbd for debugging)
+# using binaries from the proto area.
+
+[ -n "$ROOT" ] || {
+  echo "Need a bldenv to set ROOT=..."
+  exit 1;
+}
+
+# OK, setup env. to run it.
+
+LD_LIBRARY_PATH=$ROOT/usr/lib:$ROOT/lib
+export LD_LIBRARY_PATH
+
+# run with the passed options
+exec $ROOT/usr/lib/smbsrv/testoplock "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/Run-tests.sh	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
+#
+
+# Helper program to run fksmbd (user-space smbd for debugging)
+# using binaries from the proto area.
+
+[ -n "$ROOT" ] || {
+  echo "Need a bldenv to set ROOT=..."
+  exit 1;
+}
+
+# OK, setup env. to run it.
+
+LD_LIBRARY_PATH=$ROOT/usr/lib:$ROOT/lib
+export LD_LIBRARY_PATH
+
+TOL=$ROOT/usr/lib/smbsrv/testoplock
+
+TESTS=${@:-case??.txt}
+
+# run the test cases
+for t in $TESTS
+do
+    name=${t%.txt}
+    $TOL < $name.txt > $name.tmp
+    if diff -u $name.tmp $name.ref >/dev/null 2>&1 ; then
+	echo "$name PASS"
+	rm $name.tmp
+    else
+	echo "$name FAIL"
+	diff -u $name.tmp $name.ref
+    fi
+done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case01.ref	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,29 @@
+open 1
+ open 1 OK
+req 1
+ req oplock fid=1 ret oplock=0x400 status=0x0 (SUCCESS)
+show
+ ol_state=0x410 ( BATCH_OPLOCK EXCLUSIVE )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease= OgState=0x400 Brk=0x0 Excl=Y onlist:   
+brk-open 2
+*smb_oplock_ind_break fid=1 NewLevel=0x100, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+show
+ ol_state=0x100410 ( BREAK_TO_TWO BATCH_OPLOCK EXCLUSIVE )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease= OgState=0x400 Brk=0x100000 Excl=Y onlist:   
+ack 1
+ ack: break fid=1, newstate=0x100, status=0x0 (SUCCESS)
+open 2
+ open 2 OK
+req 2 0x100
+ req oplock fid=2 ret oplock=0x100 status=0x0 (SUCCESS)
+show
+ ol_state=0x100 ( LEVEL_TWO_OPLOCK )
+ Excl=n cnt_II=2 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II  
+  fid=2 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case01.txt	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,10 @@
+# Input for testoplock, case 01
+open 1
+req 1
+show
+brk-open 2
+show
+ack 1
+open 2
+req 2 0x100
+show
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case02.ref	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,29 @@
+open 1
+ open 1 OK
+req 1 0x807
+ req oplock fid=1 ret oplock=0x807 status=0x0 (SUCCESS)
+show
+ ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease= OgState=0x807 Brk=0x0 Excl=Y onlist:   
+brk-open 2
+*smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+show
+ ol_state=0x30017 ( BREAK_TO_HANDLE_CACHING BREAK_TO_READ_CACHING EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease= OgState=0x807 Brk=0x30000 Excl=Y onlist:   
+ack 1
+ ack: break fid=1, newstate=0x803, status=0x0 (SUCCESS)
+open 2
+ open 2 OK
+req 2 0x803
+ req oplock fid=2 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=2 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease= OgState=0x803 Brk=0x0 Excl=N onlist:   RH
+  fid=2 Lease= OgState=0x803 Brk=0x0 Excl=N onlist:   RH
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case02.txt	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,10 @@
+# Input for testoplock, case 02
+open 1
+req 1 0x807
+show
+brk-open 2
+show
+ack 1
+open 2
+req 2 0x803
+show
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case03.ref	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,20 @@
+open 1 3
+ open 1 OK
+req 1 0x803
+ req oplock fid=1 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease=3 OgState=0x803 Brk=0x0 Excl=N onlist:   RH
+open 2 3
+ open 2 OK
+req 2 0x803
+*smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=0, ComplStatus=0x215 (OPLOCK_SWITCHED_TO_NEW_HANDLE)
+ req oplock fid=2 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=3 OgState=0x0 Brk=0x0 Excl=N onlist:   
+  fid=2 Lease=3 OgState=0x803 Brk=0x0 Excl=N onlist:   RH
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case03.txt	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,7 @@
+# Input for testoplock, case 03
+open 1 3
+req 1 0x803
+show
+open 2 3
+req 2 0x803
+show
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case04.ref	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,20 @@
+open 1 3
+ open 1 OK
+req 1 0x801
+ req oplock fid=1 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease=3 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
+open 2 3
+ open 2 OK
+req 2 0x801
+*smb_oplock_ind_break fid=1 NewLevel=0x1, AckReq=0, ComplStatus=0x215 (OPLOCK_SWITCHED_TO_NEW_HANDLE)
+ req oplock fid=2 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=3 OgState=0x0 Brk=0x0 Excl=N onlist:   
+  fid=2 Lease=3 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case04.txt	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,7 @@
+# Input for testoplock, case 04
+open 1 3
+req 1 0x801
+show
+open 2 3
+req 2 0x801
+show
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case05.ref	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,65 @@
+open 2 4
+ open 2 OK
+req 2 0x803
+ req oplock fid=2 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=2 Lease=4 OgState=0x803 Brk=0x0 Excl=N onlist:   RH
+open 3 4
+ open 3 OK
+req 3 0x803
+*smb_oplock_ind_break fid=2 NewLevel=0x3, AckReq=0, ComplStatus=0x215 (OPLOCK_SWITCHED_TO_NEW_HANDLE)
+ req oplock fid=3 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist:   
+  fid=3 Lease=4 OgState=0x803 Brk=0x0 Excl=N onlist:   RH
+open 1
+ open 1 OK
+brk-write 1
+*smb_oplock_ind_break fid=3 NewLevel=0x0, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-write 1 ret status=0x0 (SUCCESS)
+show
+ ol_state=0x80003 ( BREAK_TO_NO_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=1
+ ofile_cnt=3
+  fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist:   
+  fid=3 Lease=4 OgState=0x803 Brk=0x80000 Excl=N onlist:    RHBQ(to none)
+  fid=1 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:   
+ack 3
+ ack: break fid=3, newstate=0x800, status=0x0 (SUCCESS)
+show
+ ol_state=0x10000000 ( NO_OPLOCK )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=3
+  fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist:   
+  fid=3 Lease=4 OgState=0x800 Brk=0x0 Excl=N onlist:   
+  fid=1 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:   
+open 4 4
+ open 4 OK
+req 4 0x803
+ req oplock fid=4 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=4
+  fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist:   
+  fid=3 Lease=4 OgState=0x800 Brk=0x0 Excl=N onlist:   
+  fid=1 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:   
+  fid=4 Lease=4 OgState=0x803 Brk=0x0 Excl=N onlist:   RH
+close 4
+*smb_oplock_ind_break fid=4 NewLevel=0x0, AckReq=0, ComplStatus=0x216 (OPLOCK_HANDLE_CLOSED)
+ close OK
+req 3 0x803
+ req oplock fid=3 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=3
+  fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist:   
+  fid=3 Lease=4 OgState=0x803 Brk=0x0 Excl=N onlist:   RH
+  fid=1 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case05.txt	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,18 @@
+# Input for testoplock, case 05
+open 2 4
+req 2 0x803
+show
+open 3 4
+req 3 0x803
+show
+open 1
+brk-write 1
+show
+ack 3
+show
+open 4 4
+req 4 0x803
+show
+close 4
+req 3 0x803
+show
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case06.ref	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,56 @@
+open 1 1
+ open 1 OK
+req 1 0x801
+ req oplock fid=1 ret oplock=0x801 status=0x0 (SUCCESS)
+open 2 2
+ open 2 OK
+req 2 0x801
+ req oplock fid=2 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=2 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
+  fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
+brk-write 1
+*smb_oplock_ind_break fid=2 NewLevel=0x0, AckReq=0, ComplStatus=0x0 (SUCCESS)
+ brk-write 1 ret status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
+  fid=2 Lease=2 OgState=0x800 Brk=0x0 Excl=N onlist:   
+open 3 2
+ open 3 OK
+req 3 0x801
+ req oplock fid=3 ret oplock=0x801 status=0x0 (SUCCESS)
+close 3
+*smb_oplock_ind_break fid=3 NewLevel=0x0, AckReq=0, ComplStatus=0x216 (OPLOCK_HANDLE_CLOSED)
+ close OK
+req 2 0x801
+ req oplock fid=2 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=2 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
+  fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
+brk-write 2
+*smb_oplock_ind_break fid=1 NewLevel=0x0, AckReq=0, ComplStatus=0x0 (SUCCESS)
+ brk-write 2 ret status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x800 Brk=0x0 Excl=N onlist:   
+  fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
+brk-write 1
+*smb_oplock_ind_break fid=2 NewLevel=0x0, AckReq=0, ComplStatus=0x0 (SUCCESS)
+ brk-write 1 ret status=0x0 (SUCCESS)
+show
+ ol_state=0x10000000 ( NO_OPLOCK )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x800 Brk=0x0 Excl=N onlist:   
+  fid=2 Lease=2 OgState=0x800 Brk=0x0 Excl=N onlist:   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case06.txt	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,24 @@
+# Input for testoplock, case 06
+# Modeled after smbtorture smb2.lease.nobreakself
+open 1 1
+req 1 0x801
+open 2 2
+req 2 0x801
+# both 1,2 should have R
+show
+# write 1 should leave 1:R 2:none
+brk-write 1
+show
+# upgrade 2 back to R
+open 3 2
+req 3 0x801
+close 3
+# ind_break will "move" the lease to h2 (1:R 2:R)
+req 2 0x801
+show
+# write 2 should leave 1:none 2:R
+brk-write 2
+show
+# write 1 should leave 1:none 2:none
+brk-write 1
+show
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case07.ref	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,20 @@
+open 1 3
+ open 1 OK
+req 1 0x803
+ req oplock fid=1 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease=3 OgState=0x803 Brk=0x0 Excl=N onlist:   RH
+open 2 3
+ open 2 OK
+req 2 0x807
+*smb_oplock_ind_break fid=1 NewLevel=0x7, AckReq=0, ComplStatus=0x215 (OPLOCK_SWITCHED_TO_NEW_HANDLE)
+ req oplock fid=2 ret oplock=0x807 status=0x0 (SUCCESS)
+show
+ ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=2) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=3 OgState=0x0 Brk=0x0 Excl=N onlist:   
+  fid=2 Lease=3 OgState=0x807 Brk=0x0 Excl=Y onlist:   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case07.txt	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,8 @@
+# Input for testoplock, case 07
+# Modeled after smbtorture smb2.lease.upgrade
+open 1 3
+req 1 0x803
+show
+open 2 3
+req 2 0x807
+show
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case08.ref	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,36 @@
+open 1 1
+ open 1 OK
+req 1 0x805
+ req oplock fid=1 ret oplock=0x805 status=0x0 (SUCCESS)
+show
+ ol_state=0x15 ( EXCLUSIVE WRITE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease=1 OgState=0x805 Brk=0x0 Excl=Y onlist:   
+open 2 2
+ open 2 OK
+brk-open 2
+*smb_oplock_ind_break fid=1 NewLevel=0x1, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+show
+ ol_state=0x10015 ( BREAK_TO_READ_CACHING EXCLUSIVE WRITE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x805 Brk=0x10000 Excl=Y onlist:   
+  fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist:   
+ack 1
+ ack: break fid=1, newstate=0x801, status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
+  fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist:   
+req 2 0x801
+ req oplock fid=2 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=2 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
+  fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case08.txt	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,13 @@
+# Input for testoplock, case 08
+# Modeled after smbtorture smb2.lease.upgrade3
+# sub-case:  "R" "RH" "RW" "R"
+open 1 1
+req 1 0x805
+show
+open 2 2
+brk-open 2
+show
+ack 1
+show
+req 2 0x801
+show
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case09.ref	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,27 @@
+open 1
+ open 1 OK
+req 1 0x100
+ req oplock fid=1 ret oplock=0x100 status=0x0 (SUCCESS)
+show
+ ol_state=0x100 ( LEVEL_TWO_OPLOCK )
+ Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II  
+open 2 2
+ open 2 OK
+brk-open 2
+ brk-open 2 ret status=0x0 (SUCCESS)
+show
+ ol_state=0x100 ( LEVEL_TWO_OPLOCK )
+ Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II  
+  fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist:   
+req 2 0x803
+ req oplock fid=2 ret oplock=0x0 status=0xc00000e2 (OPLOCK_NOT_GRANTED)
+show
+ ol_state=0x100 ( LEVEL_TWO_OPLOCK )
+ Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II  
+  fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist:   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case09.txt	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,10 @@
+# Input for testoplock, case 09
+# Modeled after smbtorture smb2.lease.oplock
+open 1
+req 1 0x100
+show
+open 2 2
+brk-open 2
+show
+req 2 0x803
+show
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case10.ref	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,31 @@
+open 1
+ open 1 OK
+req 1 0x100
+ req oplock fid=1 ret oplock=0x100 status=0x0 (SUCCESS)
+show
+ ol_state=0x100 ( LEVEL_TWO_OPLOCK )
+ Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II  
+open 2 2
+ open 2 OK
+brk-open 2
+ brk-open 2 ret status=0x0 (SUCCESS)
+show
+ ol_state=0x100 ( LEVEL_TWO_OPLOCK )
+ Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II  
+  fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist:   
+req 2 0x807
+ req oplock fid=2 ret oplock=0x0 status=0xc00000e2 (OPLOCK_NOT_GRANTED)
+req 2 0x803
+ req oplock fid=2 ret oplock=0x0 status=0xc00000e2 (OPLOCK_NOT_GRANTED)
+req 2 0x801
+ req oplock fid=2 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x101 ( LEVEL_TWO_OPLOCK READ_CACHING )
+ Excl=n cnt_II=1 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II  
+  fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case10.txt	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,12 @@
+# Input for testoplock, case 10
+# Modeled after smbtorture smb2.lease.oplock
+open 1
+req 1 0x100
+show
+open 2 2
+brk-open 2
+show
+req 2 0x807
+req 2 0x803
+req 2 0x801
+show
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case11.ref	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,30 @@
+open 1 1
+ open 1 OK
+req 1 0x807
+ req oplock fid=1 ret oplock=0x807 status=0x0 (SUCCESS)
+show
+ ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease=1 OgState=0x807 Brk=0x0 Excl=Y onlist:   
+open 2
+ open 2 OK
+brk-open 2
+*smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+ack 1
+ ack: break fid=1, newstate=0x803, status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x803 Brk=0x0 Excl=N onlist:   RH
+  fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:   
+req 2 0x100
+ req oplock fid=2 ret oplock=0x0 status=0xc00000e2 (OPLOCK_NOT_GRANTED)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x803 Brk=0x0 Excl=N onlist:   RH
+  fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case11.txt	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,11 @@
+# Input for testoplock, case 11
+# Modeled after smbtorture smb2.lease.break2
+open 1 1
+req 1 0x807
+show
+open 2
+brk-open 2
+ack 1
+show
+req 2 0x100
+show
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case12.ref	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,54 @@
+open 1 1
+ open 1 OK
+req 1 0x807
+ req oplock fid=1 ret oplock=0x807 status=0x0 (SUCCESS)
+show
+ ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease=1 OgState=0x807 Brk=0x0 Excl=Y onlist:   
+open 2
+ open 2 OK
+brk-open 2
+*smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+waiters 2 1
+ waiters 0 -> 1
+show
+ ol_state=0x30017 ( BREAK_TO_HANDLE_CACHING BREAK_TO_READ_CACHING EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x807 Brk=0x30000 Excl=Y onlist:   
+  fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:   
+open 3
+ open 3 OK
+brk-open 3 4
+ brk-open 3 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+waiters 3 2
+ waiters 1 -> 2
+show
+ ol_state=0x80017 ( BREAK_TO_NO_CACHING EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=3
+  fid=1 Lease=1 OgState=0x807 Brk=0x30000 Excl=Y onlist:   
+  fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:   
+  fid=3 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:   
+ack 1 0x803
+*smb_oplock_ind_break fid=1 NewLevel=0x0, AckReq=1, ComplStatus=0x8000002e (CANNOT_GRANT_REQUESTED_OPLOCK)
+ ack: break fid=1, newstate=0x803, status=0x0 (SUCCESS)
+show
+ ol_state=0x80003 ( BREAK_TO_NO_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=1
+ ofile_cnt=3
+  fid=1 Lease=1 OgState=0x803 Brk=0x80000 Excl=N onlist:    RHBQ(to none)
+  fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:   
+  fid=3 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:   
+ack 1 0x800
+ ack: break fid=1, newstate=0x800, status=0x0 (SUCCESS)
+show
+ ol_state=0x10000000 ( NO_OPLOCK )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=3
+  fid=1 Lease=1 OgState=0x800 Brk=0x0 Excl=N onlist:   
+  fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:   
+  fid=3 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case12.txt	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,28 @@
+# Input for testoplock, case 12
+# simulate smbtorture smb2.lease.breaking3
+#
+open 1 1
+req 1 0x807
+show
+#
+# a conflicting open (no oplock) is blocked until lease break ack
+open 2
+brk-open 2
+waiters 2 1
+show
+# should see lease break RWH to RH, and brk-open would block.
+# now a conflicting open with disp=overwrite(4), no oplock
+open 3
+brk-open 3 4
+waiters 3 2
+show
+# should see break_to_none pending (but no break ind yet)
+# and brk-open shoud block (break in progress)
+#
+# ack the first lease break above (RWH to RH)
+# should get a new break ind. (RH to none)
+ack 1 0x803
+show
+# got break ind?
+ack 1 0x800
+show
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case13.ref	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,56 @@
+open 1 1
+ open 1 OK
+req 1 0x801
+ req oplock fid=1 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
+
+open 2 1
+ open 2 OK
+brk-open 2
+ brk-open 2 ret status=0x0 (SUCCESS)
+req 2 0x807
+*smb_oplock_ind_break fid=1 NewLevel=0x7, AckReq=0, ComplStatus=0x215 (OPLOCK_SWITCHED_TO_NEW_HANDLE)
+ req oplock fid=2 ret oplock=0x807 status=0x0 (SUCCESS)
+show
+ ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=2) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x0 Brk=0x0 Excl=N onlist:   
+  fid=2 Lease=1 OgState=0x807 Brk=0x0 Excl=Y onlist:   
+
+move 2 1
+ move 2 1
+close 2
+ close OK
+show
+ ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+  fid=1 Lease=1 OgState=0x807 Brk=0x0 Excl=Y onlist:   
+
+open 3 2
+ open 3 OK
+brk-open 3
+*smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-open 3 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+ack 1
+ ack: break fid=1, newstate=0x803, status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x803 Brk=0x0 Excl=N onlist:   RH
+  fid=3 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist:   
+
+req 3 0x801
+ req oplock fid=3 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x23 ( MIXED_R_AND_RH HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=2
+  fid=1 Lease=1 OgState=0x803 Brk=0x0 Excl=N onlist:   RH
+  fid=3 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist:  R 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/case13.txt	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,25 @@
+# Input for testoplock, case 13
+# simulate smbtorture smb2.lease.complex1
+#
+open 1 1
+req 1 0x801
+show
+
+# upgrade lease 1
+open 2 1
+brk-open 2
+req 2 0x807
+show
+
+move 2 1
+close 2
+show
+
+# contend via lease2
+open 3 2
+brk-open 3
+ack 1
+show
+
+req 3 0x801
+show
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,111 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+/*
+ * Function prototypes needed by the "testoplock" program
+ * (a small subset of what the SMB server uses)
+ */
+
+#ifndef _SMB_KPROTO_H_
+#define	_SMB_KPROTO_H_
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/varargs.h>
+#include <sys/cmn_err.h>
+#include <smbsrv/smb.h>
+#include <smbsrv/smb_ktypes.h>
+
+boolean_t smb_ofile_is_open(smb_ofile_t *);
+boolean_t smb_node_is_file(smb_node_t *);
+
+/*
+ * SMB locked list function prototypes
+ */
+void	smb_llist_init(void);
+void	smb_llist_fini(void);
+void	smb_llist_constructor(smb_llist_t *, size_t, size_t);
+void	smb_llist_destructor(smb_llist_t *);
+void	smb_llist_enter(smb_llist_t *ll, krw_t);
+void	smb_llist_exit(smb_llist_t *);
+void	smb_llist_post(smb_llist_t *, void *, smb_dtorproc_t);
+void	smb_llist_flush(smb_llist_t *);
+void	smb_llist_insert_head(smb_llist_t *ll, void *obj);
+void	smb_llist_insert_tail(smb_llist_t *ll, void *obj);
+void	smb_llist_remove(smb_llist_t *ll, void *obj);
+int	smb_llist_upgrade(smb_llist_t *ll);
+uint32_t smb_llist_get_count(smb_llist_t *ll);
+#define	smb_llist_head(ll)		list_head(&(ll)->ll_list)
+#define	smb_llist_next(ll, obj)		list_next(&(ll)->ll_list, obj)
+int	smb_account_connected(smb_user_t *user);
+
+/*
+ * Common oplock functions
+ */
+uint32_t smb_oplock_request(smb_request_t *, smb_ofile_t *, uint32_t *);
+uint32_t smb_oplock_ack_break(smb_request_t *, smb_ofile_t *, uint32_t *);
+uint32_t smb_oplock_break_PARENT(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_OPEN(smb_node_t *, smb_ofile_t *,
+    uint32_t DesiredAccess, uint32_t CreateDisposition);
+uint32_t smb_oplock_break_BATCH(smb_node_t *, smb_ofile_t *,
+    uint32_t DesiredAccess, uint32_t CreateDisposition);
+uint32_t smb_oplock_break_HANDLE(smb_node_t *, smb_ofile_t *);
+void smb_oplock_break_CLOSE(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_READ(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_WRITE(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_SETINFO(smb_node_t *,
+    smb_ofile_t *ofile, uint32_t InfoClass);
+uint32_t smb_oplock_break_DELETE(smb_node_t *, smb_ofile_t *);
+
+void smb_oplock_move(smb_node_t *, smb_ofile_t *, smb_ofile_t *);
+
+/*
+ * Protocol-specific oplock functions
+ * (and "server-level" functions)
+ */
+void smb1_oplock_acquire(smb_request_t *, boolean_t);
+void smb1_oplock_break_notification(smb_request_t *, uint32_t);
+void smb2_oplock_break_notification(smb_request_t *, uint32_t);
+void smb2_lease_break_notification(smb_request_t *, uint32_t, boolean_t);
+void smb_oplock_ind_break(smb_ofile_t *, uint32_t, boolean_t, uint32_t);
+void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *,
+    uint32_t, boolean_t);
+void smb_oplock_send_brk(smb_request_t *);
+uint32_t smb_oplock_wait_break(smb_node_t *, int);
+
+int smb_lock_range_access(smb_request_t *, smb_node_t *,
+    uint64_t, uint64_t, boolean_t);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif /* _SMB_KPROTO_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_ktypes.h	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,227 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+/*
+ * Structures and type definitions needed by the "testoplock" program
+ * (a small subset of what the SMB server uses)
+ */
+
+#ifndef _SMB_KTYPES_H
+#define	_SMB_KTYPES_H
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+#include <sys/debug.h>
+#include <sys/systm.h>
+#include <sys/cred.h>
+#include <sys/list.h>
+#include <sys/sdt.h>
+
+typedef struct smb_session smb_session_t;
+typedef struct smb_user smb_user_t;
+typedef struct smb_tree smb_tree_t;
+
+
+/*
+ * Destructor object used in the locked-list delete queue.
+ */
+#define	SMB_DTOR_MAGIC		0x44544F52	/* DTOR */
+#define	SMB_DTOR_VALID(d)	\
+    ASSERT(((d) != NULL) && ((d)->dt_magic == SMB_DTOR_MAGIC))
+
+typedef void (*smb_dtorproc_t)(void *);
+
+typedef struct smb_dtor {
+	list_node_t	dt_lnd;
+	uint32_t	dt_magic;
+	void		*dt_object;
+	smb_dtorproc_t	dt_proc;
+} smb_dtor_t;
+
+typedef struct smb_llist {
+	krwlock_t	ll_lock;
+	list_t		ll_list;
+	uint32_t	ll_count;
+	uint64_t	ll_wrop;
+	kmutex_t	ll_mutex;
+	list_t		ll_deleteq;
+	uint32_t	ll_deleteq_count;
+	boolean_t	ll_flushing;
+} smb_llist_t;
+
+/*
+ * Per smb_node oplock state
+ */
+typedef struct smb_oplock {
+	kmutex_t		ol_mutex;
+	boolean_t		ol_fem;		/* fem monitor installed? */
+	struct smb_ofile	*excl_open;
+	uint32_t		ol_state;
+	int32_t			cnt_II;
+	int32_t			cnt_R;
+	int32_t			cnt_RH;
+	int32_t			cnt_RHBQ;
+	int32_t			waiters;
+	kcondvar_t		WaitingOpenCV;
+} smb_oplock_t;
+
+/*
+ * Per smb_ofile oplock state
+ */
+typedef struct smb_oplock_grant {
+	/* smb protocol-level state */
+	uint32_t		og_state;	/* latest sent to client */
+	uint32_t		og_breaking;	/* BREAK_TO... flags */
+	uint16_t		og_dialect;	/* how to send breaks */
+	/* File-system level state */
+	uint8_t			onlist_II;
+	uint8_t			onlist_R;
+	uint8_t			onlist_RH;
+	uint8_t			onlist_RHBQ;
+	uint8_t			BreakingToRead;
+} smb_oplock_grant_t;
+
+#define	SMB_LEASE_KEY_SZ	16
+
+#define	SMB_NODE_MAGIC		0x4E4F4445	/* 'NODE' */
+#define	SMB_NODE_VALID(p)	ASSERT((p)->n_magic == SMB_NODE_MAGIC)
+
+typedef enum {
+	SMB_NODE_STATE_AVAILABLE = 0,
+	SMB_NODE_STATE_DESTROYING
+} smb_node_state_t;
+
+/*
+ * waiting_event        # of clients requesting FCN
+ * n_timestamps         cached timestamps
+ * n_allocsz            cached file allocation size
+ * n_dnode              directory node
+ * n_unode              unnamed stream node
+ * delete_on_close_cred credentials for delayed delete
+ */
+typedef struct smb_node {
+	list_node_t		n_lnd;
+	uint32_t		n_magic;
+	krwlock_t		n_lock;
+	kmutex_t		n_mutex;
+	smb_node_state_t	n_state;
+	uint32_t		n_refcnt;
+	uint32_t		n_open_count;
+	volatile int		flags;
+
+	smb_llist_t		n_ofile_list;
+	smb_oplock_t		n_oplock;
+} smb_node_t;
+
+#define	NODE_FLAGS_WRITE_THROUGH	0x00100000
+#define	NODE_FLAGS_DELETE_COMMITTED	0x20000000
+#define	NODE_FLAGS_DELETE_ON_CLOSE	0x40000000
+
+/*
+ * Some flags for ofile structure
+ *
+ *	SMB_OFLAGS_SET_DELETE_ON_CLOSE
+ *   Set this flag when the corresponding open operation whose
+ *   DELETE_ON_CLOSE bit of the CreateOptions is set. If any
+ *   open file instance has this bit set, the NODE_FLAGS_DELETE_ON_CLOSE
+ *   will be set for the file node upon close.
+ */
+
+/*	SMB_OFLAGS_READONLY		0x0001 (obsolete) */
+#define	SMB_OFLAGS_EXECONLY		0x0002
+#define	SMB_OFLAGS_SET_DELETE_ON_CLOSE	0x0004
+#define	SMB_OFLAGS_LLF_POS_VALID	0x0008
+
+#define	SMB_OFILE_MAGIC		0x4F464C45	/* 'OFLE' */
+#define	SMB_OFILE_VALID(p)	\
+    ASSERT((p != NULL) && ((p)->f_magic == SMB_OFILE_MAGIC))
+
+/*
+ * This is the size of the per-handle "Lock Sequence" array.
+ * See LockSequenceIndex in [MS-SMB2] 2.2.26, and smb2_lock.c
+ */
+#define	SMB_OFILE_LSEQ_MAX		64
+
+/* {arg_open,ofile}->dh_vers values */
+typedef enum {
+	SMB2_NOT_DURABLE = 0,
+	SMB2_DURABLE_V1,
+	SMB2_DURABLE_V2,
+	SMB2_RESILIENT,
+} smb_dh_vers_t;
+
+/*
+ * See the long "Ofile State Machine" comment in smb_ofile.c
+ */
+typedef enum {
+	SMB_OFILE_STATE_ALLOC = 0,
+	SMB_OFILE_STATE_OPEN,
+	SMB_OFILE_STATE_SAVE_DH,
+	SMB_OFILE_STATE_SAVING,
+	SMB_OFILE_STATE_CLOSING,
+	SMB_OFILE_STATE_CLOSED,
+	SMB_OFILE_STATE_ORPHANED,
+	SMB_OFILE_STATE_RECONNECT,
+	SMB_OFILE_STATE_EXPIRED,
+	SMB_OFILE_STATE_SENTINEL
+} smb_ofile_state_t;
+
+typedef struct smb_ofile {
+	list_node_t		f_tree_lnd;	/* t_ofile_list */
+	list_node_t		f_node_lnd;	/* n_ofile_list */
+	list_node_t		f_dh_lnd;	/* sv_persistid_ht */
+	uint32_t		f_magic;
+	kmutex_t		f_mutex;
+	smb_ofile_state_t	f_state;
+
+	uint16_t		f_fid;
+	uint16_t		f_ftype;
+	uint32_t		f_refcnt;
+	uint32_t		f_granted_access;
+	uint32_t		f_share_access;
+
+	smb_node_t		*f_node;
+
+	smb_oplock_grant_t	f_oplock;
+	uint8_t			TargetOplockKey[SMB_LEASE_KEY_SZ];
+	uint8_t			ParentOplockKey[SMB_LEASE_KEY_SZ];
+	struct smb_lease	*f_lease;
+
+} smb_ofile_t;
+
+typedef struct smb_request {
+	list_node_t		sr_session_lnd;
+	uint32_t		sr_magic;
+	kmutex_t		sr_mutex;
+} smb_request_t;
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif /* _SMB_KTYPES_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/tol_all.d	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,106 @@
+#!/usr/sbin/dtrace -s
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+/*
+ * User-level dtrace for testoplock
+ * Usage: dtrace -s tol.d -c ./testoplock
+ */
+
+#pragma D option flowindent
+
+self int trace;
+self int mask;
+
+/*
+ * Trace almost everything
+ */
+pid$target:testoplock::entry
+{
+  self->trace++;
+}
+
+/*
+ * If traced and not masked, print entry/return
+ */
+pid$target:testoplock::entry
+/self->trace > 0 && self->mask == 0/
+{
+  printf("\t0x%x", arg0);
+  printf("\t0x%x", arg1);
+  printf("\t0x%x", arg2);
+  printf("\t0x%x", arg3);
+  printf("\t0x%x", arg4);
+  printf("\t0x%x", arg5);
+}
+
+/* Skip the bsearch calls. */
+pid$target:testoplock:xlate_nt_status:entry
+{
+  self->mask++;
+}
+
+pid$target:testoplock:xlate_nt_status:return
+{
+  self->mask--;
+}
+
+pid$target:testoplock::return
+/self->trace > 0 && self->mask == 0/
+{
+  printf("\t0x%x", arg1);
+}
+
+pid$target:testoplock::return
+{
+  self->trace--;
+}
+
+/* ---------------------- */
+
+pid$target::smb_oplock_request:entry
+{
+	self->sr = arg0;
+	self->of = arg1;
+	self->statep = arg2;
+	this->state = *(uint32_t *)copyin(self->statep, 4);
+	printf(" entry state=0x%x\n", this->state);
+}
+
+pid$target::smb_oplock_request:return
+{
+	this->sr = (userland pid`smb_request_t *)self->sr;
+	this->state = *(uint32_t *)copyin(self->statep, 4);
+	printf(" return state=0x%x\n", this->state);
+	printf("\nsr->arg.open = ");
+	print(this->sr->arg.open);
+}
+
+pid$target::smb_oplock_break_cmn:entry
+{
+	this->node = (userland pid`smb_node_t *)arg0;
+	this->ofile = (userland pid`smb_ofile_t *)arg1;
+	printf("\nnode->n_oplock = ");
+	print(this->node->n_oplock);
+	printf("\nofile->f_oplock = ");
+	print(this->ofile->f_oplock);
+}
+
+pid$target::smb_oplock_ind_break:entry
+{
+	this->ofile = (userland pid`smb_ofile_t *)arg0;
+	printf("\nofile->f_oplock = ");
+	print(this->ofile->f_oplock);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/tol_main.c	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,641 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+/*
+ * Test & debug program for oplocks
+ *
+ * This implements a simple command reader which accepts
+ * commands to simulate oplock events, and prints the
+ * state changes and actions that would happen after
+ * each event.
+ */
+
+#include <sys/types.h>
+#include <sys/debug.h>
+#include <sys/stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <smbsrv/smb_kproto.h>
+#include <smbsrv/smb_oplock.h>
+
+#define	OPLOCK_CACHE_RWH	(READ_CACHING | HANDLE_CACHING | WRITE_CACHING)
+#define	OPLOCK_TYPE	(LEVEL_TWO_OPLOCK | LEVEL_ONE_OPLOCK |\
+			BATCH_OPLOCK | OPLOCK_LEVEL_GRANULAR)
+
+#define	MAXFID 10
+
+smb_node_t root_node, test_node;
+smb_ofile_t  ofile_array[MAXFID];
+smb_request_t test_sr;
+uint32_t last_ind_break_level;
+char cmdbuf[100];
+
+extern const char *xlate_nt_status(uint32_t);
+
+#define	BIT_DEF(name) { name, #name }
+
+struct bit_defs {
+	uint32_t mask;
+	const char *name;
+} state_bits[] = {
+	BIT_DEF(NO_OPLOCK),
+	BIT_DEF(BREAK_TO_NO_CACHING),
+	BIT_DEF(BREAK_TO_WRITE_CACHING),
+	BIT_DEF(BREAK_TO_HANDLE_CACHING),
+	BIT_DEF(BREAK_TO_READ_CACHING),
+	BIT_DEF(BREAK_TO_TWO_TO_NONE),
+	BIT_DEF(BREAK_TO_NONE),
+	BIT_DEF(BREAK_TO_TWO),
+	BIT_DEF(BATCH_OPLOCK),
+	BIT_DEF(LEVEL_ONE_OPLOCK),
+	BIT_DEF(LEVEL_TWO_OPLOCK),
+	BIT_DEF(MIXED_R_AND_RH),
+	BIT_DEF(EXCLUSIVE),
+	BIT_DEF(WRITE_CACHING),
+	BIT_DEF(HANDLE_CACHING),
+	BIT_DEF(READ_CACHING),
+	{ 0, NULL }
+};
+
+/*
+ * Helper to print flags fields
+ */
+static void
+print_bits32(char *label, struct bit_defs *bit, uint32_t state)
+{
+	printf("%s0x%x (", label, state);
+	while (bit->mask != 0) {
+		if ((state & bit->mask) != 0)
+			printf(" %s", bit->name);
+		bit++;
+	}
+	printf(" )\n");
+}
+
+/*
+ * Command language:
+ *
+ */
+const char helpstr[] = "Commands:\n"
+	"help\t\tList commands\n"
+	"show\t\tShow OpLock state etc.\n"
+	"open FID\n"
+	"close FID\n"
+	"req FID [OplockLevel]\n"
+	"ack FID [OplockLevel]\n"
+	"brk-parent FID\n"
+	"brk-open [OverWrite]\n"
+	"brk-handle FID\n"
+	"brk-read FID\n"
+	"brk-write FID\n"
+	"brk-setinfo FID [InfoClass]\n"
+	"move FID1 FID2\n"
+	"waiters FID [count]\n";
+
+/*
+ * Command handlers
+ */
+
+static void
+do_show(void)
+{
+	smb_node_t *node = &test_node;
+	smb_oplock_t *ol = &node->n_oplock;
+	uint32_t state = ol->ol_state;
+	smb_ofile_t *f;
+
+	print_bits32(" ol_state=", state_bits, state);
+
+	if (ol->excl_open != NULL)
+		printf(" Excl=Y (FID=%d)", ol->excl_open->f_fid);
+	else
+		printf(" Excl=n");
+	printf(" cnt_II=%d cnt_R=%d cnt_RH=%d cnt_RHBQ=%d\n",
+	    ol->cnt_II, ol->cnt_R, ol->cnt_RH, ol->cnt_RHBQ);
+
+	printf(" ofile_cnt=%d\n", node->n_ofile_list.ll_count);
+	FOREACH_NODE_OFILE(node, f) {
+		smb_oplock_grant_t *og = &f->f_oplock;
+		printf("  fid=%d Lease=%s OgState=0x%x Brk=0x%x",
+		    f->f_fid,
+		    f->TargetOplockKey,	/* lease */
+		    f->f_oplock.og_state,
+		    f->f_oplock.og_breaking);
+		printf(" Excl=%s onlist: %s %s %s",
+		    (ol->excl_open == f) ? "Y" : "N",
+		    og->onlist_II ? "II" : "",
+		    og->onlist_R  ? "R" : "",
+		    og->onlist_RH ? "RH" : "");
+		if (og->onlist_RHBQ) {
+			printf(" RHBQ(to %s)",
+			    og->BreakingToRead ?
+			    "read" : "none");
+		}
+		printf("\n");
+	}
+}
+
+static void
+do_open(int fid, char *arg2)
+{
+	smb_node_t *node = &test_node;
+	smb_ofile_t *ofile = &ofile_array[fid];
+
+	/*
+	 * Simulate an open (minimal init)
+	 */
+	if (ofile->f_refcnt) {
+		printf("open fid %d already opened\n");
+		return;
+	}
+
+	if (arg2 != NULL)
+		strlcpy((char *)ofile->TargetOplockKey, arg2,
+		    SMB_LEASE_KEY_SZ);
+
+	ofile->f_refcnt++;
+	node->n_open_count++;
+	smb_llist_insert_tail(&node->n_ofile_list, ofile);
+	printf(" open %d OK\n", fid);
+}
+
+static void
+do_close(int fid)
+{
+	smb_node_t *node = &test_node;
+	smb_ofile_t *ofile = &ofile_array[fid];
+
+	/*
+	 * Simulate an close
+	 */
+	if (ofile->f_refcnt <= 0) {
+		printf(" close fid %d already closed\n");
+		return;
+	}
+	smb_oplock_break_CLOSE(ofile->f_node, ofile);
+
+	smb_llist_remove(&node->n_ofile_list, ofile);
+	node->n_open_count--;
+	ofile->f_refcnt--;
+
+	bzero(ofile->TargetOplockKey, SMB_LEASE_KEY_SZ);
+
+	printf(" close OK\n");
+}
+
+static void
+do_req(int fid, char *arg2)
+{
+	smb_ofile_t *ofile = &ofile_array[fid];
+	uint32_t oplock = BATCH_OPLOCK;
+	uint32_t status;
+
+	if (arg2 != NULL)
+		oplock = strtol(arg2, NULL, 16);
+
+	/*
+	 * Request an oplock
+	 */
+	status = smb_oplock_request(&test_sr, ofile, &oplock);
+	if (status == 0)
+		ofile->f_oplock.og_state = oplock;
+	printf(" req oplock fid=%d ret oplock=0x%x status=0x%x (%s)\n",
+	    fid, oplock, status, xlate_nt_status(status));
+}
+
+
+static void
+do_ack(int fid, char *arg2)
+{
+	smb_ofile_t *ofile = &ofile_array[fid];
+	uint32_t oplock;
+	uint32_t status;
+
+	/* Default to level in last smb_oplock_ind_break() */
+	oplock = last_ind_break_level;
+	if (arg2 != NULL)
+		oplock = strtol(arg2, NULL, 16);
+
+	ofile->f_oplock.og_breaking = 0;
+	status = smb_oplock_ack_break(&test_sr, ofile, &oplock);
+	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+		printf(" ack: break fid=%d, break-in-progress\n", fid);
+		ofile->f_oplock.og_state = oplock;
+	}
+	if (status == 0)
+		ofile->f_oplock.og_state = oplock;
+
+	printf(" ack: break fid=%d, newstate=0x%x, status=0x%x (%s)\n",
+	    fid, oplock, status, xlate_nt_status(status));
+}
+
+static void
+do_brk_parent(int fid)
+{
+	smb_ofile_t *ofile = &ofile_array[fid];
+	uint32_t status;
+
+	status = smb_oplock_break_PARENT(&test_node, ofile);
+	printf(" brk-parent %d ret status=0x%x (%s)\n",
+	    fid, status, xlate_nt_status(status));
+}
+
+static void
+do_brk_open(int fid, char *arg2)
+{
+	smb_ofile_t *ofile = &ofile_array[fid];
+	uint32_t status;
+	int disp = FILE_OPEN;
+
+	if (arg2 != NULL)
+		disp = strtol(arg2, NULL, 16);
+
+	status = smb_oplock_break_OPEN(&test_node, ofile, 7, disp);
+	printf(" brk-open %d ret status=0x%x (%s)\n",
+	    fid, status, xlate_nt_status(status));
+}
+
+static void
+do_brk_handle(int fid)
+{
+	smb_ofile_t *ofile = &ofile_array[fid];
+	uint32_t status;
+
+	status = smb_oplock_break_HANDLE(&test_node, ofile);
+	printf(" brk-handle %d ret status=0x%x (%s)\n",
+	    fid, status, xlate_nt_status(status));
+
+}
+
+static void
+do_brk_read(int fid)
+{
+	smb_ofile_t *ofile = &ofile_array[fid];
+	uint32_t status;
+
+	status = smb_oplock_break_READ(ofile->f_node, ofile);
+	printf(" brk-read %d ret status=0x%x (%s)\n",
+	    fid, status, xlate_nt_status(status));
+}
+
+static void
+do_brk_write(int fid)
+{
+	smb_ofile_t *ofile = &ofile_array[fid];
+	uint32_t status;
+
+	status = smb_oplock_break_WRITE(ofile->f_node, ofile);
+	printf(" brk-write %d ret status=0x%x (%s)\n",
+	    fid, status, xlate_nt_status(status));
+}
+
+static void
+do_brk_setinfo(int fid, char *arg2)
+{
+	smb_ofile_t *ofile = &ofile_array[fid];
+	uint32_t status;
+	int infoclass = FileEndOfFileInformation; /* 20 */
+
+	if (arg2 != NULL)
+		infoclass = strtol(arg2, NULL, 16);
+
+	status = smb_oplock_break_SETINFO(
+	    &test_node, ofile, infoclass);
+	printf(" brk-setinfo %d ret status=0x%x (%s)\n",
+	    fid, status, xlate_nt_status(status));
+
+}
+
+/*
+ * Move oplock to another FD, as specified,
+ * or any other available open
+ */
+static void
+do_move(int fid, char *arg2)
+{
+	smb_ofile_t *ofile = &ofile_array[fid];
+	smb_ofile_t *of2;
+	int fid2;
+
+	if (arg2 == NULL) {
+		fprintf(stderr, "move: FID2 required\n");
+		return;
+	}
+	fid2 = atoi(arg2);
+	if (fid2 <= 0 || fid2 >= MAXFID) {
+		fprintf(stderr, "move: bad FID2 %d\n", fid2);
+		return;
+	}
+	of2 = &ofile_array[fid2];
+
+	smb_oplock_move(&test_node, ofile, of2);
+	printf(" move %d %d\n", fid, fid2);
+}
+
+/*
+ * Set/clear oplock.waiters, which affects ack-break
+ */
+static void
+do_waiters(int fid, char *arg2)
+{
+	smb_node_t *node = &test_node;
+	smb_oplock_t *ol = &node->n_oplock;
+	int old, new = 0;
+
+	if (arg2 != NULL)
+		new = atoi(arg2);
+
+	old = ol->waiters;
+	ol->waiters = new;
+
+	printf(" waiters %d -> %d\n", old, new);
+}
+
+int
+main(int argc, char *argv[])
+{
+	smb_node_t *node = &test_node;
+	char *cmd;
+	char *arg1;
+	char *arg2;
+	char *savep;
+	char *sep = " \t\n";
+	char *prompt = NULL;
+	int fid;
+
+	if (isatty(0))
+		prompt = "> ";
+
+	smb_llist_constructor(&node->n_ofile_list, sizeof (smb_ofile_t),
+	    offsetof(smb_ofile_t, f_node_lnd));
+
+	for (fid = 0; fid < MAXFID; fid++) {
+		smb_ofile_t *f = &ofile_array[fid];
+
+		f->f_magic = SMB_OFILE_MAGIC;
+		mutex_init(&f->f_mutex, NULL, MUTEX_DEFAULT, NULL);
+		f->f_fid = fid;
+		f->f_ftype = SMB_FTYPE_DISK;
+		f->f_node = &test_node;
+	}
+
+	for (;;) {
+		if (prompt) {
+			fputs(prompt, stdout);
+			fflush(stdout);
+		}
+
+		cmd = fgets(cmdbuf, sizeof (cmdbuf), stdin);
+		if (cmd == NULL)
+			break;
+		if (cmd[0] == '#')
+			continue;
+
+		if (prompt == NULL) {
+			/* Put commands in the output too. */
+			fputs(cmdbuf, stdout);
+		}
+		cmd = strtok_r(cmd, sep, &savep);
+		if (cmd == NULL)
+			continue;
+
+		/*
+		 * Commands with no args
+		 */
+		if (0 == strcmp(cmd, "help")) {
+			fputs(helpstr, stdout);
+			continue;
+		}
+
+		if (0 == strcmp(cmd, "show")) {
+			do_show();
+			continue;
+		}
+
+		/*
+		 * Commands with one arg (the FID)
+		 */
+		arg1 = strtok_r(NULL, sep, &savep);
+		if (arg1 == NULL) {
+			fprintf(stderr, "%s missing arg1\n", cmd);
+			continue;
+		}
+		fid = atoi(arg1);
+		if (fid <= 0 || fid >= MAXFID) {
+			fprintf(stderr, "%s bad FID %d\n", cmd, fid);
+			continue;
+		}
+
+		if (0 == strcmp(cmd, "close")) {
+			do_close(fid);
+			continue;
+		}
+		if (0 == strcmp(cmd, "brk-parent")) {
+			do_brk_parent(fid);
+			continue;
+		}
+		if (0 == strcmp(cmd, "brk-handle")) {
+			do_brk_handle(fid);
+			continue;
+		}
+		if (0 == strcmp(cmd, "brk-read")) {
+			do_brk_read(fid);
+			continue;
+		}
+		if (0 == strcmp(cmd, "brk-write")) {
+			do_brk_write(fid);
+			continue;
+		}
+
+		/*
+		 * Commands with an (optional) arg2.
+		 */
+		arg2 = strtok_r(NULL, sep, &savep);
+
+		if (0 == strcmp(cmd, "open")) {
+			do_open(fid, arg2);
+			continue;
+		}
+		if (0 == strcmp(cmd, "req")) {
+			do_req(fid, arg2);
+			continue;
+		}
+		if (0 == strcmp(cmd, "ack")) {
+			do_ack(fid, arg2);
+			continue;
+		}
+		if (0 == strcmp(cmd, "brk-open")) {
+			do_brk_open(fid, arg2);
+			continue;
+		}
+		if (0 == strcmp(cmd, "brk-setinfo")) {
+			do_brk_setinfo(fid, arg2);
+			continue;
+		}
+		if (0 == strcmp(cmd, "move")) {
+			do_move(fid, arg2);
+			continue;
+		}
+		if (0 == strcmp(cmd, "waiters")) {
+			do_waiters(fid, arg2);
+			continue;
+		}
+
+		fprintf(stderr, "%s unknown command. Try help\n", cmd);
+	}
+	return (0);
+}
+
+/*
+ * A few functions called by the oplock code
+ * Stubbed out, and/or just print a message.
+ */
+
+boolean_t
+smb_node_is_file(smb_node_t *node)
+{
+	return (B_TRUE);
+}
+
+boolean_t
+smb_ofile_is_open(smb_ofile_t *ofile)
+{
+	return (ofile->f_refcnt != 0);
+}
+
+int
+smb_lock_range_access(
+    smb_request_t	*sr,
+    smb_node_t		*node,
+    uint64_t		start,
+    uint64_t		length,
+    boolean_t		will_write)
+{
+	return (0);
+}
+
+/*
+ * Test code replacement for: smb_oplock_send_brk()
+ */
+static void
+test_oplock_send_brk(smb_ofile_t *ofile,
+    uint32_t NewLevel, boolean_t AckReq)
+{
+	smb_oplock_grant_t *og = &ofile->f_oplock;
+
+	/* Skip building a message. */
+
+	if ((og->og_state & OPLOCK_LEVEL_GRANULAR) != 0)
+		NewLevel |= OPLOCK_LEVEL_GRANULAR;
+
+	/*
+	 * In a real server, we would send a break to the client,
+	 * and keep track (at the SMB level) whether this oplock
+	 * was obtained via a lease or an old-style oplock.
+	 */
+	if (AckReq) {
+		uint32_t BreakTo;
+
+		if ((og->og_state & OPLOCK_LEVEL_GRANULAR) != 0) {
+
+			BreakTo = (NewLevel & CACHE_RWH) << BREAK_SHIFT;
+			if (BreakTo == 0)
+				BreakTo = BREAK_TO_NO_CACHING;
+		} else {
+			if ((NewLevel & LEVEL_TWO_OPLOCK) != 0)
+				BreakTo = BREAK_TO_TWO;
+			else
+				BreakTo = BREAK_TO_NONE;
+		}
+		og->og_breaking = BreakTo;
+		last_ind_break_level = NewLevel;
+		/* Set og_state in  do_ack */
+	} else {
+		og->og_state = NewLevel;
+		/* Clear og_breaking in do_ack */
+	}
+}
+
+/*
+ * Simplified version of what's in smb_srv_oplock.c
+ */
+void
+smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel,
+    boolean_t AckReq, uint32_t status)
+{
+	smb_oplock_grant_t *og = &ofile->f_oplock;
+
+	printf("*smb_oplock_ind_break fid=%d NewLevel=0x%x,"
+	    " AckReq=%d, ComplStatus=0x%x (%s)\n",
+	    ofile->f_fid, NewLevel, AckReq,
+	    status, xlate_nt_status(status));
+
+	/*
+	 * Note that the CompletionStatus from the FS level
+	 * (smb_cmn_oplock.c) encodes what kind of action we
+	 * need to take at the SMB level.
+	 */
+	switch (status) {
+
+	case NT_STATUS_SUCCESS:
+	case NT_STATUS_CANNOT_GRANT_REQUESTED_OPLOCK:
+		test_oplock_send_brk(ofile, NewLevel, AckReq);
+		break;
+
+	case NT_STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE:
+	case NT_STATUS_OPLOCK_HANDLE_CLOSED:
+		og->og_state = OPLOCK_LEVEL_NONE;
+		break;
+
+	default:
+		/* Checked by caller. */
+		ASSERT(0);
+		break;
+	}
+}
+
+void
+smb_oplock_ind_break_in_ack(smb_request_t *sr, smb_ofile_t *ofile,
+    uint32_t NewLevel, boolean_t AckRequired)
+{
+	ASSERT(sr == &test_sr);
+	smb_oplock_ind_break(ofile, NewLevel, AckRequired, STATUS_CANT_GRANT);
+}
+
+uint32_t
+smb_oplock_wait_break(smb_node_t *node, int timeout)
+{
+	printf("*smb_oplock_wait_break (state=0x%x)\n",
+	    node->n_oplock.ol_state);
+	return (0);
+}
+
+/*
+ * There are a couple DTRACE_PROBE* in smb_cmn_oplock.c but we're
+ * not linking with the user-level dtrace support, so just
+ * stub these out.
+ */
+void
+__dtrace_fksmb___probe1(char *n, unsigned long a)
+{
+}
+void
+__dtrace_fksmb___probe2(char *n, unsigned long a, unsigned long b)
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/smbsrv/testoplock/tol_misc.c	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,179 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * A few excerpts from smb_kutil.c
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/atomic.h>
+#include <sys/debug.h>
+#include <sys/time.h>
+#include <sys/stddef.h>
+#include <smbsrv/smb_kproto.h>
+
+
+/*
+ * smb_llist_constructor
+ *
+ * This function initializes a locked list.
+ */
+void
+smb_llist_constructor(
+    smb_llist_t	*ll,
+    size_t	size,
+    size_t	offset)
+{
+	rw_init(&ll->ll_lock, NULL, RW_DEFAULT, NULL);
+	mutex_init(&ll->ll_mutex, NULL, MUTEX_DEFAULT, NULL);
+	list_create(&ll->ll_list, size, offset);
+	list_create(&ll->ll_deleteq, sizeof (smb_dtor_t),
+	    offsetof(smb_dtor_t, dt_lnd));
+	ll->ll_count = 0;
+	ll->ll_wrop = 0;
+	ll->ll_deleteq_count = 0;
+	ll->ll_flushing = B_FALSE;
+}
+
+/*
+ * Flush the delete queue and destroy a locked list.
+ */
+void
+smb_llist_destructor(
+    smb_llist_t	*ll)
+{
+	/* smb_llist_flush(ll); */
+
+	ASSERT(ll->ll_count == 0);
+	ASSERT(ll->ll_deleteq_count == 0);
+
+	rw_destroy(&ll->ll_lock);
+	list_destroy(&ll->ll_list);
+	list_destroy(&ll->ll_deleteq);
+	mutex_destroy(&ll->ll_mutex);
+}
+
+void
+smb_llist_enter(smb_llist_t *ll, krw_t mode)
+{
+	rw_enter(&ll->ll_lock, mode);
+}
+
+/*
+ * Exit the list lock and process the delete queue.
+ */
+void
+smb_llist_exit(smb_llist_t *ll)
+{
+	rw_exit(&ll->ll_lock);
+	/* smb_llist_flush(ll); */
+}
+
+/*
+ * smb_llist_upgrade
+ *
+ * This function tries to upgrade the lock of the locked list. It assumes the
+ * locked has already been entered in RW_READER mode. It first tries using the
+ * Solaris function rw_tryupgrade(). If that call fails the lock is released
+ * and reentered in RW_WRITER mode. In that last case a window is opened during
+ * which the contents of the list may have changed. The return code indicates
+ * whether or not the list was modified when the lock was exited.
+ */
+int smb_llist_upgrade(
+    smb_llist_t *ll)
+{
+	uint64_t	wrop;
+
+	if (rw_tryupgrade(&ll->ll_lock) != 0) {
+		return (0);
+	}
+	wrop = ll->ll_wrop;
+	rw_exit(&ll->ll_lock);
+	rw_enter(&ll->ll_lock, RW_WRITER);
+	return (wrop != ll->ll_wrop);
+}
+
+/*
+ * smb_llist_insert_head
+ *
+ * This function inserts the object passed a the beginning of the list. This
+ * function assumes the lock of the list has already been entered.
+ */
+void
+smb_llist_insert_head(
+    smb_llist_t	*ll,
+    void	*obj)
+{
+	list_insert_head(&ll->ll_list, obj);
+	++ll->ll_wrop;
+	++ll->ll_count;
+}
+
+/*
+ * smb_llist_insert_tail
+ *
+ * This function appends to the object passed to the list. This function assumes
+ * the lock of the list has already been entered.
+ *
+ */
+void
+smb_llist_insert_tail(
+    smb_llist_t	*ll,
+    void	*obj)
+{
+	list_insert_tail(&ll->ll_list, obj);
+	++ll->ll_wrop;
+	++ll->ll_count;
+}
+
+/*
+ * smb_llist_remove
+ *
+ * This function removes the object passed from the list. This function assumes
+ * the lock of the list has already been entered.
+ */
+void
+smb_llist_remove(
+    smb_llist_t	*ll,
+    void	*obj)
+{
+	list_remove(&ll->ll_list, obj);
+	++ll->ll_wrop;
+	--ll->ll_count;
+}
+
+/*
+ * smb_llist_get_count
+ *
+ * This function returns the number of elements in the specified list.
+ */
+uint32_t
+smb_llist_get_count(
+    smb_llist_t *ll)
+{
+	return (ll->ll_count);
+}
--- a/usr/src/cmd/zoneadm/zoneadm.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/cmd/zoneadm/zoneadm.c	Wed Jun 12 22:46:54 2019 +0200
@@ -136,7 +136,7 @@
 #define	SHELP_READY	"ready"
 #define	SHELP_SHUTDOWN	"shutdown [-r [-- boot_arguments]]"
 #define	SHELP_REBOOT	"reboot [-- boot_arguments]"
-#define	SHELP_LIST	"list [-cipv]"
+#define	SHELP_LIST	"list [-cinpv]"
 #define	SHELP_VERIFY	"verify"
 #define	SHELP_INSTALL	"install [brand-specific args]"
 #define	SHELP_UNINSTALL	"uninstall [-F] [brand-specific args]"
@@ -256,7 +256,7 @@
 		    "option.  When used with the general -z <zone> and/or -u "
 		    "<uuid-match>\n\toptions, lists only the specified "
 		    "matching zone, but lists it\n\tregardless of its state, "
-		    "and the -i and -c options are disallowed.  The\n\t-v "
+		    "and the -i, -c, and -n options are disallowed.  The\n\t-v "
 		    "option can be used to display verbose information: zone "
 		    "name, id,\n\tcurrent state, root directory and options.  "
 		    "The -p option can be used\n\tto request machine-parsable "
@@ -724,7 +724,8 @@
 }
 
 static int
-zone_print_list(zone_state_t min_state, boolean_t verbose, boolean_t parsable)
+zone_print_list(zone_state_t min_state, boolean_t verbose, boolean_t parsable,
+    boolean_t exclude_global)
 {
 	int i;
 	zone_entry_t zent;
@@ -742,8 +743,11 @@
 		 */
 		return (i);
 	}
-	for (i = 0; i < nzents; i++)
+	for (i = 0; i < nzents; i++) {
+		if (exclude_global && zents[i].zid == GLOBAL_ZONEID)
+			continue;
 		zone_print(&zents[i], verbose, parsable);
+	}
 	if (min_state >= ZONE_STATE_RUNNING)
 		return (Z_OK);
 	/*
@@ -1383,14 +1387,15 @@
 {
 	zone_entry_t *zentp, zent;
 	int arg, retv;
-	boolean_t output = B_FALSE, verbose = B_FALSE, parsable = B_FALSE;
+	boolean_t output = B_FALSE, verbose = B_FALSE, parsable = B_FALSE,
+	    exclude_global = B_FALSE;
 	zone_state_t min_state = ZONE_STATE_RUNNING;
 	zoneid_t zone_id = getzoneid();
 
 	if (target_zone == NULL) {
 		/* all zones: default view to running but allow override */
 		optind = 0;
-		while ((arg = getopt(argc, argv, "?cipv")) != EOF) {
+		while ((arg = getopt(argc, argv, "?cinpv")) != EOF) {
 			switch (arg) {
 			case '?':
 				sub_usage(SHELP_LIST, CMD_LIST);
@@ -1411,6 +1416,9 @@
 				    min_state);
 
 				break;
+			case 'n':
+				exclude_global = B_TRUE;
+				break;
 			case 'p':
 				parsable = B_TRUE;
 				break;
@@ -1428,7 +1436,8 @@
 			return (Z_ERR);
 		}
 		if (zone_id == GLOBAL_ZONEID || is_system_labeled()) {
-			retv = zone_print_list(min_state, verbose, parsable);
+			retv = zone_print_list(min_state, verbose, parsable,
+			    exclude_global);
 		} else {
 			fake_up_local_zone(zone_id, &zent);
 			retv = Z_OK;
@@ -1438,7 +1447,7 @@
 	}
 
 	/*
-	 * Specific target zone: disallow -i/-c suboptions.
+	 * Specific target zone: disallow -i/-c/-n suboptions.
 	 */
 	optind = 0;
 	while ((arg = getopt(argc, argv, "?pv")) != EOF) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/common/smbsrv/smb_cfg_util.c	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,37 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+#if !defined(_KERNEL) && !defined(_FAKE_KERNEL)
+#include <string.h>
+#else
+#include <sys/sunddi.h>
+#endif
+#include <smbsrv/smbinfo.h>
+
+void
+smb_cfg_set_require(const char *value, smb_cfg_val_t *cfg)
+{
+	if (value == NULL) {
+		*cfg = SMB_CONFIG_DISABLED;
+		return;
+	}
+
+	if (strcmp(value, "required") == 0)
+		*cfg = SMB_CONFIG_REQUIRED;
+	else if (strcmp(value, "enabled") == 0)
+		*cfg = SMB_CONFIG_ENABLED;
+	else
+		*cfg = SMB_CONFIG_DISABLED;
+}
--- a/usr/src/common/smbsrv/smb_xdr.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/common/smbsrv/smb_xdr.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
  */
 
 #include <sys/sunddi.h>
@@ -238,8 +238,6 @@
 {
 	if (!xdr_uint64_t(xdrs, &objp->ui_session_id))
 		return (FALSE);
-	if (!xdr_uint16_t(xdrs, &objp->ui_smb_uid))
-		return (FALSE);
 	if (!xdr_uint16_t(xdrs, &objp->ui_domain_len))
 		return (FALSE);
 	if (!xdr_string(xdrs, &objp->ui_domain, ~0))
--- a/usr/src/contrib/zlib/deflate.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/contrib/zlib/deflate.c	Wed Jun 12 22:46:54 2019 +0200
@@ -188,8 +188,11 @@
  * prev[] will be initialized on the fly.
  */
 #define CLEAR_HASH(s) \
-    s->head[s->hash_size-1] = NIL; \
-    zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head));
+    do { \
+        s->head[s->hash_size-1] = NIL; \
+        zmemzero((Bytef *)s->head, \
+        (unsigned)(s->hash_size-1)*sizeof(*s->head)); \
+    } while (0)
 
 /* ===========================================================================
  * Slide the hash table when sliding the window down (could be avoided with 32
--- a/usr/src/head/iso/math_c99.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/head/iso/math_c99.h	Wed Jun 12 22:46:54 2019 +0200
@@ -66,13 +66,13 @@
 #undef	isnan
 #undef	isinf
 #if __GNUC__ >= 4
-#define	isnan(x)	__builtin_isnan(x)
-#define	isinf(x)	__builtin_isinf(x)
 #define	fpclassify(x)	__builtin_fpclassify(FP_NAN, FP_INFINITE, FP_NORMAL, \
     FP_SUBNORMAL, FP_ZERO, x)
-#define	isfinite(x)	__builtin_isfinite(x)
-#define	isnormal(x)	__builtin_isnormal(x)
-#define	signbit(x)	(__builtin_signbit(x) > 0)
+#define	isnan(x)	__builtin_isnan(x)
+#define	isinf(x)	__builtin_isinf(x)
+#define	isfinite(x)	(__builtin_isfinite(x) != 0)
+#define	isnormal(x)	(__builtin_isnormal(x) != 0)
+#define	signbit(x)	(__builtin_signbit(x) != 0)
 #else  /* __GNUC__ >= 4 */
 #define	isnan(x)	__extension__( \
 			{ __typeof(x) __x_n = (x); \
--- a/usr/src/lib/fm/libdiskstatus/common/ds_scsi_uscsi.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/fm/libdiskstatus/common/ds_scsi_uscsi.c	Wed Jun 12 22:46:54 2019 +0200
@@ -1251,7 +1251,7 @@
 	 */
 	if (hdr->length == 0) {
 		dprintf("\nMode sense page 0x%x: has header length for zero\n",
-		    hdr->length);
+		    page_code);
 		ddump("Mode sense:", mode_sense_buf, nbytes);
 		return (-1);
 	}
--- a/usr/src/lib/libadutils/common/addisc.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libadutils/common/addisc.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2019 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -157,7 +157,7 @@
 #define	is_fixed(item)					\
 	((item)->state == AD_STATE_FIXED)
 
-#define	is_changed(item, num, param) 			\
+#define	is_changed(item, num, param)			\
 	((item)->param_version[num] != (param)->version)
 
 void * uuid_dup(void *);
@@ -641,13 +641,13 @@
 LDAP *
 ldap_lookup_init(ad_disc_ds_t *ds)
 {
-	int 	i;
+	int	i;
 	int	rc, ldversion;
 	int	zero = 0;
-	int 	timeoutms = 5 * 1000;
-	char 	*saslmech = "GSSAPI";
+	int	timeoutms = 5 * 1000;
+	char	*saslmech = "GSSAPI";
 	uint32_t saslflags = LDAP_SASL_INTERACTIVE;
-	LDAP 	*ld = NULL;
+	LDAP	*ld = NULL;
 
 	for (i = 0; ds[i].host[0] != '\0'; i++) {
 		if (DBG(LDAP, 2)) {
@@ -861,7 +861,7 @@
 		struct berval	**sid_ber;
 		adutils_sid_t	sid;
 		char		*sid_str;
-		char 		*name;
+		char		*name;
 		char		*dn;
 
 		sid_ber = ldap_get_values_len(*ld, entry,
@@ -1222,8 +1222,10 @@
 		return (&ctx->domain_controller);
 
 	domain_name_item = validate_DomainName(ctx);
-	if (domain_name_item == NULL)
+	if (domain_name_item == NULL) {
+		DEBUG1STATUS(ctx, "(no domain name)");
 		return (NULL);
+	}
 	domain_name = (char *)domain_name_item->value;
 
 	/* Get (optional) preferred DC. */
@@ -1236,10 +1238,74 @@
 	else {
 		if (is_fixed(&ctx->site_name))
 			validate_site = B_TRUE;
-		else if (req == AD_DISC_PREFER_SITE)
+		if (req == AD_DISC_PREFER_SITE)
 			validate_global = B_TRUE;
 	}
 
+	/*
+	 * If we're trying both site-specific and global,
+	 * try the site-specific first, then fall-back.
+	 */
+	if (validate_site) {
+		site_name_item = &ctx->site_name;
+		site_name = (char *)site_name_item->value;
+
+		if (!is_valid(&ctx->site_domain_controller) ||
+		    is_changed(&ctx->site_domain_controller, PARAM1,
+		    domain_name_item) ||
+		    is_changed(&ctx->site_domain_controller, PARAM2,
+		    site_name_item)) {
+			char rr_name[DNS_MAX_NAME];
+
+			/*
+			 * Lookup DNS SRV RR named
+			 * _ldap._tcp.<SiteName>._sites.dc._msdcs.<DomainName>
+			 */
+			DEBUG1STATUS(ctx, "DNS SRV query, dom=%s, site=%s",
+			    domain_name, site_name);
+			(void) snprintf(rr_name, sizeof (rr_name),
+			    LDAP_SRV_HEAD SITE_SRV_MIDDLE DC_SRV_TAIL,
+			    site_name);
+			DO_RES_NINIT(ctx);
+			cdc = srv_query(&ctx->res_state, rr_name,
+			    domain_name, prefer_dc);
+
+			if (cdc == NULL) {
+				DEBUG1STATUS(ctx, "(no DNS response)");
+				goto try_global;
+			}
+			log_cds(ctx, cdc);
+
+			/*
+			 * Filter out unresponsive servers, and
+			 * save the domain info we get back.
+			 */
+			dc = ldap_ping(
+			    ctx,
+			    cdc,
+			    domain_name,
+			    DS_DS_FLAG);
+			srv_free(cdc);
+			cdc = NULL;
+
+			if (dc == NULL) {
+				DEBUG1STATUS(ctx, "(no LDAP response)");
+				goto try_global;
+			}
+			log_ds(ctx, dc);
+
+			update_item(&ctx->site_domain_controller, dc,
+			    AD_STATE_AUTO, dc->ttl);
+			update_version(&ctx->site_domain_controller, PARAM1,
+			    domain_name_item);
+			update_version(&ctx->site_domain_controller, PARAM2,
+			    site_name_item);
+		}
+		return (&ctx->site_domain_controller);
+	}
+
+try_global:
+
 	if (validate_global) {
 		if (!is_valid(&ctx->domain_controller) ||
 		    is_changed(&ctx->domain_controller, PARAM1,
@@ -1288,63 +1354,6 @@
 		return (&ctx->domain_controller);
 	}
 
-	if (validate_site) {
-		site_name_item = &ctx->site_name;
-		site_name = (char *)site_name_item->value;
-
-		if (!is_valid(&ctx->site_domain_controller) ||
-		    is_changed(&ctx->site_domain_controller, PARAM1,
-		    domain_name_item) ||
-		    is_changed(&ctx->site_domain_controller, PARAM2,
-		    site_name_item)) {
-			char rr_name[DNS_MAX_NAME];
-
-			/*
-			 * Lookup DNS SRV RR named
-			 * _ldap._tcp.<SiteName>._sites.dc._msdcs.<DomainName>
-			 */
-			DEBUG1STATUS(ctx, "DNS SRV query, dom=%s, site=%s",
-			    domain_name, site_name);
-			(void) snprintf(rr_name, sizeof (rr_name),
-			    LDAP_SRV_HEAD SITE_SRV_MIDDLE DC_SRV_TAIL,
-			    site_name);
-			DO_RES_NINIT(ctx);
-			cdc = srv_query(&ctx->res_state, rr_name,
-			    domain_name, prefer_dc);
-
-			if (cdc == NULL) {
-				DEBUG1STATUS(ctx, "(no DNS response)");
-				return (NULL);
-			}
-			log_cds(ctx, cdc);
-
-			/*
-			 * Filter out unresponsive servers, and
-			 * save the domain info we get back.
-			 */
-			dc = ldap_ping(
-			    ctx,
-			    cdc,
-			    domain_name,
-			    DS_DS_FLAG);
-			srv_free(cdc);
-			cdc = NULL;
-
-			if (dc == NULL) {
-				DEBUG1STATUS(ctx, "(no LDAP response)");
-				return (NULL);
-			}
-			log_ds(ctx, dc);
-
-			update_item(&ctx->site_domain_controller, dc,
-			    AD_STATE_AUTO, dc->ttl);
-			update_version(&ctx->site_domain_controller, PARAM1,
-			    domain_name_item);
-			update_version(&ctx->site_domain_controller, PARAM2,
-			    site_name_item);
-		}
-		return (&ctx->site_domain_controller);
-	}
 	return (NULL);
 }
 
@@ -1516,8 +1525,10 @@
 		return (&ctx->global_catalog);
 
 	forest_name_item = validate_ForestName(ctx);
-	if (forest_name_item == NULL)
+	if (forest_name_item == NULL) {
+		DEBUG1STATUS(ctx, "(no forrest name)");
 		return (NULL);
+	}
 	forest_name = (char *)forest_name_item->value;
 
 	if (req == AD_DISC_GLOBAL)
@@ -1525,10 +1536,94 @@
 	else {
 		if (is_fixed(&ctx->site_name))
 			validate_site = B_TRUE;
-		else if (req == AD_DISC_PREFER_SITE)
+		if (req == AD_DISC_PREFER_SITE)
 			validate_global = B_TRUE;
 	}
 
+	/*
+	 * If we're trying both site-specific and global,
+	 * try the site-specific first, then fall-back.
+	 */
+	if (validate_site) {
+		site_name_item = &ctx->site_name;
+		site_name = (char *)site_name_item->value;
+
+		if (!is_valid(&ctx->site_global_catalog) ||
+		    is_changed(&ctx->site_global_catalog, PARAM1,
+		    forest_name_item) ||
+		    is_changed(&ctx->site_global_catalog, PARAM2,
+		    site_name_item)) {
+			char rr_name[DNS_MAX_NAME];
+
+			/*
+			 * See if our DC is also a GC.
+			 */
+			dc_item = validate_DomainController(ctx, req);
+			if (dc_item != NULL) {
+				ad_disc_ds_t *ds = dc_item->value;
+				if ((ds->flags & DS_GC_FLAG) != 0) {
+					DEBUG1STATUS(ctx,
+					    "DC is also a GC for %s in %s",
+					    forest_name, site_name);
+					gc = ds_dup(ds);
+					if (gc != NULL) {
+						gc->port = GC_PORT;
+						goto update_site;
+					}
+				}
+			}
+
+			/*
+			 * Lookup DNS SRV RR named:
+			 * _ldap._tcp.<siteName>._sites.gc.
+			 *	_msdcs.<ForestName>
+			 */
+			DEBUG1STATUS(ctx, "DNS SRV query, forest=%s, site=%s",
+			    forest_name, site_name);
+			(void) snprintf(rr_name, sizeof (rr_name),
+			    LDAP_SRV_HEAD SITE_SRV_MIDDLE GC_SRV_TAIL,
+			    site_name);
+			DO_RES_NINIT(ctx);
+			cgc = srv_query(&ctx->res_state, rr_name,
+			    forest_name, NULL);
+
+			if (cgc == NULL) {
+				DEBUG1STATUS(ctx, "(no DNS response)");
+				goto try_global;
+			}
+			log_cds(ctx, cgc);
+
+			/*
+			 * Filter out unresponsive servers, and
+			 * save the domain info we get back.
+			 */
+			gc = ldap_ping(
+			    NULL,
+			    cgc,
+			    forest_name,
+			    DS_GC_FLAG);
+			srv_free(cgc);
+			cgc = NULL;
+
+			if (gc == NULL) {
+				DEBUG1STATUS(ctx, "(no LDAP response)");
+				goto try_global;
+			}
+			log_ds(ctx, gc);
+
+		update_site:
+			update_item(&ctx->site_global_catalog, gc,
+			    AD_STATE_AUTO, gc->ttl);
+			update_version(&ctx->site_global_catalog, PARAM1,
+			    forest_name_item);
+			update_version(&ctx->site_global_catalog, PARAM2,
+			    site_name_item);
+		}
+		return (&ctx->site_global_catalog);
+	}
+
+try_global:
+
 	if (validate_global) {
 		if (!is_valid(&ctx->global_catalog) ||
 		    is_changed(&ctx->global_catalog, PARAM1,
@@ -1595,84 +1690,6 @@
 		}
 		return (&ctx->global_catalog);
 	}
-
-	if (validate_site) {
-		site_name_item = &ctx->site_name;
-		site_name = (char *)site_name_item->value;
-
-		if (!is_valid(&ctx->site_global_catalog) ||
-		    is_changed(&ctx->site_global_catalog, PARAM1,
-		    forest_name_item) ||
-		    is_changed(&ctx->site_global_catalog, PARAM2,
-		    site_name_item)) {
-			char rr_name[DNS_MAX_NAME];
-
-			/*
-			 * See if our DC is also a GC.
-			 */
-			dc_item = validate_DomainController(ctx, req);
-			if (dc_item != NULL) {
-				ad_disc_ds_t *ds = dc_item->value;
-				if ((ds->flags & DS_GC_FLAG) != 0) {
-					DEBUG1STATUS(ctx,
-					    "DC is also a GC for %s in %s",
-					    forest_name, site_name);
-					gc = ds_dup(ds);
-					if (gc != NULL) {
-						gc->port = GC_PORT;
-						goto update_site;
-					}
-				}
-			}
-
-			/*
-			 * Lookup DNS SRV RR named:
-			 * _ldap._tcp.<siteName>._sites.gc.
-			 *	_msdcs.<ForestName>
-			 */
-			DEBUG1STATUS(ctx, "DNS SRV query, forest=%s, site=%s",
-			    forest_name, site_name);
-			(void) snprintf(rr_name, sizeof (rr_name),
-			    LDAP_SRV_HEAD SITE_SRV_MIDDLE GC_SRV_TAIL,
-			    site_name);
-			DO_RES_NINIT(ctx);
-			cgc = srv_query(&ctx->res_state, rr_name,
-			    forest_name, NULL);
-
-			if (cgc == NULL) {
-				DEBUG1STATUS(ctx, "(no DNS response)");
-				return (NULL);
-			}
-			log_cds(ctx, cgc);
-
-			/*
-			 * Filter out unresponsive servers, and
-			 * save the domain info we get back.
-			 */
-			gc = ldap_ping(
-			    NULL,
-			    cgc,
-			    forest_name,
-			    DS_GC_FLAG);
-			srv_free(cgc);
-			cgc = NULL;
-
-			if (gc == NULL) {
-				DEBUG1STATUS(ctx, "(no LDAP response)");
-				return (NULL);
-			}
-			log_ds(ctx, gc);
-
-		update_site:
-			update_item(&ctx->site_global_catalog, gc,
-			    AD_STATE_AUTO, gc->ttl);
-			update_version(&ctx->site_global_catalog, PARAM1,
-			    forest_name_item);
-			update_version(&ctx->site_global_catalog, PARAM2,
-			    site_name_item);
-		}
-		return (&ctx->site_global_catalog);
-	}
 	return (NULL);
 }
 
--- a/usr/src/lib/libc/sparc/Makefile.com	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libc/sparc/Makefile.com	Wed Jun 12 22:46:54 2019 +0200
@@ -26,6 +26,7 @@
 # Copyright 2013 Garrett D'Amore <garrett@damore.org>
 # Copyright 2018 Nexenta Systems, Inc.
 # Copyright 2019 OmniOS Community Edition (OmniOSce) Association.
+# Copyright 2019 Peter Tribble.
 #
 
 LIBCDIR=	$(SRC)/lib/libc
@@ -1331,7 +1332,7 @@
 # special cases
 $(STRETS:%=pics/%): $(LIBCBASE)/crt/stret.s
 	$(AS) $(ASFLAGS) -DSTRET$(@F:stret%.o=%) $(LIBCBASE)/crt/stret.s -o $@
-	$(POST_PROCESS_O)
+	$(POST_PROCESS_S_O)
 
 $(LIBCBASE)/crt/_rtbootld.s:	$(LIBCBASE)/crt/_rtboot.s $(LIBCBASE)/crt/_rtld.c
 	$(CC) $(CPPFLAGS) $(CTF_FLAGS) -O -S $(C_PICFLAGS) \
--- a/usr/src/lib/libc/sparcv9/Makefile.com	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libc/sparcv9/Makefile.com	Wed Jun 12 22:46:54 2019 +0200
@@ -27,6 +27,7 @@
 # Copyright 2013 Garrett D'Amore <garrett@damore.org>
 # Copyright 2018 Nexenta Systems, Inc.
 # Copyright 2019 OmniOS Community Edition (OmniOSce) Association.
+# Copyright 2019 Peter Tribble.
 #
 
 LIBCDIR=	$(SRC)/lib/libc
@@ -1253,7 +1254,7 @@
 # special cases
 #$(STRETS:%=pics/%): crt/stret.s
 #	$(AS) $(ASFLAGS) -DSTRET$(@F:stret%.o=%) crt/stret.s -o $@
-#	$(POST_PROCESS_O)
+#	$(POST_PROCESS_S_O)
 
 #crt/_rtbootld.s:	crt/_rtboot.s crt/_rtld.c
 #	$(CC) $(CPPFLAGS) -O -S -K pic crt/_rtld.c -o crt/_rtld.s
--- a/usr/src/lib/libfakekernel/common/kmisc.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libfakekernel/common/kmisc.c	Wed Jun 12 22:46:54 2019 +0200
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
  * Copyright 2017 RackTop Systems.
  */
 
@@ -19,13 +19,13 @@
 #include <sys/thread.h>
 #include <sys/proc.h>
 #include <sys/zone.h>
-
 #include <sys/poll.h>
 
 #include <time.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <errno.h>
+#include <string.h>
 
 #include <fakekernel.h>
 
@@ -118,6 +118,12 @@
 	(void) poll(0, 0, msec);
 }
 
+int
+highbit(ulong_t i)
+{
+	return (fls(i));
+}
+
 /* ARGSUSED */
 int
 issig(int why)
--- a/usr/src/lib/libfakekernel/common/mapfile-vers	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libfakekernel/common/mapfile-vers	Wed Jun 12 22:46:54 2019 +0200
@@ -10,7 +10,7 @@
 #
 
 #
-# Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
 # Copyright 2017 RackTop Systems.
 # Copyright 2019 Joyent, Inc.
 #
@@ -52,6 +52,7 @@
 	cyclic_reprogram;
 
 	crfree;
+	crgetsid;
 	crgetuid;
 	crgetruid;
 	crgetgid;
@@ -96,9 +97,10 @@
 	gethrestime_sec;
 	gethrtime_unscaled;
 
-	hz;
+	highbit;
+	highbit64;
 
-	highbit64;
+	hz;
 
 	issig;
 
@@ -121,9 +123,9 @@
 	kmem_zalloc;
 
 	kmutex_destroy;
-	kmutex_init;
 	kmutex_enter;
 	kmutex_exit;
+	kmutex_init;
 
 	ksema_destroy;
 	ksema_init;
--- a/usr/src/lib/libfakekernel/common/mutex.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libfakekernel/common/mutex.c	Wed Jun 12 22:46:54 2019 +0200
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  * Copyright 2017 RackTop Systems.
  */
 
@@ -31,8 +31,9 @@
 
 extern clock_t ddi_get_lbolt(void);
 
+/* See: head/synch.h ERRORCHECKMUTEX */
 static const lwp_mutex_t default_mutex =
-	{{0, 0, 0, {USYNC_THREAD}, _MUTEX_MAGIC},
+	{{0, 0, 0, {USYNC_THREAD|LOCK_ERRORCHECK}, _MUTEX_MAGIC},
 	{{{0, 0, 0, 0, 0, 0, 0, 0}}}, 0};
 
 /* ARGSUSED */
@@ -53,8 +54,11 @@
 void
 kmutex_enter(kmutex_t *mp)
 {
+	kthread_t *t = _curthread();
+
+	VERIFY(mp->m_owner != t);
 	VERIFY(0 == _lwp_mutex_lock(&mp->m_lock));
-	mp->m_owner = _curthread();
+	mp->m_owner = t;
 }
 
 int
--- a/usr/src/lib/libfakekernel/common/rwlock.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libfakekernel/common/rwlock.c	Wed Jun 12 22:46:54 2019 +0200
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -71,6 +71,8 @@
 	if (_rw_write_held(&rwlp->rw_lock)) {
 		ASSERT(rwlp->rw_owner == _curthread());
 		rwlp->rw_owner = _KTHREAD_INVALID;
+	} else {
+		ASSERT(_rw_read_held(&rwlp->rw_lock));
 	}
 	(void) rw_unlock(&rwlp->rw_lock);
 }
--- a/usr/src/lib/libfakekernel/common/sys/cred.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libfakekernel/common/sys/cred.h	Wed Jun 12 22:46:54 2019 +0200
@@ -19,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  * Copyright 2017 RackTop Systems.
  *
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
@@ -54,6 +54,7 @@
 
 cred_t *_curcred(void);
 #define	CRED()		(_curcred())	/* current cred_t pointer */
+struct ksid;
 
 extern int ngroups_max;
 
@@ -79,6 +80,7 @@
 extern zoneid_t crgetzoneid(const cred_t *);
 extern struct zone *crgetzone(const cred_t *);
 extern projid_t crgetprojid(const cred_t *);
+extern struct ksid *crgetsid(const cred_t *, int);
 
 extern const gid_t *crgetgroups(const cred_t *);
 extern int crgetngroups(const cred_t *);
--- a/usr/src/lib/libm/common/C/jn.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libm/common/C/jn.c	Wed Jun 12 22:46:54 2019 +0200
@@ -69,7 +69,8 @@
 	one	= 1.0;
 
 GENERIC
-jn(int n, GENERIC x) {
+jn(int n, GENERIC x)
+{
 	int i, sgn;
 	GENERIC a, b, temp = 0;
 	GENERIC z, w, ox, on;
@@ -78,16 +79,18 @@
 	 * J(-n,x) = (-1)^n * J(n, x), J(n, -x) = (-1)^n * J(n, x)
 	 * Thus, J(-n,x) = J(n,-x)
 	 */
-	ox = x; on = (GENERIC)n;
+	ox = x;
+	on = (GENERIC)n;
+
 	if (n < 0) {
 		n = -n;
 		x = -x;
 	}
 	if (isnan(x))
 		return (x*x);	/* + -> * for Cheetah */
-	if (!((int) _lib_version == libm_ieee ||
-		(__xpg6 & _C99SUSv3_math_errexcept) != 0)) {
-	    if (fabs(x) > X_TLOSS)
+	if (!((int)_lib_version == libm_ieee ||
+	    (__xpg6 & _C99SUSv3_math_errexcept) != 0)) {
+		if (fabs(x) > X_TLOSS)
 			return (_SVID_libm_err(on, ox, 38));
 	}
 	if (n == 0)
@@ -95,7 +98,7 @@
 	if (n == 1)
 		return (j1(x));
 	if ((n&1) == 0)
-		sgn = 0; 			/* even n */
+		sgn = 0;			/* even n */
 	else
 		sgn = signbit(x);	/* old n  */
 	x = fabs(x);
@@ -105,7 +108,7 @@
 					 * Safe to use
 					 *  J(n+1,x)=2n/x *J(n,x)-J(n-1,x)
 					 */
-	    if (x > 1.0e91) {
+		if (x > 1.0e91) {
 				/*
 				 * x >> n**2
 				 *    Jn(x) = cos(x-(2n+1)*pi/4)*sqrt(2/x*pi)
@@ -116,124 +119,147 @@
 				 *	   n	sin(xn)*sqt2	cos(xn)*sqt2
 				 *	----------------------------------
 				 *	   0	 s-c		 c+s
-				 *	   1	-s-c 		-c+s
+				 *	   1	-s-c		-c+s
 				 *	   2	-s+c		-c-s
 				 *	   3	 s+c		 c-s
 				 */
-		switch (n&3) {
-		    case 0: temp =  cos(x)+sin(x); break;
-		    case 1: temp = -cos(x)+sin(x); break;
-		    case 2: temp = -cos(x)-sin(x); break;
-		    case 3: temp =  cos(x)-sin(x); break;
-		}
-		b = invsqrtpi*temp/sqrt(x);
-	    } else {
+			switch (n&3) {
+			case 0:
+				temp =  cos(x)+sin(x);
+				break;
+			case 1:
+				temp = -cos(x)+sin(x);
+				break;
+			case 2:
+				temp = -cos(x)-sin(x);
+				break;
+			case 3:
+				temp =  cos(x)-sin(x);
+				break;
+			}
+			b = invsqrtpi*temp/sqrt(x);
+		} else {
 			a = j0(x);
 			b = j1(x);
 			for (i = 1; i < n; i++) {
-		    temp = b;
-		    b = b*((GENERIC)(i+i)/x) - a; /* avoid underflow */
-		    a = temp;
+				temp = b;
+				/* avoid underflow */
+				b = b*((GENERIC)(i+i)/x) - a;
+				a = temp;
 			}
-	    }
-	} else {
-	    if (x < 1e-9) {	/* use J(n,x) = 1/n!*(x/2)^n */
-		b = pow(0.5*x, (GENERIC) n);
-		if (b != zero) {
-		    for (a = one, i = 1; i <= n; i++) a *= (GENERIC)i;
-		    b = b/a;
 		}
-	    } else {
-		/*
-		 * use backward recurrence
-		 * 			x	  x^2	  x^2
-		 *  J(n,x)/J(n-1,x) =  ----   ------   ------   .....
-		 *			2n  - 2(n+1) - 2(n+2)
-		 *
-		 * 			1	  1	    1
-		 *  (for large x)   =  ----  ------   ------   .....
-		 *			2n   2(n+1)   2(n+2)
-		 *			-- - ------ - ------ -
-		 *			 x	 x		 x
-		 *
-		 * Let w = 2n/x and h = 2/x, then the above quotient
-		 * is equal to the continued fraction:
-		 *		    1
-		 *	= -----------------------
-		 *			   1
-		 *	   w - -----------------
-		 *			  1
-		 * 			w+h - ---------
-		 *			   w+2h - ...
-		 *
-		 * To determine how many terms needed, let
-		 * Q(0) = w, Q(1) = w(w+h) - 1,
-		 * Q(k) = (w+k*h)*Q(k-1) - Q(k-2),
-		 * When Q(k) > 1e4	good for single
-		 * When Q(k) > 1e9	good for double
-		 * When Q(k) > 1e17	good for quaduple
-		 */
-	    /* determin k */
-		GENERIC t, v;
-		double q0, q1, h, tmp; int k, m;
-		w  = (n+n)/(double)x; h = 2.0/(double)x;
-		q0 = w;  z = w + h; q1 = w*z - 1.0; k = 1;
-		while (q1 < 1.0e9) {
-			k += 1; z += h;
-			tmp = z*q1 - q0;
-			q0 = q1;
-			q1 = tmp;
-		}
-		m = n+n;
-		for (t = zero, i = 2*(n+k); i >= m; i -= 2) t = one/(i/x-t);
-		a = t;
-		b = one;
-		/*
-		 * estimate log((2/x)^n*n!) = n*log(2/x)+n*ln(n)
-		 *  hence, if n*(log(2n/x)) > ...
-		 *  single 8.8722839355e+01
-		 *  double 7.09782712893383973096e+02
-		 *  long double 1.1356523406294143949491931077970765006170e+04
-		 *  then recurrent value may overflow and the result is
-		 *  likely underflow to zero
-		 */
-		tmp = n;
-		v = two/x;
-		tmp = tmp*log(fabs(v*tmp));
-		if (tmp < 7.09782712893383973096e+02) {
-			    for (i = n-1; i > 0; i--) {
-				temp = b;
-				b = ((i+i)/x)*b - a;
-			    a = temp;
+	} else {
+		if (x < 1e-9) {	/* use J(n,x) = 1/n!*(x/2)^n */
+			b = pow(0.5*x, (GENERIC) n);
+			if (b != zero) {
+				for (a = one, i = 1; i <= n; i++)
+					a *= (GENERIC)i;
+				b = b/a;
+			}
+		} else {
+			/*
+			 * use backward recurrence
+			 *			x	  x^2	  x^2
+			 *  J(n,x)/J(n-1,x) =  ----   ------   ------   .....
+			 *			2n  - 2(n+1) - 2(n+2)
+			 *
+			 *			1	  1	    1
+			 *  (for large x)   =  ----  ------   ------   .....
+			 *			2n   2(n+1)   2(n+2)
+			 *			-- - ------ - ------ -
+			 *			 x	 x		 x
+			 *
+			 * Let w = 2n/x and h = 2/x, then the above quotient
+			 * is equal to the continued fraction:
+			 *		    1
+			 *	= -----------------------
+			 *			   1
+			 *	   w - -----------------
+			 *			  1
+			 *			w+h - ---------
+			 *			   w+2h - ...
+			 *
+			 * To determine how many terms needed, let
+			 * Q(0) = w, Q(1) = w(w+h) - 1,
+			 * Q(k) = (w+k*h)*Q(k-1) - Q(k-2),
+			 * When Q(k) > 1e4	good for single
+			 * When Q(k) > 1e9	good for double
+			 * When Q(k) > 1e17	good for quaduple
+			 */
+			/* determine k */
+			GENERIC t, v;
+			double q0, q1, h, tmp;
+			int k, m;
+			w  = (n+n)/(double)x;
+			h = 2.0/(double)x;
+			q0 = w;
+			z = w + h;
+			q1 = w*z - 1.0;
+			k = 1;
+
+			while (q1 < 1.0e9) {
+				k += 1;
+				z += h;
+				tmp = z*q1 - q0;
+				q0 = q1;
+				q1 = tmp;
+			}
+			m = n+n;
+			for (t = zero, i = 2*(n+k); i >= m; i -= 2)
+				t = one/(i/x-t);
+			a = t;
+			b = one;
+			/*
+			 * estimate log((2/x)^n*n!) = n*log(2/x)+n*ln(n)
+			 * hence, if n*(log(2n/x)) > ...
+			 *  single:
+			 *    8.8722839355e+01
+			 *  double:
+			 *    7.09782712893383973096e+02
+			 *  long double:
+			 *    1.1356523406294143949491931077970765006170e+04
+			 * then recurrent value may overflow and the result is
+			 * likely underflow to zero
+			 */
+			tmp = n;
+			v = two/x;
+			tmp = tmp*log(fabs(v*tmp));
+			if (tmp < 7.09782712893383973096e+02) {
+				for (i = n-1; i > 0; i--) {
+					temp = b;
+					b = ((i+i)/x)*b - a;
+					a = temp;
 				}
-		} else {
+			} else {
 				for (i = n-1; i > 0; i--) {
-				    temp = b;
-				    b = ((i+i)/x)*b - a;
-				    a = temp;
+					temp = b;
+					b = ((i+i)/x)*b - a;
+					a = temp;
 					if (b > 1e100) {
 						a /= b;
 						t /= b;
 						b  = 1.0;
 					}
 				}
-		}
+			}
 			b = (t*j0(x)/b);
-	    }
+		}
 	}
-	if (sgn == 1)
+	if (sgn != 0)
 		return (-b);
 	else
 		return (b);
 }
 
 GENERIC
-yn(int n, GENERIC x) {
+yn(int n, GENERIC x)
+{
 	int i;
 	int sign;
 	GENERIC a, b, temp = 0, ox, on;
 
-	ox = x; on = (GENERIC)n;
+	ox = x;
+	on = (GENERIC)n;
 	if (isnan(x))
 		return (x*x);	/* + -> * for Cheetah */
 	if (x <= zero) {
@@ -245,9 +271,9 @@
 			return (_SVID_libm_err((GENERIC)n, x, 13));
 		}
 	}
-	if (!((int) _lib_version == libm_ieee ||
-		(__xpg6 & _C99SUSv3_math_errexcept) != 0)) {
-	    if (x > X_TLOSS)
+	if (!((int)_lib_version == libm_ieee ||
+	    (__xpg6 & _C99SUSv3_math_errexcept) != 0)) {
+		if (x > X_TLOSS)
 			return (_SVID_libm_err(on, ox, 39));
 	}
 	sign = 1;
@@ -273,15 +299,23 @@
 				 *    n	sin(xn)*sqt2	cos(xn)*sqt2
 				 *	----------------------------------
 				 *	 0	 s-c		 c+s
-				 *	 1	-s-c 		-c+s
+				 *	 1	-s-c		-c+s
 				 *	 2	-s+c		-c-s
 				 *	 3	 s+c		 c-s
 				 */
 		switch (n&3) {
-		    case 0: temp =  sin(x)-cos(x); break;
-		    case 1: temp = -sin(x)-cos(x); break;
-		    case 2: temp = -sin(x)+cos(x); break;
-		    case 3: temp =  sin(x)+cos(x); break;
+		case 0:
+			temp =  sin(x)-cos(x);
+			break;
+		case 1:
+			temp = -sin(x)-cos(x);
+			break;
+		case 2:
+			temp = -sin(x)+cos(x);
+			break;
+		case 3:
+			temp =  sin(x)+cos(x);
+			break;
 		}
 		b = invsqrtpi*temp/sqrt(x);
 	} else {
--- a/usr/src/lib/libm/common/C/tanh.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libm/common/C/tanh.c	Wed Jun 12 22:46:54 2019 +0200
@@ -67,7 +67,8 @@
 /* INDENT ON */
 
 double
-tanh(double x) {
+tanh(double x)
+{
 	double t, y, z;
 	int signx;
 	volatile double dummy __unused;
@@ -94,7 +95,7 @@
 	} else if (!finite(t))
 		return (copysign(1.0, x));
 	else
-		return (signx == 1 ? -z + small * small : z - small * small);
+		return ((signx != 0) ? -z + small * small : z - small * small);
 
-	return (signx == 1 ? -z : z);
+	return ((signx != 0) ? -z : z);
 }
--- a/usr/src/lib/libm/common/LD/__rem_pio2l.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libm/common/LD/__rem_pio2l.c	Wed Jun 12 22:46:54 2019 +0200
@@ -27,7 +27,8 @@
  * Use is subject to license terms.
  */
 
-/* __rem_pio2l(x,y)
+/*
+ * __rem_pio2l(x,y)
  *
  * return the remainder of x rem pi/2 in y[0]+y[1]
  * by calling __rem_pio2m
@@ -70,7 +71,7 @@
 	w = (long double)v[0];
 	y[0] = z + w;
 	y[1] = z - (y[0] - w);
-	if (sign == 1) {
+	if (sign != 0) {
 		y[0] = -y[0];
 		y[1] = -y[1];
 		return (-n);
--- a/usr/src/lib/libm/common/LD/jnl.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libm/common/LD/jnl.c	Wed Jun 12 22:46:54 2019 +0200
@@ -68,7 +68,8 @@
 one  = 1.0L;
 
 GENERIC
-jnl(n, x) int n; GENERIC x; {
+jnl(int n, GENERIC x)
+{
 	int i, sgn;
 	GENERIC a, b, temp = 0, z, w;
 
@@ -80,11 +81,14 @@
 		n = -n;
 		x = -x;
 	}
-	if (n == 0) return (j0l(x));
-	if (n == 1) return (j1l(x));
-	if (x != x) return x+x;
+	if (n == 0)
+		return (j0l(x));
+	if (n == 1)
+		return (j1l(x));
+	if (x != x)
+		return (x+x);
 	if ((n&1) == 0)
-		sgn = 0; 			/* even n */
+		sgn = 0;			/* even n */
 	else
 		sgn = signbitl(x);	/* old n  */
 	x = fabsl(x);
@@ -94,7 +98,7 @@
 			 * Safe to use
 			 * J(n+1,x)=2n/x *J(n,x)-J(n-1,x)
 			 */
-	    if (x > 1.0e91L) {
+		if (x > 1.0e91L) {
 				/*
 				 * x >> n**2
 				 *  Jn(x) = cos(x-(2n+1)*pi/4)*sqrt(2/x*pi)
@@ -105,139 +109,166 @@
 				 *	   n	sin(xn)*sqt2	cos(xn)*sqt2
 				 *	----------------------------------
 				 *	   0	 s-c		 c+s
-				 *	   1	-s-c 		-c+s
+				 *	   1	-s-c		-c+s
 				 *	   2	-s+c		-c-s
 				 *	   3	 s+c		 c-s
 				 */
-		switch (n&3) {
-		    case 0: temp =  cosl(x)+sinl(x); break;
-		    case 1: temp = -cosl(x)+sinl(x); break;
-		    case 2: temp = -cosl(x)-sinl(x); break;
-		    case 3: temp =  cosl(x)-sinl(x); break;
-		}
-		b = invsqrtpi*temp/sqrtl(x);
-	    } else {
+			switch (n&3) {
+			case 0:
+				temp =  cosl(x)+sinl(x);
+				break;
+			case 1:
+				temp = -cosl(x)+sinl(x);
+				break;
+			case 2:
+				temp = -cosl(x)-sinl(x);
+				break;
+			case 3:
+				temp =  cosl(x)-sinl(x);
+				break;
+			}
+			b = invsqrtpi*temp/sqrtl(x);
+		} else {
 			a = j0l(x);
 			b = j1l(x);
 			for (i = 1; i < n; i++) {
-		    temp = b;
-		    b = b*((GENERIC)(i+i)/x) - a; /* avoid underflow */
-		    a = temp;
+				temp = b;
+				/* avoid underflow */
+				b = b*((GENERIC)(i+i)/x) - a;
+				a = temp;
 			}
-	    }
-	} else {
-	    if (x < 1e-17L) {	/* use J(n,x) = 1/n!*(x/2)^n */
-		b = powl(0.5L*x, (GENERIC) n);
-		if (b != zero) {
-		    for (a = one, i = 1; i <= n; i++) a *= (GENERIC)i;
-		    b = b/a;
 		}
-	    } else {
-		/*
-		 * use backward recurrence
-		 * 			x      x^2      x^2
-		 *  J(n,x)/J(n-1,x) =  ----   ------   ------   .....
-		 *			2n  - 2(n+1) - 2(n+2)
-		 *
-		 * 			1      1        1
-		 *  (for large x)   =  ----  ------   ------   .....
-		 *			2n   2(n+1)   2(n+2)
-		 *			-- - ------ - ------ -
-		 *			 x     x         x
-		 *
-		 * Let w = 2n/x and h=2/x, then the above quotient
-		 * is equal to the continued fraction:
-		 *		    1
-		 *	= -----------------------
-		 *		       1
-		 *	   w - -----------------
-		 *			  1
-		 * 	        w+h - ---------
-		 *		       w+2h - ...
-		 *
-		 * To determine how many terms needed, let
-		 * Q(0) = w, Q(1) = w(w+h) - 1,
-		 * Q(k) = (w+k*h)*Q(k-1) - Q(k-2),
-		 * When Q(k) > 1e4	good for single
-		 * When Q(k) > 1e9	good for double
-		 * When Q(k) > 1e17	good for quaduple
-		 */
-	    /* determin k */
-		GENERIC t, v;
-		double q0, q1, h, tmp; int k, m;
-		w  = (n+n)/(double)x; h = 2.0/(double)x;
-		q0 = w;  z = w+h; q1 = w*z - 1.0; k = 1;
-		while (q1 < 1.0e17) {
-			k += 1; z += h;
-			tmp = z*q1 - q0;
-			q0 = q1;
-			q1 = tmp;
-		}
-		m = n+n;
-		for (t = zero, i = 2*(n+k); i >= m; i -= 2) t = one/(i/x-t);
-		a = t;
-		b = one;
+	} else {
+		if (x < 1e-17L) {	/* use J(n,x) = 1/n!*(x/2)^n */
+			b = powl(0.5L*x, (GENERIC)n);
+			if (b != zero) {
+				for (a = one, i = 1; i <= n; i++)
+					a *= (GENERIC)i;
+				b = b/a;
+			}
+		} else {
+			/* BEGIN CSTYLED */
+			/*
+			 * use backward recurrence
+			 *			x      x^2	x^2
+			 *  J(n,x)/J(n-1,x) =  ----   ------   ------   .....
+			 *			2n  - 2(n+1) - 2(n+2)
+			 *
+			 *			1      1	1
+			 *  (for large x)   =  ----  ------   ------   .....
+			 *			2n   2(n+1)   2(n+2)
+			 *			-- - ------ - ------ -
+			 *			 x     x         x
+			 *
+			 * Let w = 2n/x and h=2/x, then the above quotient
+			 * is equal to the continued fraction:
+			 *		    1
+			 *	= -----------------------
+			 *		       1
+			 *	   w - -----------------
+			 *			  1
+			 *		w+h - ---------
+			 *		       w+2h - ...
+			 *
+			 * To determine how many terms needed, let
+			 * Q(0) = w, Q(1) = w(w+h) - 1,
+			 * Q(k) = (w+k*h)*Q(k-1) - Q(k-2),
+			 * When Q(k) > 1e4	good for single
+			 * When Q(k) > 1e9	good for double
+			 * When Q(k) > 1e17	good for quaduple
+			 */
+			/* END CSTYLED */
+			/* determine k */
+			GENERIC t, v;
+			double q0, q1, h, tmp;
+			int k, m;
+			w  = (n+n)/(double)x;
+			h = 2.0/(double)x;
+			q0 = w;
+			z = w+h;
+			q1 = w*z - 1.0;
+			k = 1;
+			while (q1 < 1.0e17) {
+				k += 1;
+				z += h;
+				tmp = z*q1 - q0;
+				q0 = q1;
+				q1 = tmp;
+			}
+			m = n+n;
+			for (t = zero, i = 2*(n+k); i >= m; i -= 2)
+				t = one/(i/x-t);
+			a = t;
+			b = one;
 			/*
 			 * Estimate log((2/x)^n*n!) = n*log(2/x)+n*ln(n)
 			 * hence, if n*(log(2n/x)) > ...
-			 * single 8.8722839355e+01
-			 * double 7.09782712893383973096e+02
-			 * long double 1.1356523406294143949491931077970765006170e+04
+			 *  single:
+			 *    8.8722839355e+01
+			 *  double:
+			 *    7.09782712893383973096e+02
+			 *  long double:
+			 *    1.1356523406294143949491931077970765006170e+04
 			 * then recurrent value may overflow and the result is
 			 * likely underflow to zero.
 			 */
-		tmp = n;
-		v = two/x;
-		tmp = tmp*logl(fabsl(v*tmp));
-		if (tmp < 1.1356523406294143949491931077970765e+04L) {
+			tmp = n;
+			v = two/x;
+			tmp = tmp*logl(fabsl(v*tmp));
+			if (tmp < 1.1356523406294143949491931077970765e+04L) {
 				for (i = n-1; i > 0; i--) {
-				temp = b;
-				b = ((i+i)/x)*b - a;
-				a = temp;
+					temp = b;
+					b = ((i+i)/x)*b - a;
+					a = temp;
 				}
-		} else {
+			} else {
 				for (i = n-1; i > 0; i--) {
-				temp = b;
-				b = ((i+i)/x)*b - a;
-				a = temp;
-			if (b > 1e1000L) {
+					temp = b;
+					b = ((i+i)/x)*b - a;
+					a = temp;
+					if (b > 1e1000L) {
 						a /= b;
 						t /= b;
 						b  = 1.0;
 					}
 				}
-		}
+			}
 			b = (t*j0l(x)/b);
-	    }
+		}
 	}
-	if (sgn == 1)
-		return -b;
+	if (sgn != 0)
+		return (-b);
 	else
-		return b;
+		return (b);
 }
 
 GENERIC
-ynl(n, x) int n; GENERIC x; {
+ynl(int n, GENERIC x)
+{
 	int i;
 	int sign;
 	GENERIC a, b, temp = 0;
 
 	if (x != x)
-		return x+x;
+		return (x+x);
 	if (x <= zero) {
 		if (x == zero)
-			return -one/zero;
+			return (-one/zero);
 		else
-			return zero/zero;
+			return (zero/zero);
 	}
 	sign = 1;
 	if (n < 0) {
 		n = -n;
-		if ((n&1) == 1) sign = -1;
+		if ((n&1) == 1)
+			sign = -1;
 	}
-	if (n == 0) return (y0l(x));
-	if (n == 1) return (sign*y1l(x));
-	if (!finitel(x)) return zero;
+	if (n == 0)
+		return (y0l(x));
+	if (n == 1)
+		return (sign*y1l(x));
+	if (!finitel(x))
+		return (zero);
 
 	if (x > 1.0e91L) {
 				/*
@@ -249,16 +280,24 @@
 				 *
 				 *	   n	sin(xn)*sqt2	cos(xn)*sqt2
 				 *	----------------------------------
-				 * 	   0	 s-c		 c+s
-				 *	   1	-s-c 		-c+s
-				 * 	   2	-s+c		-c-s
+				 *	   0	 s-c		 c+s
+				 *	   1	-s-c		-c+s
+				 *	   2	-s+c		-c-s
 				 *	   3	 s+c		 c-s
 				 */
 		switch (n&3) {
-		    case 0: temp =  sinl(x)-cosl(x); break;
-		    case 1: temp = -sinl(x)-cosl(x); break;
-		    case 2: temp = -sinl(x)+cosl(x); break;
-		    case 3: temp =  sinl(x)+cosl(x); break;
+		case 0:
+			temp =  sinl(x)-cosl(x);
+			break;
+		case 1:
+			temp = -sinl(x)-cosl(x);
+			break;
+		case 2:
+			temp = -sinl(x)+cosl(x);
+			break;
+		case 3:
+			temp =  sinl(x)+cosl(x);
+			break;
 		}
 		b = invsqrtpi*temp/sqrtl(x);
 	} else {
@@ -277,7 +316,7 @@
 		}
 	}
 	if (sign > 0)
-		return b;
+		return (b);
 	else
-		return -b;
+		return (-b);
 }
--- a/usr/src/lib/libm/common/Q/__rem_pio2l.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libm/common/Q/__rem_pio2l.c	Wed Jun 12 22:46:54 2019 +0200
@@ -50,7 +50,8 @@
 	pio4   = 0.7853981633974483096156608458198757210495L;
 
 int
-__rem_pio2l(long double x, long double *y) {
+__rem_pio2l(long double x, long double *y)
+{
 	long double z, w;
 	double t[5], v[5];
 	int e0, i, nx, n, sign;
@@ -66,19 +67,19 @@
 	e0 = ilogbl(z) - 23;
 	z = scalbnl(z, -e0);
 	for (i = 0; i < 5; i++) {
-		t[i] = (double) ((int) (z));
-		z = (z - (long double) t[i]) * two24l;
+		t[i] = (double)((int)(z));
+		z = (z - (long double)t[i]) * two24l;
 	}
 	nx = 5;
 	while (t[nx - 1] == 0.0)
 		nx--;		/* skip zero term */
 	ipio2 = _TBL_ipio2l_inf;
-	n = __rem_pio2m(t, v, e0, nx, 3, (const int *) ipio2);
-	z = (long double) v[2] + (long double) v[1];
-	w = (long double) v[0];
+	n = __rem_pio2m(t, v, e0, nx, 3, (const int *)ipio2);
+	z = (long double)v[2] + (long double)v[1];
+	w = (long double)v[0];
 	y[0] = z + w;
 	y[1] = z - (y[0] - w);
-	if (sign == 1) {
+	if (sign != 0) {
 		y[0] = -y[0];
 		y[1] = -y[1];
 		return (-n);
--- a/usr/src/lib/libm/common/Q/atan2l.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libm/common/Q/atan2l.c	Wed Jun 12 22:46:54 2019 +0200
@@ -63,7 +63,7 @@
 
 static const long double
 	zero	=  0.0L,
-	tiny 	=  1.0e-40L,
+	tiny	=  1.0e-40L,
 	one	=  1.0L,
 	half	=  0.5L,
 	PI3o4	=  2.356194490192344928846982537459627163148L,
@@ -73,7 +73,8 @@
 	PI_lo	=  8.671810130123781024797044026043351968762e-35L;
 
 long double
-atan2l(long double y, long double x) {
+atan2l(long double y, long double x)
+{
 	long double t, z;
 	int k, m, signy, signx;
 
@@ -83,7 +84,8 @@
 	signx = signbitl(x);
 	if (x == one)
 		return (atanl(y));
-	m = signy + signx + signx;
+	/* Ensure sign indicators are boolean */
+	m = (signy != 0) + (signx != 0) + (signx != 0);
 
 	/* when y = 0 */
 	if (y == zero)
@@ -100,7 +102,7 @@
 
 	/* when x = 0 */
 	if (x == zero)
-		return (signy == 1 ? -PIo2 - tiny : PIo2 + tiny);
+		return (signy != 0 ? -PIo2 - tiny : PIo2 + tiny);
 
 	/* when x is INF */
 	if (!finitel(x)) {
@@ -130,7 +132,7 @@
 	}
 	/* when y is INF */
 	if (!finitel(y))
-		return (signy == 1 ? -PIo2 - tiny : PIo2 + tiny);
+		return (signy != 0 ? -PIo2 - tiny : PIo2 + tiny);
 
 	/* compute y/x */
 	x = fabsl(x);
@@ -156,5 +158,5 @@
 		return ((z - t) - PI);	/* atan(-,-) */
 	}
 	/* NOTREACHED */
-    return 0.0L;
+	return (0.0L);
 }
--- a/usr/src/lib/libm/common/Q/jnl.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libm/common/Q/jnl.c	Wed Jun 12 22:46:54 2019 +0200
@@ -68,7 +68,8 @@
 one  = 1.0L;
 
 GENERIC
-jnl(n, x) int n; GENERIC x; {
+jnl(int n, GENERIC x)
+{
 	int i, sgn;
 	GENERIC a, b, temp, z, w;
 
@@ -87,7 +88,7 @@
 	if (x != x)
 		return (x+x);
 	if ((n&1) == 0)
-		sgn = 0; 			/* even n */
+		sgn = 0;			/* even n */
 	else
 		sgn = signbitl(x);	/* old n  */
 	x = fabsl(x);
@@ -97,7 +98,7 @@
 					 * Safe to use
 					 * J(n+1,x)=2n/x *J(n,x)-J(n-1,x)
 					 */
-	    if (x > 1.0e91L) {
+		if (x > 1.0e91L) {
 				/*
 				 * x >> n**2
 				 *  Jn(x) = cos(x-(2n+1)*pi/4)*sqrt(2/x*pi)
@@ -108,119 +109,142 @@
 				 *	   n	sin(xn)*sqt2	cos(xn)*sqt2
 				 *	----------------------------------
 				 *	   0	 s-c		 c+s
-				 *	   1	-s-c 		-c+s
+				 *	   1	-s-c		-c+s
 				 *	   2	-s+c		-c-s
 				 *	   3	 s+c		 c-s
 				 */
-		switch (n&3) {
-		    case 0: temp =  cosl(x)+sinl(x); break;
-		    case 1: temp = -cosl(x)+sinl(x); break;
-		    case 2: temp = -cosl(x)-sinl(x); break;
-		    case 3: temp =  cosl(x)-sinl(x); break;
-		}
-		b = invsqrtpi*temp/sqrtl(x);
-	    } else {
+			switch (n&3) {
+			case 0:
+				temp =  cosl(x)+sinl(x);
+				break;
+			case 1:
+				temp = -cosl(x)+sinl(x);
+				break;
+			case 2:
+				temp = -cosl(x)-sinl(x);
+				break;
+			case 3:
+				temp =  cosl(x)-sinl(x);
+				break;
+			}
+			b = invsqrtpi*temp/sqrtl(x);
+		} else {
 			a = j0l(x);
 			b = j1l(x);
 			for (i = 1; i < n; i++) {
-		    temp = b;
-		    b = b*((GENERIC)(i+i)/x) - a; /* avoid underflow */
-		    a = temp;
+				temp = b;
+				/* avoid underflow */
+				b = b*((GENERIC)(i+i)/x) - a;
+				a = temp;
 			}
-	    }
-	} else {
-	    if (x < 1e-17L) {	/* use J(n,x) = 1/n!*(x/2)^n */
-		b = powl(0.5L*x, (GENERIC)n);
-		if (b != zero) {
-		    for (a = one, i = 1; i <= n; i++) a *= (GENERIC)i;
-		    b = b/a;
 		}
-	    } else {
-		/* use backward recurrence */
-		/*
-		 * 			x      x^2      x^2
-		 *  J(n,x)/J(n-1,x) =  ----   ------   ------   .....
-		 *			2n  - 2(n+1) - 2(n+2)
-		 *
-		 * 			1      1        1
-		 *  (for large x)   =  ----  ------   ------   .....
-		 *			2n   2(n+1)   2(n+2)
-		 *			-- - ------ - ------ -
-		 *			 x     x         x
-		 *
-		 * Let w = 2n/x and h=2/x, then the above quotient
-		 * is equal to the continued fraction:
-		 *		    1
-		 *	= -----------------------
-		 *		       1
-		 *	   w - -----------------
-		 *			  1
-		 * 	        w+h - ---------
-		 *		       w+2h - ...
-		 *
-		 * To determine how many terms needed, let
-		 * Q(0) = w, Q(1) = w(w+h) - 1,
-		 * Q(k) = (w+k*h)*Q(k-1) - Q(k-2),
-		 * When Q(k) > 1e4	good for single
-		 * When Q(k) > 1e9	good for double
-		 * When Q(k) > 1e17	good for quaduple
-		 */
-	    /* determin k */
-		GENERIC t, v;
-		double q0, q1, h, tmp; int k, m;
-		w  = (n+n)/(double)x; h = 2.0/(double)x;
-		q0 = w;  z = w+h; q1 = w*z - 1.0; k = 1;
-		while (q1 < 1.0e17) {
-			k += 1; z += h;
-			tmp = z*q1 - q0;
-			q0 = q1;
-			q1 = tmp;
+	} else {
+		if (x < 1e-17L) {	/* use J(n,x) = 1/n!*(x/2)^n */
+			b = powl(0.5L*x, (GENERIC)n);
+			if (b != zero) {
+				for (a = one, i = 1; i <= n; i++)
+					a *= (GENERIC)i;
+				b = b/a;
+			}
+		} else {
+			/* use backward recurrence */
+			/* BEGIN CSTYLED */
+			/*
+			 *			x      x^2	x^2
+			 *  J(n,x)/J(n-1,x) =  ----   ------   ------	.....
+			 *			2n  - 2(n+1) - 2(n+2)
+			 *
+			 *			1      1	1
+			 *  (for large x)   =  ----  ------   ------   .....
+			 *			2n   2(n+1)   2(n+2)
+			 *			-- - ------ - ------ -
+			 *			 x     x	 x
+			 *
+			 * Let w = 2n/x and h=2/x, then the above quotient
+			 * is equal to the continued fraction:
+			 *		    1
+			 *	= -----------------------
+			 *		       1
+			 *	   w - -----------------
+			 *			  1
+			 *		w+h - ---------
+			 *		       w+2h - ...
+			 *
+			 * To determine how many terms needed, let
+			 * Q(0) = w, Q(1) = w(w+h) - 1,
+			 * Q(k) = (w+k*h)*Q(k-1) - Q(k-2),
+			 * When Q(k) > 1e4	good for single
+			 * When Q(k) > 1e9	good for double
+			 * When Q(k) > 1e17	good for quaduple
+			 */
+			/* END CSTYLED */
+			/* determine k */
+			GENERIC t, v;
+			double q0, q1, h, tmp;
+			int k, m;
+			w  = (n+n)/(double)x;
+			h = 2.0/(double)x;
+			q0 = w;
+			z = w+h;
+			q1 = w*z - 1.0;
+			k = 1;
+			while (q1 < 1.0e17) {
+				k += 1;
+				z += h;
+				tmp = z*q1 - q0;
+				q0 = q1;
+				q1 = tmp;
+			}
+			m = n+n;
+			for (t = zero, i = 2*(n+k); i >= m; i -= 2)
+				t = one/(i/x-t);
+			a = t;
+			b = one;
+			/*
+			 * estimate log((2/x)^n*n!) = n*log(2/x)+n*ln(n)
+			 * hence, if n*(log(2n/x)) > ...
+			 *  single:
+			 *    8.8722839355e+01
+			 *  double:
+			 *    7.09782712893383973096e+02
+			 *  long double:
+			 *    1.1356523406294143949491931077970765006170e+04
+			 *  then recurrent value may overflow and the result is
+			 *  likely underflow to zero
+			 */
+			tmp = n;
+			v = two/x;
+			tmp = tmp*logl(fabsl(v*tmp));
+			if (tmp < 1.1356523406294143949491931077970765e+04L) {
+				for (i = n-1; i > 0; i--) {
+					temp = b;
+					b = ((i+i)/x)*b - a;
+					a = temp;
+				}
+			} else {
+				for (i = n-1; i > 0; i--) {
+					temp = b;
+					b = ((i+i)/x)*b - a;
+					a = temp;
+					if (b > 1e1000L) {
+						a /= b;
+						t /= b;
+						b  = 1.0;
+					}
+				}
+			}
+			b = (t*j0l(x)/b);
 		}
-		m = n+n;
-		for (t = zero, i = 2*(n+k); i >= m; i -= 2) t = one/(i/x-t);
-		a = t;
-		b = one;
-                /*
-		 * estimate log((2/x)^n*n!) = n*log(2/x)+n*ln(n)
-                 * hence, if n*(log(2n/x)) > ...
-                 *  single 8.8722839355e+01
-                 *  double 7.09782712893383973096e+02
-                 *  long double 1.1356523406294143949491931077970765006170e+04
-                 *  then recurrent value may overflow and the result is
-                 *  likely underflow to zero
-                 */
-		tmp = n;
-		v = two/x;
-		tmp = tmp*logl(fabsl(v*tmp));
-		if (tmp < 1.1356523406294143949491931077970765e+04L) {
-				for (i = n-1; i > 0; i--) {
-		        temp = b;
-		        b = ((i+i)/x)*b - a;
-		        a = temp;
-	     	    }
-		} else {
-				for (i = n-1; i > 0; i--) {
-		        temp = b;
-		        b = ((i+i)/x)*b - a;
-		        a = temp;
-			if (b > 1e1000L) {
-                            a /= b;
-                            t /= b;
-                            b  = 1.0;
-                        }
-	     	    }
-		}
-	    	b = (t*j0l(x)/b);
-	    }
 	}
-	if (sgn == 1)
+	if (sgn != 0)
 		return (-b);
 	else
 		return (b);
 }
 
-GENERIC ynl(n, x)
-int n; GENERIC x; {
+GENERIC
+ynl(int n, GENERIC x)
+{
 	int i;
 	int sign;
 	GENERIC a, b, temp;
@@ -245,24 +269,34 @@
 	if (!finitel(x))
 		return (zero);
 
-	if (x > 1.0e91L) {	/* x >> n**2
-				    Jn(x) = cos(x-(2n+1)*pi/4)*sqrt(2/x*pi)
-				    Yn(x) = sin(x-(2n+1)*pi/4)*sqrt(2/x*pi)
-				    Let s = sin(x), c = cos(x),
-					xn = x-(2n+1)*pi/4, sqt2 = sqrt(2), then
-
-					   n	sin(xn)*sqt2	cos(xn)*sqt2
-					----------------------------------
-					   0	 s-c		 c+s
-					   1	-s-c 		-c+s
-					   2	-s+c		-c-s
-					   3	 s+c		 c-s
-				 */
+	if (x > 1.0e91L) {
+		/*
+		 * x >> n**2
+		 *   Jn(x) = cos(x-(2n+1)*pi/4)*sqrt(2/x*pi)
+		 *   Yn(x) = sin(x-(2n+1)*pi/4)*sqrt(2/x*pi)
+		 *   Let s = sin(x), c = cos(x),
+		 *	xn = x-(2n+1)*pi/4, sqt2 = sqrt(2), then
+		 *
+		 *	   n	sin(xn)*sqt2	cos(xn)*sqt2
+		 *	----------------------------------
+		 *	   0	 s-c		 c+s
+		 *	   1	-s-c		-c+s
+		 *	   2	-s+c		-c-s
+		 *	   3	 s+c		 c-s
+		 */
 		switch (n&3) {
-		    case 0: temp =  sinl(x)-cosl(x); break;
-		    case 1: temp = -sinl(x)-cosl(x); break;
-		    case 2: temp = -sinl(x)+cosl(x); break;
-		    case 3: temp =  sinl(x)+cosl(x); break;
+		case 0:
+			temp =  sinl(x)-cosl(x);
+			break;
+		case 1:
+			temp = -sinl(x)-cosl(x);
+			break;
+		case 2:
+			temp = -sinl(x)+cosl(x);
+			break;
+		case 3:
+			temp =  sinl(x)+cosl(x);
+			break;
 		}
 		b = invsqrtpi*temp/sqrtl(x);
 	} else {
--- a/usr/src/lib/libmd/Makefile.targ	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libmd/Makefile.targ	Wed Jun 12 22:46:54 2019 +0200
@@ -22,6 +22,7 @@
 #
 # Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 # Copyright 2013 Saso Kiselkov. All rights reserved.
+# Copyright 2019 Peter Tribble.
 #
 
 COMDIR =	$(SRC)/common/crypto
@@ -45,7 +46,7 @@
 pics/sha1_asm.o: $(COMDIR)/sha1/sparc/$(PLATFORM)/sha1_asm.s
 		$(COMPILE.s) -o pics/sha1_asm.o \
 		$(COMDIR)/sha1/sparc/$(PLATFORM)/sha1_asm.s
-		$(POST_PROCESS_O)
+		$(POST_PROCESS_S_O)
 
 pics/%.o: $(COMDIR)/sha2/%.c
 		$(COMPILE.c) -I$(COMDIR)/sha2 -o $@ $<
--- a/usr/src/lib/libshare/smb/Makefile.com	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libshare/smb/Makefile.com	Wed Jun 12 22:46:54 2019 +0200
@@ -22,7 +22,7 @@
 # Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-# Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
 #
 # Copyright (c) 2018, Joyent, Inc.
 
@@ -33,7 +33,7 @@
 SMBCOMMON_DIR	= $(SRC)/common/smbsrv
 
 LIBOBJS =	libshare_smb.o smb_share_doorclnt.o
-SMBCOMMON_OBJ =	smb_door_legacy.o
+SMBCOMMON_OBJ =	smb_door_legacy.o smb_cfg_util.o
 SMBBASE_OBJ =	smb_cfg.o smb_scfutil.o
 OBJECTS =	$(LIBOBJS) $(SMBCOMMON_OBJ) $(SMBBASE_OBJ)
 
@@ -56,7 +56,7 @@
 SMATCH=off
 
 CPPFLAGS +=	-D_REENTRANT -I$(ADJUNCT_PROTO)/usr/include/libxml2 \
-  		-I$(SRCDIR)/../common
+		-I$(SRCDIR)/../common
 $(ENABLE_SMB_PRINTING) CPPFLAGS += -DHAVE_CUPS
 
 .KEEP_STATE:
@@ -75,6 +75,10 @@
 	$(COMPILE.c) -o $@ $(SMBCOMMON_DIR)/smb_door_legacy.c
 	$(POST_PROCESS_O)
 
+pics/smb_cfg_util.o:       $(SMBCOMMON_DIR)/smb_cfg_util.c
+	$(COMPILE.c) -o $@ $(SMBCOMMON_DIR)/smb_cfg_util.c
+	$(POST_PROCESS_O)
+
 pics/smb_cfg.o:       $(SMBBASE_DIR)/smb_cfg.c
 	$(COMPILE.c) -o $@ $(SMBBASE_DIR)/smb_cfg.c
 	$(POST_PROCESS_O)
--- a/usr/src/lib/libshare/smb/libshare_smb.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libshare/smb/libshare_smb.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -85,6 +85,7 @@
 static int cmd_validator(int, char *);
 static int disposition_validator(int, char *);
 static int max_protocol_validator(int, char *);
+static int require_validator(int, char *);
 
 static int smb_enable_resource(sa_resource_t);
 static int smb_disable_resource(sa_resource_t);
@@ -178,7 +179,9 @@
 	{ SHOPT_GUEST,		OPT_TYPE_BOOLEAN },
 	{ SHOPT_DFSROOT,	OPT_TYPE_BOOLEAN },
 	{ SHOPT_DESCRIPTION,	OPT_TYPE_STRING },
+	{ SHOPT_FSO,		OPT_TYPE_BOOLEAN },
 	{ SHOPT_QUOTAS,		OPT_TYPE_BOOLEAN },
+	{ SHOPT_ENCRYPT,	OPT_TYPE_STRING },
 	{ NULL, NULL }
 };
 
@@ -917,11 +920,33 @@
 	    disposition_validator, SMB_REFRESH_REFRESH },
 	{ SMB_CI_MAX_PROTOCOL, 0, MAX_VALUE_BUFLEN, max_protocol_validator,
 	    SMB_REFRESH_REFRESH },
+	{ SMB_CI_ENCRYPT, 0, MAX_VALUE_BUFLEN, require_validator,
+	    SMB_REFRESH_REFRESH },
+	{ SMB_CI_OPLOCK_ENABLE, 0, 0, true_false_validator,
+	    SMB_REFRESH_REFRESH },
 };
 
 #define	SMB_OPT_NUM \
 	(sizeof (smb_proto_options) / sizeof (smb_proto_options[0]))
 
+static int
+require_validator(int index, char *value)
+{
+	if (string_length_check_validator(index, value) != SA_OK)
+		return (SA_BAD_VALUE);
+
+	if (strcmp(value, "required") == 0)
+		return (SA_OK);
+
+	if (strcmp(value, "disabled") == 0)
+		return (SA_OK);
+
+	if (strcmp(value, "enabled") == 0)
+		return (SA_OK);
+
+	return (SA_BAD_VALUE);
+}
+
 /*
  * Check the range of value as int range.
  */
@@ -1535,7 +1560,11 @@
 				opt = &smb_proto_options[index];
 
 				/* Save to SMF */
-				(void) smb_config_set(opt->smb_index, value);
+				if (smb_config_set(opt->smb_index,
+				    value) != 0) {
+					ret = SA_BAD_VALUE;
+					goto out;
+				}
 				/*
 				 * Specialized refresh mechanisms can
 				 * be flagged in the proto_options and
@@ -1551,6 +1580,7 @@
 		}
 	}
 
+out:
 	if (name != NULL)
 		sa_free_attr_string(name);
 	if (value != NULL)
@@ -1938,7 +1968,7 @@
 
 static void
 smb_sprint_option(char **rbuff, size_t *rbuffsize, size_t incr,
-			sa_property_t prop, int sep)
+    sa_property_t prop, int sep)
 {
 	char *name;
 	char *value;
@@ -2116,6 +2146,7 @@
 	char *rname;
 	char *val = NULL;
 	char csc_value[SMB_CSC_BUFSZ];
+	char strbuf[sizeof ("required")];
 
 	bzero(si, sizeof (smb_share_t));
 
@@ -2160,10 +2191,15 @@
 	if (smb_saprop_getbool(opts, SHOPT_DFSROOT, B_FALSE))
 		si->shr_flags |= SMB_SHRF_DFSROOT;
 
+	if (smb_saprop_getbool(opts, SHOPT_FSO, B_FALSE))
+		si->shr_flags |= SMB_SHRF_FSO;
+
 	/* Quotas are enabled by default. */
-	si->shr_flags |= SMB_SHRF_QUOTAS;
-	if (!smb_saprop_getbool(opts, SHOPT_QUOTAS, B_TRUE))
-		si->shr_flags &= ~SMB_SHRF_QUOTAS;
+	if (smb_saprop_getbool(opts, SHOPT_QUOTAS, B_TRUE))
+		si->shr_flags |= SMB_SHRF_QUOTAS;
+
+	if (smb_saprop_getstr(opts, SHOPT_ENCRYPT, strbuf, sizeof (strbuf)))
+		smb_cfg_set_require(strbuf, &si->shr_encrypt);
 
 	(void) smb_saprop_getstr(opts, SHOPT_AD_CONTAINER, si->shr_container,
 	    sizeof (si->shr_container));
--- a/usr/src/lib/libsocket/inet/rcmd.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libsocket/inet/rcmd.c	Wed Jun 12 22:46:54 2019 +0200
@@ -25,7 +25,7 @@
  */
 
 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
-/*	  All Rights Reserved  	*/
+/*	  All Rights Reserved	*/
 
 /*
  * University Copyright- Copyright (c) 1982, 1986, 1988
@@ -799,8 +799,8 @@
 			nodomain = 1;
 			return (0);
 		}
-		ldomain[MAXHOSTNAMELEN] = NULL;
-		if ((domainp = index(ldomain, '.')) == (char *)NULL) {
+		ldomain[MAXHOSTNAMELEN] = '\0';
+		if ((domainp = index(ldomain, '.')) == NULL) {
 			nodomain = 1;
 			return (0);
 		}
--- a/usr/src/lib/libsys/Makefile.targ	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/libsys/Makefile.targ	Wed Jun 12 22:46:54 2019 +0200
@@ -21,6 +21,7 @@
 #
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
+# Copyright 2019 Peter Tribble.
 #
 
 BUILD.SO +=	$(PICS)
@@ -35,4 +36,4 @@
 
 pics/%.o:	%.s
 		$(COMPILE.s) -o $@ $<
-		$(POST_PROCESS_O)
+		$(POST_PROCESS_S_O)
--- a/usr/src/lib/pkcs11/libsoftcrypto/capabilities/sun4u/Makefile.targ	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/pkcs11/libsoftcrypto/capabilities/sun4u/Makefile.targ	Wed Jun 12 22:46:54 2019 +0200
@@ -21,18 +21,19 @@
 
 #
 # Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright 2019 Peter Tribble.
 #
 
 include		../../Makefile.targ
 
 pics/aes_crypt_asm.o: $(AES_DIR)/sun4u/aes_crypt_asm.s
 		$(COMPILE.s) -o $@ $(AES_DIR)/sun4u/aes_crypt_asm.s
-		$(POST_PROCESS_O)
+		$(POST_PROCESS_S_O)
 
 pics/arcfour_crypt_asm.o: $(ARCFOUR_DIR)/sun4u/arcfour_crypt_asm.s
 		$(COMPILE.s) -o $@ $(ARCFOUR_DIR)/sun4u/arcfour_crypt_asm.s
-		$(POST_PROCESS_O)
+		$(POST_PROCESS_S_O)
 
 pics/des_crypt_asm.o: $(DES_DIR)/sun4u/des_crypt_asm.s
 		$(COMPILE.s) -o $@ $(DES_DIR)/sun4u/des_crypt_asm.s
-		$(POST_PROCESS_O)
+		$(POST_PROCESS_S_O)
--- a/usr/src/lib/smbsrv/libfksmbsrv/Makefile.com	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libfksmbsrv/Makefile.com	Wed Jun 12 22:46:54 2019 +0200
@@ -31,6 +31,7 @@
 
 OBJS_LOCAL = \
 		fksmb_cred.o \
+		fksmb_encrypt_pkcs.o \
 		fksmb_fem.o \
 		fksmb_idmap.o \
 		fksmb_init.o \
@@ -54,6 +55,7 @@
 		smb_alloc.o				\
 		smb_authenticate.o			\
 		smb_close.o				\
+		smb_cmn_oplock.o			\
 		smb_cmn_rename.o			\
 		smb_cmn_setfile.o			\
 		smb_common_open.o			\
@@ -108,6 +110,7 @@
 		smb_session_setup_andx.o		\
 		smb_set_fileinfo.o			\
 		smb_signing.o				\
+		smb_srv_oplock.o			\
 		smb_thread.o				\
 		smb_tree.o				\
 		smb_trans2_create_directory.o		\
@@ -123,6 +126,7 @@
 		\
 		smb2_aapl.o \
 		smb2_dispatch.o \
+		smb2_durable.o \
 		smb2_cancel.o \
 		smb2_change_notify.o \
 		smb2_close.o \
@@ -130,6 +134,7 @@
 		smb2_echo.o \
 		smb2_flush.o \
 		smb2_ioctl.o \
+		smb2_lease.o \
 		smb2_lock.o \
 		smb2_logoff.o \
 		smb2_negotiate.o \
@@ -151,12 +156,15 @@
 		smb2_signing.o \
 		smb2_tree_connect.o \
 		smb2_tree_disconn.o \
-		smb2_write.o
+		smb2_write.o \
+	        \
+	        smb3_encrypt.o
 
 # Can't just link with -lsmb because of user vs kernel API
 # i.e. can't call free with mem from kmem_alloc, which is
 # what happens if we just link with -lsmb
 OBJS_CMN_SMBSRV = \
+		smb_cfg_util.o \
 		smb_inet.o \
 		smb_match.o \
 		smb_msgbuf.o \
--- a/usr/src/lib/smbsrv/libfksmbsrv/common/fake_vop.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libfksmbsrv/common/fake_vop.c	Wed Jun 12 22:46:54 2019 +0200
@@ -1128,7 +1128,7 @@
 		break;
 
 	case _PC_ACL_ENABLED:
-		val = 0;
+		val = _ACL_ACE_ENABLED;
 		break;
 
 	case _PC_CASE_BEHAVIOR:
--- a/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_cred.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_cred.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <sys/types.h>
@@ -38,11 +38,18 @@
  * we don't bother with real credential.  Everything here uses
  * the ordinary credentials of the process running this.
  */
+
+/*
+ * This library does not implement real credentials. All contexts
+ * use an opaque cred_t object, and all activity happens in the
+ * context of the user who runs the program.
+ */
 cred_t *
 smb_cred_create(smb_token_t *token)
 {
+	_NOTE(ARGUNUSED(token))
 	cred_t *cr;
-	cr = (cred_t *)token;	/* hack */
+	cr = CRED();
 	return (cr);
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_encrypt_pkcs.c	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,115 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+/*
+ * Helper functions for SMB3 encryption using PKCS#11
+ *
+ * There are two implementations of these functions:
+ * This one (for user space) and another for kernel.
+ * See: uts/common/fs/smbsrv/smb3_encrypt_kcf.c
+ *
+ * NOTE: CCM is not implemented in PKCS yet, so these are just stubs.
+ */
+
+#include <smbsrv/smb_kcrypt.h>
+#include <smbsrv/smb2_kproto.h>
+
+/*
+ * SMB3 encryption helpers:
+ * (getmech, init, update, final)
+ */
+
+/* ARGSUSED */
+int
+smb3_encrypt_getmech(smb_crypto_mech_t *mech)
+{
+	cmn_err(CE_NOTE, "fksmbsrv does not support SMB3 Encryption");
+	return (-1);
+}
+
+/* ARGSUSED */
+void
+smb3_crypto_init_param(smb3_crypto_param_t *param,
+    uint8_t *nonce, size_t noncesize, uint8_t *auth, size_t authsize,
+    size_t datasize)
+{
+}
+
+/*
+ * Start the KCF session, load the key
+ */
+
+/* ARGSUSED */
+static int
+smb3_crypto_init(smb3_enc_ctx_t *ctxp, smb_crypto_mech_t *mech,
+    uint8_t *key, size_t key_len, smb3_crypto_param_t *param,
+    boolean_t is_encrypt)
+{
+	return (-1);
+}
+
+/* ARGSUSED */
+int
+smb3_encrypt_init(smb3_enc_ctx_t *ctxp, smb_crypto_mech_t *mech,
+    smb3_crypto_param_t *param, uint8_t *key, size_t keylen,
+    uint8_t *buf, size_t buflen)
+{
+	return (smb3_crypto_init(ctxp, mech, key, keylen, param, B_TRUE));
+}
+
+int
+smb3_decrypt_init(smb3_enc_ctx_t *ctxp, smb_crypto_mech_t *mech,
+    smb3_crypto_param_t *param, uint8_t *key, size_t keylen)
+{
+	return (smb3_crypto_init(ctxp, mech, key, keylen, param, B_FALSE));
+}
+
+/*
+ * Digest one segment
+ */
+
+/* ARGSUSED */
+int
+smb3_encrypt_update(smb3_enc_ctx_t *ctxp, uint8_t *in, size_t len)
+{
+	return (-1);
+}
+
+/* ARGSUSED */
+int
+smb3_decrypt_update(smb3_enc_ctx_t *ctxp, uint8_t *in, size_t len)
+{
+	return (-1);
+}
+
+/* ARGSUSED */
+int
+smb3_encrypt_final(smb3_enc_ctx_t *ctxp, uint8_t *digest16)
+{
+	return (-1);
+}
+
+/* ARGSUSED */
+int
+smb3_decrypt_final(smb3_enc_ctx_t *ctxp, uint8_t *buf, size_t buflen)
+{
+	return (-1);
+}
+
+/* ARGSUSED */
+void
+smb3_encrypt_cancel(smb3_enc_ctx_t *ctxp)
+{
+}
--- a/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_init.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_init.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <sys/types.h>
@@ -49,17 +49,11 @@
  * with Windows NT4.0. Previous experiments with NT4.0 resulted in directory
  * listing problems so this buffer size is configurable based on the end-user
  * environment. When in doubt use 37KB.
- *
- * smb_raw_mode: read_raw and write_raw supported (1) or NOT supported (0).
  */
 int	smb_maxbufsize = SMB_NT_MAXBUF;
-int	smb_oplock_levelII = 1;
-int	smb_oplock_timeout = OPLOCK_STD_TIMEOUT;
-int	smb_oplock_min_timeout = OPLOCK_MIN_TIMEOUT;
 int	smb_flush_required = 1;
 int	smb_dirsymlink_enable = 1;
 int	smb_sign_debug = 0;
-int	smb_raw_mode = 0;
 int	smb_shortnames = 1;
 uint_t	smb_audit_flags =
 #ifdef	DEBUG
--- a/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_sign_pkcs.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_sign_pkcs.c	Wed Jun 12 22:46:54 2019 +0200
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -23,29 +23,52 @@
 
 #include <stdlib.h>
 #include <smbsrv/smb_kproto.h>
-#include <smbsrv/smb_signing.h>
+#include <smbsrv/smb_kcrypt.h>
 #include <security/cryptoki.h>
 #include <security/pkcs11.h>
 
 /*
+ * Common function to see if a mech is available.
+ */
+static int
+find_mech(smb_crypto_mech_t *mech, ulong_t mid)
+{
+	CK_SESSION_HANDLE hdl;
+	CK_RV rv;
+
+	rv = SUNW_C_GetMechSession(mid, &hdl);
+	if (rv != CKR_OK) {
+		cmn_err(CE_NOTE, "PKCS#11: no mech 0x%x",
+		    (unsigned int)mid);
+		return (-1);
+	}
+	(void) C_CloseSession(hdl);
+
+	mech->mechanism = mid;
+	mech->pParameter = NULL;
+	mech->ulParameterLen = 0;
+	return (0);
+}
+
+/*
  * SMB1 signing helpers:
  * (getmech, init, update, final)
  */
 
+/*
+ * Find out if we have this mech.
+ */
 int
-smb_md5_getmech(smb_sign_mech_t *mech)
+smb_md5_getmech(smb_crypto_mech_t *mech)
 {
-	mech->mechanism = CKM_MD5;
-	mech->pParameter = NULL;
-	mech->ulParameterLen = 0;
-	return (0);
+	return (find_mech(mech, CKM_MD5));
 }
 
 /*
  * Start PKCS#11 session.
  */
 int
-smb_md5_init(smb_sign_ctx_t *ctxp, smb_sign_mech_t *mech)
+smb_md5_init(smb_sign_ctx_t *ctxp, smb_crypto_mech_t *mech)
 {
 	CK_RV rv;
 
@@ -93,20 +116,20 @@
  * (getmech, init, update, final)
  */
 
+/*
+ * Find out if we have this mech.
+ */
 int
-smb2_hmac_getmech(smb_sign_mech_t *mech)
+smb2_hmac_getmech(smb_crypto_mech_t *mech)
 {
-	mech->mechanism = CKM_SHA256_HMAC;
-	mech->pParameter = NULL;
-	mech->ulParameterLen = 0;
-	return (0);
+	return (find_mech(mech, CKM_SHA256_HMAC));
 }
 
 /*
  * Start PKCS#11 session, load the key.
  */
 int
-smb2_hmac_init(smb_sign_ctx_t *ctxp, smb_sign_mech_t *mech,
+smb2_hmac_init(smb_sign_ctx_t *ctxp, smb_crypto_mech_t *mech,
     uint8_t *key, size_t key_len)
 {
 	CK_OBJECT_HANDLE hkey = 0;
@@ -161,3 +184,73 @@
 
 	return (rv == CKR_OK ? 0 : -1);
 }
+
+/*
+ * SMB3 signing helpers:
+ * (getmech, init, update, final)
+ */
+
+/*
+ * Find out if we have this mech.
+ */
+int
+smb3_cmac_getmech(smb_crypto_mech_t *mech)
+{
+	return (find_mech(mech, CKM_AES_CMAC));
+}
+
+/*
+ * Start PKCS#11 session, load the key.
+ */
+int
+smb3_cmac_init(smb_sign_ctx_t *ctxp, smb_crypto_mech_t *mech,
+    uint8_t *key, size_t key_len)
+{
+	CK_OBJECT_HANDLE hkey = 0;
+	CK_RV rv;
+
+	rv = SUNW_C_GetMechSession(mech->mechanism, ctxp);
+	if (rv != CKR_OK)
+		return (-1);
+
+	rv = SUNW_C_KeyToObject(*ctxp, mech->mechanism,
+	    key, key_len, &hkey);
+	if (rv != CKR_OK)
+		return (-1);
+
+	rv = C_SignInit(*ctxp, mech, hkey);
+	(void) C_DestroyObject(*ctxp, hkey);
+
+	return (rv == CKR_OK ? 0 : -1);
+}
+
+/*
+ * Digest one segment
+ */
+int
+smb3_cmac_update(smb_sign_ctx_t ctx, uint8_t *in, size_t len)
+{
+	CK_RV rv;
+
+	rv = C_SignUpdate(ctx, in, len);
+	if (rv != CKR_OK)
+		(void) C_CloseSession(ctx);
+
+	return (rv == CKR_OK ? 0 : -1);
+}
+
+/*
+ * Note, the SMB2 signature is just the AES CMAC digest.
+ * (both are 16 bytes long)
+ */
+int
+smb3_cmac_final(smb_sign_ctx_t ctx, uint8_t *digest)
+{
+	CK_ULONG len = SMB2_SIG_SIZE;
+	CK_RV rv;
+
+	rv = C_SignFinal(ctx, digest, &len);
+	(void) C_CloseSession(ctx);
+
+	return (rv == CKR_OK ? 0 : -1);
+}
--- a/usr/src/lib/smbsrv/libfksmbsrv/common/sys/sunddi.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libfksmbsrv/common/sys/sunddi.h	Wed Jun 12 22:46:54 2019 +0200
@@ -21,6 +21,7 @@
 
 /*
  * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #ifndef	_SYS_SUNDDI_H
@@ -101,19 +102,7 @@
 extern int ddi_strtoll(const char *, char **, int, longlong_t *);
 extern int ddi_strtoull(const char *, char **, int, u_longlong_t *);
 
-/*
- * kiconv functions and their macros.
- */
-#define	KICONV_IGNORE_NULL	(0x0001)
-#define	KICONV_REPLACE_INVALID	(0x0002)
-
-extern kiconv_t kiconv_open(const char *, const char *);
-extern size_t kiconv(kiconv_t, char **, size_t *, char **, size_t *, int *);
-extern int kiconv_close(kiconv_t);
-extern size_t kiconvstr(const char *, const char *, char *, size_t *, char *,
-	size_t *, int, int *);
-
-#endif	/* _KERNEL */
+#endif	/* _KERNEL || _FAKE_KERNEL */
 
 #ifdef	__cplusplus
 }
--- a/usr/src/lib/smbsrv/libmlsvc/common/libmlsvc.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libmlsvc/common/libmlsvc.h	Wed Jun 12 22:46:54 2019 +0200
@@ -50,6 +50,16 @@
 #include <smbsrv/smb_dfs.h>
 #include <smbsrv/libsmb.h>
 
+/*
+ * XXX: Some temporary left-overs from the old ntstatus.h
+ * Should eliminate uses of these macros when convenient.
+ */
+/* This used to OR in the severity bits. */
+#define	NT_SC_ERROR(S)		(S)
+/* This used to mask off the severity bits. */
+#define	NT_SC_VALUE(S)		(S)
+/* XXX end of temporary left-overs. */
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
--- a/usr/src/lib/smbsrv/libmlsvc/common/lsalib.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libmlsvc/common/lsalib.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -81,6 +81,9 @@
 	uint32_t status;
 	char *slash;
 
+	if (account == NULL)
+		return (NT_STATUS_NONE_MAPPED);
+
 	(void) strsubst(account, '/', '\\');
 	(void) strcanon(account, "\\");
 	/* \john -> john */
--- a/usr/src/lib/smbsrv/libmlsvc/common/smb_share.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libmlsvc/common/smb_share.c	Wed Jun 12 22:46:54 2019 +0200
@@ -19,7 +19,7 @@
  * CDDL HEADER END
  *
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2016 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
  */
 
 /*
@@ -770,6 +770,10 @@
 	si->shr_flags &= ~SMB_SHRF_DFSROOT;
 	si->shr_flags |= flag;
 
+	flag = (new_si->shr_flags & SMB_SHRF_FSO);
+	si->shr_flags &= ~SMB_SHRF_FSO;
+	si->shr_flags |= flag;
+
 	flag = (new_si->shr_flags & SMB_SHRF_QUOTAS);
 	si->shr_flags &= ~SMB_SHRF_QUOTAS;
 	si->shr_flags |= flag;
@@ -784,6 +788,8 @@
 	si->shr_flags &= ~SMB_SHRF_ACC_ALL;
 	si->shr_flags |= access;
 
+	si->shr_encrypt = new_si->shr_encrypt;
+
 	if (access & SMB_SHRF_ACC_NONE)
 		(void) strlcpy(si->shr_access_none, new_si->shr_access_none,
 		    sizeof (si->shr_access_none));
@@ -1769,6 +1775,12 @@
 		free(val);
 	}
 
+	val = smb_shr_sa_getprop(opts, SHOPT_FSO);
+	if (val != NULL) {
+		smb_shr_sa_setflag(val, si, SMB_SHRF_FSO);
+		free(val);
+	}
+
 	val = smb_shr_sa_getprop(opts, SHOPT_QUOTAS);
 	if (val != NULL) {
 		/* Turn the flag on or off */
@@ -1779,6 +1791,12 @@
 		si->shr_flags |= SMB_SHRF_QUOTAS;
 	}
 
+	val = smb_shr_sa_getprop(opts, SHOPT_ENCRYPT);
+	if (val != NULL) {
+		smb_cfg_set_require(val, &si->shr_encrypt);
+		free(val);
+	}
+
 	val = smb_shr_sa_getprop(opts, SHOPT_CSC);
 	if (val != NULL) {
 		smb_shr_sa_csc_option(val, si);
@@ -2546,9 +2564,18 @@
 		rc |= nvlist_add_string(smb, SHOPT_GUEST, "true");
 	if ((si->shr_flags & SMB_SHRF_DFSROOT) != 0)
 		rc |= nvlist_add_string(smb, SHOPT_DFSROOT, "true");
+	if ((si->shr_flags & SMB_SHRF_FSO) != 0)
+		rc |= nvlist_add_string(smb, SHOPT_FSO, "true");
 	if ((si->shr_flags & SMB_SHRF_QUOTAS) != 0)
 		rc |= nvlist_add_string(smb, SHOPT_QUOTAS, "true");
 
+	if (si->shr_encrypt == SMB_CONFIG_REQUIRED)
+		rc |= nvlist_add_string(smb, SHOPT_ENCRYPT, "required");
+	else if (si->shr_encrypt == SMB_CONFIG_ENABLED)
+		rc |= nvlist_add_string(smb, SHOPT_ENCRYPT, "enabled");
+	else
+		rc |= nvlist_add_string(smb, SHOPT_ENCRYPT, "disabled");
+
 	if ((si->shr_flags & SMB_SHRF_AUTOHOME) != 0) {
 		rc |= nvlist_add_string(smb, SHOPT_AUTOHOME, "true");
 		rc |= nvlist_add_uint32(smb, "uid", si->shr_uid);
--- a/usr/src/lib/smbsrv/libmlsvc/common/srvsvc_svc.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libmlsvc/common/srvsvc_svc.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -1002,11 +1002,11 @@
 
 		status = srvsvc_share_getsd(mxa, &si, &sd);
 		if (status == ERROR_SUCCESS) {
-			info503->shi503_reserved = sd.sd_size;
-			info503->shi503_security_descriptor = sd.sd_buf;
+			info1501->shi1501_reserved = sd.sd_size;
+			info1501->shi1501_security_descriptor = sd.sd_buf;
 		} else {
-			info503->shi503_reserved = 0;
-			info503->shi503_security_descriptor = NULL;
+			info1501->shi1501_reserved = 0;
+			info1501->shi1501_security_descriptor = NULL;
 		}
 
 		param->result.ru.info1501 = info1501;
@@ -1622,8 +1622,8 @@
  * On success, the return value is NERR_Success/ERROR_SUCCESS.
  * On failure, the return value can be one of the following errors:
  *
- * ERROR_ACCESS_DENIED 		The user does not have access to the
- * 				requested information.
+ * ERROR_ACCESS_DENIED		The user does not have access to the
+ *				requested information.
  * ERROR_INVALID_PARAMETER	The specified parameter is invalid.
  * ERROR_NOT_ENOUGH_MEMORY	Insufficient memory is available.
  * NERR_ClientNameNotFound	A session does not exist with that
--- a/usr/src/lib/smbsrv/libmlsvc/common/winreg_svc.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libmlsvc/common/winreg_svc.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,6 +21,7 @@
 
 /*
  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -907,7 +908,8 @@
 
 	name = (char *)param->value_name.str;
 
-	if (strcasecmp(name, "PrimaryModule") == 0) {
+	if (name == NULL ||
+	    strcasecmp(name, "PrimaryModule") == 0) {
 		param->status = ERROR_FILE_NOT_FOUND;
 		return (NDR_DRC_OK);
 	}
--- a/usr/src/lib/smbsrv/libsmb/Makefile.com	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libsmb/Makefile.com	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
 #
 #
 # Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
-# Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
 #
 # Copyright (c) 2018, Joyent, Inc.
 
@@ -28,6 +28,7 @@
 VERS= .1
 
 OBJS_SHARED = 			\
+	smb_cfg_util.o		\
 	smb_door_legacy.o 	\
 	smb_inet.o		\
 	smb_msgbuf.o		\
--- a/usr/src/lib/smbsrv/libsmb/common/libsmb.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libsmb/common/libsmb.h	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #ifndef	_LIBSMB_H
@@ -157,6 +157,7 @@
 	SMB_CI_INITIAL_CREDITS,
 	SMB_CI_MAXIMUM_CREDITS,
 	SMB_CI_MAX_PROTOCOL,
+	SMB_CI_ENCRYPT,
 
 	SMB_CI_MAX
 } smb_cfg_id_t;
@@ -217,6 +218,8 @@
 extern uint32_t smb_config_get_max_protocol(void);
 extern void smb_config_upgrade(void);
 
+extern smb_cfg_val_t smb_config_get_require(smb_cfg_id_t);
+
 extern void smb_load_kconfig(smb_kmod_cfg_t *kcfg);
 extern uint32_t smb_crc_gen(uint8_t *, size_t);
 
--- a/usr/src/lib/smbsrv/libsmb/common/mapfile-vers	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libsmb/common/mapfile-vers	Wed Jun 12 22:46:54 2019 +0200
@@ -19,7 +19,7 @@
 #
 #
 # Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
-# Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
 #
 
 #
@@ -93,6 +93,7 @@
 	smb_cache_ready;
 	smb_cache_refreshing;
 	smb_cache_remove;
+	smb_cfg_set_require;
 	smb_chk_hostaccess;
 	smb_codepage_init;
 	smb_common_decode;
--- a/usr/src/lib/smbsrv/libsmb/common/smb_cfg.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libsmb/common/smb_cfg.c	Wed Jun 12 22:46:54 2019 +0200
@@ -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 2016 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -66,10 +66,11 @@
 #define	MACHINE_UUID			"machine_uuid"
 #define	IDMAP_DOMAIN			"domain_name"
 #define	IDMAP_PREF_DC			"preferred_dc"
+#define	IDMAP_SITE_NAME			"site_name"
 #define	IDMAP_PG_NAME			"config"
 
-#define	SMB_SECMODE_WORKGRP_STR 	"workgroup"
-#define	SMB_SECMODE_DOMAIN_STR  	"domain"
+#define	SMB_SECMODE_WORKGRP_STR		"workgroup"
+#define	SMB_SECMODE_DOMAIN_STR		"domain"
 
 #define	SMB_ENC_LEN	1024
 #define	SMB_DEC_LEN	256
@@ -145,10 +146,29 @@
 	{SMB_CI_INITIAL_CREDITS, "initial_credits", SCF_TYPE_INTEGER, 0},
 	{SMB_CI_MAXIMUM_CREDITS, "maximum_credits", SCF_TYPE_INTEGER, 0},
 	{SMB_CI_MAX_PROTOCOL, "max_protocol", SCF_TYPE_ASTRING, 0},
+	{SMB_CI_ENCRYPT, "encrypt", SCF_TYPE_ASTRING, 0},
 
 	/* SMB_CI_MAX */
 };
 
+/*
+ * We store the max SMB protocol version in SMF as a string,
+ * (for convenience of svccfg etc) but the programmatic get/set
+ * interfaces use the numeric form.
+ *
+ * The numeric values are as defined in the [MS-SMB2] spec.
+ * except for how we represent "1" (for SMB1) which is an
+ * arbitrary value below SMB2_VERS_BASE.
+ */
+static struct str_val
+smb_versions[] = {
+	{ "3.0",	SMB_VERS_3_0 },
+	{ "2.1",	SMB_VERS_2_1 },
+	{ "2.002",	SMB_VERS_2_002 },
+	{ "1",		SMB_VERS_1 },
+	{ NULL,		0 }
+};
+
 static smb_cfg_param_t *smb_config_getent(smb_cfg_id_t);
 
 static boolean_t smb_is_base64(unsigned char c);
@@ -156,6 +176,22 @@
 static char *smb_base64_decode(char *encoded_str);
 static int smb_config_get_idmap_preferred_dc(char *, int);
 static int smb_config_set_idmap_preferred_dc(char *);
+static int smb_config_get_idmap_site_name(char *, int);
+static int smb_config_set_idmap_site_name(char *);
+
+static uint32_t
+smb_convert_version_str(const char *version)
+{
+	uint32_t dialect = 0;
+	int i;
+
+	for (i = 0; smb_versions[i].str != NULL; i++) {
+		if (strcmp(version, smb_versions[i].str) == 0)
+			dialect = smb_versions[i].val;
+	}
+
+	return (dialect);
+}
 
 char *
 smb_config_getname(smb_cfg_id_t id)
@@ -377,6 +413,8 @@
 	cfg = smb_config_getent(id);
 	assert(cfg->sc_type == SCF_TYPE_ASTRING);
 
+	if (id == SMB_CI_ADS_SITE)
+		return (smb_config_get_idmap_site_name(cbuf, bufsz));
 	if (id == SMB_CI_DOMAIN_SRV)
 		return (smb_config_get_idmap_preferred_dc(cbuf, bufsz));
 
@@ -583,6 +621,8 @@
 	cfg = smb_config_getent(id);
 	assert(cfg->sc_type == SCF_TYPE_ASTRING);
 
+	if (id == SMB_CI_ADS_SITE)
+		return (smb_config_set_idmap_site_name(value));
 	if (id == SMB_CI_DOMAIN_SRV)
 		return (smb_config_set_idmap_preferred_dc(value));
 
@@ -624,7 +664,22 @@
 		value = tmp;
 	}
 
-	rc = smb_smf_set_string_property(handle, cfg->sc_name, value);
+	/*
+	 * We don't want people who care enough about protecting their data
+	 * by requiring encryption to accidentally expose their data
+	 * by lowering the protocol, so prevent them from going below 3.0
+	 * if encryption is required.
+	 */
+	if (id == SMB_CI_MAX_PROTOCOL &&
+	    smb_config_get_require(SMB_CI_ENCRYPT) == SMB_CONFIG_REQUIRED &&
+	    smb_config_get_max_protocol() >= SMB_VERS_3_0 &&
+	    smb_convert_version_str(value) < SMB_VERS_3_0) {
+		syslog(LOG_ERR, "Cannot set smbd/max_protocol below 3.0"
+		    " while smbd/encrypt == required.");
+		rc = SMBD_SMF_INVALID_ARG;
+	} else {
+		rc = smb_smf_set_string_property(handle, cfg->sc_name, value);
+	}
 
 	free(tmp);
 	(void) smb_smf_end_transaction(handle);
@@ -877,6 +932,30 @@
 	    IDMAP_PREF_DC, value));
 }
 
+static int
+smb_config_get_idmap_site_name(char *cbuf, int bufsz)
+{
+	char *s;
+	int len, rc = -1;
+
+	s = smb_config_getenv_generic(IDMAP_SITE_NAME,
+	    IDMAP_FMRI_PREFIX, IDMAP_PG_NAME);
+	if (s != NULL) {
+		len = strlcpy(cbuf, s, bufsz);
+		if (len < bufsz)
+			rc = 0;
+		free(s);
+	}
+	return (rc);
+}
+
+static int
+smb_config_set_idmap_site_name(char *value)
+{
+	return (smb_config_setenv_generic(IDMAP_FMRI_PREFIX, IDMAP_PG_NAME,
+	    IDMAP_SITE_NAME, value));
+}
+
 /*
  * smb_config_set_idmap_domain
  *
@@ -1087,43 +1166,27 @@
 	return (NULL);
 }
 
-
 /*
- * We store the max SMB protocol version in SMF as a string,
- * (for convenience of svccfg etc) but the programmatic get/set
- * interfaces use the numeric form.
- *
- * The numeric values are as defined in the [MS-SMB2] spec.
- * except for how we represent "1" (for SMB1) which is an
- * arbitrary value below SMB2_VERS_BASE.
+ * The service manifest has empty values by default for min_protocol and
+ * max_protocol. The expectation is that when those values are empty, we don't
+ * constrain the range of supported protocol versions (and allow use of the
+ * whole range that we implement). For that reason, this should usually be the
+ * highest protocol version we implement.
  */
-static struct str_val
-smb_versions[] = {
-	{ "3.0",	SMB_VERS_3_0 },
-	{ "2.1",	SMB_VERS_2_1 },
-	{ "2.002",	SMB_VERS_2_002 },
-	{ "1",		SMB_VERS_1 },
-	{ NULL,		0 }
-};
-
-/*
- * This really should be the latest (SMB_VERS_3_0)
- * but we're being cautious with SMB3 for a while.
- */
-uint32_t max_protocol_default = SMB_VERS_2_1;
+uint32_t max_protocol_default = SMB_VERS_3_0;
 
 uint32_t
 smb_config_get_max_protocol(void)
 {
 	char str[SMB_VERSTR_LEN];
-	int i, rc;
+	int rc;
+	uint32_t max;
 
 	rc = smb_config_getstr(SMB_CI_MAX_PROTOCOL, str, sizeof (str));
 	if (rc == SMBD_SMF_OK) {
-		for (i = 0; smb_versions[i].str != NULL; i++) {
-			if (strcmp(str, smb_versions[i].str) == 0)
-				return (smb_versions[i].val);
-		}
+		max = smb_convert_version_str(str);
+		if (max != 0)
+			return (max);
 		if (str[0] != '\0') {
 			syslog(LOG_ERR, "smbd/max_protocol value invalid");
 		}
@@ -1135,12 +1198,8 @@
 int
 smb_config_check_protocol(char *value)
 {
-	int i;
-
-	for (i = 0; smb_versions[i].str != NULL; i++) {
-		if (strcmp(value, smb_versions[i].str) == 0)
-			return (0);
-	}
+	if (smb_convert_version_str(value) != 0)
+		return (0);
 
 	return (-1);
 }
@@ -1248,3 +1307,21 @@
 {
 	upgrade_smb2_enable();
 }
+
+smb_cfg_val_t
+smb_config_get_require(smb_cfg_id_t id)
+{
+	int rc;
+	char str[sizeof ("required")];
+
+	rc = smb_config_getstr(id, str, sizeof (str));
+	if (rc != SMBD_SMF_OK)
+		return (SMB_CONFIG_DISABLED);
+
+	if (strncmp(str, "required", sizeof (str)) == 0)
+		return (SMB_CONFIG_REQUIRED);
+	if (strncmp(str, "enabled", sizeof (str)) == 0)
+		return (SMB_CONFIG_ENABLED);
+
+	return (SMB_CONFIG_DISABLED);
+}
--- a/usr/src/lib/smbsrv/libsmb/common/smb_info.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libsmb/common/smb_info.c	Wed Jun 12 22:46:54 2019 +0200
@@ -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 2016 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <sys/types.h>
@@ -158,6 +158,7 @@
 	kcfg->skc_traverse_mounts = smb_config_getbool(SMB_CI_TRAVERSE_MOUNTS);
 	kcfg->skc_max_protocol = smb_config_get_max_protocol();
 	kcfg->skc_secmode = smb_config_get_secmode();
+	kcfg->skc_encrypt = smb_config_get_require(SMB_CI_ENCRYPT);
 
 	(void) smb_getdomainname(kcfg->skc_nbdomain,
 	    sizeof (kcfg->skc_nbdomain));
--- a/usr/src/lib/smbsrv/libsmb/common/smb_kmod.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/lib/smbsrv/libsmb/common/smb_kmod.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  * Copyright 2017 Joyent, Inc.
  */
 
@@ -94,6 +94,7 @@
 	ioc.version = cfg->skc_version;
 	ioc.initial_credits = cfg->skc_initial_credits;
 	ioc.maximum_credits = cfg->skc_maximum_credits;
+	ioc.encrypt = cfg->skc_encrypt;
 
 	(void) memcpy(ioc.machine_uuid, cfg->skc_machine_uuid, sizeof (uuid_t));
 	(void) memcpy(ioc.negtok, cfg->skc_negtok, sizeof (ioc.negtok));
--- a/usr/src/man/man1m/sharemgr.1m	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/man/man1m/sharemgr.1m	Wed Jun 12 22:46:54 2019 +0200
@@ -1,5 +1,6 @@
 '\" te
 .\" Copyright (c) 2008, Sun Microsystems, Inc. All Rights Reserved
+.\" Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
 .\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License. You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.
 .\"  See the License for the specific language governing permissions and limitations under the License. When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with
 .\" the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
@@ -1068,6 +1069,33 @@
 .sp
 .ne 2
 .na
+\fB\fBencrypt=\fR\fIstring\fR\fR
+.ad
+.sp .6
+.RS 4n
+Controls SMB3 per-share encryption. This is similar to the global smbd/encrypt
+option. For requests on a particular share, the server's behavior is controlled
+by the stricter of this option and smbd/encrypt.
+.sp
+When set to \fBdisabled\fR, the server will not ask clients to encrypt requests.
+When set to \fBenabled\fR, the server will ask clients to encrypt requests,
+but will not require that they do so. Any message than can be encrypted
+will be encrypted.
+When set to \fBrequired\fR, the server will deny access to or disconnect
+any client that does not support encryption or fails to encrypt requests
+that they should.
+.sp
+In other words, the \fBenabled\fR behavior is that any message that CAN
+be encrypted SHOULD be encrypted, while the \fBrequired\fR behavior is that any
+message that CAN be encrypted MUST be encrypted.
+.sp
+This property is not defined by default.
+.sp
+.RE
+
+.sp
+.ne 2
+.na
 \fB\fBro=\fIaccess-list\fR\fR\fR
 .ad
 .sp .6
--- a/usr/src/man/man1m/zoneadm.1m	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/man/man1m/zoneadm.1m	Wed Jun 12 22:46:54 2019 +0200
@@ -306,8 +306,8 @@
 regardless of its state. In this case, the \fB-i\fR and \fB-c\fR options are
 disallowed.
 .sp
-If neither the \fB-i\fR or \fB-c\fR options are given, all running zones are
-listed.
+If neither the \fB-i\fR, \fB-c\fR, or \fB-n\fR options are given, all running
+zones are listed.
 .sp
 The following \fIlist_options\fR are supported:
 .sp
@@ -333,6 +333,16 @@
 .sp
 .ne 2
 .na
+\fB\fB-n\fR\fR
+.ad
+.sp .6
+.RS 4n
+Do not include the global zone in the list of zones returned.
+.RE
+
+.sp
+.ne 2
+.na
 \fB\fB-p\fR\fR
 .ad
 .sp .6
--- a/usr/src/man/man4/smb.4	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/man/man4/smb.4	Wed Jun 12 22:46:54 2019 +0200
@@ -1,10 +1,22 @@
 '\" te
 .\" Copyright (c) 2009, Sun Microsystems, Inc. All Rights Reserved.
-.\" Copyright 2011, Nexenta Systems, Inc. All Rights Reserved.
-.\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License"). You may not use this file except in compliance with the License. You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.
-.\"  See the License for the specific language governing permissions and limitations under the License. When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE. If applicable, add the following below this CDDL HEADER, with the
-.\" fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH SMB 4 "Sep 25, 2009"
+.\" Copyright 2016, Nexenta Systems, Inc. All Rights Reserved.
+.\" The contents of this file are subject to the terms of the
+.\" Common Development and Distribution License (the "License").
+.\" You may not use this file except in compliance with the License.
+.\"
+.\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+.\" or http://www.opensolaris.org/os/licensing.
+.\" See the License for the specific language governing permissions
+.\" and limitations under the License.
+.\"
+.\" When distributing Covered Code, include this CDDL HEADER in each
+.\" file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+.\" If applicable, add the following below this CDDL HEADER, with the
+.\" fields enclosed by brackets "[]" replaced with your own identifying
+.\" information: Portions Copyright [yyyy] [name of copyright owner]
+.\"
+.TH SMB 4 "Apr 23, 2015"
 .SH NAME
 smb \- configuration properties for Solaris CIFS server
 .SH DESCRIPTION
@@ -100,7 +112,31 @@
 .sp
 .ne 2
 .na
-\fB\fBipv6_enabled\fR\fR
+\fB\fBencrypt\fR\fR
+.ad
+.sp .6
+.RS 4n
+Controls SMB3 Encryption. For requests on a particular share, the server's
+behavior is controlled by the stricter of this option and the per-share
+"encrypt" option.
+.sp
+When set to \fBdisabled\fR, the server will not ask clients to encrypt requests.
+When set to \fBenabled\fR, the server will ask clients to encrypt requests,
+but will not require that they do so. Any message that can be encrypted
+will be encrypted.
+When set to \fBrequired\fR, the server will deny access to or disconnect
+any client that does not support encryption or fails to encrypt requests
+that they should.
+.sp
+In other words, the \fBenabled\fR behavior is that any message that CAN
+be encrypted SHOULD be encrypted, while the \fBrequired\fR behavior is that any
+message that CAN be encrypted MUST be encrypted.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fBipv6_enable\fR\fR
 .ad
 .sp .6
 .RS 4n
@@ -117,7 +153,7 @@
 .RS 4n
 Specifies the number of seconds before an idle SMB connection is dropped by the
 Solaris CIFS server. If set to 0, idle connections are not dropped. Valid
-values are 0 and from 20 seconds and above. The default value is 5400 seconds.
+values are 0 and from 20 seconds and above. The default value is 0.
 .RE
 
 .sp
@@ -347,6 +383,21 @@
 .sp
 .ne 2
 .na
+\fB\fBoplock_enable\fR\fR
+.ad
+.sp .6
+.RS 4n
+Controls whether "oplocks" may be granted by the SMB server.
+The term "oplock" is short for "opportunistic lock", which is
+the legacy name for cache delegations in SMB.
+By default, oplocks are enabled.
+Note that if oplocks are disabled, file I/O perfrormance may be
+severely reduced.
+.RE
+
+.sp
+.ne 2
+.na
 \fB\fBpdc\fR\fR
 .ad
 .sp .6
--- a/usr/src/man/man9e/open.9e	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/man/man9e/open.9e	Wed Jun 12 22:46:54 2019 +0200
@@ -1,10 +1,11 @@
 '\" te
 .\"  Copyright 1989 AT&T
 .\"  Copyright (c) 2008, Sun Microsystems, Inc.  All Rights Reserved
+.\"  Copyright 2019, Joyent, Inc.
 .\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License.
 .\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.  See the License for the specific language governing permissions and limitations under the License.
 .\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH OPEN 9E "Apr 24, 2008"
+.TH OPEN 9E "March 7, 2019"
 .SH NAME
 open \- gain access to a device
 .SH SYNOPSIS
@@ -40,13 +41,11 @@
 .fi
 
 .SH INTERFACE LEVEL
-.sp
 .LP
 Architecture independent level 1 (DDI/DKI). This entry point is required, but
 it can be \fBnulldev\fR(9F)
 .SH PARAMETERS
 .SS "Block and Character"
-.sp
 .ne 2
 .na
 \fB\fIdevp\fR\fR
@@ -163,7 +162,6 @@
 .RE
 
 .SS "STREAMS"
-.sp
 .ne 2
 .na
 \fB\fIq\fR\fR
@@ -244,7 +242,6 @@
 .RE
 
 .SH DESCRIPTION
-.sp
 .LP
 The driver's  \fBopen()\fR function is called by the kernel during an
 \fBopen\fR(2) or a \fBmount\fR(2) on the special file for the device. A device
@@ -258,6 +255,13 @@
 using the user credentials pointed to by \fIcred_p\fR.
 .sp
 .LP
+When exclusive access is requested by including the \fBFEXCL\fR flag in
+\fIflag\fR or \fIoflag\fR, but the caller cannot be granted exclusive access
+to the device because it is already open, then the device driver should
+conventionally return \fBEBUSY\fR. If instead, exclusive opens are not
+supported, then the driver should return \fBENOTSUP\fR or \fBEINVAL\fR.
+.sp
+.LP
 The kernel provides \fBopen()\fR \fBclose()\fR exclusion guarantees to the
 driver at *\fIdevp\fR, \fIotyp\fR granularity. This delays new \fBopen()\fR
 calls to the driver while a last-reference \fBclose()\fR call is executing. If
@@ -296,12 +300,10 @@
 .in -2
 
 .SH RETURN VALUES
-.sp
 .LP
 The \fBopen()\fR function should return \fB0\fR for success, or the appropriate
 error number.
 .SH SEE ALSO
-.sp
 .LP
 \fBclose\fR(2), \fBexit\fR(2), \fBmmap\fR(2), \fBmount\fR(2), \fBmunmap\fR(2),
 \fBopen\fR(2), \fBIntro\fR(9E), \fBattach\fR(9E), \fBclose\fR(9E),
@@ -314,7 +316,6 @@
 .LP
 \fISTREAMS Programming Guide\fR
 .SH WARNINGS
-.sp
 .LP
 Do not attempt to change the major number.
 .sp
--- a/usr/src/test/zfs-tests/tests/functional/cli_root/zpool_create/zpool_create_020_pos.ksh	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/test/zfs-tests/tests/functional/cli_root/zpool_create/zpool_create_020_pos.ksh	Wed Jun 12 22:46:54 2019 +0200
@@ -27,6 +27,7 @@
 
 #
 # Copyright (c) 2012, 2016 by Delphix. All rights reserved.
+# Copyright 2019 Joyent, Inc.
 #
 
 . $STF_SUITE/include/libtest.shlib
@@ -49,12 +50,14 @@
 	if poolexists $TESTPOOL ; then
                 destroy_pool $TESTPOOL
         fi
-	if [ -d ${TESTPOOL}.root ]
+	if [ -d $TESTPOOL_DIR ]
 	then
-		log_must rmdir ${TESTPOOL}.root
+		log_must rmdir $TESTPOOL_DIR
 	fi
 }
 
+TESTPOOL_DIR=/${TESTPOOL}.root
+
 log_onexit cleanup
 
 log_assert "zpool create -R works as expected"
@@ -65,17 +68,17 @@
 	disk=$DISK0
 fi
 
-log_must mkdir /${TESTPOOL}.root
-log_must zpool create -R /${TESTPOOL}.root $TESTPOOL $disk
-if [ ! -d /${TESTPOOL}.root ]
+log_must mkdir $TESTPOOL_DIR
+log_must zpool create -R $TESTPOOL_DIR $TESTPOOL $disk
+if [ ! -d $TESTPOOL_DIR ]
 then
-	log_fail "Mountpoint was not create when using zpool with -R flag!"
+	log_fail "Mountpoint was not created when using zpool with -R flag!"
 fi
 
 FS=$(zfs list $TESTPOOL)
 if [ -z "$FS" ]
 then
-	log_fail "Mounted filesystem at /${TESTPOOL}.root isn't ZFS!"
+	log_fail "Mounted filesystem at $TESTPOOL_DIR isn't ZFS!"
 fi
 
 log_must zpool get all $TESTPOOL
@@ -89,7 +92,7 @@
 fi
 
 # check that the root = /mountpoint property is set correctly
-grep "$TESTPOOL[ ]*altroot[ ]*/${TESTPOOL}.root" /tmp/values.$$ > /dev/null 2>&1
+grep "$TESTPOOL[ ]*altroot[ ]*$TESTPOOL_DIR" /tmp/values.$$ > /dev/null 2>&1
 if [ $? -ne 0 ]
 then
 	log_fail "zpool property root was not found in pool output."
--- a/usr/src/tools/cpcgen/cpcgen.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/tools/cpcgen/cpcgen.c	Wed Jun 12 22:46:54 2019 +0200
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2019 Joyent, Inc.
+ * Copyright 2019, Joyent, Inc.
  */
 
 /*
@@ -419,13 +419,13 @@
 
 	errno = 0;
 	l = strtol(fam, &last, 16);
-	if (errno != 0 || l < 0 || l > UINT_MAX || *last != '\0') {
+	if (errno != 0 || l < 0 || l >= INT_MAX || *last != '\0') {
 		errx(EXIT_FAILURE, "failed to parse family \"%s\"", fam);
 	}
 	*family = (uint_t)l;
 
 	l = strtol(mod, &last, 16);
-	if (errno != 0 || l < 0 || l > UINT_MAX || *last != '\0') {
+	if (errno != 0 || l < 0 || l >= INT_MAX || *last != '\0') {
 		errx(EXIT_FAILURE, "failed to parse model \"%s\"", mod);
 	}
 	*model = (uint_t)l;
--- a/usr/src/tools/quick/make-idmap	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/tools/quick/make-idmap	Wed Jun 12 22:46:54 2019 +0200
@@ -186,7 +186,6 @@
 do_tar() {
 	git_rev=`git rev-parse --short=8 HEAD`
 	files="
-kernel/misc/idmap
 kernel/misc/amd64/idmap
 lib/svc/manifest/system/idmap.xml
 usr/lib/idmapd
--- a/usr/src/ucblib/libucb/Makefile.com	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/ucblib/libucb/Makefile.com	Wed Jun 12 22:46:54 2019 +0200
@@ -22,6 +22,8 @@
 # Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
 #
 # Copyright (c) 2018, Joyent, Inc.
+# Copyright 2019 Peter Tribble.
+#
 
 LIBRARY=	libucb.a
 VERS=		.1
@@ -133,7 +135,7 @@
 
 pics/%.o: ../$(MACH)/sys/%.s
 	$(BUILD.s)
-	$(POST_PROCESS_O)
+	$(POST_PROCESS_S_O)
 
 #
 # Include library targets
--- a/usr/src/ucblib/libucb/sparc/Makefile	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/ucblib/libucb/sparc/Makefile	Wed Jun 12 22:46:54 2019 +0200
@@ -22,8 +22,10 @@
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
+# Copyright 2019 Peter Tribble.
+#
 
-SYSOBJS=	 	\
+SYSOBJS=		\
 	setjmp.o	\
 	signal.o
 
@@ -35,7 +37,7 @@
 # sparc-specific rule
 pics/%.o: sys/%.s
 	$(BUILD.s)
-	$(POST_PROCESS_O)
+	$(POST_PROCESS_S_O)
 
 .KEEP_STATE:
 
--- a/usr/src/ucblib/libucb/sparcv9/Makefile	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/ucblib/libucb/sparcv9/Makefile	Wed Jun 12 22:46:54 2019 +0200
@@ -22,8 +22,10 @@
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
+# Copyright 2019 Peter Tribble.
+#
 
-SYSOBJS=	 	\
+SYSOBJS=		\
 	setjmp.o	\
 	signal.o
 
@@ -41,7 +43,7 @@
 
 pics/%.o: sys/%.s
 	$(BUILD.s)
-	$(POST_PROCESS_O)
+	$(POST_PROCESS_S_O)
 
 .KEEP_STATE:
 
--- a/usr/src/uts/common/Makefile.files	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/Makefile.files	Wed Jun 12 22:46:54 2019 +0200
@@ -1123,6 +1123,7 @@
 		nfs4_deleg_ops.o nfs4_srv_readdir.o nfs4_dispatch.o
 
 SMBSRV_SHARED_OBJS += \
+		smb_cfg_util.o \
 		smb_door_legacy.o \
 		smb_inet.o \
 		smb_match.o \
@@ -1144,6 +1145,7 @@
 		smb_alloc.o				\
 		smb_authenticate.o			\
 		smb_close.o				\
+		smb_cmn_oplock.o			\
 		smb_cmn_rename.o			\
 		smb_cmn_setfile.o			\
 		smb_common_open.o			\
@@ -1204,6 +1206,7 @@
 		smb_set_fileinfo.o			\
 		smb_sign_kcf.o				\
 		smb_signing.o				\
+		smb_srv_oplock.o			\
 		smb_thread.o				\
 		smb_tree.o				\
 		smb_trans2_create_directory.o		\
@@ -1219,6 +1222,7 @@
 		\
 		smb2_aapl.o \
 		smb2_dispatch.o \
+		smb2_durable.o \
 		smb2_cancel.o \
 		smb2_change_notify.o \
 		smb2_close.o \
@@ -1226,6 +1230,7 @@
 		smb2_echo.o \
 		smb2_flush.o \
 		smb2_ioctl.o \
+		smb2_lease.o \
 		smb2_lock.o \
 		smb2_logoff.o \
 		smb2_negotiate.o \
@@ -1247,7 +1252,10 @@
 		smb2_signing.o \
 		smb2_tree_connect.o \
 		smb2_tree_disconn.o \
-		smb2_write.o
+		smb2_write.o \
+		\
+		smb3_encrypt.o \
+		smb3_encrypt_kcf.o
 
 PCFS_OBJS +=	pc_alloc.o	pc_dir.o	pc_node.o	pc_subr.o \
 		pc_vfsops.o	pc_vnops.o
--- a/usr/src/uts/common/fs/smbsrv/smb2_aapl.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_aapl.c	Wed Jun 12 22:46:54 2019 +0200
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -40,6 +40,8 @@
  * modes only when we have a trivial ACL.
  */
 
+uint64_t smb2_aapl_volume_caps = kAAPL_SUPPORTS_FULL_SYNC;
+
 /*
  * Normally suppress file IDs for MacOS because it
  * requires them to be unique per share, and ours
@@ -130,7 +132,7 @@
 		(void) smb_mbc_encodef(mbcout, "q", server_caps);
 	}
 	if ((server_bitmap & kAAPL_VOLUME_CAPS) != 0) {
-		(void) smb_mbc_encodef(mbcout, "q", 0);
+		(void) smb_mbc_encodef(mbcout, "q", smb2_aapl_volume_caps);
 	}
 
 	/* Pad2, null model string. */
--- a/usr/src/uts/common/fs/smbsrv/smb2_change_notify.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_change_notify.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates.
- * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -33,8 +33,6 @@
 /* For the output DataOffset fields in here. */
 #define	DATA_OFF	(SMB2_HDR_SIZE + 8)
 
-static smb_sdrc_t smb2_change_notify_async(smb_request_t *);
-
 smb_sdrc_t
 smb2_change_notify(smb_request_t *sr)
 {
@@ -68,6 +66,16 @@
 	if (status != 0)
 		goto errout; /* Bad FID */
 
+	/*
+	 * Only deal with change notify last in a compound,
+	 * because it blocks indefinitely.  This status gets
+	 * "sticky" handling in smb2sr_work().
+	 */
+	if (sr->smb2_next_command != 0) {
+		status = NT_STATUS_INSUFFICIENT_RESOURCES;
+		goto errout;
+	}
+
 	CompletionFilter &= FILE_NOTIFY_VALID_MASK;
 	if (iFlags & SMB2_WATCH_TREE)
 		CompletionFilter |= FILE_NOTIFY_CHANGE_EV_SUBDIR;
@@ -79,18 +87,23 @@
 	 * Check for events and consume, non-blocking.
 	 * Special return STATUS_PENDING means:
 	 *   No events; caller must call "act2" next.
-	 * SMB2 does that in the "async" handler.
+	 * SMB2 does that in "async mode".
 	 */
 	status = smb_notify_act1(sr, oBufLength, CompletionFilter);
 	if (status == NT_STATUS_PENDING) {
-		status = smb2sr_go_async(sr, smb2_change_notify_async);
+		status = smb2sr_go_async(sr);
+		if (status != 0)
+			goto errout;
+		status = smb_notify_act2(sr);
+		if (status == NT_STATUS_PENDING) {
+			/* See next: smb2_change_notify_finish */
+			return (SDRC_SR_KEPT);
+		}
 	}
 
 errout:
 	sr->smb2_status = status;
-	if (status != NT_STATUS_PENDING) {
-		DTRACE_SMB2_DONE(op__ChangeNotify, smb_request_t *, sr);
-	}
+	DTRACE_SMB2_DONE(op__ChangeNotify, smb_request_t *, sr);
 
 	if (NT_SC_SEVERITY(status) == NT_STATUS_SEVERITY_SUCCESS) {
 		oBufLength = sr->raw_data.chain_offset;
@@ -108,30 +121,6 @@
 }
 
 /*
- * This is called when the dispatch loop has made it to the end of a
- * compound request, and we had a notify that will require blocking.
- */
-static smb_sdrc_t
-smb2_change_notify_async(smb_request_t *sr)
-{
-	uint32_t status;
-
-	status = smb_notify_act2(sr);
-	if (status == NT_STATUS_PENDING) {
-		/* See next: smb2_change_notify_finish */
-		return (SDRC_SR_KEPT);
-	}
-
-	/* Note: Never NT_STATUS_NOTIFY_ENUM_DIR here. */
-	ASSERT(status != NT_STATUS_NOTIFY_ENUM_DIR);
-
-	if (status != 0)
-		smb2sr_put_error(sr, status);
-
-	return (SDRC_SUCCESS);
-}
-
-/*
  * This is called via taskq_dispatch in smb_notify.c
  * to finish up an NT transact notify change request.
  * Build an SMB2 Change Notify reply and send it.
@@ -139,7 +128,8 @@
 void
 smb2_change_notify_finish(void *arg)
 {
-	smb_request_t	*sr = arg;
+	smb_request_t *sr = arg;
+	smb_disp_stats_t *sds;
 	uint32_t status;
 	uint32_t oBufLength;
 
@@ -150,6 +140,10 @@
 	 */
 	status = smb_notify_act3(sr);
 
+	/*
+	 * The prior thread returned SDRC_SR_KEPT and skiped
+	 * the dtrace DONE probe, so fire that here.
+	 */
 	sr->smb2_status = status;
 	DTRACE_SMB2_DONE(op__ChangeNotify, smb_request_t *, sr);
 
@@ -165,5 +159,24 @@
 		smb2sr_put_error(sr, status);
 	}
 
-	smb2sr_finish_async(sr);
+	/*
+	 * Record some statistics: (just tx bytes here)
+	 */
+	sds = &sr->session->s_server->sv_disp_stats2[SMB2_CHANGE_NOTIFY];
+	atomic_add_64(&sds->sdt_txb, (int64_t)(sr->reply.chain_offset));
+
+	/*
+	 * Put (overwrite) the final SMB2 header,
+	 * sign, send.
+	 */
+	(void) smb2_encode_header(sr, B_TRUE);
+	if (sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED)
+		smb2_sign_reply(sr);
+	smb2_send_reply(sr);
+
+	mutex_enter(&sr->sr_mutex);
+	sr->sr_state = SMB_REQ_STATE_COMPLETED;
+	mutex_exit(&sr->sr_mutex);
+
+	smb_request_free(sr);
 }
--- a/usr/src/uts/common/fs/smbsrv/smb2_create.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_create.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,6 +21,25 @@
 #include <smbsrv/smb2_kproto.h>
 #include <smbsrv/smb_fsops.h>
 
+#define	DH_PERSISTENT	SMB2_DHANDLE_FLAG_PERSISTENT
+
+/*
+ * Compile-time check that the SMB2_LEASE_... definitions
+ * match the (internal) equivalents from ntifs.h
+ */
+#if SMB2_LEASE_NONE != OPLOCK_LEVEL_NONE
+#error "SMB2_LEASE_NONE"
+#endif
+#if SMB2_LEASE_READ_CACHING != OPLOCK_LEVEL_CACHE_READ
+#error "SMB2_LEASE_READ_CACHING"
+#endif
+#if SMB2_LEASE_HANDLE_CACHING != OPLOCK_LEVEL_CACHE_HANDLE
+#error "SMB2_LEASE_HANDLE_CACHING"
+#endif
+#if SMB2_LEASE_WRITE_CACHING != OPLOCK_LEVEL_CACHE_WRITE
+#error "SMB2_LEASE_WRITE_CACHING"
+#endif
+
 /*
  * Some flags used locally to keep track of which Create Context
  * names have been provided and/or requested.
@@ -35,6 +54,8 @@
 #define	CCTX_QUERY_ON_DISK_ID		0x80
 #define	CCTX_REQUEST_LEASE		0x100
 #define	CCTX_AAPL_EXT			0x200
+#define	CCTX_DH_REQUEST_V2		0x400
+#define	CCTX_DH_RECONNECT_V2		0x800
 
 typedef struct smb2_create_ctx_elem {
 	uint32_t cce_len;
@@ -54,10 +75,15 @@
 	smb2_create_ctx_elem_t cc_in_time_warp;
 	smb2_create_ctx_elem_t cc_in_req_lease;
 	smb2_create_ctx_elem_t cc_in_aapl;
+	smb2_create_ctx_elem_t cc_in_dh_request_v2;
+	smb2_create_ctx_elem_t cc_in_dh_reconnect_v2;
 	/* Elements we my place in the response */
 	smb2_create_ctx_elem_t cc_out_max_access;
 	smb2_create_ctx_elem_t cc_out_file_id;
 	smb2_create_ctx_elem_t cc_out_aapl;
+	smb2_create_ctx_elem_t cc_out_req_lease;
+	smb2_create_ctx_elem_t cc_out_dh_request;
+	smb2_create_ctx_elem_t cc_out_dh_request_v2;
 } smb2_create_ctx_t;
 
 static uint32_t smb2_decode_create_ctx(
@@ -68,6 +94,8 @@
 	mbuf_chain_t *, smb2_create_ctx_elem_t *, uint32_t);
 static void smb2_free_create_ctx(smb2_create_ctx_t *);
 
+int smb2_enable_dh = 1;
+
 smb_sdrc_t
 smb2_create(smb_request_t *sr)
 {
@@ -78,7 +106,6 @@
 	smb_ofile_t *of = NULL;
 	uint16_t StructSize;
 	uint8_t SecurityFlags;
-	uint8_t OplockLevel;
 	uint32_t ImpersonationLevel;
 	uint64_t SmbCreateFlags;
 	uint64_t Reserved4;
@@ -86,8 +113,9 @@
 	uint16_t NameLength;
 	uint32_t CreateCtxOffset;
 	uint32_t CreateCtxLength;
-	smb2fid_t smb2fid;
+	smb2fid_t smb2fid = { 0, 0 };
 	uint32_t status;
+	int dh_flags;
 	int skip;
 	int rc = 0;
 
@@ -99,7 +127,6 @@
 	 * if we already have one, release it now.
 	 */
 	if (sr->fid_ofile != NULL) {
-		smb_ofile_request_complete(sr->fid_ofile);
 		smb_ofile_release(sr->fid_ofile);
 		sr->fid_ofile = NULL;
 	}
@@ -213,29 +240,145 @@
 	 */
 
 	/*
+	 * Only disk trees get durable handles.
+	 */
+	if (smb2_enable_dh == 0 ||
+	    (sr->tid_tree->t_res_type & STYPE_MASK) != STYPE_DISKTREE) {
+		cctx.cc_in_flags &=
+		    ~(CCTX_DH_REQUEST | CCTX_DH_REQUEST_V2 |
+		    CCTX_DH_RECONNECT | CCTX_DH_RECONNECT_V2);
+	}
+
+	/*
+	 * DH v2 is only valid in SMB3.0 and later.
+	 * If seen in earlier dialects, ignore.
+	 */
+	if (sr->session->dialect < SMB_VERS_3_0) {
+		cctx.cc_in_flags &=
+		    ~(CCTX_DH_REQUEST_V2|CCTX_DH_RECONNECT_V2);
+	}
+
+	/*
+	 * It is an error to specify more than one Durable Handle
+	 * operation in a single create, except when only the v1
+	 * REQUEST and RECONNECT operations are specified. In that
+	 * case, the v1 REQUEST is ignored.
+	 */
+	dh_flags = cctx.cc_in_flags &
+	    (CCTX_DH_REQUEST | CCTX_DH_REQUEST_V2 |
+	    CCTX_DH_RECONNECT | CCTX_DH_RECONNECT_V2);
+	if ((dh_flags & (dh_flags - 1)) != 0 &&
+	    dh_flags != (CCTX_DH_REQUEST|CCTX_DH_RECONNECT)) {
+		status = NT_STATUS_INVALID_PARAMETER;
+		goto cmd_done;
+	}
+
+	/*
+	 * Reconnect is special in MANY ways, including the
+	 * somewhat surprising (specified) behavior that
+	 * most other creat parameters are ignored, and
+	 * many create context types are ignored too.
+	 */
+	op->dh_vers = SMB2_NOT_DURABLE;
+	op->dh_v2_flags = 0;
+	if ((cctx.cc_in_flags &
+	    (CCTX_DH_RECONNECT|CCTX_DH_RECONNECT_V2)) != 0) {
+
+		if ((cctx.cc_in_flags & CCTX_DH_RECONNECT_V2) != 0)
+			op->dh_vers = SMB2_DURABLE_V2;
+		else
+			op->dh_vers = SMB2_DURABLE_V1;
+
+		/* Ignore these create contexts. */
+		cctx.cc_in_flags &=
+		    ~(CCTX_DH_REQUEST |
+		    CCTX_DH_REQUEST_V2 |
+		    CCTX_EA_BUFFER |
+		    CCTX_SD_BUFFER |
+		    CCTX_ALLOCATION_SIZE |
+		    CCTX_TIMEWARP_TOKEN |
+		    CCTX_QUERY_ON_DISK_ID);
+
+		/*
+		 * Reconnect check needs to know if a lease was requested.
+		 * The requested oplock level is ignored in reconnect, so
+		 * using op_oplock_level to convey this info.
+		 */
+		if (cctx.cc_in_flags & CCTX_REQUEST_LEASE)
+			op->op_oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+		else
+			op->op_oplock_level = 0;
+
+		status = smb2_dh_reconnect(sr);
+		if (status != NT_STATUS_SUCCESS)
+			goto cmd_done;
+
+		/*
+		 * Skip most open execution during reconnect,
+		 * but need (reclaimed) oplock state in *op.
+		 */
+		of = sr->fid_ofile;
+		smb2_oplock_reconnect(sr);
+		goto reconnect_done;
+	}
+
+	/*
+	 * Real create (of a new handle, not reconnect)
+	 */
+
+	/*
 	 * Validate the requested oplock level.
-	 * Convert the SMB2 oplock level into SMB1 form.
+	 * Conversion to internal form is in smb2_oplock_acquire()
 	 */
 	switch (op->op_oplock_level) {
-	case SMB2_OPLOCK_LEVEL_NONE:
-		op->op_oplock_level = SMB_OPLOCK_NONE;
-		break;
-	case SMB2_OPLOCK_LEVEL_II:
-		op->op_oplock_level = SMB_OPLOCK_LEVEL_II;
+	case SMB2_OPLOCK_LEVEL_NONE:		/* OPLOCK_LEVEL_NONE */
+	case SMB2_OPLOCK_LEVEL_II:		/* OPLOCK_LEVEL_TWO */
+	case SMB2_OPLOCK_LEVEL_EXCLUSIVE:	/* OPLOCK_LEVEL_ONE */
+	case SMB2_OPLOCK_LEVEL_BATCH:		/* OPLOCK_LEVEL_BATCH */
+		/*
+		 * Ignore lease create context (if any)
+		 */
+		cctx.cc_in_flags &= ~CCTX_REQUEST_LEASE;
 		break;
-	case SMB2_OPLOCK_LEVEL_EXCLUSIVE:
-		op->op_oplock_level = SMB_OPLOCK_EXCLUSIVE;
+
+	case SMB2_OPLOCK_LEVEL_LEASE:		/* OPLOCK_LEVEL_GRANULAR */
+		/*
+		 * Require a lease create context.
+		 */
+		if ((cctx.cc_in_flags & CCTX_REQUEST_LEASE) == 0) {
+			cmn_err(CE_NOTE, "smb2:create, oplock=ff and no lease");
+			status = NT_STATUS_INVALID_PARAMETER;
+			goto cmd_done;
+		}
+
+		/*
+		 * Validate lease request state
+		 * Only a few valid combinations.
+		 */
+		switch (op->lease_state) {
+		case SMB2_LEASE_NONE:
+		case SMB2_LEASE_READ_CACHING:
+		case SMB2_LEASE_READ_CACHING | SMB2_LEASE_HANDLE_CACHING:
+		case SMB2_LEASE_READ_CACHING | SMB2_LEASE_WRITE_CACHING:
+		case SMB2_LEASE_READ_CACHING | SMB2_LEASE_WRITE_CACHING |
+		    SMB2_LEASE_HANDLE_CACHING:
+			break;
+
+		default:
+			/*
+			 * Invalid lease state flags
+			 * Just force to "none".
+			 */
+			op->lease_state = SMB2_LEASE_NONE;
+			break;
+		}
 		break;
-	case SMB2_OPLOCK_LEVEL_BATCH:
-		op->op_oplock_level = SMB_OPLOCK_BATCH;
-		break;
-	case SMB2_OPLOCK_LEVEL_LEASE:	/* not yet */
+
 	default:
 		/* Unknown SMB2 oplock level. */
 		status = NT_STATUS_INVALID_PARAMETER;
 		goto cmd_done;
 	}
-	op->op_oplock_levelII = B_TRUE;
 
 	/*
 	 * Only disk trees get oplocks or leases.
@@ -245,6 +388,14 @@
 		cctx.cc_in_flags &= ~CCTX_REQUEST_LEASE;
 	}
 
+	if ((cctx.cc_in_flags &
+	    (CCTX_DH_REQUEST|CCTX_DH_REQUEST_V2)) != 0) {
+		if ((cctx.cc_in_flags & CCTX_DH_REQUEST_V2) != 0)
+			op->dh_vers = SMB2_DURABLE_V2;
+		else
+			op->dh_vers = SMB2_DURABLE_V1;
+	}
+
 	if (cctx.cc_in_flags & CCTX_EA_BUFFER) {
 		status = NT_STATUS_EAS_NOT_SUPPORTED;
 		goto cmd_done;
@@ -285,6 +436,85 @@
 	of = sr->fid_ofile;
 
 	/*
+	 * Set the "persistent" part of the file ID
+	 * (only for DISK shares).  Need this even for
+	 * non-durable handles in case we get the ioctl
+	 * to set "resiliency" on this handle.
+	 */
+	if (of->f_ftype == SMB_FTYPE_DISK)
+		smb_ofile_set_persistid(of);
+
+	/*
+	 * [MS-SMB2] 3.3.5.9.8
+	 * Handling the SMB2_CREATE_REQUEST_LEASE Create Context
+	 */
+	if ((cctx.cc_in_flags & CCTX_REQUEST_LEASE) != 0) {
+		status = smb2_lease_create(sr);
+		if (status != NT_STATUS_SUCCESS) {
+			if (op->action_taken == SMB_OACT_CREATED) {
+				smb_ofile_set_delete_on_close(sr, of);
+			}
+			goto cmd_done;
+		}
+	}
+	if (op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE) {
+		smb2_lease_acquire(sr);
+	} else if (op->op_oplock_level != SMB2_OPLOCK_LEVEL_NONE) {
+		smb2_oplock_acquire(sr);
+	}
+
+	/*
+	 * Make this a durable open, but only if:
+	 * (durable handle requested and...)
+	 *
+	 * 1. op_oplock_level == SMB2_OPLOCK_LEVEL_BATCH
+	 * 2. A lease is requested with handle caching
+	 *    - for v1, the lease must not be on a directory
+	 * 3. For v2, flags has "persistent" (tree is CA)
+	 *    (when tree not CA, turned off persist above)
+	 *
+	 * Otherwise, DH requests are ignored, so we set
+	 * dh_vers = not durable
+	 */
+	if ((cctx.cc_in_flags &
+	    (CCTX_DH_REQUEST|CCTX_DH_REQUEST_V2)) != 0 &&
+	    smb_node_is_file(of->f_node) &&
+	    ((op->op_oplock_level == SMB2_OPLOCK_LEVEL_BATCH) ||
+	    (op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE &&
+	    (op->lease_state & OPLOCK_LEVEL_CACHE_HANDLE) != 0))) {
+		/*
+		 * OK, make this handle "durable"
+		 */
+		if (op->dh_vers == SMB2_DURABLE_V2) {
+			(void) memcpy(of->dh_create_guid,
+			    op->create_guid, UUID_LEN);
+
+			/* no persistent handles yet */
+			of->dh_persist = B_FALSE;
+		}
+		if (op->dh_vers != SMB2_NOT_DURABLE) {
+			uint32_t msto;
+
+			of->dh_vers = op->dh_vers;
+			of->dh_expire_time = 0;
+
+			/*
+			 * Client may provide timeout=0 to request
+			 * the default timeout (in mSec.)
+			 */
+			msto = op->dh_timeout;
+			if (msto == 0)
+				msto = smb2_dh_def_timeout;
+			if (msto > smb2_dh_max_timeout)
+				msto = smb2_dh_max_timeout;
+			op->dh_timeout = msto;
+			of->dh_timeout_offset = MSEC2NSEC(msto);
+		}
+	} else {
+		op->dh_vers = SMB2_NOT_DURABLE;
+	}
+
+	/*
 	 * NB: after the above smb_common_open() success,
 	 * we have a handle allocated (sr->fid_ofile).
 	 * If we don't return success, we must close it.
@@ -293,14 +523,15 @@
 	 * though it could later be something larger,
 	 * (16 bytes) similar to an NFSv4 open handle.
 	 */
-	smb2fid.persistent = 0;
+reconnect_done:
+	smb2fid.persistent = of->f_persistid;
 	smb2fid.temporal = sr->smb_fid;
 
 	switch (sr->tid_tree->t_res_type & STYPE_MASK) {
 	case STYPE_DISKTREE:
 	case STYPE_PRINTQ:
 		if (op->create_options & FILE_DELETE_ON_CLOSE)
-			smb_ofile_set_delete_on_close(of);
+			smb_ofile_set_delete_on_close(sr, of);
 		break;
 	}
 
@@ -344,6 +575,25 @@
 	}
 
 	/*
+	 * If a lease was requested, and we got one...
+	 */
+	if ((cctx.cc_in_flags & CCTX_REQUEST_LEASE) != 0 &&
+	    op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE)
+		cctx.cc_out_flags |= CCTX_REQUEST_LEASE;
+
+	/*
+	 * If a durable handle was requested and we got one...
+	 */
+	if ((cctx.cc_in_flags & CCTX_DH_REQUEST) != 0 &&
+	    of->dh_vers == SMB2_DURABLE_V1) {
+		cctx.cc_out_flags |= CCTX_DH_REQUEST;
+	}
+	if ((cctx.cc_in_flags & CCTX_DH_REQUEST_V2) != 0 &&
+	    of->dh_vers == SMB2_DURABLE_V2) {
+		cctx.cc_out_flags |= CCTX_DH_REQUEST_V2;
+	}
+
+	/*
 	 * This marks the end of the "body" section and the
 	 * beginning of the "encode" section.  Any errors
 	 * encoding the response should use: goto errout
@@ -366,26 +616,6 @@
 	}
 
 	/*
-	 * Convert the negotiated Oplock level back into
-	 * SMB2 encoding form.
-	 */
-	switch (op->op_oplock_level) {
-	default:
-	case SMB_OPLOCK_NONE:
-		OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
-		break;
-	case SMB_OPLOCK_LEVEL_II:
-		OplockLevel = SMB2_OPLOCK_LEVEL_II;
-		break;
-	case SMB_OPLOCK_EXCLUSIVE:
-		OplockLevel = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
-		break;
-	case SMB_OPLOCK_BATCH:
-		OplockLevel = SMB2_OPLOCK_LEVEL_BATCH;
-		break;
-	}
-
-	/*
 	 * Encode the SMB2 Create reply
 	 */
 	attr = &op->fqi.fq_fattr;
@@ -393,7 +623,7 @@
 	    &sr->reply,
 	    "wb.lTTTTqqllqqll",
 	    89,	/* StructSize */	/* w */
-	    OplockLevel,		/* b */
+	    op->op_oplock_level,	/* b */
 	    op->action_taken,		/* l */
 	    &attr->sa_crtime,		/* T */
 	    &attr->sa_vattr.va_atime,	/* T */
@@ -554,6 +784,18 @@
 			cc->cc_in_flags |= CCTX_AAPL_EXT;
 			cce = &cc->cc_in_aapl;
 			break;
+		case SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2: /* ("DH2Q") */
+			cc->cc_in_flags |= CCTX_DH_REQUEST_V2;
+			cce = &cc->cc_in_dh_request_v2;
+			break;
+		case SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2: /* ("DH2C") */
+			cc->cc_in_flags |= CCTX_DH_RECONNECT_V2;
+			cce = &cc->cc_in_dh_reconnect_v2;
+			break;
+		case 0x9ccbcf9e: /* SVHDX_OPEN_DEVICE_CONTEXT */
+			/* 9ccbcf9e 04c1e643 980e158d a1f6ec83 */
+			/* silently ignore */
+			break;
 		default:
 			/*
 			 * Unknown create context values are normal, and
@@ -613,6 +855,81 @@
 			op->create_timewarp = B_TRUE;
 			break;
 
+		/*
+		 * Note: This handles both V1 and V2 leases,
+		 * which differ only by their length.
+		 */
+		case SMB2_CREATE_REQUEST_LEASE:		/* ("RqLs") */
+			if (data_len == 52) {
+				op->lease_version = 2;
+			} else if (data_len == 32) {
+				op->lease_version = 1;
+			} else {
+				cmn_err(CE_NOTE, "Cctx RqLs bad len=0x%x",
+				    data_len);
+			}
+			rc = smb_mbc_decodef(&cce->cce_mbc, "#cllq",
+			    UUID_LEN,			/* # */
+			    op->lease_key,		/* c */
+			    &op->lease_state,		/* l */
+			    &op->lease_flags,		/* l */
+			    &nttime);	/* (ignored)	   q */
+			if (rc != 0)
+				goto errout;
+			if (op->lease_version == 2) {
+				rc = smb_mbc_decodef(&cce->cce_mbc,
+				    "#cw..",
+				    UUID_LEN,
+				    op->parent_lease_key,
+				    &op->lease_epoch);
+				if (rc != 0)
+					goto errout;
+			} else {
+				bzero(op->parent_lease_key, UUID_LEN);
+			}
+			break;
+
+		case SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2: /* ("DH2C") */
+			rc = smb_mbc_decodef(&cce->cce_mbc, "qq#cl",
+			    &op->dh_fileid.persistent,	/* q */
+			    &op->dh_fileid.temporal,	/* q */
+			    UUID_LEN,			/* # */
+			    op->create_guid,		/* c */
+			    &op->dh_v2_flags);		/* l */
+			if (rc != 0)
+				goto errout;
+			break;
+
+		case SMB2_CREATE_DURABLE_HANDLE_RECONNECT: /* ("DHnC") */
+			rc = smb_mbc_decodef(&cce->cce_mbc, "qq",
+			    &op->dh_fileid.persistent, /* q */
+			    &op->dh_fileid.temporal); /* q */
+			if (rc != 0)
+				goto errout;
+			bzero(op->create_guid, UUID_LEN);
+			op->dh_v2_flags = 0;
+			break;
+
+		case SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2: /* ("DH2Q") */
+			rc = smb_mbc_decodef(&cce->cce_mbc,
+			    "ll8.#c",
+			    &op->dh_timeout,	/* l */
+			    &op->dh_v2_flags,	/* l */
+			    /* reserved */	/* 8. */
+			    UUID_LEN, /* # */
+			    op->create_guid); /* c */
+			if (rc != 0)
+				goto errout;
+			break;
+
+		case SMB2_CREATE_DURABLE_HANDLE_REQUEST: /* ("DHnQ") */
+			rc = smb_mbc_decodef(&cce->cce_mbc,
+			    "16."); /* reserved */
+			if (rc != 0)
+				goto errout;
+			op->dh_timeout = 0;	/* default */
+			op->dh_v2_flags = 0;
+			break;
 		}
 
 	next_cc:
@@ -708,6 +1025,65 @@
 		    mbc->chain_offset - last_top);
 	}
 
+	if (cc->cc_out_flags & CCTX_REQUEST_LEASE) {
+		cce = &cc->cc_out_req_lease;
+
+		cce->cce_mbc.max_bytes = cce->cce_len = 32;
+		(void) smb_mbc_encodef(&cce->cce_mbc, "#cllq",
+		    UUID_LEN,			/* # */
+		    op->lease_key,		/* c */
+		    op->lease_state,		/* l */
+		    op->lease_flags,		/* l */
+		    0LL);			/* q */
+		if (op->lease_version == 2) {
+			cce->cce_mbc.max_bytes = cce->cce_len = 52;
+			(void) smb_mbc_encodef(&cce->cce_mbc,
+			    "#cw..",
+			    UUID_LEN,
+			    op->parent_lease_key,
+			    op->lease_epoch);
+		}
+
+		last_top = mbc->chain_offset;
+		rc = smb2_encode_create_ctx_elem(mbc, cce,
+		    SMB2_CREATE_REQUEST_LEASE);
+		if (rc)
+			return (NT_STATUS_INTERNAL_ERROR);
+		(void) smb_mbc_poke(mbc, last_top, "l",
+		    mbc->chain_offset - last_top);
+	}
+
+	if (cc->cc_out_flags & CCTX_DH_REQUEST) {
+		cce = &cc->cc_out_dh_request;
+
+		cce->cce_mbc.max_bytes = cce->cce_len = 8;
+		(void) smb_mbc_encodef(&cce->cce_mbc, "q", 0LL);
+
+		last_top = mbc->chain_offset;
+		rc = smb2_encode_create_ctx_elem(mbc, cce,
+		    SMB2_CREATE_DURABLE_HANDLE_REQUEST);
+		if (rc)
+			return (NT_STATUS_INTERNAL_ERROR);
+		(void) smb_mbc_poke(mbc, last_top, "l",
+		    mbc->chain_offset - last_top);
+	}
+
+	if (cc->cc_out_flags & CCTX_DH_REQUEST_V2) {
+		cce = &cc->cc_out_dh_request_v2;
+
+		cce->cce_mbc.max_bytes = cce->cce_len = 8;
+		(void) smb_mbc_encodef(&cce->cce_mbc, "ll",
+		    op->dh_timeout, op->dh_v2_flags);
+
+		last_top = mbc->chain_offset;
+		rc = smb2_encode_create_ctx_elem(mbc, cce,
+		    SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2);
+		if (rc)
+			return (NT_STATUS_INTERNAL_ERROR);
+		(void) smb_mbc_poke(mbc, last_top, "l",
+		    mbc->chain_offset - last_top);
+	}
+
 	if (last_top >= 0)
 		(void) smb_mbc_poke(mbc, last_top, "l", 0);
 
@@ -736,6 +1112,7 @@
 	 *	a: this header (16 bytes)
 	 *	b: the name (4 bytes, 4 pad)
 	 *	c: the payload (variable)
+	 *	d: padding (to align 8)
 	 *
 	 * Note that "Next elem." is filled in later.
 	 */
@@ -759,6 +1136,8 @@
 	    cce->cce_len,	/* # */
 	    &cce->cce_mbc);	/* C */
 
+	(void) smb_mbc_put_align(out_mbc, 8);
+
 	return (rc);
 }
 
@@ -779,4 +1158,16 @@
 		cce = &cc->cc_out_aapl;
 		MBC_FLUSH(&cce->cce_mbc);
 	}
+	if (cc->cc_out_flags & CCTX_REQUEST_LEASE) {
+		cce = &cc->cc_out_req_lease;
+		MBC_FLUSH(&cce->cce_mbc);
+	}
+	if (cc->cc_out_flags & CCTX_DH_REQUEST) {
+		cce = &cc->cc_out_dh_request;
+		MBC_FLUSH(&cce->cce_mbc);
+	}
+	if (cc->cc_out_flags & CCTX_DH_REQUEST_V2) {
+		cce = &cc->cc_out_dh_request_v2;
+		MBC_FLUSH(&cce->cce_mbc);
+	}
 }
--- a/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c	Wed Jun 12 22:46:54 2019 +0200
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 
@@ -18,55 +18,12 @@
 #include <smbsrv/smb_kstat.h>
 #include <smbsrv/smb2.h>
 
-/*
- * Saved state for a command that "goes async".  When a compound request
- * contains a command that may block indefinitely, the compound reply is
- * composed with an "interim response" for that command, and information
- * needed to actually dispatch that command is saved on a list of "async"
- * commands for this compound request.  After the compound reply is sent,
- * the list of async commands is processed, and those may block as long
- * as they need to without affecting the initial compound request.
- *
- * Now interestingly, this "async" mechanism is not used with the full
- * range of asynchrony that one might imagine.  The design of async
- * request processing can be drastically simplified if we can assume
- * that there's no need to run more than one async command at a time.
- * With that simplifying assumption, we can continue using the current
- * "one worker thread per request message" model, which has very simple
- * locking rules etc.  The same worker thread that handles the initial
- * compound request can handle the list of async requests.
- *
- * As it turns out, SMB2 clients do not try to use more than one "async"
- * command in a compound.  If they were to do so, the [MS-SMB2] spec.
- * allows us to decline additional async requests with an error.
- *
- * smb_async_req_t is the struct used to save an "async" request on
- * the list of requests that had an interim reply in the initial
- * compound reply.  This includes everything needed to restart
- * processing at the async command.
- */
+#define	SMB2_ASYNCID(sr) (sr->smb2_messageid ^ (1ULL << 62))
 
-typedef struct smb2_async_req {
-
-	smb_sdrc_t		(*ar_func)(smb_request_t *);
-
-	int ar_cmd_hdr;		/* smb2_cmd_hdr offset */
-	int ar_cmd_len;		/* length from hdr */
-
-	/*
-	 * SMB2 header fields.
-	 */
-	uint16_t		ar_cmd_code;
-	uint16_t		ar_uid;
-	uint16_t		ar_tid;
-	uint32_t		ar_pid;
-	uint32_t		ar_hdr_flags;
-	uint64_t		ar_messageid;
-} smb2_async_req_t;
-
-void smb2sr_do_async(smb_request_t *);
 smb_sdrc_t smb2_invalid_cmd(smb_request_t *);
 static void smb2_tq_work(void *);
+static void smb2sr_run_postwork(smb_request_t *);
+static int smb3_decrypt_msg(smb_request_t *);
 
 static const smb_disp_entry_t
 smb2_disp_table[SMB2__NCMDS] = {
@@ -176,6 +133,18 @@
 	if (smb_mbc_peek(mbc, 0, "l", &magic) != 0)
 		goto drop;
 
+	/* 0xFD S M B */
+	if (magic == SMB3_ENCRYPTED_MAGIC) {
+		if (smb3_decrypt_msg(sr) != 0)
+			goto drop;
+		/*
+		 * Should now be looking at an un-encrypted
+		 * SMB2 message header.
+		 */
+		if (smb_mbc_peek(mbc, 0, "l", &magic) != 0)
+			goto drop;
+	}
+
 	if (magic != SMB2_PROTOCOL_MAGIC)
 		goto drop;
 
@@ -287,6 +256,168 @@
 	smb_srqueue_runq_exit(srq);
 }
 
+static int
+smb3_decrypt_msg(smb_request_t *sr)
+{
+	int save_offset;
+
+	if (sr->session->dialect < SMB_VERS_3_0) {
+		cmn_err(CE_WARN, "encrypted message in SMB 2.x");
+		return (-1);
+	}
+
+	sr->encrypted = B_TRUE;
+	save_offset = sr->command.chain_offset;
+	if (smb3_decode_tform_header(sr) != 0) {
+		cmn_err(CE_WARN, "bad transform header");
+		return (-1);
+	}
+	sr->command.chain_offset = save_offset;
+
+	sr->tform_ssn = smb_session_lookup_ssnid(sr->session,
+	    sr->smb3_tform_ssnid);
+	if (sr->tform_ssn == NULL) {
+		cmn_err(CE_WARN, "transform header: session not found");
+		return (-1);
+	}
+
+	if (smb3_decrypt_sr(sr) != 0) {
+		cmn_err(CE_WARN, "smb3 decryption failed");
+		return (-1);
+	}
+
+	return (0);
+}
+
+/*
+ * SMB2 credits determine how many simultaneous commands the
+ * client may issue, and bounds the range of message IDs those
+ * commands may use.  With multi-credit support, commands may
+ * use ranges of message IDs, where the credits used by each
+ * command are proportional to their data transfer size.
+ *
+ * Every command may request an increase or decrease of
+ * the currently granted credits, based on the difference
+ * between the credit request and the credit charge.
+ * [MS-SMB2] 3.3.1.2 Algorithm for the Granting of Credits
+ *
+ * Most commands have credit_request=1, credit_charge=1,
+ * which keeps the credit grant unchanged.
+ *
+ * All we're really doing here (for now) is reducing the
+ * credit_response if the client requests a credit increase
+ * that would take their credit over the maximum, and
+ * limiting the decrease so they don't run out of credits.
+ *
+ * Later, this could do something dynamic based on load.
+ *
+ * One other non-obvious bit about credits: We keep the
+ * session s_max_credits low until the 1st authentication,
+ * at which point we'll set the normal maximum_credits.
+ * Some clients ask for more credits with session setup,
+ * and we need to handle that requested increase _after_
+ * the command-specific handler returns so it won't be
+ * restricted to the lower (pre-auth) limit.
+ */
+static inline void
+smb2_credit_decrease(smb_request_t *sr)
+{
+	smb_session_t *session = sr->session;
+	uint16_t cur, d;
+
+	mutex_enter(&session->s_credits_mutex);
+	cur = session->s_cur_credits;
+
+	/* Handle credit decrease. */
+	d = sr->smb2_credit_charge - sr->smb2_credit_request;
+	cur -= d;
+	if (cur & 0x8000) {
+		/*
+		 * underflow (bad credit charge or request)
+		 * leave credits unchanged (response=charge)
+		 */
+		cur = session->s_cur_credits;
+		sr->smb2_credit_response = sr->smb2_credit_charge;
+		DTRACE_PROBE1(smb2__credit__neg, smb_request_t *, sr);
+	}
+
+	/*
+	 * The server MUST ensure that the number of credits
+	 * held by the client is never reduced to zero.
+	 * [MS-SMB2] 3.3.1.2
+	 */
+	if (cur == 0) {
+		cur = 1;
+		sr->smb2_credit_response += 1;
+		DTRACE_PROBE1(smb2__credit__min, smb_request_t *, sr);
+	}
+
+	DTRACE_PROBE3(smb2__credit__decrease,
+	    smb_request_t *, sr, int, (int)cur,
+	    int, (int)session->s_cur_credits);
+
+	session->s_cur_credits = cur;
+	mutex_exit(&session->s_credits_mutex);
+}
+
+/*
+ * Second half of SMB2 credit handling (increases)
+ */
+static inline void
+smb2_credit_increase(smb_request_t *sr)
+{
+	smb_session_t *session = sr->session;
+	uint16_t cur, d;
+
+	mutex_enter(&session->s_credits_mutex);
+	cur = session->s_cur_credits;
+
+	/* Handle credit increase. */
+	d = sr->smb2_credit_request - sr->smb2_credit_charge;
+	cur += d;
+
+	/*
+	 * If new credits would be above max,
+	 * reduce the credit grant.
+	 */
+	if (cur > session->s_max_credits) {
+		d = cur - session->s_max_credits;
+		cur = session->s_max_credits;
+		sr->smb2_credit_response -= d;
+		DTRACE_PROBE1(smb2__credit__max, smb_request_t, sr);
+	}
+
+	DTRACE_PROBE3(smb2__credit__increase,
+	    smb_request_t *, sr, int, (int)cur,
+	    int, (int)session->s_cur_credits);
+
+	session->s_cur_credits = cur;
+	mutex_exit(&session->s_credits_mutex);
+}
+
+/*
+ * Record some statistics:  latency, rx bytes, tx bytes
+ * per:  server, session & kshare.
+ */
+static inline void
+smb2_record_stats(smb_request_t *sr, smb_disp_stats_t *sds, boolean_t tx_only)
+{
+	hrtime_t	dt;
+	int64_t		rxb;
+	int64_t		txb;
+
+	dt = gethrtime() - sr->sr_time_start;
+	rxb = (int64_t)(sr->command.chain_offset - sr->smb2_cmd_hdr);
+	txb = (int64_t)(sr->reply.chain_offset - sr->smb2_reply_hdr);
+
+	if (!tx_only) {
+		smb_server_inc_req(sr->sr_server);
+		smb_latency_add_sample(&sds->sdt_lat, dt);
+		atomic_add_64(&sds->sdt_rxb, rxb);
+	}
+	atomic_add_64(&sds->sdt_txb, txb);
+}
+
 /*
  * smb2sr_work
  *
@@ -325,6 +456,7 @@
 
 	session = sr->session;
 
+	ASSERT(sr->smb2_async == B_FALSE);
 	ASSERT(sr->tid_tree == 0);
 	ASSERT(sr->uid_user == 0);
 	ASSERT(sr->fid_ofile == 0);
@@ -376,6 +508,15 @@
 		goto cleanup;
 	}
 	related = (sr->smb2_hdr_flags & SMB2_FLAGS_RELATED_OPERATIONS);
+	sr->smb2_hdr_flags |= SMB2_FLAGS_SERVER_TO_REDIR;
+	if (sr->smb2_hdr_flags & SMB2_FLAGS_ASYNC_COMMAND) {
+		/* Probably an async cancel. */
+		DTRACE_PROBE1(smb2__dispatch__async, smb_request_t *, sr);
+	} else if (sr->smb2_async) {
+		/* Previous command in compound went async. */
+		sr->smb2_hdr_flags |= SMB2_FLAGS_ASYNC_COMMAND;
+		sr->smb2_async_id = SMB2_ASYNCID(sr);
+	}
 
 	/*
 	 * In case we bail out with an error before we get to the
@@ -388,11 +529,17 @@
 	sr->smb2_credit_response = sr->smb2_credit_charge;
 
 	/*
-	 * Reserve space for the reply header, and save the offset.
-	 * The reply header will be overwritten later.  If we have
-	 * already exhausted the output space, then this client is
-	 * trying something funny.  Log it and kill 'em.
+	 * Write a tentative reply header.
+	 *
+	 * We could just leave this blank, but if we're using the
+	 * mdb module feature that extracts packets, it's useful
+	 * to have the header mostly correct here.
+	 *
+	 * If we have already exhausted the output space, then the
+	 * client is trying something funny.  Log it and kill 'em.
 	 */
+	sr->smb2_next_reply = 0;
+	ASSERT((sr->reply.chain_offset & 7) == 0);
 	sr->smb2_reply_hdr = sr->reply.chain_offset;
 	if ((rc = smb2_encode_header(sr, B_FALSE)) != 0) {
 		cmn_err(CE_WARN, "clnt %s excessive reply",
@@ -469,7 +616,6 @@
 		 * avoid dangling references: file, tree, user
 		 */
 		if (sr->fid_ofile != NULL) {
-			smb_ofile_request_complete(sr->fid_ofile);
 			smb_ofile_release(sr->fid_ofile);
 			sr->fid_ofile = NULL;
 		}
@@ -503,23 +649,77 @@
 				    NT_STATUS_INVALID_PARAMETER);
 				goto cmd_done;
 			}
-			sr->smb_uid = sr->uid_user->u_uid;
+			sr->smb2_ssnid = sr->uid_user->u_ssnid;
 		} else {
 			/*
 			 * Lookup the UID
 			 * [MS-SMB2] 3.3.5.2 Verifying the Session
 			 */
 			ASSERT(sr->uid_user == NULL);
-			sr->uid_user = smb_session_lookup_uid(session,
-			    sr->smb_uid);
+			/*
+			 * [MS-SMB2] 3.3.5.2.7 Handling Compounded Requests
+			 *
+			 * If this is an encrypted compound request,
+			 * ensure that the ssnid in the request
+			 * is the same as the tform ssnid if this
+			 * message is not related.
+			 *
+			 * The reasons this is done seem to apply equally
+			 * to uncompounded requests, so we apply it to all.
+			 */
+
+			if (sr->encrypted &&
+			    sr->smb2_ssnid != sr->smb3_tform_ssnid) {
+				disconnect = B_TRUE;
+				goto cleanup; /* just do this for now */
+			}
+
+			sr->uid_user = smb_session_lookup_ssnid(session,
+			    sr->smb2_ssnid);
 			if (sr->uid_user == NULL) {
 				smb2sr_put_error(sr,
 				    NT_STATUS_USER_SESSION_DELETED);
 				goto cmd_done;
 			}
+
+			/*
+			 * [MS-SMB2] 3.3.5.2.9 Verifying the Session
+			 *
+			 * If we're talking 3.x,
+			 * RejectUnencryptedAccess is TRUE,
+			 * Session.EncryptData is TRUE,
+			 * and the message wasn't encrypted,
+			 * return ACCESS_DENIED.
+			 *
+			 * Note that Session.EncryptData can only be TRUE when
+			 * we're talking 3.x.
+			 */
+
+			if (sr->uid_user->u_encrypt ==
+			    SMB_CONFIG_REQUIRED &&
+			    !sr->encrypted) {
+				smb2sr_put_error(sr,
+				    NT_STATUS_ACCESS_DENIED);
+				goto cmd_done;
+			}
+
 			sr->user_cr = smb_user_getcred(sr->uid_user);
 		}
 		ASSERT(sr->uid_user != NULL);
+
+		/*
+		 * Encrypt if:
+		 * - The cmd is not SESSION_SETUP or NEGOTIATE; AND
+		 * - Session.EncryptData is TRUE
+		 *
+		 * Those commands suppress UID, so they can't be the cmd here.
+		 */
+		if (sr->uid_user->u_encrypt != SMB_CONFIG_DISABLED &&
+		    sr->tform_ssn == NULL) {
+			smb_user_hold_internal(sr->uid_user);
+			sr->tform_ssn = sr->uid_user;
+			sr->smb3_tform_ssnid = sr->smb2_ssnid;
+		}
 	}
 
 	if ((sdd->sdt_flags & SDDF_SUPPRESS_TID) == 0) {
@@ -550,8 +750,49 @@
 				    NT_STATUS_NETWORK_NAME_DELETED);
 				goto cmd_done;
 			}
+
+			/*
+			 * [MS-SMB2] 3.3.5.2.11 Verifying the Tree Connect
+			 *
+			 * If we support 3.x, RejectUnencryptedAccess is TRUE,
+			 * if Tcon.EncryptData is TRUE or
+			 * global EncryptData is TRUE and
+			 * the message wasn't encrypted, or
+			 * if Tcon.EncryptData is TRUE or
+			 * global EncryptData is TRUE or
+			 * the request was encrypted and
+			 * the connection doesn't support encryption,
+			 * return ACCESS_DENIED.
+			 *
+			 * If RejectUnencryptedAccess is TRUE, we force
+			 * max_protocol to at least 3.0. Additionally,
+			 * if the tree requires encryption, we don't care
+			 * what we support, we still enforce encryption.
+			 */
+			if (sr->tid_tree->t_encrypt == SMB_CONFIG_REQUIRED &&
+			    (!sr->encrypted ||
+			    (session->srv_cap & SMB2_CAP_ENCRYPTION) == 0)) {
+				smb2sr_put_error(sr,
+				    NT_STATUS_ACCESS_DENIED);
+				goto cmd_done;
+			}
 		}
 		ASSERT(sr->tid_tree != NULL);
+
+		/*
+		 * Encrypt if:
+		 * - The cmd is not TREE_CONNECT; AND
+		 * - Tree.EncryptData is TRUE
+		 *
+		 * TREE_CONNECT suppresses TID, so that can't be the cmd here.
+		 * NOTE: assumes we can't have a tree without a user
+		 */
+		if (sr->tid_tree->t_encrypt != SMB_CONFIG_DISABLED &&
+		    sr->tform_ssn == NULL) {
+			smb_user_hold_internal(sr->uid_user);
+			sr->tform_ssn = sr->uid_user;
+			sr->smb3_tform_ssnid = sr->smb2_ssnid;
+		}
 	}
 
 	/*
@@ -573,9 +814,14 @@
 	 * The SDDF_SUPPRESS_UID dispatch is set for requests that
 	 * don't need a UID (user).  These also don't require a
 	 * signature check here.
+	 *
+	 * [MS-SMB2] 3.3.5.2.4 Verifying the Signature
+	 *
+	 * If the packet was successfully decrypted, the message
+	 * signature has already been verified, so we can skip this.
 	 */
 	if ((sdd->sdt_flags & SDDF_SUPPRESS_UID) == 0 &&
-	    sr->uid_user != NULL &&
+	    !sr->encrypted && sr->uid_user != NULL &&
 	    (sr->uid_user->u_sign_flags & SMB_SIGNING_CHECK) != 0) {
 		/*
 		 * This request type should be signed, and
@@ -587,7 +833,7 @@
 		}
 		rc = smb2_sign_check_request(sr);
 		if (rc != 0) {
-			DTRACE_PROBE1(smb2__sign__check, smb_request_t, sr);
+			DTRACE_PROBE1(smb2__sign__check, smb_request_t *, sr);
 			smb2sr_put_error(sr, NT_STATUS_ACCESS_DENIED);
 			goto cmd_done;
 		}
@@ -603,72 +849,16 @@
 	sr->smb_data.chain_offset = sr->smb2_cmd_hdr + SMB2_HDR_SIZE;
 
 	/*
-	 * SMB2 credits determine how many simultaneous commands the
-	 * client may issue, and bounds the range of message IDs those
-	 * commands may use.  With multi-credit support, commands may
-	 * use ranges of message IDs, where the credits used by each
-	 * command are proportional to their data transfer size.
-	 *
-	 * Every command may request an increase or decrease of
-	 * the currently granted credits, based on the difference
-	 * between the credit request and the credit charge.
-	 * [MS-SMB2] 3.3.1.2 Algorithm for the Granting of Credits
-	 *
-	 * Most commands have credit_request=1, credit_charge=1,
-	 * which keeps the credit grant unchanged.
+	 * Credit adjustments (decrease)
 	 *
-	 * All we're really doing here (for now) is reducing the
-	 * credit_response if the client requests a credit increase
-	 * that would take their credit over the maximum, and
-	 * limiting the decrease so they don't run out of credits.
-	 *
-	 * Later, this could do something dynamic based on load.
-	 *
-	 * One other non-obvious bit about credits: We keep the
-	 * session s_max_credits low until the 1st authentication,
-	 * at which point we'll set the normal maximum_credits.
-	 * Some clients ask for more credits with session setup,
-	 * and we need to handle that requested increase _after_
-	 * the command-specific handler returns so it won't be
-	 * restricted to the lower (pre-auth) limit.
+	 * If we've gone async, credit adjustments were done
+	 * when we sent the interim reply.
 	 */
-	sr->smb2_credit_response = sr->smb2_credit_request;
-	if (sr->smb2_credit_request < sr->smb2_credit_charge) {
-		uint16_t cur, d;
-
-		mutex_enter(&session->s_credits_mutex);
-		cur = session->s_cur_credits;
-
-		/* Handle credit decrease. */
-		d = sr->smb2_credit_charge - sr->smb2_credit_request;
-		cur -= d;
-		if (cur & 0x8000) {
-			/*
-			 * underflow (bad credit charge or request)
-			 * leave credits unchanged (response=charge)
-			 */
-			cur = session->s_cur_credits;
-			sr->smb2_credit_response = sr->smb2_credit_charge;
-			DTRACE_PROBE1(smb2__credit__neg, smb_request_t, sr);
+	if (!sr->smb2_async) {
+		sr->smb2_credit_response = sr->smb2_credit_request;
+		if (sr->smb2_credit_request < sr->smb2_credit_charge) {
+			smb2_credit_decrease(sr);
 		}
-
-		/*
-		 * The server MUST ensure that the number of credits
-		 * held by the client is never reduced to zero.
-		 * [MS-SMB2] 3.3.1.2
-		 */
-		if (cur == 0) {
-			cur = 1;
-			sr->smb2_credit_response += 1;
-			DTRACE_PROBE1(smb2__credit__min, smb_request_t, sr);
-		}
-
-		DTRACE_PROBE3(smb2__credit__decrease,
-		    smb_request_t, sr, int, (int)cur,
-		    int, (int)session->s_cur_credits);
-
-		session->s_cur_credits = cur;
-		mutex_exit(&session->s_credits_mutex);
 	}
 
 	/*
@@ -685,61 +875,26 @@
 		smb2sr_put_error(sr, sr->smb2_status);
 	}
 
+	/*
+	 * When the sdt_function returns SDRC_SR_KEPT, it means
+	 * this SR may have been passed to another thread so we
+	 * MUST NOT touch it anymore.
+	 */
+	if (rc == SDRC_SR_KEPT)
+		return;
+
 	MBC_FLUSH(&sr->raw_data);
 
 	/*
-	 * Second half of SMB2 credit handling (increases)
+	 * Credit adjustments (increase)
 	 */
-	if (sr->smb2_credit_request > sr->smb2_credit_charge) {
-		uint16_t cur, d;
-
-		mutex_enter(&session->s_credits_mutex);
-		cur = session->s_cur_credits;
-
-		/* Handle credit increase. */
-		d = sr->smb2_credit_request - sr->smb2_credit_charge;
-		cur += d;
-
-		/*
-		 * If new credits would be above max,
-		 * reduce the credit grant.
-		 */
-		if (cur > session->s_max_credits) {
-			d = cur - session->s_max_credits;
-			cur = session->s_max_credits;
-			sr->smb2_credit_response -= d;
-			DTRACE_PROBE1(smb2__credit__max, smb_request_t, sr);
+	if (!sr->smb2_async) {
+		if (sr->smb2_credit_request > sr->smb2_credit_charge) {
+			smb2_credit_increase(sr);
 		}
-
-		DTRACE_PROBE3(smb2__credit__increase,
-		    smb_request_t, sr, int, (int)cur,
-		    int, (int)session->s_cur_credits);
-
-		session->s_cur_credits = cur;
-		mutex_exit(&session->s_credits_mutex);
 	}
 
 cmd_done:
-	/*
-	 * Pad the reply to align(8) if necessary.
-	 */
-	if (sr->reply.chain_offset & 7) {
-		int padsz = 8 - (sr->reply.chain_offset & 7);
-		(void) smb_mbc_encodef(&sr->reply, "#.", padsz);
-	}
-	ASSERT((sr->reply.chain_offset & 7) == 0);
-
-	/*
-	 * Record some statistics: latency, rx bytes, tx bytes.
-	 */
-	smb_server_inc_req(sr->sr_server);
-	smb_latency_add_sample(&sds->sdt_lat,
-	    gethrtime() - sr->sr_time_start);
-	atomic_add_64(&sds->sdt_rxb,
-	    (int64_t)(sr->command.chain_offset - sr->smb2_cmd_hdr));
-	atomic_add_64(&sds->sdt_txb,
-	    (int64_t)(sr->reply.chain_offset - sr->smb2_reply_hdr));
-
 	switch (rc) {
 	case SDRC_SUCCESS:
 		break;
@@ -777,11 +932,27 @@
 	}
 
 	/*
+	 * Pad the reply to align(8) if there will be another.
+	 * (We don't compound async replies.)
+	 */
+	if (!sr->smb2_async && sr->smb2_next_command != 0)
+		(void) smb_mbc_put_align(&sr->reply, 8);
+
+	/*
+	 * Record some statistics.  Uses:
+	 *   rxb = command.chain_offset - smb2_cmd_hdr;
+	 *   txb = reply.chain_offset - smb2_reply_hdr;
+	 * which at this point represent the current cmd/reply.
+	 *
+	 * Note: If async, this does txb only, and
+	 * skips the smb_latency_add_sample() calls.
+	 */
+	smb2_record_stats(sr, sds, sr->smb2_async);
+
+	/*
 	 * If there's a next command, figure out where it starts,
-	 * and fill in the next command offset for the reply.
-	 * Note: We sanity checked smb2_next_command above
-	 * (the offset to the next command).  Similarly set
-	 * smb2_next_reply as the offset to the next reply.
+	 * and fill in the next header offset for the reply.
+	 * Note: We sanity checked smb2_next_command above.
 	 */
 	if (sr->smb2_next_command != 0) {
 		sr->command.chain_offset =
@@ -789,52 +960,38 @@
 		sr->smb2_next_reply =
 		    sr->reply.chain_offset - sr->smb2_reply_hdr;
 	} else {
-		sr->smb2_next_reply = 0;
+		ASSERT(sr->smb2_next_reply == 0);
 	}
 
 	/*
-	 * Overwrite the SMB2 header for the response of
-	 * this command (possibly part of a compound).
-	 * encode_header adds: SMB2_FLAGS_SERVER_TO_REDIR
+	 * Overwrite the (now final) SMB2 header for this response.
 	 */
 	(void) smb2_encode_header(sr, B_TRUE);
 
-	if (sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED)
+	/* Don't sign if we're going to encrypt */
+	if (sr->tform_ssn == NULL &&
+	    (sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED) != 0)
 		smb2_sign_reply(sr);
 
-	if (sr->smb2_next_command != 0)
-		goto cmd_start;
-
 	/*
-	 * We've done all the commands in this compound.
-	 * Send it out.
+	 * Non-async runs the whole compound before send.
+	 * When we've gone async, send each individually.
 	 */
+	if (!sr->smb2_async && sr->smb2_next_command != 0)
+		goto cmd_start;
 	smb2_send_reply(sr);
-
-	/*
-	 * If any of the requests "went async", process those now.
-	 * The async. function "keeps" this sr, changing its state
-	 * to completed and calling smb_request_free().
-	 */
-	if (sr->sr_async_req != NULL) {
-		smb2sr_do_async(sr);
-		return;
+	if (sr->smb2_async && sr->smb2_next_command != 0) {
+		MBC_FLUSH(&sr->reply);	/* New reply buffer. */
+		ASSERT(sr->reply.max_bytes == sr->session->reply_max_bytes);
+		goto cmd_start;
 	}
 
 cleanup:
-	if (disconnect) {
-		smb_rwx_rwenter(&session->s_lock, RW_WRITER);
-		switch (session->s_state) {
-		case SMB_SESSION_STATE_DISCONNECTED:
-		case SMB_SESSION_STATE_TERMINATED:
-			break;
-		default:
-			smb_soshutdown(session->sock);
-			session->s_state = SMB_SESSION_STATE_DISCONNECTED;
-			break;
-		}
-		smb_rwx_rwexit(&session->s_lock);
-	}
+	if (disconnect)
+		smb_session_disconnect(session);
+
+	if (sr->sr_postwork != NULL)
+		smb2sr_run_postwork(sr);
 
 	mutex_enter(&sr->sr_mutex);
 	sr->sr_state = SMB_REQ_STATE_COMPLETED;
@@ -844,226 +1001,354 @@
 }
 
 /*
- * Dispatch an async request using saved information.
- * See smb2sr_save_async and [MS-SMB2] 3.3.4.2
+ * Build interim responses for the current and all following
+ * requests in this compound, then send the compound response,
+ * leaving the SR state so that smb2sr_work() can continue its
+ * processing of this compound in "async mode".
  *
- * This is sort of a "lite" version of smb2sr_work.  Initialize the
- * command and reply areas as they were when the command-speicific
- * handler started (in case it needs to decode anything again).
- * Call the async function, which builds the command-specific part
- * of the response.  Finally, send the response and free the sr.
- */
-void
-smb2sr_do_async(smb_request_t *sr)
-{
-	const smb_disp_entry_t	*sdd;
-	smb2_async_req_t	*ar;
-	smb_sdrc_t		(*ar_func)(smb_request_t *);
-	int sdrc;
-
-	/*
-	 * Restore what smb2_decode_header found.
-	 * (In lieu of decoding it again.)
-	 */
-	ar = sr->sr_async_req;
-	sr->smb2_cmd_hdr   = ar->ar_cmd_hdr;
-	sr->smb2_cmd_code  = ar->ar_cmd_code;
-	sr->smb2_hdr_flags = ar->ar_hdr_flags;
-	sr->smb2_async_id  = (uintptr_t)ar;
-	sr->smb2_messageid = ar->ar_messageid;
-	sr->smb_pid = ar->ar_pid;
-	sr->smb_tid = ar->ar_tid;
-	sr->smb_uid = ar->ar_uid;
-	sr->smb2_status = 0;
-
-	/*
-	 * Async requests don't grant credits, because any credits
-	 * should have gone out with the interim reply.
-	 * An async reply goes alone (no next reply).
-	 */
-	sr->smb2_credit_response = 0;
-	sr->smb2_next_reply = 0;
-
-	/*
-	 * Setup input mbuf_chain
-	 */
-	ASSERT(ar->ar_cmd_len >= SMB2_HDR_SIZE);
-	(void) MBC_SHADOW_CHAIN(&sr->smb_data, &sr->command,
-	    sr->smb2_cmd_hdr + SMB2_HDR_SIZE,
-	    ar->ar_cmd_len - SMB2_HDR_SIZE);
-
-	/*
-	 * Done with sr_async_req
-	 */
-	ar_func = ar->ar_func;
-	kmem_free(ar, sizeof (*ar));
-	sr->sr_async_req = ar = NULL;
-
-	/*
-	 * Setup output mbuf_chain
-	 */
-	MBC_FLUSH(&sr->reply);
-	sr->smb2_reply_hdr = sr->reply.chain_offset;
-	(void) smb2_encode_header(sr, B_FALSE);
-
-	VERIFY3U(sr->smb2_cmd_code, <, SMB2_INVALID_CMD);
-	sdd = &smb2_disp_table[sr->smb2_cmd_code];
-
-	/*
-	 * Keep the UID, TID, ofile we have.
-	 */
-	if ((sdd->sdt_flags & SDDF_SUPPRESS_UID) == 0 &&
-	    sr->uid_user == NULL) {
-		smb2sr_put_error(sr, NT_STATUS_USER_SESSION_DELETED);
-		goto cmd_done;
-	}
-	if ((sdd->sdt_flags & SDDF_SUPPRESS_TID) == 0 &&
-	    sr->tid_tree == NULL) {
-		smb2sr_put_error(sr, NT_STATUS_NETWORK_NAME_DELETED);
-		goto cmd_done;
-	}
-
-	/*
-	 * Signature already verified
-	 * Credits handled...
-	 *
-	 * Just call the async handler function.
-	 */
-	sdrc = ar_func(sr);
-	switch (sdrc) {
-	case SDRC_SUCCESS:
-		break;
-	case SDRC_ERROR:
-		if (sr->smb2_status == 0)
-			sr->smb2_status = NT_STATUS_INTERNAL_ERROR;
-		break;
-	case SDRC_SR_KEPT:
-		/* This SR will be completed later. */
-		return;
-	}
-
-cmd_done:
-	smb2sr_finish_async(sr);
-}
-
-void
-smb2sr_finish_async(smb_request_t *sr)
-{
-	smb_disp_stats_t	*sds;
-
-	/*
-	 * Pad the reply to align(8) if necessary.
-	 */
-	if (sr->reply.chain_offset & 7) {
-		int padsz = 8 - (sr->reply.chain_offset & 7);
-		(void) smb_mbc_encodef(&sr->reply, "#.", padsz);
-	}
-	ASSERT((sr->reply.chain_offset & 7) == 0);
-
-	/*
-	 * Record some statistics: (just tx bytes here)
-	 */
-	sds = &sr->session->s_server->sv_disp_stats2[sr->smb2_cmd_code];
-	atomic_add_64(&sds->sdt_txb, (int64_t)(sr->reply.chain_offset));
-
-	/*
-	 * Put (overwrite) the final SMB2 header.
-	 * The call adds: SMB2_FLAGS_SERVER_TO_REDIR
-	 */
-	(void) smb2_encode_header(sr, B_TRUE);
-
-	if (sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED)
-		smb2_sign_reply(sr);
-
-	smb2_send_reply(sr);
-
-	/*
-	 * Done.  Unlink and free.
-	 */
-
-	mutex_enter(&sr->sr_mutex);
-	sr->sr_state = SMB_REQ_STATE_COMPLETED;
-	mutex_exit(&sr->sr_mutex);
-
-	smb_request_free(sr);
-}
-
-/*
- * In preparation for sending an "interim response", save
- * all the state we'll need to run an async command later,
- * and assign an "async id" for this (now async) command.
- * See [MS-SMB2] 3.3.4.2
- *
- * If more than one request in a compound request tries to
- * "go async", we can "say no".  See [MS-SMB2] 3.3.4.2
- *	If an operation would require asynchronous processing
- *	but resources are constrained, the server MAY choose to
- *	fail that operation with STATUS_INSUFFICIENT_RESOURCES.
- *
- * For simplicity, we further restrict the cases where we're
- * willing to "go async", and only allow the last command in a
- * compound to "go async".  It happens that this is the only
- * case where we're actually asked to go async anyway. This
- * simplification also means there can be at most one command
- * in a compound that "goes async" (the last one).
- *
- * If we agree to "go async", this should return STATUS_PENDING.
+ * If we agree to "go async", this should return STATUS_SUCCESS.
  * Otherwise return STATUS_INSUFFICIENT_RESOURCES for this and
  * all requests following this request.  (See the comments re.
  * "sticky" smb2_status values in smb2sr_work).
  *
  * Note: the Async ID we assign here is arbitrary, and need only
  * be unique among pending async responses on this connection, so
- * this just uses an object address as the Async ID.
+ * this just uses a modified messageID, which is already unique.
  *
- * Also, the assigned worker is the ONLY thread using this
- * async request object (sr_async_req) so no locking.
+ * Credits:  All credit changes should happen via the interim
+ * responses, so we have to manage credits here.  After this
+ * returns to smb2sr_work, the final replies for all these
+ * commands will have smb2_credit_response = smb2_credit_charge
+ * (meaning no further changes to the clients' credits).
  */
 uint32_t
-smb2sr_go_async(smb_request_t *sr,
-    smb_sdrc_t (*async_func)(smb_request_t *))
+smb2sr_go_async(smb_request_t *sr)
 {
-	smb2_async_req_t *ar;
+	smb_session_t *session;
+	smb_disp_stats_t *sds;
+	uint16_t cmd_idx;
+	int32_t saved_com_offset;
+	uint32_t saved_cmd_hdr;
+	uint16_t saved_cred_resp;
+	uint32_t saved_hdr_flags;
+	uint32_t saved_reply_hdr;
+	uint32_t msg_len;
+	boolean_t disconnect = B_FALSE;
+
+	if (sr->smb2_async) {
+		/* already went async in some previous cmd. */
+		return (NT_STATUS_SUCCESS);
+	}
+	sr->smb2_async = B_TRUE;
+
+	/* The "server" session always runs async. */
+	session = sr->session;
+	if (session->sock == NULL)
+		return (NT_STATUS_SUCCESS);
+
+	sds = NULL;
+	saved_com_offset = sr->command.chain_offset;
+	saved_cmd_hdr = sr->smb2_cmd_hdr;
+	saved_cred_resp = sr->smb2_credit_response;
+	saved_hdr_flags = sr->smb2_hdr_flags;
+	saved_reply_hdr = sr->smb2_reply_hdr;
+
+	/*
+	 * The command-specific handler should not yet have put any
+	 * data in the reply except for the (place holder) header.
+	 */
+	if (sr->reply.chain_offset != sr->smb2_reply_hdr + SMB2_HDR_SIZE) {
+		ASSERT3U(sr->reply.chain_offset, ==,
+		    sr->smb2_reply_hdr + SMB2_HDR_SIZE);
+		return (NT_STATUS_INTERNAL_ERROR);
+	}
+
+	/*
+	 * Rewind to the start of the current header in both the
+	 * command and reply bufers, so the loop below can just
+	 * decode/encode just in every pass.  This means the
+	 * current command header is decoded again, but that
+	 * avoids having to special-case the first loop pass.
+	 */
+	sr->command.chain_offset = sr->smb2_cmd_hdr;
+	sr->reply.chain_offset = sr->smb2_reply_hdr;
+
+	/*
+	 * This command processing loop is a simplified version of
+	 * smb2sr_work() that just puts an "interim response" for
+	 * every command in the compound (NT_STATUS_PENDING).
+	 */
+cmd_start:
+	sr->smb2_status = NT_STATUS_PENDING;
 
-	if (sr->smb2_next_command != 0)
-		return (NT_STATUS_INSUFFICIENT_RESOURCES);
+	/*
+	 * Decode the request header
+	 */
+	sr->smb2_cmd_hdr = sr->command.chain_offset;
+	if ((smb2_decode_header(sr)) != 0) {
+		cmn_err(CE_WARN, "clnt %s bad SMB2 header",
+		    session->ip_addr_str);
+		disconnect = B_TRUE;
+		goto cleanup;
+	}
+	sr->smb2_hdr_flags |=  (SMB2_FLAGS_SERVER_TO_REDIR |
+				SMB2_FLAGS_ASYNC_COMMAND);
+	sr->smb2_async_id = SMB2_ASYNCID(sr);
+
+	/*
+	 * In case we bail out...
+	 */
+	if (sr->smb2_credit_charge == 0)
+		sr->smb2_credit_charge = 1;
+	sr->smb2_credit_response = sr->smb2_credit_charge;
+
+	/*
+	 * Write a tentative reply header.
+	 */
+	sr->smb2_next_reply = 0;
+	ASSERT((sr->reply.chain_offset & 7) == 0);
+	sr->smb2_reply_hdr = sr->reply.chain_offset;
+	if ((smb2_encode_header(sr, B_FALSE)) != 0) {
+		cmn_err(CE_WARN, "clnt %s excessive reply",
+		    session->ip_addr_str);
+		disconnect = B_TRUE;
+		goto cleanup;
+	}
 
-	ASSERT(sr->sr_async_req == NULL);
-	ar = kmem_zalloc(sizeof (*ar), KM_SLEEP);
+	/*
+	 * Figure out the length of data...
+	 */
+	if (sr->smb2_next_command != 0) {
+		/* [MS-SMB2] says this is 8-byte aligned */
+		msg_len = sr->smb2_next_command;
+		if ((msg_len & 7) != 0 || (msg_len < SMB2_HDR_SIZE) ||
+		    ((sr->smb2_cmd_hdr + msg_len) > sr->command.max_bytes)) {
+			cmn_err(CE_WARN, "clnt %s bad SMB2 next cmd",
+			    session->ip_addr_str);
+			disconnect = B_TRUE;
+			goto cleanup;
+		}
+	} else {
+		msg_len = sr->command.max_bytes - sr->smb2_cmd_hdr;
+	}
+
+	/*
+	 * We just skip any data, so no shadow chain etc.
+	 */
+	sr->command.chain_offset = sr->smb2_cmd_hdr + msg_len;
+	ASSERT(sr->command.chain_offset <= sr->command.max_bytes);
+
+	/*
+	 * Validate the commmand code...
+	 */
+	if (sr->smb2_cmd_code < SMB2_INVALID_CMD)
+		cmd_idx = sr->smb2_cmd_code;
+	else
+		cmd_idx = SMB2_INVALID_CMD;
+	sds = &session->s_server->sv_disp_stats2[cmd_idx];
+
+	/*
+	 * Don't change (user, tree, file) because we want them
+	 * exactly as they were when we entered.  That also means
+	 * we may not have the right user in sr->uid_user for
+	 * signature checks, so leave that until smb2sr_work
+	 * runs these commands "for real".  Therefore, here
+	 * we behave as if: (sr->uid_user == NULL)
+	 */
+	sr->smb2_hdr_flags &= ~SMB2_FLAGS_SIGNED;
 
 	/*
-	 * Place an interim response in the compound reply.
+	 * Credit adjustments (decrease)
+	 *
+	 * NOTE: interim responses are not signed.
+	 * Any attacker can modify the credit grant
+	 * in the response. Because of this property,
+	 * it is no worse to assume the credit charge and grant
+	 * are sane without verifying the signature,
+	 * and that saves us a whole lot of work.
+	 * If the credits WERE modified, we'll find out
+	 * when we verify the signature later,
+	 * which nullifies any changes caused here.
+	 *
+	 * Skip this on the first command, because the
+	 * credit decrease was done by the caller.
+	 */
+	if (sr->smb2_cmd_hdr != saved_cmd_hdr) {
+		sr->smb2_credit_response = sr->smb2_credit_request;
+		if (sr->smb2_credit_request < sr->smb2_credit_charge) {
+			smb2_credit_decrease(sr);
+		}
+	}
+
+	/*
+	 * The real work: ... (would be here)
+	 */
+	smb2sr_put_error(sr, sr->smb2_status);
+
+	/*
+	 * Credit adjustments (increase)
+	 */
+	if (sr->smb2_credit_request > sr->smb2_credit_charge) {
+		smb2_credit_increase(sr);
+	}
+
+	/* cmd_done: label */
+
+	/*
+	 * Pad the reply to align(8) if there will be another.
+	 * This (interim) reply uses compounding.
+	 */
+	if (sr->smb2_next_command != 0)
+		(void) smb_mbc_put_align(&sr->reply, 8);
+
+	/*
+	 * Record some statistics.  Uses:
+	 *   rxb = command.chain_offset - smb2_cmd_hdr;
+	 *   txb = reply.chain_offset - smb2_reply_hdr;
+	 * which at this point represent the current cmd/reply.
 	 *
-	 * Turn on the "async" flag for both the (synchronous)
-	 * interim response and the (later) async response,
-	 * by storing that in flags before coping into ar.
+	 * Note: We're doing smb_latency_add_sample() for all
+	 * remaining commands NOW, which means we won't include
+	 * the async part of their work in latency statistics.
+	 * That's intentional, as the async part of a command
+	 * would otherwise skew our latency statistics.
+	 */
+	smb2_record_stats(sr, sds, B_FALSE);
+
+	/*
+	 * If there's a next command, figure out where it starts,
+	 * and fill in the next header offset for the reply.
+	 * Note: We sanity checked smb2_next_command above.
+	 */
+	if (sr->smb2_next_command != 0) {
+		sr->command.chain_offset =
+		    sr->smb2_cmd_hdr + sr->smb2_next_command;
+		sr->smb2_next_reply =
+		    sr->reply.chain_offset - sr->smb2_reply_hdr;
+	} else {
+		ASSERT(sr->smb2_next_reply == 0);
+	}
+
+	/*
+	 * Overwrite the (now final) SMB2 header for this response.
+	 */
+	(void) smb2_encode_header(sr, B_TRUE);
+
+	/*
+	 * Process whole compound before sending.
+	 */
+	if (sr->smb2_next_command != 0)
+		goto cmd_start;
+	smb2_send_reply(sr);
+
+	ASSERT(!disconnect);
+
+cleanup:
+	/*
+	 * Restore caller's command processing state.
+	 */
+	sr->smb2_cmd_hdr = saved_cmd_hdr;
+	sr->command.chain_offset = saved_cmd_hdr;
+	(void) smb2_decode_header(sr);
+	sr->command.chain_offset = saved_com_offset;
+
+	sr->smb2_credit_response = saved_cred_resp;
+	sr->smb2_hdr_flags = saved_hdr_flags;
+	sr->smb2_status = NT_STATUS_SUCCESS;
+
+	/*
+	 * In here, the "disconnect" flag just means we had an
+	 * error decoding or encoding something.  Rather than
+	 * actually disconnect here, let's assume whatever
+	 * problem we encountered will be seen by the caller
+	 * as they continue processing the compound, and just
+	 * restore everything and return an error.
+	 */
+	if (disconnect) {
+		sr->smb2_async = B_FALSE;
+		sr->smb2_reply_hdr = saved_reply_hdr;
+		sr->reply.chain_offset = sr->smb2_reply_hdr;
+		(void) smb2_encode_header(sr, B_FALSE);
+		return (NT_STATUS_INVALID_PARAMETER);
+	}
+
+	/*
+	 * The compound reply buffer we sent is now gone.
+	 * Setup a new reply buffer for the caller.
 	 */
 	sr->smb2_hdr_flags |= SMB2_FLAGS_ASYNC_COMMAND;
-	sr->smb2_async_id = (uintptr_t)ar;
+	sr->smb2_async_id = SMB2_ASYNCID(sr);
+	sr->smb2_next_reply = 0;
+	MBC_FLUSH(&sr->reply);
+	ASSERT(sr->reply.max_bytes == sr->session->reply_max_bytes);
+	ASSERT(sr->reply.chain_offset == 0);
+	sr->smb2_reply_hdr = 0;
+	(void) smb2_encode_header(sr, B_FALSE);
+
+	return (NT_STATUS_SUCCESS);
+}
 
-	ar->ar_func = async_func;
-	ar->ar_cmd_hdr = sr->smb2_cmd_hdr;
-	ar->ar_cmd_len = sr->smb_data.max_bytes - sr->smb2_cmd_hdr;
+int
+smb3_decode_tform_header(smb_request_t *sr)
+{
+	uint16_t flags;
+	int rc;
+	uint32_t protocolid;
+
+	rc = smb_mbc_decodef(
+	    &sr->command, "l16c16cl..wq",
+	    &protocolid,	/*  l  */
+	    sr->smb2_sig,	/* 16c */
+	    sr->nonce,	/* 16c */
+	    &sr->msgsize,	/* l */
+	    /* reserved	  .. */
+	    &flags,		/* w */
+	    &sr->smb3_tform_ssnid); /* q */
+	if (rc)
+		return (rc);
+
+	ASSERT3U(protocolid, ==, SMB3_ENCRYPTED_MAGIC);
 
-	ar->ar_cmd_code = sr->smb2_cmd_code;
-	ar->ar_hdr_flags = sr->smb2_hdr_flags;
-	ar->ar_messageid = sr->smb2_messageid;
-	ar->ar_pid = sr->smb_pid;
-	ar->ar_tid = sr->smb_tid;
-	ar->ar_uid = sr->smb_uid;
+	if (flags != 1) {
+#ifdef DEBUG
+		cmn_err(CE_NOTE, "flags field not 1: %x", flags);
+#endif
+		return (-1);
+	}
+
+	/*
+	 * MsgSize is the amount of data the client tell us to decrypt.
+	 * Make sure this value is not too big and not too small.
+	 */
+	if (sr->msgsize < SMB2_HDR_SIZE ||
+	    sr->msgsize > sr->session->cmd_max_bytes ||
+	    sr->msgsize > sr->command.max_bytes - SMB3_TFORM_HDR_SIZE)
+		return (-1);
 
-	sr->sr_async_req = ar;
+	return (rc);
+}
+
+int
+smb3_encode_tform_header(smb_request_t *sr, struct mbuf_chain *mbc)
+{
+	int rc;
 
-	/* Interim responses are NOT signed. */
-	sr->smb2_hdr_flags &= ~SMB2_FLAGS_SIGNED;
+	/* Signature and Nonce are added in smb3_encrypt_sr */
+	rc = smb_mbc_encodef(
+	    mbc, "l32.lwwq",
+	    SMB3_ENCRYPTED_MAGIC, /* l */
+	    /* signature(16), nonce(16) 32. */
+	    sr->msgsize,	/* l */
+	    0, /* reserved	   w */
+	    1, /* flags		   w */
+	    sr->smb3_tform_ssnid); /* q */
 
-	return (NT_STATUS_PENDING);
+	return (rc);
 }
 
 int
 smb2_decode_header(smb_request_t *sr)
 {
-	uint64_t ssnid;
 	uint32_t pid, tid;
 	uint16_t hdr_len;
 	int rc;
@@ -1081,7 +1366,7 @@
 	    &sr->smb2_messageid,	/* q */
 	    &pid,			/* l */
 	    &tid,			/* l */
-	    &ssnid,			/* q */
+	    &sr->smb2_ssnid,		/* q */
 	    sr->smb2_sig);		/* 16c */
 	if (rc)
 		return (rc);
@@ -1089,12 +1374,13 @@
 	if (hdr_len != SMB2_HDR_SIZE)
 		return (-1);
 
-	sr->smb_uid = (uint16_t)ssnid;	/* XXX wide UIDs */
-
 	if (sr->smb2_hdr_flags & SMB2_FLAGS_ASYNC_COMMAND) {
 		sr->smb2_async_id = pid |
 		    ((uint64_t)tid) << 32;
+		sr->smb_pid = 0;
+		sr->smb_tid = 0;
 	} else {
+		sr->smb2_async_id = 0;
 		sr->smb_pid = pid;
 		sr->smb_tid = (uint16_t)tid; /* XXX wide TIDs */
 	}
@@ -1105,9 +1391,7 @@
 int
 smb2_encode_header(smb_request_t *sr, boolean_t overwrite)
 {
-	uint64_t ssnid = sr->smb_uid;
 	uint64_t pid_tid_aid; /* pid+tid, or async id */
-	uint32_t reply_hdr_flags;
 	int rc;
 
 	if (sr->smb2_hdr_flags & SMB2_FLAGS_ASYNC_COMMAND) {
@@ -1116,7 +1400,6 @@
 		pid_tid_aid = sr->smb_pid |
 		    ((uint64_t)sr->smb_tid) << 32;
 	}
-	reply_hdr_flags = sr->smb2_hdr_flags | SMB2_FLAGS_SERVER_TO_REDIR;
 
 	if (overwrite) {
 		rc = smb_mbc_poke(&sr->reply,
@@ -1127,11 +1410,11 @@
 		    sr->smb2_status,		/* l */
 		    sr->smb2_cmd_code,		/* w */
 		    sr->smb2_credit_response,	/* w */
-		    reply_hdr_flags,		/* l */
+		    sr->smb2_hdr_flags,		/* l */
 		    sr->smb2_next_reply,	/* l */
 		    sr->smb2_messageid,		/* q */
 		    pid_tid_aid,		/* q */
-		    ssnid,			/* q */
+		    sr->smb2_ssnid,		/* q */
 		    sr->smb2_sig);		/* 16c */
 	} else {
 		rc = smb_mbc_encodef(&sr->reply,
@@ -1141,11 +1424,11 @@
 		    sr->smb2_status,		/* l */
 		    sr->smb2_cmd_code,		/* w */
 		    sr->smb2_credit_response,	/* w */
-		    reply_hdr_flags,		/* l */
+		    sr->smb2_hdr_flags,		/* l */
 		    sr->smb2_next_reply,	/* l */
 		    sr->smb2_messageid,		/* q */
 		    pid_tid_aid,		/* q */
-		    ssnid,			/* q */
+		    sr->smb2_ssnid,		/* q */
 		    sr->smb2_sig);		/* 16c */
 	}
 
@@ -1155,9 +1438,62 @@
 void
 smb2_send_reply(smb_request_t *sr)
 {
+	struct mbuf_chain enc_reply;
+	smb_session_t *session = sr->session;
+	void *tmpbuf;
+	size_t buflen;
+	struct mbuf_chain tmp;
 
-	if (smb_session_send(sr->session, 0, &sr->reply) == 0)
-		sr->reply.chain = 0;
+	/*
+	 * [MS-SMB2] 3.3.4.1.4 Encrypting the Message
+	 *
+	 * When the connection supports encryption and the dialect
+	 * is 3.x, encrypt if:
+	 * - The request was encrypted OR
+	 * - The cmd is not SESSION_SETUP or NEGOTIATE AND
+	 * -- Session.EncryptData is TRUE OR
+	 * -- The cmd is not TREE_CONNECT AND
+	 * --- Tree.EncryptData is TRUE
+	 *
+	 * This boils down to sr->tform_ssn != NULL, and the rest
+	 * is enforced when tform_ssn is set.
+	 */
+
+	if ((session->capabilities & SMB2_CAP_ENCRYPTION) == 0 ||
+	    sr->tform_ssn == NULL) {
+		if (smb_session_send(sr->session, 0, &sr->reply) == 0)
+			sr->reply.chain = 0;
+		return;
+	}
+
+	sr->msgsize = sr->reply.chain_offset;
+	(void) MBC_SHADOW_CHAIN(&tmp, &sr->reply,
+	    0, sr->msgsize);
+
+	buflen = SMB3_TFORM_HDR_SIZE + sr->msgsize;
+
+	/* taken from smb_request_init_command_mbuf */
+	tmpbuf = kmem_alloc(buflen, KM_SLEEP);
+	MBC_ATTACH_BUF(&enc_reply, tmpbuf, buflen);
+	enc_reply.flags = 0;
+	enc_reply.shadow_of = NULL;
+
+	if (smb3_encode_tform_header(sr, &enc_reply) != 0) {
+		cmn_err(CE_WARN, "couldn't encode transform header");
+		goto errout;
+	}
+	if (smb3_encrypt_sr(sr, &tmp, &enc_reply) != 0) {
+		cmn_err(CE_WARN, "smb3 encryption failed");
+		goto errout;
+	}
+
+	if (smb_session_send(sr->session, 0, &enc_reply) == 0)
+		enc_reply.chain = 0;
+	return;
+
+errout:
+	kmem_free(tmpbuf, buflen);
+	smb_session_disconnect(sr->session);
 }
 
 /*
@@ -1265,7 +1601,8 @@
 		sr->smb_fid = (uint16_t)fid->temporal;
 		sr->fid_ofile = smb_ofile_lookup_by_fid(sr, sr->smb_fid);
 	}
-	if (sr->fid_ofile == NULL)
+	if (sr->fid_ofile == NULL ||
+	    sr->fid_ofile->f_persistid != fid->persistent)
 		return (NT_STATUS_FILE_CLOSED);
 
 	return (0);
@@ -1341,3 +1678,57 @@
 		}
 	}
 }
+
+/*
+ * Append new_sr to the postwork queue.  sr->smb2_cmd_code encodes
+ * the action that should be run by this sr.
+ *
+ * This queue is rarely used (and normally empty) so we're OK
+ * using a simple "walk to tail and insert" here.
+ */
+void
+smb2sr_append_postwork(smb_request_t *top_sr, smb_request_t *new_sr)
+{
+	smb_request_t *last_sr;
+
+	ASSERT(top_sr->session->dialect >= SMB_VERS_2_BASE);
+
+	last_sr = top_sr;
+	while (last_sr->sr_postwork != NULL)
+		last_sr = last_sr->sr_postwork;
+
+	last_sr->sr_postwork = new_sr;
+}
+
+/*
+ * Run any "post work" that was appended to the main SR while it
+ * was running.  This is called after the request has been sent
+ * for the main SR, and used in cases i.e. the oplock code, where
+ * we need to send something to the client only _after_ the main
+ * sr request has gone out.
+ */
+static void
+smb2sr_run_postwork(smb_request_t *top_sr)
+{
+	smb_request_t *post_sr;	/* the one we're running */
+	smb_request_t *next_sr;
+
+	while ((post_sr = top_sr->sr_postwork) != NULL) {
+		next_sr = post_sr->sr_postwork;
+		top_sr->sr_postwork = next_sr;
+		post_sr->sr_postwork = NULL;
+
+		post_sr->sr_worker = top_sr->sr_worker;
+		post_sr->sr_state = SMB_REQ_STATE_ACTIVE;
+
+		switch (post_sr->smb2_cmd_code) {
+		case SMB2_OPLOCK_BREAK:
+			smb_oplock_send_brk(post_sr);
+			break;
+		default:
+			ASSERT(0);
+		}
+		post_sr->sr_state = SMB_REQ_STATE_COMPLETED;
+		smb_request_free(post_sr);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/fs/smbsrv/smb2_durable.c	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,478 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+/*
+ * SMB2 Durable Handle support
+ */
+
+#include <sys/types.h>
+#include <sys/cmn_err.h>
+#include <sys/fcntl.h>
+#include <sys/nbmlock.h>
+#include <smbsrv/string.h>
+#include <smbsrv/smb_kproto.h>
+#include <smbsrv/smb_fsops.h>
+#include <smbsrv/smbinfo.h>
+#include <smbsrv/smb2_kproto.h>
+
+/* Windows default values from [MS-SMB2] */
+/*
+ * (times in seconds)
+ * resilient:
+ * MaxTimeout = 300 (win7+)
+ * if timeout > MaxTimeout, ERROR
+ * if timeout != 0, timeout = req.timeout
+ * if timeout == 0, timeout = (infinity) (Win7/w2k8r2)
+ * if timeout == 0, timeout = 120 (Win8+)
+ * v2:
+ * if timeout != 0, timeout = MIN(timeout, 300) (spec)
+ * if timeout != 0, timeout = timeout (win8/2k12)
+ * if timeout == 0, timeout = Share.CATimeout. \
+ *	if Share.CATimeout == 0, timeout = 60 (win8/w2k12)
+ * if timeout == 0, timeout = 180 (win8.1/w2k12r2)
+ * open.timeout = 60 (win8/w2k12r2) (i.e. we ignore the request)
+ * v1:
+ * open.timeout = 16 minutes
+ */
+
+uint32_t smb2_dh_def_timeout = 60 * MILLISEC;	/* mSec. */
+uint32_t smb2_dh_max_timeout = 300 * MILLISEC;	/* mSec. */
+
+uint32_t smb2_res_def_timeout = 120 * MILLISEC;	/* mSec. */
+uint32_t smb2_res_max_timeout = 300 * MILLISEC;	/* mSec. */
+
+/*
+ * smb_dh_should_save
+ *
+ * During session tear-down, decide whether to keep a durable handle.
+ *
+ * There are two cases where we save durable handles:
+ * 1. An SMB2 LOGOFF request was received
+ * 2. An unexpected disconnect from the client
+ *    Note: Specifying a PrevSessionID in session setup
+ *    is considered a disconnect (we just haven't learned about it yet)
+ * In every other case, we close durable handles.
+ *
+ * [MS-SMB2] 3.3.5.6 SMB2_LOGOFF
+ * [MS-SMB2] 3.3.7.1 Handling Loss of a Connection
+ *
+ * If any of the following are true, preserve for reconnect:
+ *
+ * - Open.IsResilient is TRUE.
+ *
+ * - Open.OplockLevel == SMB2_OPLOCK_LEVEL_BATCH and
+ *   Open.OplockState == Held, and Open.IsDurable is TRUE.
+ *
+ * - Open.OplockLevel == SMB2_OPLOCK_LEVEL_LEASE,
+ *   Lease.LeaseState SMB2_LEASE_HANDLE_CACHING,
+ *   Open.OplockState == Held, and Open.IsDurable is TRUE.
+ *
+ * - Open.IsPersistent is TRUE.
+ */
+boolean_t
+smb_dh_should_save(smb_ofile_t *of)
+{
+	ASSERT(MUTEX_HELD(&of->f_mutex));
+	ASSERT(of->dh_vers != SMB2_NOT_DURABLE);
+
+	if (of->f_user->preserve_opens == SMB2_DH_PRESERVE_NONE)
+		return (B_FALSE);
+
+	if (of->f_user->preserve_opens == SMB2_DH_PRESERVE_ALL)
+		return (B_TRUE);
+
+	switch (of->dh_vers) {
+	case SMB2_RESILIENT:
+		return (B_TRUE);
+
+	case SMB2_DURABLE_V2:
+		if (of->dh_persist)
+			return (B_TRUE);
+		/* FALLTHROUGH */
+	case SMB2_DURABLE_V1:
+		/* IS durable (v1 or v2) */
+		if ((of->f_oplock.og_state & (OPLOCK_LEVEL_BATCH |
+		    OPLOCK_LEVEL_CACHE_HANDLE)) != 0)
+			return (B_TRUE);
+		/* FALLTHROUGH */
+	case SMB2_NOT_DURABLE:
+	default:
+		break;
+	}
+
+	return (B_FALSE);
+}
+
+/*
+ * Requirements for ofile found during reconnect (MS-SMB2 3.3.5.9.7):
+ * - security descriptor must match provided descriptor
+ *
+ * If file is leased:
+ * - lease must be requested
+ * - client guid must match session guid
+ * - file name must match given name
+ * - lease key must match provided lease key
+ * If file is not leased:
+ * - Lease must not be requested
+ *
+ * dh_v2 only:
+ * - SMB2_DHANDLE_FLAG_PERSISTENT must be set if dh_persist is true
+ * - SMB2_DHANDLE_FLAG_PERSISTENT must not be set if dh_persist is false
+ * - desired access, share access, and create_options must be ignored
+ * - createguid must match
+ */
+static uint32_t
+smb2_dh_reconnect_checks(smb_request_t *sr, smb_ofile_t *of)
+{
+	smb_arg_open_t	*op = &sr->sr_open;
+	char *fname;
+
+	if (of->f_lease != NULL) {
+		if (bcmp(sr->session->clnt_uuid,
+		    of->f_lease->ls_clnt, 16) != 0)
+			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
+
+		if (op->op_oplock_level != SMB2_OPLOCK_LEVEL_LEASE)
+			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
+		if (bcmp(op->lease_key, of->f_lease->ls_key,
+		    SMB_LEASE_KEY_SZ) != 0)
+			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
+
+		/*
+		 * We're supposed to check the name is the same.
+		 * Not really necessary to do this, so just do
+		 * minimal effort (check last component)
+		 */
+		fname = strrchr(op->fqi.fq_path.pn_path, '\\');
+		if (fname != NULL)
+			fname++;
+		else
+			fname = op->fqi.fq_path.pn_path;
+		if (smb_strcasecmp(fname, of->f_node->od_name, 0) != 0) {
+#ifdef	DEBUG
+			cmn_err(CE_NOTE, "reconnect name <%s> of name <%s>",
+			    fname, of->f_node->od_name);
+#endif
+			return (NT_STATUS_INVALID_PARAMETER);
+		}
+	} else {
+		if (op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE)
+			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
+	}
+
+	if (op->dh_vers == SMB2_DURABLE_V2) {
+		boolean_t op_persist =
+		    ((op->dh_v2_flags & SMB2_DHANDLE_FLAG_PERSISTENT) != 0);
+		if (of->dh_persist != op_persist)
+			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
+		if (memcmp(op->create_guid, of->dh_create_guid, UUID_LEN))
+			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
+	}
+
+	if (!smb_is_same_user(sr->user_cr, of->f_cr))
+		return (NT_STATUS_ACCESS_DENIED);
+
+	return (NT_STATUS_SUCCESS);
+}
+
+/*
+ * [MS-SMB2] 3.3.5.9.7 and 3.3.5.9.12 (durable reconnect v1/v2)
+ *
+ * Looks up an ofile on the server's sv_dh_list by the persistid.
+ * If found, it validates the request.
+ * (see smb2_dh_reconnect_checks() for details)
+ * If the checks are passed, add it onto the new tree's list.
+ *
+ * Note that the oplock break code path can get to an ofile via the node
+ * ofile list.  It starts with a ref taken in smb_ofile_hold_olbrk, which
+ * waits if the ofile is found in state RECONNECT.  That wait happens with
+ * the node ofile list lock held as reader, and the oplock mutex held.
+ * Implications of that are: While we're in state RECONNECT, we shoud NOT
+ * block (at least, not for long) and must not try to enter any of the
+ * node ofile list lock or oplock mutex.  Thankfully, we don't need to
+ * enter those while reclaiming an orphaned ofile.
+ */
+uint32_t
+smb2_dh_reconnect(smb_request_t *sr)
+{
+	smb_arg_open_t	*op = &sr->sr_open;
+	smb_tree_t *tree = sr->tid_tree;
+	smb_ofile_t *of;
+	cred_t *old_cr;
+	uint32_t status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+	uint16_t fid = 0;
+
+	if (smb_idpool_alloc(&tree->t_fid_pool, &fid))
+		return (NT_STATUS_TOO_MANY_OPENED_FILES);
+
+	/* Find orphaned handle. */
+	of = smb_ofile_lookup_by_persistid(sr, op->dh_fileid.persistent);
+	if (of == NULL)
+		goto errout;
+
+	mutex_enter(&of->f_mutex);
+	if (of->f_state != SMB_OFILE_STATE_ORPHANED) {
+		mutex_exit(&of->f_mutex);
+		goto errout;
+	}
+
+	status = smb2_dh_reconnect_checks(sr, of);
+	if (status != NT_STATUS_SUCCESS) {
+		mutex_exit(&of->f_mutex);
+		goto errout;
+	}
+
+	/*
+	 * Note: cv_broadcast(&of->f_cv) when we're
+	 * done messing around in this state.
+	 * See: smb_ofile_hold_olbrk()
+	 */
+	of->f_state = SMB_OFILE_STATE_RECONNECT;
+	mutex_exit(&of->f_mutex);
+
+	/*
+	 * At this point, we should be the only thread with a ref on the
+	 * ofile, and the RECONNECT state should prevent new refs from
+	 * being granted, or other durable threads from observing or
+	 * reclaiming it. Put this ofile in the new tree, similar to
+	 * the last part of smb_ofile_open.
+	 */
+
+	old_cr = of->f_cr;
+	of->f_cr = sr->user_cr;
+	crhold(of->f_cr);
+	crfree(old_cr);
+
+	of->f_session = sr->session; /* hold is via user and tree */
+	smb_user_hold_internal(sr->uid_user);
+	of->f_user = sr->uid_user;
+	smb_tree_hold_internal(tree);
+	of->f_tree = tree;
+	of->f_fid = fid;
+
+	smb_llist_enter(&tree->t_ofile_list, RW_WRITER);
+	smb_llist_insert_tail(&tree->t_ofile_list, of);
+	smb_llist_exit(&tree->t_ofile_list);
+	atomic_inc_32(&tree->t_open_files);
+	atomic_inc_32(&sr->session->s_file_cnt);
+
+	/*
+	 * The ofile is now in the caller's session & tree.
+	 *
+	 * In case smb_ofile_hold or smb_oplock_send_brk() are
+	 * waiting for state RECONNECT to complete, wakeup.
+	 */
+	mutex_enter(&of->f_mutex);
+	of->dh_expire_time = 0;
+	of->f_state = SMB_OFILE_STATE_OPEN;
+	cv_broadcast(&of->f_cv);
+	mutex_exit(&of->f_mutex);
+
+	/*
+	 * The ofile is now visible in the new session.
+	 * From here, this is similar to the last part of
+	 * smb_common_open().
+	 */
+	op->fqi.fq_fattr.sa_mask = SMB_AT_ALL;
+	(void) smb_node_getattr(sr, of->f_node, zone_kcred(), of,
+	    &op->fqi.fq_fattr);
+
+	/*
+	 * Set up the fileid and dosattr in open_param for response
+	 */
+	op->fileid = op->fqi.fq_fattr.sa_vattr.va_nodeid;
+	op->dattr = op->fqi.fq_fattr.sa_dosattr;
+
+	/*
+	 * Set up the file type in open_param for the response
+	 * The ref. from ofile lookup is "given" to fid_ofile.
+	 */
+	op->ftype = SMB_FTYPE_DISK;
+	sr->smb_fid = of->f_fid;
+	sr->fid_ofile = of;
+
+	if (smb_node_is_file(of->f_node)) {
+		op->dsize = op->fqi.fq_fattr.sa_vattr.va_size;
+	} else {
+		/* directory or symlink */
+		op->dsize = 0;
+	}
+
+	op->create_options = 0; /* no more modifications wanted */
+	op->action_taken = SMB_OACT_OPENED;
+	return (NT_STATUS_SUCCESS);
+
+errout:
+	if (of != NULL)
+		smb_ofile_release(of);
+	if (fid != 0)
+		smb_idpool_free(&tree->t_fid_pool, fid);
+
+	return (status);
+}
+
+/*
+ * Durable handle expiration
+ * ofile state is _EXPIRED
+ */
+static void
+smb2_dh_expire(void *arg)
+{
+	smb_ofile_t *of = (smb_ofile_t *)arg;
+
+	smb_ofile_close(of, 0);
+	smb_ofile_release(of);
+}
+
+void
+smb2_durable_timers(smb_server_t *sv)
+{
+	smb_hash_t *hash;
+	smb_llist_t *bucket;
+	smb_ofile_t *of;
+	hrtime_t now;
+	int i;
+
+	hash = sv->sv_persistid_ht;
+	now = gethrtime();
+
+	for (i = 0; i < hash->num_buckets; i++) {
+		bucket = &hash->buckets[i].b_list;
+		smb_llist_enter(bucket, RW_READER);
+		for (of = smb_llist_head(bucket);
+		    of != NULL;
+		    of = smb_llist_next(bucket, of)) {
+			SMB_OFILE_VALID(of);
+
+			/*
+			 * Check outside the mutex first to avoid some
+			 * mutex_enter work in this loop.  If the state
+			 * changes under foot, the worst that happens
+			 * is we either enter the mutex when we might
+			 * not have needed to, or we miss some DH in
+			 * this pass and get it on the next.
+			 */
+			if (of->f_state != SMB_OFILE_STATE_ORPHANED)
+				continue;
+
+			mutex_enter(&of->f_mutex);
+			/* STATE_ORPHANED implies dh_expire_time != 0 */
+			if (of->f_state == SMB_OFILE_STATE_ORPHANED &&
+			    of->dh_expire_time <= now) {
+				of->f_state = SMB_OFILE_STATE_EXPIRED;
+				/* inline smb_ofile_hold_internal() */
+				of->f_refcnt++;
+				smb_llist_post(bucket, of, smb2_dh_expire);
+			}
+			mutex_exit(&of->f_mutex);
+		}
+		smb_llist_exit(bucket);
+	}
+}
+
+/*
+ * Clean out durable handles during shutdown.
+ * Like, smb2_durable_timers but expire all,
+ * and make sure the hash buckets are empty.
+ */
+void
+smb2_dh_shutdown(smb_server_t *sv)
+{
+	smb_hash_t *hash;
+	smb_llist_t *bucket;
+	smb_ofile_t *of;
+	int i;
+
+	hash = sv->sv_persistid_ht;
+
+	for (i = 0; i < hash->num_buckets; i++) {
+		bucket = &hash->buckets[i].b_list;
+		smb_llist_enter(bucket, RW_READER);
+		of = smb_llist_head(bucket);
+		while (of != NULL) {
+			SMB_OFILE_VALID(of);
+			mutex_enter(&of->f_mutex);
+
+			switch (of->f_state) {
+			case SMB_OFILE_STATE_ORPHANED:
+				of->f_state = SMB_OFILE_STATE_EXPIRED;
+				/* inline smb_ofile_hold_internal() */
+				of->f_refcnt++;
+				smb_llist_post(bucket, of, smb2_dh_expire);
+				break;
+			default:
+				break;
+			}
+			mutex_exit(&of->f_mutex);
+			of = smb_llist_next(bucket, of);
+		}
+		smb_llist_exit(bucket);
+	}
+
+#ifdef	DEBUG
+	for (i = 0; i < hash->num_buckets; i++) {
+		bucket = &hash->buckets[i].b_list;
+		smb_llist_enter(bucket, RW_READER);
+		of = smb_llist_head(bucket);
+		while (of != NULL) {
+			SMB_OFILE_VALID(of);
+			cmn_err(CE_NOTE, "dh_shutdown leaked of=%p",
+			    (void *)of);
+			of = smb_llist_next(bucket, of);
+		}
+		smb_llist_exit(bucket);
+	}
+#endif	// DEBUG
+}
+
+uint32_t
+smb2_fsctl_resiliency(smb_request_t *sr, smb_fsctl_t *fsctl)
+{
+	uint32_t timeout;
+	smb_ofile_t *of = sr->fid_ofile;
+
+	/*
+	 * Note: The spec does not explicitly prohibit resilient directories
+	 * the same way it prohibits durable directories. We prohibit them
+	 * anyway as a simplifying assumption, as there doesn't seem to be
+	 * much use for it. (HYPER-V only seems to use it on files anyway)
+	 */
+	if (fsctl->InputCount < 8 || !smb_node_is_file(of->f_node))
+		return (NT_STATUS_INVALID_PARAMETER);
+
+	(void) smb_mbc_decodef(fsctl->in_mbc, "l4.",
+	    &timeout); /* milliseconds */
+
+	if (smb2_enable_dh == 0)
+		return (NT_STATUS_NOT_SUPPORTED);
+
+	/*
+	 * The spec wants us to return INVALID_PARAMETER if the timeout
+	 * is too large, but we have no way of informing the client
+	 * what an appropriate timeout is, so just set the timeout to
+	 * our max and return SUCCESS.
+	 */
+	if (timeout == 0)
+		timeout = smb2_res_def_timeout;
+	if (timeout > smb2_res_max_timeout)
+		timeout = smb2_res_max_timeout;
+
+	mutex_enter(&of->f_mutex);
+	of->dh_vers = SMB2_RESILIENT;
+	of->dh_timeout_offset = MSEC2NSEC(timeout);
+	mutex_exit(&of->f_mutex);
+
+	return (NT_STATUS_SUCCESS);
+}
--- a/usr/src/uts/common/fs/smbsrv/smb2_ioctl.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_ioctl.c	Wed Jun 12 22:46:54 2019 +0200
@@ -275,8 +275,7 @@
 	{ FSCTL_SRV_COPYCHUNK_WRITE,	0,	smb2_fsctl_notsup },
 	{ FSCTL_SRV_READ_HASH,		0,	smb2_fsctl_notsup },
 
-	{ FSCTL_LMR_REQUEST_RESILIENCY,
-	    ITF_NO_FID,		smb2_fsctl_notsup },
+	{ FSCTL_LMR_REQUEST_RESILIENCY,	0,	smb2_fsctl_resiliency },
 	{ FSCTL_QUERY_NETWORK_INTERFACE_INFO,
 	    ITF_NO_FID,		smb2_fsctl_notsup },
 	{ FSCTL_VALIDATE_NEGOTIATE_INFO,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/fs/smbsrv/smb2_lease.c	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,720 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+/*
+ * Dispatch function for SMB2_OPLOCK_BREAK
+ */
+
+#include <smbsrv/smb2_kproto.h>
+#include <smbsrv/smb_oplock.h>
+
+/* StructSize for the two "break" message formats. */
+#define	SSZ_OPLOCK	24
+#define	SSZ_LEASE_ACK	36
+#define	SSZ_LEASE_BRK	44
+
+#define	NODE_FLAGS_DELETING	(NODE_FLAGS_DELETE_ON_CLOSE |\
+				NODE_FLAGS_DELETE_COMMITTED)
+
+static const char lease_zero[UUID_LEN] = { 0 };
+
+static kmem_cache_t	*smb_lease_cache = NULL;
+
+void
+smb2_lease_init()
+{
+	if (smb_lease_cache != NULL)
+		return;
+
+	smb_lease_cache = kmem_cache_create("smb_lease_cache",
+	    sizeof (smb_lease_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
+}
+
+void
+smb2_lease_fini()
+{
+	if (smb_lease_cache != NULL) {
+		kmem_cache_destroy(smb_lease_cache);
+		smb_lease_cache = NULL;
+	}
+}
+
+static void
+smb2_lease_hold(smb_lease_t *ls)
+{
+	mutex_enter(&ls->ls_mutex);
+	ls->ls_refcnt++;
+	mutex_exit(&ls->ls_mutex);
+}
+
+void
+smb2_lease_rele(smb_lease_t *ls)
+{
+	smb_llist_t *bucket;
+
+	mutex_enter(&ls->ls_mutex);
+	ls->ls_refcnt--;
+	if (ls->ls_refcnt != 0) {
+		mutex_exit(&ls->ls_mutex);
+		return;
+	}
+	mutex_exit(&ls->ls_mutex);
+
+	/*
+	 * Get the list lock, then re-check the refcnt
+	 * and if it's still zero, unlink & destroy.
+	 */
+	bucket = ls->ls_bucket;
+	smb_llist_enter(bucket, RW_WRITER);
+
+	mutex_enter(&ls->ls_mutex);
+	if (ls->ls_refcnt == 0)
+		smb_llist_remove(bucket, ls);
+	mutex_exit(&ls->ls_mutex);
+
+	if (ls->ls_refcnt == 0) {
+		mutex_destroy(&ls->ls_mutex);
+		kmem_cache_free(smb_lease_cache, ls);
+	}
+
+	smb_llist_exit(bucket);
+}
+
+/*
+ * Compute a hash from a uuid
+ * Based on mod_hash_bystr()
+ */
+static uint_t
+smb_hash_uuid(const uint8_t *uuid)
+{
+	char *k = (char *)uuid;
+	uint_t hash = 0;
+	uint_t g;
+	int i;
+
+	ASSERT(k);
+	for (i = 0; i < UUID_LEN; i++) {
+		hash = (hash << 4) + k[i];
+		if ((g = (hash & 0xf0000000)) != 0) {
+			hash ^= (g >> 24);
+			hash ^= g;
+		}
+	}
+	return (hash);
+}
+
+/*
+ * Add or update a lease table entry for a new ofile.
+ * (in the per-session lease table)
+ * See [MS-SMB2] 3.3.5.9.8
+ * Handling the SMB2_CREATE_REQUEST_LEASE Create Context
+ */
+uint32_t
+smb2_lease_create(smb_request_t *sr)
+{
+	smb_arg_open_t *op = &sr->arg.open;
+	uint8_t *key = op->lease_key;
+	uint8_t *clnt = sr->session->clnt_uuid;
+	smb_ofile_t *of = sr->fid_ofile;
+	smb_hash_t *ht = sr->sr_server->sv_lease_ht;
+	smb_llist_t *bucket;
+	smb_lease_t *lease;
+	smb_lease_t *newlease;
+	size_t hashkey;
+	uint32_t status = NT_STATUS_INVALID_PARAMETER;
+
+	if (bcmp(key, lease_zero, UUID_LEN) == 0)
+		return (status);
+
+	/*
+	 * Find or create, and add a ref for the new ofile.
+	 */
+	hashkey = smb_hash_uuid(key);
+	hashkey &= (ht->num_buckets - 1);
+	bucket = &ht->buckets[hashkey].b_list;
+
+	newlease = kmem_cache_alloc(smb_lease_cache, KM_SLEEP);
+	bzero(newlease, sizeof (smb_lease_t));
+	mutex_init(&newlease->ls_mutex, NULL, MUTEX_DEFAULT, NULL);
+	newlease->ls_bucket = bucket;
+	newlease->ls_node = of->f_node;
+	newlease->ls_refcnt = 1;
+	newlease->ls_epoch = op->lease_epoch;
+	newlease->ls_version = op->lease_version;
+	bcopy(key, newlease->ls_key, UUID_LEN);
+	bcopy(clnt, newlease->ls_clnt, UUID_LEN);
+
+	smb_llist_enter(bucket, RW_WRITER);
+	for (lease = smb_llist_head(bucket); lease != NULL;
+	    lease = smb_llist_next(bucket, lease)) {
+		/*
+		 * Looking for this lease ID, on a node
+		 * that's not being deleted.
+		 */
+		if (bcmp(lease->ls_key, key, UUID_LEN) == 0 &&
+		    bcmp(lease->ls_clnt, clnt, UUID_LEN) == 0 &&
+		    (lease->ls_node->flags & NODE_FLAGS_DELETING) == 0)
+			break;
+	}
+	if (lease != NULL) {
+		/*
+		 * Found existing lease.  Make sure it refers to
+		 * the same node...
+		 */
+		if (lease->ls_node == of->f_node) {
+			smb2_lease_hold(lease);
+		} else {
+			/* Same lease ID, different node! */
+#ifdef DEBUG
+			cmn_err(CE_NOTE, "new lease on node %p (%s) "
+			    "conflicts with existing node %p (%s)",
+			    (void *) of->f_node,
+			    of->f_node->od_name,
+			    (void *) lease->ls_node,
+			    lease->ls_node->od_name);
+#endif
+			DTRACE_PROBE2(dup_lease, smb_request_t, sr,
+			    smb_lease_t, lease);
+			lease = NULL; /* error */
+		}
+	} else {
+		lease = newlease;
+		smb_llist_insert_head(bucket, lease);
+		newlease = NULL; /* don't free */
+	}
+	smb_llist_exit(bucket);
+
+	if (newlease != NULL) {
+		mutex_destroy(&newlease->ls_mutex);
+		kmem_cache_free(smb_lease_cache, newlease);
+	}
+
+	if (lease != NULL) {
+		of->f_lease = lease;
+		status = NT_STATUS_SUCCESS;
+	}
+
+	return (status);
+}
+
+/*
+ * Find the lease for a given: client_uuid, lease_key
+ * Returns the lease with a new ref.
+ */
+smb_lease_t *
+smb2_lease_lookup(smb_server_t *sv, uint8_t *clnt_uuid, uint8_t *lease_key)
+{
+	smb_hash_t *ht = sv->sv_lease_ht;
+	smb_llist_t *bucket;
+	smb_lease_t *lease;
+	size_t hashkey;
+
+	hashkey = smb_hash_uuid(lease_key);
+	hashkey &= (ht->num_buckets - 1);
+	bucket = &ht->buckets[hashkey].b_list;
+
+	smb_llist_enter(bucket, RW_READER);
+	lease = smb_llist_head(bucket);
+	while (lease != NULL) {
+		if (bcmp(lease->ls_key, lease_key, UUID_LEN) == 0 &&
+		    bcmp(lease->ls_clnt, clnt_uuid, UUID_LEN) == 0) {
+			smb2_lease_hold(lease);
+			break;
+		}
+		lease = smb_llist_next(bucket, lease);
+	}
+	smb_llist_exit(bucket);
+
+	return (lease);
+}
+
+/*
+ * Find an smb_ofile_t in the current tree that shares the
+ * specified lease and has some oplock breaking flags set.
+ * If lease not found, NT_STATUS_OBJECT_NAME_NOT_FOUND.
+ * If ofile not breaking NT_STATUS_UNSUCCESSFUL.
+ * On success, ofile (held) in sr->fid_ofile.
+ */
+static uint32_t
+find_breaking_ofile(smb_request_t *sr, uint8_t *lease_key)
+{
+	smb_tree_t	*tree = sr->tid_tree;
+	smb_lease_t	*lease;
+	smb_llist_t	*of_list;
+	smb_ofile_t	*o;
+	uint32_t	status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+
+	SMB_TREE_VALID(tree);
+	of_list = &tree->t_ofile_list;
+
+	smb_llist_enter(of_list, RW_READER);
+	for (o = smb_llist_head(of_list); o != NULL;
+	    o = smb_llist_next(of_list, o)) {
+
+		ASSERT(o->f_magic == SMB_OFILE_MAGIC);
+		ASSERT(o->f_tree == tree);
+
+		if ((lease = o->f_lease) == NULL)
+			continue; // no lease
+
+		if (bcmp(lease->ls_key, lease_key, UUID_LEN) != 0)
+			continue; // wrong lease
+
+		/*
+		 * Now we know the lease exists, so if we don't
+		 * find an ofile with breaking flags, return:
+		 */
+		status = NT_STATUS_UNSUCCESSFUL;
+
+		if (o->f_oplock.og_breaking == 0)
+			continue; // not breaking
+
+		/* Found breaking ofile. */
+		if (smb_ofile_hold(o)) {
+			sr->fid_ofile = o;
+			status = NT_STATUS_SUCCESS;
+			break;
+		}
+	}
+	smb_llist_exit(of_list);
+
+	return (status);
+}
+
+/*
+ * This is called by smb2_oplock_break_ack when the struct size
+ * indicates this is a lease break (SZ_LEASE).  See:
+ * [MS-SMB2] 3.3.5.22.2 Processing a Lease Acknowledgment
+ */
+smb_sdrc_t
+smb2_lease_break_ack(smb_request_t *sr)
+{
+	smb_lease_t *lease;
+	smb_ofile_t *ofile;
+	uint8_t LeaseKey[UUID_LEN];
+	uint32_t LeaseState;
+	uint32_t LeaseBreakTo;
+	uint32_t status;
+	int rc = 0;
+
+	if (sr->session->dialect < SMB_VERS_2_1)
+		return (SDRC_ERROR);
+
+	/*
+	 * Decode an SMB2 Lease Acknowldgement
+	 * [MS-SMB2] 2.2.24.2
+	 * Note: Struct size decoded by caller.
+	 */
+	rc = smb_mbc_decodef(
+	    &sr->smb_data, "6.#cl8.",
+	    /* reserved		  6. */
+	    UUID_LEN,		/* # */
+	    LeaseKey,		/* c */
+	    &LeaseState);	/* l */
+	    /* duration		  8. */
+	if (rc != 0)
+		return (SDRC_ERROR);
+
+	status = find_breaking_ofile(sr, LeaseKey);
+
+	DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr);
+	if (status != 0)
+		goto errout;
+
+	/* Success, so have sr->fid_ofile and lease */
+	ofile = sr->fid_ofile;
+	lease = ofile->f_lease;
+
+	/*
+	 * Process the lease break ack.
+	 *
+	 * If the new LeaseState has any bits in excess of
+	 * the lease state we sent in the break, error...
+	 */
+	LeaseBreakTo = (lease->ls_breaking >> BREAK_SHIFT) &
+	    OPLOCK_LEVEL_CACHE_MASK;
+	if ((LeaseState & ~LeaseBreakTo) != 0) {
+		status = NT_STATUS_REQUEST_NOT_ACCEPTED;
+		goto errout;
+	}
+
+	ofile->f_oplock.og_breaking = 0;
+	lease->ls_breaking = 0;
+
+	LeaseState |= OPLOCK_LEVEL_GRANULAR;
+	status = smb_oplock_ack_break(sr, ofile, &LeaseState);
+	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+		(void) smb2sr_go_async(sr);
+		(void) smb_oplock_wait_break(ofile->f_node, 0);
+		status = NT_STATUS_SUCCESS;
+	}
+
+	ofile->f_oplock.og_state = LeaseState;
+	lease->ls_state = LeaseState &
+	    OPLOCK_LEVEL_CACHE_MASK;
+
+errout:
+	sr->smb2_status = status;
+	DTRACE_SMB2_DONE(op__OplockBreak, smb_request_t *, sr);
+	if (status) {
+		smb2sr_put_error(sr, status);
+		return (SDRC_SUCCESS);
+	}
+
+	/*
+	 * Encode an SMB2 Lease Ack. response
+	 * [MS-SMB2] 2.2.25.2
+	 */
+	LeaseState &= OPLOCK_LEVEL_CACHE_MASK;
+	(void) smb_mbc_encodef(
+	    &sr->reply, "w6.#cl8.",
+	    SSZ_LEASE_ACK,	/* w */
+	    /* reserved		  6. */
+	    UUID_LEN,		/* # */
+	    LeaseKey,		/* c */
+	    LeaseState);	/* l */
+	    /* duration		  8. */
+
+	return (SDRC_SUCCESS);
+
+}
+
+/*
+ * Compose an SMB2 Lease Break Notification packet, including
+ * the SMB2 header and everything, in sr->reply.
+ * The caller will send it and free the request.
+ *
+ * [MS-SMB2] 2.2.23.2 Lease Break Notification
+ */
+void
+smb2_lease_break_notification(smb_request_t *sr, uint32_t NewLevel,
+    boolean_t AckReq)
+{
+	smb_ofile_t *ofile = sr->fid_ofile;
+	smb_oplock_grant_t *og = &ofile->f_oplock;
+	smb_lease_t *ls = ofile->f_lease;
+	uint32_t oldcache;
+	uint32_t newcache;
+	uint16_t Epoch;
+	uint16_t Flags;
+
+	/*
+	 * Convert internal level to SMB2
+	 */
+	oldcache = og->og_state & OPLOCK_LEVEL_CACHE_MASK;
+	newcache = NewLevel & OPLOCK_LEVEL_CACHE_MASK;
+	if (ls->ls_version < 2)
+		Epoch = 0;
+	else
+		Epoch = ls->ls_epoch;
+
+	/*
+	 * SMB2 Header
+	 */
+	sr->smb2_cmd_code = SMB2_OPLOCK_BREAK;
+	sr->smb2_hdr_flags = SMB2_FLAGS_SERVER_TO_REDIR;
+	sr->smb_tid = 0;
+	sr->smb_pid = 0;
+	sr->smb2_ssnid = 0;
+	sr->smb2_messageid = UINT64_MAX;
+	(void) smb2_encode_header(sr, B_FALSE);
+
+	/*
+	 * SMB2 Oplock Break, variable part
+	 *
+	 * [MS-SMB2] says the current lease state preceeds the
+	 * new lease state, but that looks like an error...
+	 */
+	Flags = AckReq ? SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED : 0;
+	(void) smb_mbc_encodef(
+	    &sr->reply, "wwl#cll4.4.4.",
+	    SSZ_LEASE_BRK,		/* w */
+	    Epoch,			/* w */
+	    Flags,			/* l */
+	    SMB_LEASE_KEY_SZ,		/* # */
+	    ls->ls_key,			/* c */
+	    oldcache,		/* cur.st  l */
+	    newcache);		/* new.st  l */
+	    /* reserved (4.4.4.) */
+}
+
+/*
+ * Client has an open handle and requests a lease.
+ * Convert SMB2 lease request info in to internal form,
+ * call common oplock code, convert result to SMB2.
+ *
+ * If necessary, "go async" here.
+ */
+void
+smb2_lease_acquire(smb_request_t *sr)
+{
+	smb_arg_open_t *op = &sr->arg.open;
+	smb_ofile_t *ofile = sr->fid_ofile;
+	smb_lease_t *lease = ofile->f_lease;
+	uint32_t status = NT_STATUS_OPLOCK_NOT_GRANTED;
+	uint32_t have, want; /* lease flags */
+	boolean_t NewGrant = B_FALSE;
+
+	/* Only disk trees get oplocks. */
+	ASSERT((sr->tid_tree->t_res_type & STYPE_MASK) == STYPE_DISKTREE);
+
+	/*
+	 * Only plain files (for now).
+	 * Later, test SMB2_CAP_DIRECTORY_LEASING
+	 */
+	if (!smb_node_is_file(ofile->f_node)) {
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+		return;
+	}
+
+	if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_OPLOCKS)) {
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+		return;
+	}
+
+	/*
+	 * SMB2: Convert to internal form.
+	 * Caller should have setup the lease.
+	 */
+	ASSERT(op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE);
+	ASSERT(lease != NULL);
+	if (lease == NULL) {
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+		return;
+	}
+	op->op_oplock_state = OPLOCK_LEVEL_GRANULAR |
+	    (op->lease_state & CACHE_RWH);
+
+	/*
+	 * Tree options may force shared oplocks,
+	 * in which case we reduce the request.
+	 */
+	if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) {
+		op->op_oplock_state &= ~WRITE_CACHING;
+	}
+
+	/*
+	 * Disallow downgrade
+	 *
+	 * Note that open with a lease is not allowed to turn off
+	 * any cache rights.  If the client tries to "downgrade",
+	 * any bits, just return the existing lease cache bits.
+	 */
+	have = lease->ls_state & CACHE_RWH;
+	want = op->op_oplock_state & CACHE_RWH;
+	if ((have & ~want) != 0) {
+		op->op_oplock_state = have |
+		    OPLOCK_LEVEL_GRANULAR;
+		goto done;
+	}
+
+	/*
+	 * Handle oplock requests in three parts:
+	 *	a: Requests with WRITE_CACHING
+	 *	b: Requests with HANDLE_CACHING
+	 *	c: Requests with READ_CACHING
+	 * reducing the request before b and c.
+	 *
+	 * In each: first check if the lease grants the
+	 * (possibly reduced) request, in which case we
+	 * leave the lease unchanged and return what's
+	 * granted by the lease.  Otherwise, try to get
+	 * the oplock, and if the succeeds, wait for any
+	 * breaks, update the lease, and return.
+	 */
+
+	/*
+	 * Try exclusive (request is RW or RWH)
+	 */
+	if ((op->op_oplock_state & WRITE_CACHING) != 0) {
+		want = op->op_oplock_state & CACHE_RWH;
+		if (have == want)
+			goto done;
+
+		status = smb_oplock_request(sr, ofile,
+		    &op->op_oplock_state);
+		if (status == NT_STATUS_SUCCESS ||
+		    status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+			NewGrant = B_TRUE;
+			goto done;
+		}
+
+		/*
+		 * We did not get the exclusive oplock.
+		 *
+		 * There are odd rules about lease upgrade.
+		 * If the existing lease grants R and the
+		 * client fails to upgrade it to "RWH"
+		 * (presumably due to handle conflicts)
+		 * then just return the existing lease,
+		 * even though upgrade to RH would work.
+		 */
+		if (have != 0) {
+			op->op_oplock_state = have |
+			    OPLOCK_LEVEL_GRANULAR;
+			goto done;
+		}
+
+		/*
+		 * Keep trying without write.
+		 * Need to re-init op_oplock_state
+		 */
+		op->op_oplock_state = OPLOCK_LEVEL_GRANULAR |
+		    (op->lease_state & CACHE_RH);
+	}
+
+	/*
+	 * Try shared ("RH")
+	 */
+	if ((op->op_oplock_state & HANDLE_CACHING) != 0) {
+		want = op->op_oplock_state & CACHE_RWH;
+		if (have == want)
+			goto done;
+
+		status = smb_oplock_request(sr, ofile,
+		    &op->op_oplock_state);
+		if (status == NT_STATUS_SUCCESS ||
+		    status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+			NewGrant = B_TRUE;
+			goto done;
+		}
+
+		/*
+		 * We did not get "RH", probably because
+		 * ther were (old style) Level II oplocks.
+		 * Continue, try for just read.
+		 */
+		op->op_oplock_state = OPLOCK_LEVEL_GRANULAR |
+		    (op->lease_state & CACHE_R);
+	}
+
+	/*
+	 * Try shared ("R")
+	 */
+	if ((op->op_oplock_state & READ_CACHING) != 0) {
+		want = op->op_oplock_state & CACHE_RWH;
+		if (have == want)
+			goto done;
+
+		status = smb_oplock_request(sr, ofile,
+		    &op->op_oplock_state);
+		if (status == NT_STATUS_SUCCESS ||
+		    status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+			NewGrant = B_TRUE;
+			goto done;
+		}
+
+		/*
+		 * We did not get "R".
+		 * Fall into "none".
+		 */
+	}
+
+	/*
+	 * None of the above were able to get an oplock.
+	 * The lease has no caching rights, and we didn't
+	 * add any in this request.  Return it as-is.
+	 */
+	op->op_oplock_state = OPLOCK_LEVEL_GRANULAR;
+
+done:
+	if (NewGrant) {
+		/*
+		 * After a new oplock grant, the status return
+		 * may indicate we need to wait for breaks.
+		 */
+		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+			(void) smb2sr_go_async(sr);
+			(void) smb_oplock_wait_break(ofile->f_node, 0);
+			status = NT_STATUS_SUCCESS;
+		}
+		ASSERT(status == NT_STATUS_SUCCESS);
+
+		/*
+		 * Keep track of what we got (in ofile->f_oplock.og_state)
+		 * so we'll know what we had when sending a break later.
+		 * Also update the lease with the new oplock state.
+		 * Also track which ofile on the lease owns the oplock.
+		 * The og_dialect here is the oplock dialect, not the
+		 * SMB dialect.  Leasing, so SMB 2.1 (or later).
+		 */
+		ofile->f_oplock.og_dialect = SMB_VERS_2_1;
+		ofile->f_oplock.og_state = op->op_oplock_state;
+		mutex_enter(&lease->ls_mutex);
+		lease->ls_state = op->op_oplock_state & CACHE_RWH;
+		lease->ls_oplock_ofile = ofile;
+		lease->ls_epoch++;
+		mutex_exit(&lease->ls_mutex);
+	}
+
+	/*
+	 * Convert internal oplock state to SMB2
+	 */
+	op->op_oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+	op->lease_state = lease->ls_state & CACHE_RWH;
+	op->lease_flags = (lease->ls_breaking != 0) ?
+	    SMB2_LEASE_FLAG_BREAK_IN_PROGRESS : 0;
+	op->lease_epoch = lease->ls_epoch;
+	op->lease_version = lease->ls_version;
+}
+
+/*
+ * This ofile has a lease and is about to close.
+ * Called by smb_ofile_close when there's a lease.
+ *
+ * With leases, just one ofile on a lease owns the oplock.
+ * If an ofile with a lease is closed and it's the one that
+ * owns the oplock, try to move the oplock to another ofile
+ * on the same lease.
+ */
+void
+smb2_lease_ofile_close(smb_ofile_t *ofile)
+{
+	smb_node_t *node = ofile->f_node;
+	smb_lease_t *lease = ofile->f_lease;
+	smb_ofile_t *o;
+
+	/*
+	 * If this ofile was not the oplock owner for this lease,
+	 * we can leave things as they are.
+	 */
+	if (lease->ls_oplock_ofile != ofile)
+		return;
+
+	/*
+	 * Find another ofile to which we can move the oplock.
+	 * The ofile must be open and allow a new ref.
+	 */
+	smb_llist_enter(&node->n_ofile_list, RW_READER);
+	FOREACH_NODE_OFILE(node, o) {
+		if (o == ofile)
+			continue;
+		if (o->f_lease != lease)
+			continue;
+		/* If we can get a hold, use this ofile. */
+		if (smb_ofile_hold(o))
+			break;
+	}
+	if (o == NULL) {
+		/* Normal for last close on a lease. */
+		smb_llist_exit(&node->n_ofile_list);
+		return;
+	}
+	smb_oplock_move(node, ofile, o);
+	lease->ls_oplock_ofile = o;
+
+	smb_llist_exit(&node->n_ofile_list);
+	smb_ofile_release(o);
+}
--- a/usr/src/uts/common/fs/smbsrv/smb2_lock.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_lock.c	Wed Jun 12 22:46:54 2019 +0200
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -19,6 +19,12 @@
 
 #include <smbsrv/smb2_kproto.h>
 
+/*
+ * [MS-SMB2] 2.2.26 LockSequenceIndex, LockSequenceNumber.
+ */
+#define	SMB2_LSN_SHIFT	4
+#define	SMB2_LSN_MASK	0xf
+
 typedef struct SMB2_LOCK_ELEMENT {
 	uint64_t Offset;
 	uint64_t Length;
@@ -28,7 +34,10 @@
 
 static uint32_t smb2_unlock(smb_request_t *);
 static uint32_t smb2_locks(smb_request_t *);
-static smb_sdrc_t smb2_lock_async(smb_request_t *);
+static uint32_t smb2_lock_blocking(smb_request_t *);
+
+static boolean_t smb2_lock_chk_lockseq(smb_ofile_t *, uint32_t);
+static void smb2_lock_set_lockseq(smb_ofile_t *, uint32_t);
 
 /*
  * This is a somewhat arbitrary sanity limit on the length of the
@@ -79,6 +88,26 @@
 	}
 
 	/*
+	 * Check the LockSequence to determine whether a previous
+	 * lock request succeeded, but the client disconnected
+	 * (retaining a durable or resilient handle).  If so, this
+	 * is a lock "replay".  We'll find the lock sequence here
+	 * and return success without processing the lock again.
+	 */
+	if (sr->session->dialect < SMB_VERS_2_1)
+		LockSequence = 0;
+	if ((sr->session->dialect == SMB_VERS_2_1) &&
+	    sr->fid_ofile->dh_vers != SMB2_RESILIENT)
+		LockSequence = 0;
+	/* dialect 3.0 or later can always use LockSequence */
+
+	if (LockSequence != 0 &&
+	    smb2_lock_chk_lockseq(sr->fid_ofile, LockSequence)) {
+		status = NT_STATUS_SUCCESS;
+		goto errout;
+	}
+
+	/*
 	 * Parse the array of SMB2_LOCK_ELEMENT structs.
 	 * This array is free'd in smb_srm_fini.
 	 */
@@ -115,9 +144,7 @@
 
 errout:
 	sr->smb2_status = status;
-	if (status != NT_STATUS_PENDING) {
-		DTRACE_SMB2_DONE(op__Lock, smb_request_t *, sr);
-	}
+	DTRACE_SMB2_DONE(op__Lock, smb_request_t *, sr);
 
 	if (status) {
 		smb2sr_put_error(sr, status);
@@ -125,7 +152,7 @@
 	}
 
 	/*
-	 * Encode SMB2 Lock reply (sync)
+	 * Encode SMB2 Lock reply
 	 */
 	(void) smb_mbc_encodef(
 	    &sr->reply, "w..",
@@ -160,7 +187,9 @@
 		if (status != 0)
 			break;
 	}
-	(void) LockSequence; /* todo */
+	if (status == 0 && LockSequence != 0) {
+		smb2_lock_set_lockseq(sr->fid_ofile, LockSequence);
+	}
 
 	return (status);
 }
@@ -174,6 +203,7 @@
 	lock_elem_t *lk;
 	lock_elem_t *lvec = sr->arg.lock.lvec;
 	uint32_t LockCount = sr->arg.lock.lcnt;
+	uint32_t LockSequence = sr->arg.lock.lseq;
 	uint32_t i;
 	uint32_t ltype;
 	uint32_t pid = 0;	/* SMB2 ignores lock PIDs */
@@ -193,7 +223,7 @@
 			 * invalid parameter.
 			 */
 			if (i == 0 && LockCount == 1) {
-				status = smb2sr_go_async(sr, smb2_lock_async);
+				status = smb2_lock_blocking(sr);
 				return (status);
 			}
 			/* FALLTHROUGH */
@@ -236,19 +266,22 @@
 			    lk->Offset, lk->Length, pid);
 		}
 	}
+	if (status == 0 && LockSequence != 0)
+		smb2_lock_set_lockseq(sr->fid_ofile, LockSequence);
 
 	return (status);
 }
 
 /*
- * Async handler for blocking lock requests.
+ * Handler for blocking lock requests, which may "go async".
  * Always exactly one lock request here.
  */
-static smb_sdrc_t
-smb2_lock_async(smb_request_t *sr)
+static uint32_t
+smb2_lock_blocking(smb_request_t *sr)
 {
 	lock_elem_t *lk = sr->arg.lock.lvec;
 	uint32_t LockCount = sr->arg.lock.lcnt;
+	uint32_t LockSequence = sr->arg.lock.lseq;
 	uint32_t status;
 	uint32_t ltype;
 	uint32_t pid = 0;	/* SMB2 ignores lock PIDs */
@@ -268,28 +301,106 @@
 
 	default:
 		ASSERT(0);
-		status = NT_STATUS_INTERNAL_ERROR;
-		goto errout;
-	}
-
-	status = smb_lock_range(sr, lk->Offset, lk->Length, pid,
-	    ltype, timeout);
-
-errout:
-	sr->smb2_status = status;
-	DTRACE_SMB2_DONE(op__Lock, smb_request_t *, sr);
-
-	if (status != 0) {
-		smb2sr_put_error(sr, status);
-		return (SDRC_SUCCESS);
+		return (NT_STATUS_INTERNAL_ERROR);
 	}
 
 	/*
-	 * SMB2 Lock reply (async)
+	 * Try the lock first with timeout=0 as we can often
+	 * get a lock without going async and avoid an extra
+	 * round trip with the client.  Also, only go async
+	 * for status returns that mean we will block.
 	 */
-	(void) smb_mbc_encodef(
-	    &sr->reply, "w..",
-	    4); /* StructSize	w */
-	    /* reserved		.. */
-	return (SDRC_SUCCESS);
+	status = smb_lock_range(sr, lk->Offset, lk->Length, pid, ltype, 0);
+	if (status == NT_STATUS_LOCK_NOT_GRANTED ||
+	    status == NT_STATUS_FILE_LOCK_CONFLICT) {
+		status = smb2sr_go_async(sr);
+		if (status != 0)
+			return (status);
+		status = smb_lock_range(sr, lk->Offset, lk->Length,
+		    pid, ltype, timeout);
+	}
+
+	if (status == 0 && LockSequence != 0)
+		smb2_lock_set_lockseq(sr->fid_ofile, LockSequence);
+
+	return (status);
 }
+
+/*
+ * Check whether we've stored a given LockSequence
+ *
+ * [MS-SMB2] 3.3.5.14
+ *
+ * The server verifies the LockSequence by performing the following steps:
+ *
+ * 1. The server MUST use LockSequenceIndex as an index into the
+ * Open.LockSequenceArray in order to locate the sequence number entry.
+ * If the index exceeds the maximum extent of the Open.LockSequenceArray,
+ * or LockSequenceIndex is 0, or if the sequence number entry is empty,
+ * the server MUST skip step 2 and continue lock/unlock processing.
+ *
+ * 2. The server MUST compare LockSequenceNumber to the SequenceNumber of
+ * the entry located in step 1. If the sequence numbers are equal, the
+ * server MUST complete the lock/unlock request with success. Otherwise,
+ * the server MUST reset the entry value to empty and continue lock/unlock
+ * processing.
+ */
+boolean_t
+smb2_lock_chk_lockseq(smb_ofile_t *ofile, uint32_t lockseq)
+{
+	uint32_t lsi;
+	uint8_t lsn;
+	boolean_t rv;
+
+	/*
+	 * LockSequenceNumber is the low four bits.
+	 * LockSequenceIndex is the remaining 28 bits.
+	 * valid range is 1..64, which we convert to an
+	 * array index in the range 0..63
+	 */
+	lsn = lockseq & SMB2_LSN_MASK;
+	lsi = (lockseq >> SMB2_LSN_SHIFT);
+	if (lsi == 0 || lsi > SMB_OFILE_LSEQ_MAX)
+		return (B_FALSE);
+	--lsi;
+
+	mutex_enter(&ofile->f_mutex);
+
+	if (ofile->f_lock_seq[lsi] == lsn) {
+		rv = B_TRUE;
+	} else {
+		ofile->f_lock_seq[lsi] = (uint8_t)-1;	/* "Empty" */
+		rv = B_FALSE;
+	}
+
+	mutex_exit(&ofile->f_mutex);
+
+	return (rv);
+}
+
+static void
+smb2_lock_set_lockseq(smb_ofile_t *ofile, uint32_t lockseq)
+{
+	uint32_t lsi;
+	uint8_t lsn;
+
+	/*
+	 * LockSequenceNumber is the low four bits.
+	 * LockSequenceIndex is the remaining 28 bits.
+	 * valid range is 1..64, which we convert to an
+	 * array index in the range 0..63
+	 */
+	lsn = lockseq & SMB2_LSN_MASK;
+	lsi = (lockseq >> SMB2_LSN_SHIFT);
+	if (lsi == 0 || lsi > SMB_OFILE_LSEQ_MAX) {
+		cmn_err(CE_NOTE, "smb2_lock_set_lockseq, index=%u", lsi);
+		return;
+	}
+	--lsi;
+
+	mutex_enter(&ofile->f_mutex);
+
+	ofile->f_lock_seq[lsi] = lsn;
+
+	mutex_exit(&ofile->f_mutex);
+}
--- a/usr/src/uts/common/fs/smbsrv/smb2_logoff.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_logoff.c	Wed Jun 12 22:46:54 2019 +0200
@@ -43,6 +43,7 @@
 
 	DTRACE_SMB2_START(op__Logoff, smb_request_t *, sr);
 
+	sr->uid_user->preserve_opens = SMB2_DH_PRESERVE_ALL;
 	smb_user_logoff(sr->uid_user);
 
 	DTRACE_SMB2_DONE(op__Logoff, smb_request_t *, sr);
--- a/usr/src/uts/common/fs/smbsrv/smb2_negotiate.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_negotiate.c	Wed Jun 12 22:46:54 2019 +0200
@@ -24,7 +24,9 @@
 
 uint32_t smb2srv_capabilities =
 	SMB2_CAP_DFS |
-	SMB2_CAP_LARGE_MTU;
+	SMB2_CAP_LEASING |
+	SMB2_CAP_LARGE_MTU |
+	SMB2_CAP_ENCRYPTION;
 
 /*
  * These are not intended as customer tunables, but dev. & test folks
@@ -67,6 +69,7 @@
 static uint16_t smb2_versions[] = {
 	0x202,	/* SMB 2.002 */
 	0x210,	/* SMB 2.1 */
+	0x300,	/* SMB 3.0 */
 };
 static uint16_t smb2_nversions =
     sizeof (smb2_versions) / sizeof (smb2_versions[0]);
@@ -103,7 +106,6 @@
 	smb_session_t *s = sr->session;
 	smb_arg_negotiate_t *negprot = sr->sr_negprot;
 	uint16_t smb2_version;
-	uint16_t secmode2;
 	int rc;
 
 	/*
@@ -122,33 +124,31 @@
 		s->s_state = SMB_SESSION_STATE_NEGOTIATED;
 		/* Allow normal SMB2 requests now. */
 		s->newrq_func = smb2sr_newrq;
-
-		/*
-		 * Translate SMB1 sec. mode to SMB2.
-		 */
-		secmode2 = 0;
-		if (s->secmode & NEGOTIATE_SECURITY_SIGNATURES_ENABLED)
-			secmode2 |= SMB2_NEGOTIATE_SIGNING_ENABLED;
-		if (s->secmode & NEGOTIATE_SECURITY_SIGNATURES_REQUIRED)
-			secmode2 |= SMB2_NEGOTIATE_SIGNING_REQUIRED;
-		s->secmode = secmode2;
 		break;
 	case DIALECT_SMB2XXX:	/* SMB 2.??? (wildcard vers) */
 		/*
 		 * Expecting an SMB2 negotiate next, so keep the
-		 * initial s->newrq_func.  Note that secmode is
-		 * fiction good enough to pass the signing check
-		 * in smb2_negotiate_common().  We'll check the
-		 * real secmode when the 2nd negotiate comes.
+		 * initial s->newrq_func.
 		 */
 		smb2_version = 0x2FF;
-		s->secmode = SMB2_NEGOTIATE_SIGNING_ENABLED;
 		break;
 	default:
 		return (SDRC_DROP_VC);
 	}
 
 	/*
+	 * Clients that negotiate SMB2 from SMB1 have not yet had the
+	 * opportunity to provide us with a secmode. However, any
+	 * client that negotiates SMB2 should support signing, so
+	 * this should be fiction good enough to pass the signing
+	 * check in smb2_negotiate_common(). Even if the client
+	 * doesn't support signing and we require it, we'll fail them
+	 * later when they fail to sign the packet. For 2.???,
+	 * we'll check the real secmode when the 2nd negotiate comes.
+	 */
+	s->cli_secmode = SMB2_NEGOTIATE_SIGNING_ENABLED;
+
+	/*
 	 * We did not decode an SMB2 header, so make sure
 	 * the SMB2 header fields are initialized.
 	 * (Most are zero from smb_request_alloc.)
@@ -170,6 +170,21 @@
 	return (SDRC_NO_REPLY);
 }
 
+static uint16_t
+smb2_find_best_dialect(smb_session_t *s, uint16_t cl_versions[],
+    uint16_t version_cnt)
+{
+	uint16_t best_version = 0;
+	int i;
+
+	for (i = 0; i < version_cnt; i++)
+		if (smb2_supported_version(s, cl_versions[i]) &&
+		    best_version < cl_versions[i])
+			best_version = cl_versions[i];
+
+	return (best_version);
+}
+
 /*
  * SMB2 Negotiate gets special handling.  This is called directly by
  * the reader thread (see smbsr_newrq_initial) with what _should_ be
@@ -191,7 +206,7 @@
 smb2_newrq_negotiate(smb_request_t *sr)
 {
 	smb_session_t *s = sr->session;
-	int i, rc;
+	int rc;
 	uint16_t struct_size;
 	uint16_t best_version;
 	uint16_t version_cnt;
@@ -210,13 +225,13 @@
 	 * Decode SMB2 Negotiate (fixed-size part)
 	 */
 	rc = smb_mbc_decodef(
-	    &sr->command, "www..l16.8.",
+	    &sr->command, "www..l16c8.",
 	    &struct_size,	/* w */
 	    &version_cnt,	/* w */
-	    &s->secmode,	/* w */
+	    &s->cli_secmode,	/* w */
 	    /* reserved		(..) */
-	    &s->capabilities);	/* l */
-	    /* clnt_uuid	 16. */
+	    &s->capabilities,	/* l */
+	    s->clnt_uuid);	/* 16c */
 	    /* start_time	  8. */
 	if (rc != 0)
 		return (rc);
@@ -238,11 +253,7 @@
 	 * supports, which we have decoded into cl_versions[].
 	 * We walk the array and pick the highest supported.
 	 */
-	best_version = 0;
-	for (i = 0; i < version_cnt; i++)
-		if (smb2_supported_version(s, cl_versions[i]) &&
-		    best_version < cl_versions[i])
-			best_version = cl_versions[i];
+	best_version = smb2_find_best_dialect(s, cl_versions, version_cnt);
 	if (best_version == 0)
 		return (SDRC_DROP_VC);
 	s->dialect = best_version;
@@ -283,19 +294,16 @@
 
 	/*
 	 * Negotiation itself.  First the Security Mode.
-	 * The caller stashed the client's secmode in s->secmode,
-	 * which we validate, and then replace with the server's
-	 * secmode, which is all we care about after this.
 	 */
 	secmode = SMB2_NEGOTIATE_SIGNING_ENABLED;
 	if (sr->sr_cfg->skc_signing_required) {
 		secmode |= SMB2_NEGOTIATE_SIGNING_REQUIRED;
 		/* Make sure client at least enables signing. */
-		if ((s->secmode & secmode) == 0) {
+		if ((s->cli_secmode & secmode) == 0) {
 			sr->smb2_status = NT_STATUS_INVALID_PARAMETER;
 		}
 	}
-	s->secmode = secmode;
+	s->srv_secmode = secmode;
 
 	s->cmd_max_bytes = smb2_tcp_bufsize;
 	s->reply_max_bytes = smb2_tcp_bufsize;
@@ -326,6 +334,27 @@
 	}
 
 	/*
+	 * If the version is 0x2FF, we haven't completed negotiate.
+	 * Don't initialize until we have our final request.
+	 */
+	if (version != 0x2FF)
+		smb2_sign_init_mech(s);
+
+	/*
+	 * [MS-SMB2] 3.3.5.4 Receiving an SMB2 NEGOTIATE Request
+	 *
+	 * Only set CAP_ENCRYPTION if this is 3.0 or 3.0.2 and
+	 * the client has it set.
+	 */
+
+	if (s->dialect < SMB_VERS_3_0 ||
+	    !SMB3_CLIENT_ENCRYPTS(sr) ||
+	    smb3_encrypt_init_mech(s) != 0)
+		s->srv_cap = smb2srv_capabilities & ~SMB2_CAP_ENCRYPTION;
+	else
+		s->srv_cap = smb2srv_capabilities;
+
+	/*
 	 * See notes above smb2_max_rwsize, smb2_old_rwsize
 	 */
 	if (s->capabilities & SMB2_CAP_LARGE_MTU)
@@ -337,12 +366,12 @@
 	    &sr->reply,
 	    "wwww#cllllTTwwl#c",
 	    65,	/* StructSize */	/* w */
-	    s->secmode,			/* w */
+	    s->srv_secmode,		/* w */
 	    version,			/* w */
 	    0, /* reserved */		/* w */
 	    UUID_LEN,			/* # */
 	    &s->s_cfg.skc_machine_uuid, /* c */
-	    smb2srv_capabilities,	/* l */
+	    s->srv_cap,			/* l */
 	    smb2_max_trans,		/* l */
 	    max_rwsize,			/* l */
 	    max_rwsize,			/* l */
@@ -399,15 +428,50 @@
 	 * If we don't the client closes the connection.
 	 */
 
+	/* dialects[8] taken from cl_versions[8] in smb2_newrq_negotiate */
+	uint32_t capabilities;
+	uint16_t secmode, num_dialects, dialects[8];
+	uint8_t clnt_guid[16];
+
+	if (fsctl->InputCount < 24)
+		goto drop;
+
+	(void) smb_mbc_decodef(fsctl->in_mbc, "l16cww",
+	    &capabilities, /* l */
+	    &clnt_guid, /* 16c */
+	    &secmode, /* w */
+	    &num_dialects); /* w */
+
+	if (num_dialects == 0 || num_dialects > 8)
+		goto drop;
+	if (secmode != s->cli_secmode)
+		goto drop;
+	if (capabilities != s->capabilities)
+		goto drop;
+	if (memcmp(clnt_guid, s->clnt_uuid, sizeof (clnt_guid)) != 0)
+		goto drop;
+
+	if (fsctl->InputCount < (24 + num_dialects * sizeof (*dialects)))
+		goto drop;
+
+	rc = smb_mbc_decodef(fsctl->in_mbc, "#w", num_dialects, dialects);
+	if (rc != 0)
+		goto drop;
+
+	if (smb2_find_best_dialect(s, dialects, num_dialects) != s->dialect)
+		goto drop;
+
 	rc = smb_mbc_encodef(
 	    fsctl->out_mbc, "l#cww",
-	    smb2srv_capabilities,	/* l */
+	    s->srv_cap,			/* l */
 	    UUID_LEN,			/* # */
 	    &s->s_cfg.skc_machine_uuid, /* c */
-	    s->secmode,			/* w */
+	    s->srv_secmode,		/* w */
 	    s->dialect);		/* w */
-	if (rc)
-		return (NT_STATUS_INTERNAL_ERROR);
+	if (rc == 0)
+		return (rc);
 
-	return (0);
+drop:
+	smb_session_disconnect(s);
+	return (NT_STATUS_ACCESS_DENIED);
 }
--- a/usr/src/uts/common/fs/smbsrv/smb2_oplock.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_oplock.c	Wed Jun 12 22:46:54 2019 +0200
@@ -19,69 +19,123 @@
 
 #include <smbsrv/smb2_kproto.h>
 
+#define	BATCH_OR_EXCL	(OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE)
+
+/* StructSize for the two "break" message formats. */
+#define	SSZ_OPLOCK	24
+#define	SSZ_LEASE	36
+
 /*
  * SMB2 Oplock Break Acknowledgement
- * [MS-SMB2 2.2.24]
+ * [MS-SMB2] 3.3.5.22.1 Processing an Oplock Acknowledgment
+ * Called via smb2_disp_table[]
  */
 smb_sdrc_t
 smb2_oplock_break_ack(smb_request_t *sr)
 {
-	smb_node_t *node;
+	smb_ofile_t *ofile;
 	smb2fid_t smb2fid;
 	uint32_t status;
+	uint32_t NewLevel;
+	uint8_t smbOplockLevel;
+	int rc = 0;
 	uint16_t StructSize;
-	uint8_t OplockLevel;
-	uint8_t brk;
-	int rc = 0;
 
 	/*
-	 * Decode the SMB2 Oplock Break Ack.
+	 * Decode the SMB2 Oplock Break Ack (24 bytes) or
+	 * Lease Break Ack (36 bytes), starting with just
+	 * the StructSize, which tells us what this is.
+	 */
+	rc = smb_mbc_decodef(&sr->smb_data, "w", &StructSize);
+	if (rc != 0)
+		return (SDRC_ERROR);
+
+	if (StructSize == SSZ_LEASE) {
+		/* See smb2_lease.c */
+		return (smb2_lease_break_ack(sr));
+	}
+	if (StructSize != SSZ_OPLOCK)
+		return (SDRC_ERROR);
+
+	/*
+	 * Decode an SMB2 Oplock Break Ack.
+	 * [MS-SMB2] 2.2.24.1
+	 * Note: Struct size decoded above.
 	 */
 	rc = smb_mbc_decodef(
-	    &sr->smb_data, "wb5.qq",
-	    &StructSize,		/* w */
-	    &OplockLevel,		/* b */
+	    &sr->smb_data, "b5.qq",
+	    &smbOplockLevel,		/* b */
 	    /* reserved			  5. */
 	    &smb2fid.persistent,	/* q */
 	    &smb2fid.temporal);		/* q */
-	if (rc || StructSize != 24)
+	if (rc != 0)
 		return (SDRC_ERROR);
 
+	/* Find the ofile */
 	status = smb2sr_lookup_fid(sr, &smb2fid);
+	/* Success or NT_STATUS_FILE_CLOSED */
+
 	DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr);
-	if (status)
+	if (status != 0)
 		goto errout;
-	if ((node = sr->fid_ofile->f_node) == NULL) {
-		/* Not a regular file */
-		status = NT_STATUS_INVALID_PARAMETER;
+
+	/*
+	 * Process an (old-style) oplock break ack.
+	 */
+	switch (smbOplockLevel) {
+	case SMB2_OPLOCK_LEVEL_NONE:	/* 0x00 */
+		NewLevel = OPLOCK_LEVEL_NONE;
+		break;
+	case SMB2_OPLOCK_LEVEL_II:	/* 0x01 */
+		NewLevel = OPLOCK_LEVEL_TWO;
+		break;
+	case SMB2_OPLOCK_LEVEL_EXCLUSIVE: /* 0x08 */
+		NewLevel = OPLOCK_LEVEL_ONE;
+		break;
+	case SMB2_OPLOCK_LEVEL_BATCH:	/* 0x09 */
+		NewLevel = OPLOCK_LEVEL_BATCH;
+		break;
+	case SMB2_OPLOCK_LEVEL_LEASE:	/* 0xFF */
+	default:
+		NewLevel = OPLOCK_LEVEL_NONE;
+		break;
+	}
+
+	ofile = sr->fid_ofile;
+	ofile->f_oplock.og_breaking = 0;
+	status = smb_oplock_ack_break(sr, ofile, &NewLevel);
+	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+		status = smb2sr_go_async(sr);
+		if (status != 0)
+			goto errout;
+		(void) smb_oplock_wait_break(ofile->f_node, 0);
+		status = 0;
+	}
+	if (status != 0) {
+		NewLevel = OPLOCK_LEVEL_NONE;
 		goto errout;
 	}
 
-	/*
-	 * Process the oplock break ack.  We only expect levels
-	 * at or below the hightest break levels we send, which is
-	 * currently SMB2_OPLOCK_LEVEL_II.
-	 */
-	switch (OplockLevel) {
-	case SMB2_OPLOCK_LEVEL_NONE:	/* 0x00 */
-		brk = SMB_OPLOCK_BREAK_TO_NONE;
+	ofile->f_oplock.og_state = NewLevel;
+	switch (NewLevel & OPLOCK_LEVEL_TYPE_MASK) {
+	case OPLOCK_LEVEL_NONE:
+		smbOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
+		break;
+	case OPLOCK_LEVEL_TWO:
+		smbOplockLevel = SMB2_OPLOCK_LEVEL_II;
 		break;
-
-	case SMB2_OPLOCK_LEVEL_II:	/* 0x01 */
-		brk = SMB_OPLOCK_BREAK_TO_LEVEL_II;
+	case OPLOCK_LEVEL_ONE:
+		smbOplockLevel = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+		break;
+	case OPLOCK_LEVEL_BATCH:
+		smbOplockLevel = SMB2_OPLOCK_LEVEL_BATCH;
 		break;
-
-	/* We don't break to these levels (yet). */
-	case SMB2_OPLOCK_LEVEL_EXCLUSIVE: /* 0x08 */
-	case SMB2_OPLOCK_LEVEL_BATCH:	/* 0x09 */
-	case SMB2_OPLOCK_LEVEL_LEASE:	/* 0xFF */
-	default: /* gcc -Wuninitialized */
-		status = NT_STATUS_INVALID_PARAMETER;
-		goto errout;
+	case OPLOCK_LEVEL_GRANULAR:
+	default:
+		smbOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
+		break;
 	}
 
-	smb_oplock_ack(node, sr->fid_ofile, brk);
-
 errout:
 	sr->smb2_status = status;
 	DTRACE_SMB2_DONE(op__OplockBreak, smb_request_t *, sr);
@@ -91,17 +145,17 @@
 	}
 
 	/*
-	 * Generate SMB2 Oplock Break response
-	 * [MS-SMB2] 2.2.25
+	 * Encode an SMB2 Oplock Break Ack response
+	 * [MS-SMB2] 2.2.25.1
 	 */
-	StructSize = 24;
 	(void) smb_mbc_encodef(
 	    &sr->reply, "wb5.qq",
-	    StructSize,			/* w */
-	    OplockLevel,		/* b */
+	    SSZ_OPLOCK,			/* w */
+	    smbOplockLevel,		/* b */
 	    /* reserved			  5. */
 	    smb2fid.persistent,		/* q */
 	    smb2fid.temporal);		/* q */
+
 	return (SDRC_SUCCESS);
 }
 
@@ -111,21 +165,24 @@
  * The caller will send it and free the request.
  */
 void
-smb2_oplock_break_notification(smb_request_t *sr, uint8_t brk)
+smb2_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel)
 {
 	smb_ofile_t *ofile = sr->fid_ofile;
 	smb2fid_t smb2fid;
 	uint16_t StructSize;
 	uint8_t OplockLevel;
 
-	switch (brk) {
+	/*
+	 * Convert internal level to SMB2
+	 */
+	switch (NewLevel) {
 	default:
 		ASSERT(0);
 		/* FALLTHROUGH */
-	case SMB_OPLOCK_BREAK_TO_NONE:
+	case OPLOCK_LEVEL_NONE:
 		OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
 		break;
-	case SMB_OPLOCK_BREAK_TO_LEVEL_II:
+	case OPLOCK_LEVEL_TWO:
 		OplockLevel = SMB2_OPLOCK_LEVEL_II;
 		break;
 	}
@@ -135,9 +192,9 @@
 	 */
 	sr->smb2_cmd_code = SMB2_OPLOCK_BREAK;
 	sr->smb2_hdr_flags = SMB2_FLAGS_SERVER_TO_REDIR;
-	sr->smb_tid = ofile->f_tree->t_tid;
+	sr->smb_tid = 0;
 	sr->smb_pid = 0;
-	sr->smb_uid = 0;
+	sr->smb2_ssnid = 0;
 	sr->smb2_messageid = UINT64_MAX;
 	(void) smb2_encode_header(sr, B_FALSE);
 
@@ -145,7 +202,7 @@
 	 * SMB2 Oplock Break, variable part
 	 */
 	StructSize = 24;
-	smb2fid.persistent = 0;
+	smb2fid.persistent = ofile->f_persistid;
 	smb2fid.temporal = ofile->f_fid;
 	(void) smb_mbc_encodef(
 	    &sr->reply, "wb5.qq",
@@ -155,3 +212,176 @@
 	    smb2fid.persistent,	/* q */
 	    smb2fid.temporal);	/* q */
 }
+
+/*
+ * Client has an open handle and requests an oplock.
+ * Convert SMB2 oplock request info in to internal form,
+ * call common oplock code, convert result to SMB2.
+ *
+ * If necessary, "go async" here.
+ */
+void
+smb2_oplock_acquire(smb_request_t *sr)
+{
+	smb_arg_open_t *op = &sr->arg.open;
+	smb_ofile_t *ofile = sr->fid_ofile;
+	uint32_t status;
+
+	/* Only disk trees get oplocks. */
+	ASSERT((sr->tid_tree->t_res_type & STYPE_MASK) == STYPE_DISKTREE);
+
+	/* Only plain files... */
+	if (!smb_node_is_file(ofile->f_node)) {
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+		return;
+	}
+
+	if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_OPLOCKS)) {
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+		return;
+	}
+
+	/*
+	 * SMB2: Convert to internal form.
+	 */
+	switch (op->op_oplock_level) {
+	case SMB2_OPLOCK_LEVEL_BATCH:
+		op->op_oplock_state = OPLOCK_LEVEL_BATCH;
+		break;
+	case SMB2_OPLOCK_LEVEL_EXCLUSIVE:
+		op->op_oplock_state = OPLOCK_LEVEL_ONE;
+		break;
+	case SMB2_OPLOCK_LEVEL_II:
+		op->op_oplock_state = OPLOCK_LEVEL_TWO;
+		break;
+	case SMB2_OPLOCK_LEVEL_LEASE:
+		ASSERT(0); /* Handled elsewhere */
+		/* FALLTHROUGH */
+	case SMB2_OPLOCK_LEVEL_NONE:
+	default:
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+		return;
+	}
+
+	/*
+	 * Tree options may force shared oplocks,
+	 * in which case we reduce the request.
+	 */
+	if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) {
+		op->op_oplock_state = OPLOCK_LEVEL_TWO;
+	}
+
+	/*
+	 * Try exclusive first, if requested
+	 */
+	if ((op->op_oplock_state & BATCH_OR_EXCL) != 0) {
+		status = smb_oplock_request(sr, ofile,
+		    &op->op_oplock_state);
+	} else {
+		status = NT_STATUS_OPLOCK_NOT_GRANTED;
+	}
+
+	/*
+	 * If exclusive failed (or the tree forced shared oplocks)
+	 * try for a shared oplock (Level II)
+	 */
+	if (status == NT_STATUS_OPLOCK_NOT_GRANTED) {
+		op->op_oplock_state = OPLOCK_LEVEL_TWO;
+		status = smb_oplock_request(sr, ofile,
+		    &op->op_oplock_state);
+	}
+
+	/*
+	 * Either of the above may have returned the
+	 * status code that says we should wait.
+	 */
+	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+		(void) smb2sr_go_async(sr);
+		(void) smb_oplock_wait_break(ofile->f_node, 0);
+		status = 0;
+	}
+
+	/*
+	 * Keep track of what we got (in ofile->f_oplock.og_state)
+	 * so we'll know what we had when sending a break later.
+	 * The og_dialect here is the oplock dialect, not the
+	 * SMB dialect.  No lease here, so SMB 2.0.
+	 */
+	ofile->f_oplock.og_dialect = SMB_VERS_2_002;
+	switch (status) {
+	case NT_STATUS_SUCCESS:
+		ofile->f_oplock.og_state = op->op_oplock_state;
+		break;
+	case NT_STATUS_OPLOCK_NOT_GRANTED:
+		ofile->f_oplock.og_state = 0;
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+		return;
+	default:
+		/* Caller did not check args sufficiently? */
+		cmn_err(CE_NOTE, "clnt %s oplock req. err 0x%x",
+		    sr->session->ip_addr_str, status);
+		ofile->f_oplock.og_state = 0;
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+		return;
+	}
+
+	/*
+	 * Have STATUS_SUCCESS
+	 * Convert internal oplock state to SMB2
+	 */
+	if (op->op_oplock_state & OPLOCK_LEVEL_GRANULAR) {
+		ASSERT(0);
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+	} else if (op->op_oplock_state & OPLOCK_LEVEL_BATCH) {
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_BATCH;
+	} else if (op->op_oplock_state & OPLOCK_LEVEL_ONE) {
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+	} else if (op->op_oplock_state & OPLOCK_LEVEL_TWO) {
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_II;
+	} else {
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+	}
+}
+
+/*
+ * smb2_oplock_reconnect()  Helper for smb2_dh_reconnect
+ * Get oplock state into op->op_oplock_level etc.
+ *
+ * Similar to the end of smb2_lease_acquire (for leases) or
+ * the end of smb2_oplock_acquire (for old-style oplocks).
+ */
+void
+smb2_oplock_reconnect(smb_request_t *sr)
+{
+	smb_arg_open_t *op = &sr->arg.open;
+	smb_ofile_t *ofile = sr->fid_ofile;
+
+	op->op_oplock_state = ofile->f_oplock.og_state;
+	if (ofile->f_lease != NULL) {
+		smb_lease_t *ls = ofile->f_lease;
+
+		op->op_oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+		op->lease_state = ls->ls_state &
+		    OPLOCK_LEVEL_CACHE_MASK;
+		op->lease_flags = (ls->ls_breaking != 0) ?
+		    SMB2_LEASE_FLAG_BREAK_IN_PROGRESS : 0;
+		op->lease_epoch = ls->ls_epoch;
+		op->lease_version = ls->ls_version;
+	} else {
+		switch (op->op_oplock_state & OPLOCK_LEVEL_TYPE_MASK) {
+		default:
+		case OPLOCK_LEVEL_NONE:
+			op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+			break;
+		case OPLOCK_LEVEL_TWO:
+			op->op_oplock_level = SMB2_OPLOCK_LEVEL_II;
+			break;
+		case OPLOCK_LEVEL_ONE:
+			op->op_oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+			break;
+		case OPLOCK_LEVEL_BATCH:
+			op->op_oplock_level = SMB2_OPLOCK_LEVEL_BATCH;
+			break;
+		}
+	}
+}
--- a/usr/src/uts/common/fs/smbsrv/smb2_query_dir.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_query_dir.c	Wed Jun 12 22:46:54 2019 +0200
@@ -309,7 +309,7 @@
 smb2_find_entries(smb_request_t *sr, smb_odir_t *od, smb2_find_args_t *args)
 {
 	smb_odir_resume_t odir_resume;
-	char 		*tbuf = NULL;
+	char		*tbuf = NULL;
 	size_t		tbuflen = 0;
 	uint16_t	count;
 	uint16_t	minsize;
@@ -437,7 +437,7 @@
 	smb_macinfo_t	*macinfo = &args->fa_mi;
 	uint8_t		buf83[26];
 	smb_msgbuf_t	mb;
-	int		namelen, padsz;
+	int		namelen;
 	int		shortlen = 0;
 	int		rc, starting_offset;
 	uint32_t	next_entry_offset;
@@ -647,11 +647,8 @@
 		return (NT_STATUS_BUFFER_OVERFLOW);
 
 	/* Next entry needs to be 8-byte aligned. */
-	padsz = sr->raw_data.chain_offset & 7;
-	if (padsz) {
-		padsz = 8 - padsz;
-		(void) smb_mbc_encodef(&sr->raw_data, "#.", padsz);
-	}
+	(void) smb_mbc_put_align(&sr->raw_data, 8);
+
 	next_entry_offset = sr->raw_data.chain_offset -	starting_offset;
 	(void) smb_mbc_poke(&sr->raw_data, starting_offset, "l",
 	    next_entry_offset);
--- a/usr/src/uts/common/fs/smbsrv/smb2_session_setup.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_session_setup.c	Wed Jun 12 22:46:54 2019 +0200
@@ -37,7 +37,7 @@
 	uint32_t Channel;
 	uint16_t SecBufOffset;
 	uint16_t SecBufLength;
-	uint64_t PrevSessionId;
+	uint64_t PrevSsnId;
 	uint16_t SessionFlags;
 	uint32_t status;
 	int skip;
@@ -55,7 +55,7 @@
 	    &Channel,		/* l */
 	    &SecBufOffset,	/* w */
 	    &SecBufLength,	/* w */
-	    &PrevSessionId);	/* q */
+	    &PrevSsnId);	/* q */
 	if (rc)
 		return (SDRC_ERROR);
 
@@ -87,6 +87,37 @@
 	DTRACE_SMB2_START(op__SessionSetup, smb_request_t *, sr);
 
 	/*
+	 * [MS-SMB2] 3.3.5.5 Receiving an SMB2 SESSION_SETUP Request
+	 *
+	 * If we support 3.x, RejectUnencryptedAccess is TRUE,
+	 * global EncryptData is TRUE, but we're not talking
+	 * 3.x or the client doesn't support encryption,
+	 * return ACCESS_DENIED.
+	 *
+	 * If RejectUnencryptedAccess is TRUE, we force max_protocol
+	 * to at least 3.0.
+	 */
+	if (sr->sr_server->sv_cfg.skc_encrypt == SMB_CONFIG_REQUIRED &&
+	    (sr->session->dialect < SMB_VERS_3_0 ||
+	    !SMB3_CLIENT_ENCRYPTS(sr))) {
+		status = NT_STATUS_ACCESS_DENIED;
+		goto errout;
+	}
+
+	/*
+	 * SMB3 multi-channel features are not supported.
+	 * Once they are, this will check the dialect and
+	 * whether multi-channel was negotiated, i.e.
+	 *	if (sr->session->dialect < SMB_VERS_3_0 ||
+	 *	    s->IsMultiChannelCapable == False)
+	 *		return (error...)
+	 */
+	if (Flags & SMB2_SESSION_FLAG_BINDING) {
+		status = NT_STATUS_REQUEST_NOT_ACCEPTED;
+		goto errout;
+	}
+
+	/*
 	 * The real auth. work happens in here.
 	 */
 	status = smb_authenticate_ext(sr);
@@ -98,11 +129,26 @@
 	switch (status) {
 
 	case NT_STATUS_SUCCESS:	/* Authenticated */
-		if (sr->uid_user->u_flags & SMB_USER_FLAG_GUEST)
+		if ((sr->uid_user->u_flags & SMB_USER_FLAG_GUEST) != 0)
 			SessionFlags |= SMB2_SESSION_FLAG_IS_GUEST;
-		if (sr->uid_user->u_flags & SMB_USER_FLAG_ANON)
+		if ((sr->uid_user->u_flags & SMB_USER_FLAG_ANON) != 0)
 			SessionFlags |= SMB2_SESSION_FLAG_IS_NULL;
+		if (sr->uid_user->u_encrypt != SMB_CONFIG_DISABLED)
+			SessionFlags |= SMB2_SESSION_FLAG_ENCRYPT_DATA;
 		smb2_ss_adjust_credits(sr);
+
+		/*
+		 * PrevSsnId is a session that the client is reporting as
+		 * having gone away, and for which we might not yet have seen
+		 * a disconnect. We need to log off the previous session so
+		 * any durable handles in that session will become orphans
+		 * that can be reclaimed in this new session.  Note that
+		 * either zero or the _current_ session ID means there is
+		 * no previous session to logoff.
+		 */
+		if (PrevSsnId != 0 &&
+		    PrevSsnId != sr->smb2_ssnid)
+			smb_server_logoff_ssnid(sr, PrevSsnId);
 		break;
 
 	/*
@@ -115,6 +161,7 @@
 		break;
 
 	default:
+errout:
 		SecBufLength = 0;
 		sr->smb2_status = status;
 		break;
--- a/usr/src/uts/common/fs/smbsrv/smb2_signing.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_signing.c	Wed Jun 12 22:46:54 2019 +0200
@@ -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.
  */
 /*
  * These routines provide the SMB MAC signing for the SMB2 server.
@@ -39,8 +39,8 @@
  */
 
 #include <sys/uio.h>
-#include <smbsrv/smb_kproto.h>
-#include <smbsrv/smb_signing.h>
+#include <smbsrv/smb2_kproto.h>
+#include <smbsrv/smb_kcrypt.h>
 #include <sys/isa_defs.h>
 #include <sys/byteorder.h>
 #include <sys/cmn_err.h>
@@ -48,13 +48,46 @@
 #define	SMB2_SIG_OFFS	48
 #define	SMB2_SIG_SIZE	16
 
+typedef struct mac_ops {
+	int (*mac_init)(smb_sign_ctx_t *, smb_crypto_mech_t *,
+			uint8_t *, size_t);
+	int (*mac_update)(smb_sign_ctx_t, uint8_t *, size_t);
+	int (*mac_final)(smb_sign_ctx_t, uint8_t *);
+} mac_ops_t;
+
+static int smb2_sign_calc_common(smb_request_t *, struct mbuf_chain *,
+    uint8_t *, mac_ops_t *);
+
+/*
+ * SMB2 wrapper functions
+ */
+
+static mac_ops_t
+smb2_sign_ops = {
+	smb2_hmac_init,
+	smb2_hmac_update,
+	smb2_hmac_final
+};
+
+static int
+smb2_sign_calc(smb_request_t *sr,
+    struct mbuf_chain *mbc,
+    uint8_t *digest16)
+{
+	int rv;
+
+	rv = smb2_sign_calc_common(sr, mbc, digest16, &smb2_sign_ops);
+
+	return (rv);
+}
+
 /*
  * Called during session destroy.
  */
 static void
 smb2_sign_fini(smb_session_t *s)
 {
-	smb_sign_mech_t *mech;
+	smb_crypto_mech_t *mech;
 
 	if ((mech = s->sign_mech) != NULL) {
 		kmem_free(mech, sizeof (*mech));
@@ -63,20 +96,84 @@
 }
 
 /*
+ * SMB3 wrapper functions
+ */
+
+static struct mac_ops
+smb3_sign_ops = {
+	smb3_cmac_init,
+	smb3_cmac_update,
+	smb3_cmac_final
+};
+
+static int
+smb3_sign_calc(smb_request_t *sr,
+    struct mbuf_chain *mbc,
+    uint8_t *digest16)
+{
+	int rv;
+
+	rv = smb2_sign_calc_common(sr, mbc, digest16, &smb3_sign_ops);
+
+	return (rv);
+}
+
+/*
+ * Input to KDF for SigningKey.
+ * See comment for smb3_do_kdf for content.
+ */
+static uint8_t sign_kdf_input[29] = {
+	0, 0, 0, 1, 'S', 'M', 'B', '2',
+	'A', 'E', 'S', 'C', 'M', 'A', 'C', 0,
+	0, 'S', 'm', 'b', 'S', 'i', 'g', 'n',
+	0, 0, 0, 0, 0x80 };
+
+void
+smb2_sign_init_mech(smb_session_t *s)
+{
+	smb_crypto_mech_t *mech;
+	int (*get_mech)(smb_crypto_mech_t *);
+	int (*sign_calc)(smb_request_t *, struct mbuf_chain *, uint8_t *);
+	int rc;
+
+	if (s->sign_mech != NULL)
+		return;
+
+	if (s->dialect >= SMB_VERS_3_0) {
+		get_mech = smb3_cmac_getmech;
+		sign_calc = smb3_sign_calc;
+	} else {
+		get_mech = smb2_hmac_getmech;
+		sign_calc = smb2_sign_calc;
+	}
+
+	mech = kmem_zalloc(sizeof (*mech), KM_SLEEP);
+	rc = get_mech(mech);
+	if (rc != 0) {
+		kmem_free(mech, sizeof (*mech));
+		return;
+	}
+	s->sign_mech = mech;
+	s->sign_calc = sign_calc;
+	s->sign_fini = smb2_sign_fini;
+}
+
+/*
  * smb2_sign_begin
+ * Handles both SMB2 & SMB3
  *
  * Get the mechanism info.
  * Intializes MAC key based on the user session key and store it in
  * the signing structure.  This begins signing on this session.
  */
-int
+void
 smb2_sign_begin(smb_request_t *sr, smb_token_t *token)
 {
 	smb_session_t *s = sr->session;
 	smb_user_t *u = sr->uid_user;
 	struct smb_key *sign_key = &u->u_sign_key;
-	smb_sign_mech_t *mech;
-	int rc;
+
+	sign_key->len = 0;
 
 	/*
 	 * We should normally have a session key here because
@@ -84,46 +181,42 @@
 	 * However, buggy clients could get us here without a
 	 * session key, in which case we'll fail later when a
 	 * request that requires signing can't be checked.
-	 */
-	if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0)
-		return (0);
-
-	/*
-	 * Session-level initialization (once per session)
-	 * Get mech handle, sign_fini function.
+	 * Also, don't bother initializing if we don't have a mechanism.
 	 */
-	smb_rwx_rwenter(&s->s_lock, RW_WRITER);
-	if (s->sign_mech == NULL) {
-		mech = kmem_zalloc(sizeof (*mech), KM_SLEEP);
-		rc = smb2_hmac_getmech(mech);
-		if (rc != 0) {
-			kmem_free(mech, sizeof (*mech));
-			smb_rwx_rwexit(&s->s_lock);
-			return (rc);
-		}
-		s->sign_mech = mech;
-		s->sign_fini = smb2_sign_fini;
-	}
-	smb_rwx_rwexit(&s->s_lock);
+	if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0 ||
+	    s->sign_mech == NULL)
+		return;
 
 	/*
 	 * Compute and store the signing key, which lives in
 	 * the user structure.
 	 */
-	sign_key->len = SMB2_SIG_SIZE;
-
-	/*
-	 * For SMB2, the signing key is just the first 16 bytes
-	 * of the session key (truncated or padded with zeros).
-	 * [MS-SMB2] 3.2.5.3.1
-	 */
-	bcopy(token->tkn_ssnkey.val, sign_key->key,
-	    MIN(token->tkn_ssnkey.len, sign_key->len));
+	if (s->dialect >= SMB_VERS_3_0) {
+		/*
+		 * For SMB3, the signing key is a "KDF" hash of the
+		 * session key.
+		 */
+		if (smb3_do_kdf(sign_key->key, sign_kdf_input,
+		    sizeof (sign_kdf_input), token->tkn_ssnkey.val,
+		    token->tkn_ssnkey.len) != 0)
+			return;
+		sign_key->len = SMB3_KEYLEN;
+	} else {
+		/*
+		 * For SMB2, the signing key is just the first 16 bytes
+		 * of the session key (truncated or padded with zeros).
+		 * [MS-SMB2] 3.2.5.3.1
+		 */
+		sign_key->len = SMB2_KEYLEN;
+		bcopy(token->tkn_ssnkey.val, sign_key->key,
+		    MIN(token->tkn_ssnkey.len, sign_key->len));
+	}
 
 	mutex_enter(&u->u_mutex);
-	if (s->secmode & SMB2_NEGOTIATE_SIGNING_ENABLED)
+	if ((s->srv_secmode & SMB2_NEGOTIATE_SIGNING_ENABLED) != 0)
 		u->u_sign_flags |= SMB_SIGNING_ENABLED;
-	if (s->secmode & SMB2_NEGOTIATE_SIGNING_REQUIRED)
+	if ((s->srv_secmode & SMB2_NEGOTIATE_SIGNING_REQUIRED) != 0 ||
+	    (s->cli_secmode & SMB2_NEGOTIATE_SIGNING_REQUIRED) != 0)
 		u->u_sign_flags |=
 		    SMB_SIGNING_ENABLED | SMB_SIGNING_CHECK;
 	mutex_exit(&u->u_mutex);
@@ -136,25 +229,23 @@
 	 */
 	if (u->u_sign_flags & SMB_SIGNING_ENABLED)
 		sr->smb2_hdr_flags |= SMB2_FLAGS_SIGNED;
-
-	return (0);
 }
 
 /*
- * smb2_sign_calc
+ * smb2_sign_calc_common
  *
  * Calculates MAC signature for the given buffer and returns
  * it in the mac_sign parameter.
  *
- * The signature is in the last 16 bytes of the SMB2 header.
- * The signature algorighm is to compute HMAC SHA256 over the
- * entire command, with the signature field set to zeros.
+ * The signature algorithm is to compute HMAC SHA256 or AES_CMAC
+ * over the entire command, with the signature field set to zeros.
  *
  * Return 0 if  success else -1
  */
+
 static int
-smb2_sign_calc(smb_request_t *sr, struct mbuf_chain *mbc,
-    uint8_t *digest)
+smb2_sign_calc_common(smb_request_t *sr, struct mbuf_chain *mbc,
+    uint8_t *digest, mac_ops_t *ops)
 {
 	uint8_t tmp_hdr[SMB2_HDR_SIZE];
 	smb_sign_ctx_t ctx = 0;
@@ -167,7 +258,8 @@
 	if (s->sign_mech == NULL || sign_key->len == 0)
 		return (-1);
 
-	rc = smb2_hmac_init(&ctx, s->sign_mech, sign_key->key, sign_key->len);
+	/* smb2_hmac_init or smb3_cmac_init */
+	rc = ops->mac_init(&ctx, s->sign_mech, sign_key->key, sign_key->len);
 	if (rc != 0)
 		return (rc);
 
@@ -182,7 +274,8 @@
 	if (smb_mbc_peek(mbc, offset, "#c", tlen, tmp_hdr) != 0)
 		return (-1);
 	bzero(tmp_hdr + SMB2_SIG_OFFS, SMB2_SIG_SIZE);
-	if ((rc = smb2_hmac_update(ctx, tmp_hdr, tlen)) != 0)
+	/* smb2_hmac_update or smb3_cmac_update */
+	if ((rc = ops->mac_update(ctx, tmp_hdr, tlen)) != 0)
 		return (rc);
 	offset += tlen;
 	resid -= tlen;
@@ -210,7 +303,8 @@
 	tlen = mbuf->m_len - offset;
 	if (tlen > resid)
 		tlen = resid;
-	rc = smb2_hmac_update(ctx, (uint8_t *)mbuf->m_data + offset, tlen);
+	/* smb2_hmac_update or smb3_cmac_update */
+	rc = ops->mac_update(ctx, (uint8_t *)mbuf->m_data + offset, tlen);
 	if (rc != 0)
 		return (rc);
 	resid -= tlen;
@@ -225,17 +319,20 @@
 		tlen = mbuf->m_len;
 		if (tlen > resid)
 			tlen = resid;
-		rc = smb2_hmac_update(ctx, (uint8_t *)mbuf->m_data, tlen);
+		rc = ops->mac_update(ctx, (uint8_t *)mbuf->m_data, tlen);
 		if (rc != 0)
 			return (rc);
 		resid -= tlen;
 	}
 
 	/*
+	 * smb2_hmac_final or smb3_cmac_final
 	 * Note: digest is _always_ SMB2_SIG_SIZE,
 	 * even if the mech uses a longer one.
+	 *
+	 * smb2_hmac_update or smb3_cmac_update
 	 */
-	if ((rc = smb2_hmac_final(ctx, digest)) != 0)
+	if ((rc = ops->mac_final(ctx, digest)) != 0)
 		return (rc);
 
 	return (0);
@@ -260,6 +357,7 @@
 	uint8_t req_sig[SMB2_SIG_SIZE];
 	uint8_t vfy_sig[SMB2_SIG_SIZE];
 	struct mbuf_chain *mbc = &sr->smb_data;
+	smb_session_t *s = sr->session;
 	smb_user_t *u = sr->uid_user;
 	int sig_off;
 
@@ -267,9 +365,13 @@
 	 * Don't check commands with a zero session ID.
 	 * [MS-SMB2] 3.3.4.1.1
 	 */
-	if (sr->smb_uid == 0 || u == NULL)
+	if (sr->smb2_ssnid == 0 || u == NULL)
 		return (0);
 
+	/* In case _sign_begin failed. */
+	if (s->sign_calc == NULL)
+		return (-1);
+
 	/* Get the request signature. */
 	sig_off = sr->smb2_cmd_hdr + SMB2_SIG_OFFS;
 	if (smb_mbc_peek(mbc, sig_off, "#c", SMB2_SIG_SIZE, req_sig) != 0)
@@ -277,8 +379,9 @@
 
 	/*
 	 * Compute the correct signature and compare.
+	 * smb2_sign_calc() or smb3_sign_calc()
 	 */
-	if (smb2_sign_calc(sr, mbc, vfy_sig) != 0)
+	if (s->sign_calc(sr, mbc, vfy_sig) != 0)
 		return (-1);
 	if (memcmp(vfy_sig, req_sig, SMB2_SIG_SIZE) != 0) {
 		cmn_err(CE_NOTE, "smb2_sign_check_request: bad signature");
@@ -300,11 +403,14 @@
 {
 	uint8_t reply_sig[SMB2_SIG_SIZE];
 	struct mbuf_chain tmp_mbc;
+	smb_session_t *s = sr->session;
 	smb_user_t *u = sr->uid_user;
 	int hdr_off, msg_len;
 
 	if (u == NULL)
 		return;
+	if (s->sign_calc == NULL)
+		return;
 
 	msg_len = sr->reply.chain_offset - sr->smb2_reply_hdr;
 	(void) MBC_SHADOW_CHAIN(&tmp_mbc, &sr->reply,
@@ -312,8 +418,9 @@
 
 	/*
 	 * Calculate the MAC signature for this reply.
+	 * smb2_sign_calc() or smb3_sign_calc()
 	 */
-	if (smb2_sign_calc(sr, &tmp_mbc, reply_sig) != 0)
+	if (s->sign_calc(sr, &tmp_mbc, reply_sig) != 0)
 		return;
 
 	/*
@@ -323,3 +430,63 @@
 	(void) smb_mbc_poke(&sr->reply, hdr_off, "#c",
 	    SMB2_SIG_SIZE, reply_sig);
 }
+
+/*
+ * Derive SMB3 key as described in [MS-SMB2] 3.1.4.2
+ * and [NIST SP800-108]
+ *
+ * r = 32, L = 128, PRF = HMAC-SHA256, key = (session key)
+ *
+ * Note that these describe pre-3.1.1 inputs.
+ *
+ * Session.SigningKey for binding a session:
+ * - Session.SessionKey as K1
+ * - label = SMB2AESCMAC (size 12)
+ * - context = SmbSign (size 8)
+ * Channel.SigningKey for for all other requests
+ * - if SMB2_SESSION_FLAG_BINDING, GSS key (in Session.SessionKey?) as K1;
+ * - otherwise, Session.SessionKey as K1
+ * - label = SMB2AESCMAC (size 12)
+ * - context = SmbSign (size 8)
+ * Session.ApplicationKey for ... (not sure what yet)
+ * - Session.SessionKey as K1
+ * - label = SMB2APP (size 8)
+ * - context = SmbRpc (size 7)
+ * Session.EncryptionKey for encrypting server messages
+ * - Session.SessionKey as K1
+ * - label = "SMB2AESCCM" (size 11)
+ * - context = "ServerOut" (size 10)
+ * Session.DecryptionKey for decrypting client requests
+ * - Session.SessionKey as K1
+ * - label = "SMB2AESCCM" (size 11)
+ * - context = "ServerIn " (size 10) (Note the space)
+ */
+
+int
+smb3_do_kdf(void *outbuf, void *input, size_t input_len,
+    uint8_t *key, uint32_t key_len)
+{
+	uint8_t digest32[SHA256_DIGEST_LENGTH];
+	smb_crypto_mech_t mech;
+	smb_sign_ctx_t hctx = 0;
+	int rc;
+
+	bzero(&mech, sizeof (mech));
+	if ((rc = smb2_hmac_getmech(&mech)) != 0)
+		return (rc);
+
+	/* Limit the SessionKey input to its maximum size (16 bytes) */
+	rc = smb2_hmac_init(&hctx, &mech, key, MIN(key_len, SMB2_KEYLEN));
+	if (rc != 0)
+		return (rc);
+
+	if ((rc = smb2_hmac_update(hctx, input, input_len)) != 0)
+		return (rc);
+
+	if ((rc = smb2_hmac_final(hctx, digest32)) != 0)
+		return (rc);
+
+	/* Output is first 16 bytes of digest. */
+	bcopy(digest32, outbuf, SMB3_KEYLEN);
+	return (0);
+}
--- a/usr/src/uts/common/fs/smbsrv/smb2_tree_connect.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_tree_connect.c	Wed Jun 12 22:46:54 2019 +0200
@@ -68,6 +68,16 @@
 
 	DTRACE_SMB2_START(op__TreeConnect, smb_request_t *, sr);
 
+	/*
+	 * [MS-SMB2] 3.3.5.7 Receiving an SMB2 TREE_CONNECT Request
+	 *
+	 * If RejectUnencryptedAccess is TRUE,
+	 * global EncryptData or Share.EncryptData is TRUE,
+	 * we support 3.x, and srv_cap doesn't indicate encryption support,
+	 * return ACCESS_DENIED.
+	 *
+	 * This also applies to SMB1, so do it in smb_tree_connect_core.
+	 */
 	status = smb_tree_connect(sr);
 
 	sr->smb2_status = status;
@@ -98,7 +108,11 @@
 	/*
 	 * XXX These need work..
 	 */
-	ShareFlags = 0;
+	if (tree->t_encrypt != SMB_CONFIG_DISABLED)
+		ShareFlags = SMB2_SHAREFLAG_ENCRYPT_DATA;
+	else
+		ShareFlags = 0;
+
 	Capabilities = 0;
 
 	/*
--- a/usr/src/uts/common/fs/smbsrv/smb2_write.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb2_write.c	Wed Jun 12 22:46:54 2019 +0200
@@ -132,8 +132,8 @@
 		if (rc)
 			break;
 		of->f_written = B_TRUE;
-		if (!smb_node_is_dir(of->f_node))
-			smb_oplock_break_levelII(of->f_node);
+		/* This revokes read cache delegations. */
+		(void) smb_oplock_break_WRITE(of->f_node, of);
 		break;
 
 	case STYPE_IPC:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/fs/smbsrv/smb3_encrypt.c	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,397 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+/*
+ * Routines for smb3 encryption.
+ */
+
+#include <smbsrv/smb2_kproto.h>
+#include <smbsrv/smb_kcrypt.h>
+#include <sys/random.h>
+#include <sys/cmn_err.h>
+
+#define	SMB3_NONCE_OFFS		20
+#define	SMB3_SIG_OFFS		4
+#define	SMB3_NONCE_SIZE		11 /* 12 for gcm later */
+
+/*
+ * Inputs to KDF for EncryptionKey and DecryptionKey.
+ * See comment for smb3_do_kdf for content.
+ */
+static uint8_t encrypt_kdf_input[30] = {
+	0, 0, 0, 1, 'S', 'M', 'B', '2',
+	'A', 'E', 'S', 'C', 'C', 'M', 0, 0,
+	'S', 'e', 'r', 'v', 'e', 'r', 'O',
+	'u', 't', 0, 0, 0, 0, 0x80 };
+
+static uint8_t decrypt_kdf_input[30] = {
+	0, 0, 0, 1, 'S', 'M', 'B', '2',
+	'A', 'E', 'S', 'C', 'C', 'M', 0, 0,
+	'S', 'e', 'r', 'v', 'e', 'r', 'I',
+	'n', ' ', 0, 0, 0, 0, 0x80 };
+
+/*
+ * Arbitrary value used to prevent nonce reuse via overflow. Currently
+ * 2^64 - 2^32 - 1. Assumes we can't have (or are unlikely to have)
+ * 2^32 concurrent messages when we hit this number.
+ */
+static uint64_t smb3_max_nonce = 0xffffffff00000000ULL;
+
+/*
+ * Nonce generation based on draft-mcgrew-iv-gen-01
+ * "Generation of Deterministic Initialization Vectors (IVs) and Nonces"
+ *
+ * Generate an 8-byte random salt and a 3-byte random 'fixed' value.
+ * then, nonce = (++counter ^ salt) || fixed
+ *
+ * This protects against nonce-reuse (8-byte counter), as well as known
+ * attacks on reusing nonces with different keys
+ */
+
+void
+smb3_encrypt_init_nonce(smb_user_t *user)
+{
+	user->u_nonce_cnt = 0;
+	(void) random_get_pseudo_bytes(user->u_nonce_fixed,
+	    sizeof (user->u_nonce_fixed));
+	(void) random_get_pseudo_bytes((uint8_t *)&user->u_salt,
+	    sizeof (user->u_salt));
+}
+
+int
+smb3_encrypt_gen_nonce(smb_user_t *user, uint8_t *buf, size_t len)
+{
+	uint64_t cnt = atomic_inc_64_nv(&user->u_nonce_cnt);
+
+	/*
+	 * Nonces must be unique per-key for the life of the key.
+	 * Bail before we roll over to avoid breaking the crypto.
+	 */
+
+	if (cnt > smb3_max_nonce)
+		return (-1);
+
+	cnt ^= user->u_salt;
+	bcopy((uint8_t *)&cnt, buf, sizeof (cnt));
+
+	ASSERT(len > sizeof (cnt));
+	bcopy(user->u_nonce_fixed, buf + sizeof (cnt), len - sizeof (cnt));
+	return (0);
+}
+
+int
+smb3_encrypt_init_mech(smb_session_t *s)
+{
+	smb_crypto_mech_t *mech;
+	int rc;
+
+	if (s->enc_mech != NULL)
+		return (0);
+
+	mech = kmem_zalloc(sizeof (*mech), KM_SLEEP);
+	rc = smb3_encrypt_getmech(mech);
+	if (rc != 0) {
+		kmem_free(mech, sizeof (*mech));
+		return (rc);
+	}
+	s->enc_mech = mech;
+
+	return (0);
+}
+
+/*
+ * Initializes keys/state required for SMB3 Encryption.
+ * Note: If a failure occurs here, don't fail the request.
+ * Instead, return an error when we attempt to encrypt/decrypt.
+ */
+void
+smb3_encrypt_begin(smb_request_t *sr, smb_token_t *token)
+{
+	smb_session_t *s = sr->session;
+	smb_user_t *u = sr->uid_user;
+	struct smb_key *enc_key = &u->u_enc_key;
+	struct smb_key *dec_key = &u->u_dec_key;
+
+	/*
+	 * In order to enforce encryption, all users need to
+	 * have Session.EncryptData properly set, even anon/guest.
+	 */
+	u->u_encrypt = s->s_server->sv_cfg.skc_encrypt;
+	enc_key->len = 0;
+	dec_key->len = 0;
+
+	/*
+	 * If we don't have a session key, we'll fail later when a
+	 * request that requires (en/de)cryption can't be (en/de)crypted.
+	 * Also don't bother initializing if we don't have a mechanism.
+	 */
+	if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0 ||
+	    s->enc_mech == NULL)
+		return;
+
+	/*
+	 * Compute and store the encryption keys, which live in
+	 * the user structure.
+	 */
+
+	/*
+	 * For SMB3, the encrypt/decrypt keys are derived from
+	 * the session key using KDF in counter mode.
+	 */
+	if (smb3_do_kdf(enc_key->key, encrypt_kdf_input,
+	    sizeof (encrypt_kdf_input), token->tkn_ssnkey.val,
+	    token->tkn_ssnkey.len) != 0)
+		return;
+
+	if (smb3_do_kdf(dec_key->key, decrypt_kdf_input,
+	    sizeof (decrypt_kdf_input), token->tkn_ssnkey.val,
+	    token->tkn_ssnkey.len) != 0)
+		return;
+
+	smb3_encrypt_init_nonce(u);
+
+	enc_key->len = SMB3_KEYLEN;
+	dec_key->len = SMB3_KEYLEN;
+}
+
+/*
+ * Decrypt the request in sr->command.
+ * This decrypts "in place", though due to CCM's design,
+ * it processes all input before doing any output.
+ */
+int
+smb3_decrypt_sr(smb_request_t *sr)
+{
+	struct mbuf_chain *mbc = &sr->command;
+	smb_session_t *s = sr->session;
+	smb_user_t *u = sr->tform_ssn;
+	uint8_t tmp_hdr[SMB2_HDR_SIZE];
+	smb3_enc_ctx_t ctx;
+	struct smb_key *dec_key = &u->u_dec_key;
+	struct mbuf *mbuf;
+	int offset, resid, tlen, rc;
+	smb3_crypto_param_t param;
+	smb_crypto_mech_t mech;
+
+	ASSERT(u != NULL);
+	if (s->enc_mech == NULL || dec_key->len != 16) {
+		return (-1);
+	}
+
+	tlen = SMB3_TFORM_HDR_SIZE - SMB3_NONCE_OFFS;
+	offset = mbc->chain_offset + SMB3_NONCE_OFFS;
+	resid = mbc->max_bytes - offset;
+
+	if (resid < (sr->msgsize + tlen)) {
+		cmn_err(CE_WARN, "too little data to decrypt");
+		return (-1);
+	}
+
+	if (smb_mbc_peek(mbc, offset, "#c", tlen, tmp_hdr) != 0) {
+		return (-1);
+	}
+
+	offset += tlen;
+	resid -= tlen;
+
+	/*
+	 * The transform header, minus the PROTOCOL_ID and the
+	 * SIGNATURE, is authenticated but not encrypted.
+	 */
+	smb3_crypto_init_param(&param, sr->nonce, SMB3_NONCE_SIZE,
+	    tmp_hdr, tlen, sr->msgsize + SMB2_SIG_SIZE);
+
+	/*
+	 * Unlike signing, which uses one global mech struct,
+	 * encryption requires modifying the mech to add a
+	 * per-use param struct. Thus, we need to make a copy.
+	 */
+	mech = *(smb_crypto_mech_t *)s->enc_mech;
+	rc = smb3_decrypt_init(&ctx, &mech, &param,
+	    dec_key->key, dec_key->len);
+	if (rc != 0) {
+		return (rc);
+	}
+
+	/*
+	 * Digest the rest of the SMB packet, starting at the data
+	 * just after the SMB header.
+	 *
+	 * Advance to the src mbuf where we start digesting.
+	 */
+	mbuf = mbc->chain;
+	while (mbuf != NULL && (offset >= mbuf->m_len)) {
+		offset -= mbuf->m_len;
+		mbuf = mbuf->m_next;
+	}
+
+	if (mbuf == NULL)
+		return (-1);
+
+	/*
+	 * Digest the remainder of this mbuf, limited to the
+	 * residual count, and starting at the current offset.
+	 */
+	tlen = mbuf->m_len - offset;
+	if (tlen > resid)
+		tlen = resid;
+
+	rc = smb3_decrypt_update(&ctx, (uint8_t *)mbuf->m_data + offset, tlen);
+	if (rc != 0) {
+		return (rc);
+	}
+	resid -= tlen;
+
+	/*
+	 * Digest any more mbufs in the chain.
+	 */
+	while (resid > 0) {
+		mbuf = mbuf->m_next;
+		if (mbuf == NULL) {
+			smb3_encrypt_cancel(&ctx);
+			return (-1);
+		}
+		tlen = mbuf->m_len;
+		if (tlen > resid)
+			tlen = resid;
+		rc = smb3_decrypt_update(&ctx, (uint8_t *)mbuf->m_data, tlen);
+		if (rc != 0) {
+			return (rc);
+		}
+		resid -= tlen;
+	}
+
+	/*
+	 * AES_CCM processes the signature like normal data.
+	 */
+	rc = smb3_decrypt_update(&ctx, sr->smb2_sig, SMB2_SIG_SIZE);
+
+	if (rc != 0) {
+		cmn_err(CE_WARN, "failed to process signature");
+		return (rc);
+	}
+	/*
+	 * smb3_decrypt_final will return an error
+	 * if the signatures don't match.
+	 */
+	rc = smb3_decrypt_final(&ctx, sr->sr_request_buf, sr->sr_req_length);
+
+	/*
+	 * We had to decode TFORM_HDR_SIZE bytes before we got here,
+	 * and we just peeked the first TFORM_HDR_SIZE bytes at the
+	 * beginning of this function, so this can't underflow.
+	 */
+	ASSERT(sr->command.max_bytes > SMB3_TFORM_HDR_SIZE);
+	sr->command.max_bytes -= SMB3_TFORM_HDR_SIZE;
+	return (rc);
+}
+
+/*
+ * Encrypt the response in in_mbc, and output
+ * an encrypted response in out_mbc.
+ * The data in in_mbc is preserved.
+ */
+int
+smb3_encrypt_sr(smb_request_t *sr, struct mbuf_chain *in_mbc,
+    struct mbuf_chain *out_mbc)
+{
+	smb_session_t *s = sr->session;
+	smb_user_t *u = sr->tform_ssn;
+	uint8_t *buf = (uint8_t *)out_mbc->chain->m_data;
+	size_t buflen = out_mbc->max_bytes;
+	smb3_enc_ctx_t ctx;
+	struct smb_key *enc_key = &u->u_enc_key;
+	struct mbuf *mbuf;
+	int resid, tlen, rc;
+	smb3_crypto_param_t param;
+	smb_crypto_mech_t mech;
+
+	ASSERT(u != NULL);
+	if (s->enc_mech == NULL || enc_key->len != 16) {
+		return (-1);
+	}
+
+	rc = smb3_encrypt_gen_nonce(u, sr->nonce, SMB3_NONCE_SIZE);
+
+	if (rc != 0) {
+		cmn_err(CE_WARN, "ran out of nonces");
+		return (-1);
+	}
+
+	(void) smb_mbc_poke(out_mbc, SMB3_NONCE_OFFS, "#c",
+	    SMB3_NONCE_SIZE, sr->nonce);
+
+	resid = in_mbc->max_bytes;
+
+	/*
+	 * The transform header, minus the PROTOCOL_ID and the
+	 * SIGNATURE, is authenticated but not encrypted.
+	 */
+	smb3_crypto_init_param(&param,
+	    sr->nonce, SMB3_NONCE_SIZE,
+	    buf + SMB3_NONCE_OFFS, SMB3_TFORM_HDR_SIZE - SMB3_NONCE_OFFS,
+	    resid);
+
+	/*
+	 * Unlike signing, which uses one global mech struct,
+	 * encryption requires modifying the mech to add a
+	 * per-use param struct. Thus, we need to make a copy.
+	 */
+	mech = *(smb_crypto_mech_t *)s->enc_mech;
+	rc = smb3_encrypt_init(&ctx, &mech, &param,
+	    enc_key->key, enc_key->len, buf + SMB3_TFORM_HDR_SIZE,
+	    buflen - SMB3_TFORM_HDR_SIZE);
+	if (rc != 0) {
+		return (rc);
+	}
+
+	/*
+	 * Unlike signing and decryption, we're processing the entirety of the
+	 * message here, so we don't skip anything.
+	 */
+	mbuf = in_mbc->chain;
+	while (resid > 0 && mbuf != NULL) {
+		tlen = mbuf->m_len;
+		if (tlen > resid)
+			tlen = resid;
+		rc = smb3_encrypt_update(&ctx, (uint8_t *)mbuf->m_data, tlen);
+		if (rc != 0) {
+			return (rc);
+		}
+		resid -= tlen;
+		mbuf = mbuf->m_next;
+	}
+
+	if (mbuf == NULL && resid > 0) {
+		cmn_err(CE_WARN, "not enough data to encrypt");
+		smb3_encrypt_cancel(&ctx);
+		return (-1);
+	}
+
+	rc = smb3_encrypt_final(&ctx, buf + SMB3_SIG_OFFS);
+
+	return (rc);
+}
+
+void
+smb3_encrypt_fini(smb_session_t *s)
+{
+	smb_crypto_mech_t *mech;
+
+	if ((mech = s->enc_mech) != NULL) {
+		kmem_free(mech, sizeof (*mech));
+		s->enc_mech = NULL;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/fs/smbsrv/smb3_encrypt_kcf.c	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,235 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+/*
+ * Helper functions for SMB3 encryption using the
+ * Kernel Cryptographic Framework (KCF)
+ *
+ * There are two implementations of these functions:
+ * This one (for kernel) and another for user space:
+ * See: lib/smbsrv/libfksmbsrv/common/fksmb_encrypt_pkcs.c
+ */
+
+#include <sys/crypto/api.h>
+#include <smbsrv/smb_kcrypt.h>
+#include <smbsrv/smb2_kproto.h>
+#include <sys/cmn_err.h>
+
+/*
+ * SMB3 encryption helpers:
+ * (getmech, init, update, final)
+ */
+
+int
+smb3_encrypt_getmech(smb_crypto_mech_t *mech)
+{
+	crypto_mech_type_t t;
+
+	t = crypto_mech2id(SUN_CKM_AES_CCM);
+	if (t == CRYPTO_MECH_INVALID) {
+		cmn_err(CE_NOTE, "smb: no kcf mech: %s", SUN_CKM_AES_CCM);
+		return (-1);
+	}
+	mech->cm_type = t;
+
+	return (0);
+}
+
+void
+smb3_crypto_init_param(smb3_crypto_param_t *param,
+    uint8_t *nonce, size_t noncesize, uint8_t *auth, size_t authsize,
+    size_t datasize)
+{
+	param->ulMACSize = SMB2_SIG_SIZE;
+	param->ulNonceSize = noncesize;
+	param->nonce = nonce;
+	param->ulDataSize = datasize;
+	param->ulAuthDataSize = authsize;
+	param->authData = auth;
+}
+
+/*
+ * Start the KCF session, load the key
+ */
+static int
+smb3_crypto_init(smb3_enc_ctx_t *ctxp, smb_crypto_mech_t *mech,
+    uint8_t *key, size_t key_len, smb3_crypto_param_t *param,
+    boolean_t is_encrypt)
+{
+	crypto_key_t ckey;
+	int rv;
+
+	bzero(&ckey, sizeof (ckey));
+	ckey.ck_format = CRYPTO_KEY_RAW;
+	ckey.ck_data = key;
+	ckey.ck_length = key_len * 8; /* in bits */
+
+	mech->cm_param = (caddr_t)param;
+	mech->cm_param_len = sizeof (*param);
+
+	if (is_encrypt)
+		rv = crypto_encrypt_init(mech, &ckey, NULL, &ctxp->ctx, NULL);
+	else
+		rv = crypto_decrypt_init(mech, &ckey, NULL, &ctxp->ctx, NULL);
+
+	if (rv != CRYPTO_SUCCESS) {
+		if (is_encrypt)
+			cmn_err(CE_WARN,
+			    "crypto_encrypt_init failed: 0x%x", rv);
+		else
+			cmn_err(CE_WARN,
+			    "crypto_decrypt_init failed: 0x%x", rv);
+	}
+
+	return (rv == CRYPTO_SUCCESS ? 0 : -1);
+}
+
+int
+smb3_encrypt_init(smb3_enc_ctx_t *ctxp, smb_crypto_mech_t *mech,
+    smb3_crypto_param_t *param, uint8_t *key, size_t keylen,
+    uint8_t *buf, size_t buflen)
+{
+
+	bzero(&ctxp->output, sizeof (ctxp->output));
+	ctxp->output.cd_format = CRYPTO_DATA_RAW;
+	ctxp->output.cd_length = buflen;
+	ctxp->output.cd_raw.iov_len = buflen;
+	ctxp->output.cd_raw.iov_base = (void *)buf;
+
+	return (smb3_crypto_init(ctxp, mech, key, keylen,
+	    param, B_TRUE));
+}
+
+int
+smb3_decrypt_init(smb3_enc_ctx_t *ctxp, smb_crypto_mech_t *mech,
+    smb3_crypto_param_t *param, uint8_t *key, size_t keylen)
+{
+	return (smb3_crypto_init(ctxp, mech, key, keylen,
+	    param, B_FALSE));
+}
+
+/*
+ * Digest one segment
+ */
+int
+smb3_encrypt_update(smb3_enc_ctx_t *ctxp, uint8_t *in, size_t len)
+{
+	crypto_data_t data;
+	int rv;
+
+	bzero(&data, sizeof (data));
+	data.cd_format = CRYPTO_DATA_RAW;
+	data.cd_length = len;
+	data.cd_raw.iov_base = (void *)in;
+	data.cd_raw.iov_len = len;
+
+	rv = crypto_encrypt_update(ctxp->ctx, &data, &ctxp->output, NULL);
+
+	if (rv != CRYPTO_SUCCESS) {
+		cmn_err(CE_WARN, "crypto_encrypt_update failed: 0x%x", rv);
+		crypto_cancel_ctx(ctxp->ctx);
+		return (-1);
+	}
+
+	len = ctxp->output.cd_length;
+	ctxp->len -= len;
+	ctxp->output.cd_offset += len;
+	ctxp->output.cd_length = ctxp->len;
+
+	return (0);
+}
+
+int
+smb3_decrypt_update(smb3_enc_ctx_t *ctxp, uint8_t *in, size_t len)
+{
+	crypto_data_t data;
+	int rv;
+
+	bzero(&data, sizeof (data));
+	data.cd_format = CRYPTO_DATA_RAW;
+	data.cd_length = len;
+	data.cd_raw.iov_base = (void *)in;
+	data.cd_raw.iov_len = len;
+
+	/*
+	 * AES_CCM does not output data until decrypt_final,
+	 * and only does so if the signature matches.
+	 */
+	rv = crypto_decrypt_update(ctxp->ctx, &data, NULL, NULL);
+
+	if (rv != CRYPTO_SUCCESS) {
+		cmn_err(CE_WARN, "crypto_decrypt_update failed: 0x%x", rv);
+		crypto_cancel_ctx(ctxp->ctx);
+		return (-1);
+	}
+
+	return (0);
+}
+
+int
+smb3_encrypt_final(smb3_enc_ctx_t *ctxp, uint8_t *digest16)
+{
+	crypto_data_t out;
+	int rv;
+	uint8_t buf[SMB2_SIG_SIZE + 16] = {0};
+	size_t outlen;
+
+	bzero(&out, sizeof (out));
+	out.cd_format = CRYPTO_DATA_RAW;
+	out.cd_length = sizeof (buf);
+	out.cd_raw.iov_len = sizeof (buf);
+	out.cd_raw.iov_base = (void *)buf;
+
+	rv = crypto_encrypt_final(ctxp->ctx, &out, 0);
+
+	if (rv != CRYPTO_SUCCESS) {
+		cmn_err(CE_WARN, "crypto_encrypt_final failed: 0x%x", rv);
+		return (-1);
+	}
+
+	outlen = out.cd_offset - SMB2_SIG_SIZE;
+	if (outlen > 0)
+		bcopy(buf, ctxp->output.cd_raw.iov_base +
+		    ctxp->output.cd_offset, outlen);
+	bcopy(buf + outlen, digest16, SMB2_SIG_SIZE);
+
+	return (0);
+}
+
+int
+smb3_decrypt_final(smb3_enc_ctx_t *ctxp, uint8_t *buf, size_t buflen)
+{
+	crypto_data_t out;
+	int rv;
+
+	bzero(&out, sizeof (out));
+	out.cd_format = CRYPTO_DATA_RAW;
+	out.cd_length = buflen;
+	out.cd_raw.iov_len = buflen;
+	out.cd_raw.iov_base = (void *)buf;
+
+	rv = crypto_decrypt_final(ctxp->ctx, &out, NULL);
+
+	if (rv != CRYPTO_SUCCESS)
+		cmn_err(CE_WARN, "crypto_decrypt_final failed: 0x%x", rv);
+
+	return (rv == CRYPTO_SUCCESS ? 0 : -1);
+}
+
+void
+smb3_encrypt_cancel(smb3_enc_ctx_t *ctxp)
+{
+	crypto_cancel_ctx(ctxp->ctx);
+}
--- a/usr/src/uts/common/fs/smbsrv/smb_authenticate.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_authenticate.c	Wed Jun 12 22:46:54 2019 +0200
@@ -51,6 +51,7 @@
 
 /*
  * Handle old-style session setup (non-extended security)
+ * Note: Used only by SMB1
  *
  * The user information is passed to smbd for authentication.
  * If smbd can authenticate the user an access token is returned and we
@@ -69,6 +70,7 @@
 	/* user cleanup in smb_request_free */
 	sr->uid_user = user;
 	sr->smb_uid = user->u_uid;
+	sr->smb2_ssnid = 0;
 
 	/*
 	 * Open a connection to the local logon service.
@@ -231,20 +233,42 @@
 	ASSERT(sr->uid_user == NULL);
 
 	/*
-	 * On the first request (UID==0) create a USER object.
-	 * On subsequent requests (UID!=0) find the USER object.
+	 * Paranoid:  While finding/creating the user object, make sure
+	 * SMB2 ignores smb_uid, and SMB1 ignores smb2_ssnid.  The
+	 * logic below assumes the "other" one is always zero; both
+	 * the "first request" tests and smb_session_lookup_uid_st.
+	 */
+	if (sr->session->dialect >= SMB_VERS_2_BASE) {
+		/* SMB2+ ignores smb_uid */
+		ASSERT(sr->smb_uid == 0);
+		sr->smb_uid = 0;
+	} else {
+		/* SMB1 ignores smb2_ssnid */
+		ASSERT(sr->smb2_ssnid == 0);
+		sr->smb2_ssnid = 0;
+	}
+
+	/*
+	 * On the first request (UID/ssnid==0) create a USER object.
+	 * On subsequent requests (UID/ssnid!=0) find the USER object.
 	 * Either way, sr->uid_user is set, so our ref. on the
 	 * user object is dropped during normal cleanup work
 	 * for the smb_request (sr).  Ditto u_authsock.
 	 */
-	if (sr->smb_uid == 0) {
+	if (sr->smb2_ssnid == 0 && sr->smb_uid == 0) {
 		user = smb_user_new(sr->session);
 		if (user == NULL)
 			return (NT_STATUS_TOO_MANY_SESSIONS);
 
 		/* user cleanup in smb_request_free */
 		sr->uid_user = user;
-		sr->smb_uid = user->u_uid;
+		if (sr->session->dialect >= SMB_VERS_2_BASE) {
+			/* Intentionally leave smb_uid=0 for SMB2 */
+			sr->smb2_ssnid = user->u_ssnid;
+		} else {
+			/* Intentionally leave smb2_ssnid=0 for SMB1 */
+			sr->smb_uid = user->u_uid;
+		}
 
 		/*
 		 * Open a connection to the local logon service.
@@ -263,7 +287,7 @@
 		msg_hdr.lmh_msgtype = LSA_MTYPE_ESFIRST;
 	} else {
 		user = smb_session_lookup_uid_st(sr->session,
-		    sr->smb_uid, SMB_USER_STATE_LOGGING_ON);
+		    sr->smb2_ssnid, sr->smb_uid, SMB_USER_STATE_LOGGING_ON);
 		if (user == NULL)
 			return (NT_STATUS_USER_SESSION_DELETED);
 
@@ -388,7 +412,6 @@
 	uint32_t	rlen = 0;
 	uint32_t	privileges;
 	uint32_t	status;
-	int		rc;
 	bool_t		ok;
 
 	msg_hdr.lmh_msgtype = LSA_MTYPE_GETTOK;
@@ -445,18 +468,30 @@
 	crfree(cr);
 
 	/*
+	 * Some basic processing for encryption needs to be done,
+	 * even for anonymous/guest sessions. In particular,
+	 * we need to set Session.EncryptData.
+	 *
+	 * Windows handling of anon/guest and encryption is strange.
+	 * It allows these accounts to get through session setup,
+	 * even when they provide no key material.
+	 * Additionally, Windows somehow manages to have key material
+	 * for anonymous accounts under unknown circumstances.
+	 * As such, We set EncryptData on anon/guest to behave like Windows,
+	 * at least through Session Setup.
+	 */
+	if (sr->session->dialect >= SMB_VERS_3_0)
+		smb3_encrypt_begin(sr, token);
+
+	/*
 	 * Save the session key, and (maybe) enable signing,
 	 * but only for real logon (not ANON or GUEST).
 	 */
 	if ((token->tkn_flags & (SMB_ATF_GUEST | SMB_ATF_ANON)) == 0) {
 		if (sr->session->dialect >= SMB_VERS_2_BASE) {
-			rc = smb2_sign_begin(sr, token);
+			smb2_sign_begin(sr, token);
 		} else {
-			rc = smb_sign_begin(sr, token);
-		}
-		if (rc != 0) {
-			status = NT_STATUS_INTERNAL_ERROR;
-			goto errout;
+			smb_sign_begin(sr, token);
 		}
 	}
 
@@ -549,7 +584,7 @@
  */
 static uint32_t
 smb_authsock_sendrecv(smb_request_t *sr, smb_lsa_msg_hdr_t *hdr,
-	void *sndbuf, void **recvbuf)
+    void *sndbuf, void **recvbuf)
 {
 	smb_user_t *user = sr->uid_user;
 	ksocket_t so;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,3545 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+/*
+ * (SMB1/SMB2) common (FS-level) Oplock support.
+ *
+ * This is the file-system (FS) level oplock code.  This level
+ * knows about the rules by which various kinds of oplocks may
+ * coexist and how they interact.  Note that this code should
+ * have NO knowledge of specific SMB protocol details.  Those
+ * details are handled in smb_srv_oplock.c and related.
+ *
+ * This file is intentionally written to very closely follow the
+ * [MS-FSA] specification sections about oplocks.  Almost every
+ * section of code is preceeded by a block of text from that
+ * specification describing the logic.  Where the implementation
+ * differs from what the spec. describes, there are notes like:
+ * Implementation specific: ...
+ */
+
+#include <smbsrv/smb_kproto.h>
+#include <smbsrv/smb_oplock.h>
+
+/*
+ * Several short-hand defines and enums used in this file.
+ */
+
+#define	NODE_FLAGS_DELETING	(NODE_FLAGS_DELETE_ON_CLOSE |\
+				NODE_FLAGS_DELETE_COMMITTED)
+
+static uint32_t
+smb_oplock_req_excl(
+    smb_ofile_t *ofile,		/* in: the "Open" */
+    uint32_t *rop);		/* in: "RequestedOplock", out:NewOplockLevel */
+
+static uint32_t
+smb_oplock_req_shared(
+    smb_ofile_t *ofile,		/* the "Open" */
+    uint32_t *rop,		/* in: "RequestedOplock", out:NewOplockLevel */
+    boolean_t GrantingInAck);
+
+static uint32_t smb_oplock_break_cmn(smb_node_t *node,
+    smb_ofile_t *ofile, uint32_t BreakCacheLevel);
+
+
+/*
+ * [MS-FSA] 2.1.4.12.2 Algorithm to Compare Oplock Keys
+ *
+ * The inputs for this algorithm are:
+ *
+ *	OperationOpen: The Open used in the request that can
+ *	  cause an oplock to break.
+ *	OplockOpen: The Open originally used to request the oplock,
+ *	  as specified in section 2.1.5.17.
+ *	Flags: If unspecified it is considered to contain 0.
+ *	  Valid nonzero values are:
+ *		PARENT_OBJECT
+ *
+ * This algorithm returns TRUE if the appropriate oplock key field of
+ * OperationOpen equals OplockOpen.TargetOplockKey, and FALSE otherwise.
+ *
+ * Note: Unlike many comparison functions, ARG ORDER MATTERS.
+ */
+
+static boolean_t
+CompareOplockKeys(smb_ofile_t *OperOpen, smb_ofile_t *OplockOpen, int flags)
+{
+	static const uint8_t key0[SMB_LEASE_KEY_SZ] = { 0 };
+
+	/*
+	 * When we're called via FEM, (smb_oplock_break_...)
+	 * the OperOpen arg is NULL because I/O outside of SMB
+	 * doesn't have an "ofile".  That's "not a match".
+	 */
+	if (OperOpen == NULL)
+		return (B_FALSE);
+	ASSERT(OplockOpen != NULL);
+
+	/*
+	 * If OperationOpen equals OplockOpen:
+	 * Return TRUE.
+	 */
+	if (OperOpen == OplockOpen)
+		return (B_TRUE);
+
+	/*
+	 * If both OperationOpen.TargetOplockKey and
+	 * OperationOpen.ParentOplockKey are empty
+	 * or both OplockOpen.TargetOplockKey and
+	 * OplockOpen.ParentOplockKey are empty:
+	 * Return FALSE.
+	 */
+	if (bcmp(OperOpen->TargetOplockKey, key0, sizeof (key0)) == 0 &&
+	    bcmp(OperOpen->ParentOplockKey, key0, sizeof (key0)) == 0)
+		return (B_FALSE);
+	if (bcmp(OplockOpen->TargetOplockKey, key0, sizeof (key0)) == 0 &&
+	    bcmp(OplockOpen->ParentOplockKey, key0, sizeof (key0)) == 0)
+		return (B_FALSE);
+
+	/*
+	 * If OplockOpen.TargetOplockKey is empty or...
+	 */
+	if (bcmp(OplockOpen->TargetOplockKey, key0, sizeof (key0)) == 0)
+		return (B_FALSE);
+
+	/*
+	 * If Flags contains PARENT_OBJECT:
+	 */
+	if ((flags & PARENT_OBJECT) != 0) {
+		/*
+		 * If OperationOpen.ParentOplockKey is empty:
+		 * Return FALSE.
+		 */
+		if (bcmp(OperOpen->ParentOplockKey, key0, sizeof (key0)) == 0)
+			return (B_FALSE);
+
+		/*
+		 * If OperationOpen.ParentOplockKey equals
+		 * OplockOpen.TargetOplockKey:
+		 * return TRUE, else FALSE
+		 */
+		if (bcmp(OperOpen->ParentOplockKey,
+		    OplockOpen->TargetOplockKey,
+		    SMB_LEASE_KEY_SZ) == 0) {
+			return (B_TRUE);
+		}
+	} else {
+		/*
+		 * ... from above:
+		 * (Flags does not contain PARENT_OBJECT and
+		 * OperationOpen.TargetOplockKey is empty):
+		 * Return FALSE.
+		 */
+		if (bcmp(OperOpen->TargetOplockKey, key0, sizeof (key0)) == 0)
+			return (B_FALSE);
+
+		/*
+		 * If OperationOpen.TargetOplockKey equals
+		 * OplockOpen.TargetOplockKey:
+		 *  Return TRUE, else FALSE
+		 */
+		if (bcmp(OperOpen->TargetOplockKey,
+		    OplockOpen->TargetOplockKey,
+		    SMB_LEASE_KEY_SZ) == 0) {
+			return (B_TRUE);
+		}
+	}
+
+	return (B_FALSE);
+}
+
+/*
+ * 2.1.4.13 Algorithm to Recompute the State of a Shared Oplock
+ *
+ * The inputs for this algorithm are:
+ *	ThisOplock: The Oplock on whose state is being recomputed.
+ */
+static void
+RecomputeOplockState(smb_node_t *node)
+{
+	smb_oplock_t *ol = &node->n_oplock;
+
+	ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock));
+	ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
+
+	/*
+	 * If ThisOplock.IIOplocks, ThisOplock.ROplocks, ThisOplock.RHOplocks,
+	 * and ThisOplock.RHBreakQueue are all empty:
+	 *	Set ThisOplock.State to NO_OPLOCK.
+	 */
+	if (ol->cnt_II == 0 && ol->cnt_R == 0 &&
+	    ol->cnt_RH == 0 && ol->cnt_RHBQ == 0) {
+		ol->ol_state = NO_OPLOCK;
+		return;
+	}
+
+	/*
+	 * Else If ThisOplock.ROplocks is not empty and either
+	 *    ThisOplock.RHOplocks or ThisOplock.RHBreakQueue are not empty:
+	 *	Set ThisOplock.State to
+	 *	  (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH).
+	 */
+	else if (ol->cnt_R != 0 && (ol->cnt_RH != 0 || ol->cnt_RHBQ != 0)) {
+		ol->ol_state = (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH);
+	}
+
+	/*
+	 * Else If ThisOplock.ROplocks is empty and
+	 * ThisOplock.RHOplocks is not empty:
+	 *	Set ThisOplock.State to (READ_CACHING|HANDLE_CACHING).
+	 */
+	else if (ol->cnt_R == 0 && ol->cnt_RH != 0) {
+		ol->ol_state = (READ_CACHING|HANDLE_CACHING);
+	}
+
+	/*
+	 * Else If ThisOplock.ROplocks is not empty and
+	 * ThisOplock.IIOplocks is not empty:
+	 *	Set ThisOplock.State to (READ_CACHING|LEVEL_TWO_OPLOCK).
+	 */
+	else if (ol->cnt_R != 0 && ol->cnt_II != 0) {
+		ol->ol_state = (READ_CACHING|LEVEL_TWO_OPLOCK);
+	}
+
+	/*
+	 * Else If ThisOplock.ROplocks is not empty and
+	 * ThisOplock.IIOplocks is empty:
+	 *	Set ThisOplock.State to READ_CACHING.
+	 */
+	else if (ol->cnt_R != 0 && ol->cnt_II == 0) {
+		ol->ol_state = READ_CACHING;
+	}
+
+	/*
+	 * Else If ThisOplock.ROplocks is empty and
+	 * ThisOplock.IIOplocks is not empty:
+	 *	Set ThisOplock.State to LEVEL_TWO_OPLOCK.
+	 */
+	else if (ol->cnt_R == 0 && ol->cnt_II != 0) {
+		ol->ol_state = LEVEL_TWO_OPLOCK;
+	}
+
+	else {
+		smb_ofile_t *o;
+		int cntBrkToRead;
+
+		/*
+		 * ThisOplock.RHBreakQueue MUST be non-empty by this point.
+		 */
+		ASSERT(ol->cnt_RHBQ != 0);
+
+		/*
+		 * How many on RHBQ have BreakingToRead set?
+		 */
+		cntBrkToRead = 0;
+		FOREACH_NODE_OFILE(node, o) {
+			if (o->f_oplock.onlist_RHBQ == 0)
+				continue;
+			if (o->f_oplock.BreakingToRead)
+				cntBrkToRead++;
+		}
+
+		/*
+		 * If RHOpContext.BreakingToRead is TRUE for
+		 *  every RHOpContext on ThisOplock.RHBreakQueue:
+		 */
+		if (cntBrkToRead == ol->cnt_RHBQ) {
+			/*
+			 * Set ThisOplock.State to
+			 * (READ_CACHING|HANDLE_CACHING|BREAK_TO_READ_CACHING).
+			 */
+			ol->ol_state = (READ_CACHING|HANDLE_CACHING|
+			    BREAK_TO_READ_CACHING);
+		}
+
+		/*
+		 * Else If RHOpContext.BreakingToRead is FALSE for
+		 *  every RHOpContext on ThisOplock.RHBreakQueue:
+		 */
+		else if (cntBrkToRead == 0) {
+			/*
+			 * Set ThisOplock.State to
+			 *  (READ_CACHING|HANDLE_CACHING|BREAK_TO_NO_CACHING).
+			 */
+			ol->ol_state = (READ_CACHING|HANDLE_CACHING|
+			    BREAK_TO_NO_CACHING);
+		} else {
+			/*
+			 * Set ThisOplock.State to
+			 *  (READ_CACHING|HANDLE_CACHING).
+			 */
+			ol->ol_state = (READ_CACHING|HANDLE_CACHING);
+		}
+	}
+}
+
+/*
+ * [MS-FSA] 2.1.5.17 Server Requests an Oplock
+ *
+ * The server (caller) provides:
+ *	Open - The Open on which the oplock is being requested. (ofile)
+ *	Type - The type of oplock being requested. Valid values are as follows:
+ *		LEVEL_TWO (Corresponds to SMB2_OPLOCK_LEVEL_II)
+ *		LEVEL_ONE (Corresponds to SMB2_OPLOCK_LEVEL_EXCLUSIVE)
+ *		LEVEL_BATCH (Corresponds to SMB2_OPLOCK_LEVEL_BATCH)
+ *		LEVEL_GRANULAR (Corresponds to SMB2_OPLOCK_LEVEL_LEASE)
+ *	RequestedOplockLevel - A combination of zero or more of the
+ *	  following flags (ignored if Type != LEVEL_GRANULAR)
+ *		READ_CACHING
+ *		HANDLE_CACHING
+ *		WRITE_CACHING
+ *
+ *	(Type + RequestedOplockLevel come in *statep)
+ *
+ * Returns:
+ *	*statep = NewOplockLevel (possibly less than requested)
+ *		  containing: LEVEL_NONE, LEVEL_TWO + cache_flags
+ *	NTSTATUS
+ */
+
+uint32_t
+smb_oplock_request(smb_request_t *sr, smb_ofile_t *ofile, uint32_t *statep)
+{
+	smb_node_t *node = ofile->f_node;
+	uint32_t type = *statep & OPLOCK_LEVEL_TYPE_MASK;
+	uint32_t level = *statep & OPLOCK_LEVEL_CACHE_MASK;
+	uint32_t status;
+
+	*statep = LEVEL_NONE;
+
+	/*
+	 * If Open.Stream.StreamType is DirectoryStream:
+	 *	The operation MUST be failed with STATUS_INVALID_PARAMETER
+	 *	under either of the following conditions:
+	 *	* Type is not LEVEL_GRANULAR.
+	 *	* Type is LEVEL_GRANULAR but RequestedOplockLevel is
+	 *	  neither READ_CACHING nor (READ_CACHING|HANDLE_CACHING).
+	 */
+	if (!smb_node_is_file(node)) {
+		/* ofile is a directory. */
+		if (type != LEVEL_GRANULAR)
+			return (NT_STATUS_INVALID_PARAMETER);
+		if (level != READ_CACHING &&
+		    level != (READ_CACHING|HANDLE_CACHING))
+			return (NT_STATUS_INVALID_PARAMETER);
+		/*
+		 * We're not supporting directory leases yet.
+		 * Todo.
+		 */
+		return (NT_STATUS_OPLOCK_NOT_GRANTED);
+	}
+
+	smb_llist_enter(&node->n_ofile_list, RW_READER);
+	mutex_enter(&node->n_oplock.ol_mutex);
+
+	/*
+	 * If Type is LEVEL_ONE or LEVEL_BATCH:
+	 * The operation MUST be failed with STATUS_OPLOCK_NOT_GRANTED
+	 * under either of the following conditions:
+	 *	Open.File.OpenList contains more than one Open
+	 *	  whose Stream is the same as Open.Stream.
+	 *	Open.Mode contains either FILE_SYNCHRONOUS_IO_ALERT or
+	 *	  FILE_SYNCHRONOUS_IO_NONALERT.
+	 * Request an exclusive oplock according to the algorithm in
+	 * section 2.1.5.17.1, setting the algorithm's params as follows:
+	 *	Pass in the current Open.
+	 *	RequestedOplock = Type.
+	 * The operation MUST at this point return any status code
+	 * returned by the exclusive oplock request algorithm.
+	 */
+	if (type == LEVEL_ONE || type == LEVEL_BATCH) {
+		if (node->n_open_count > 1) {
+			status = NT_STATUS_OPLOCK_NOT_GRANTED;
+			goto out;
+		}
+		/* XXX: Should be a flag on the ofile. */
+		if (node->flags & NODE_FLAGS_WRITE_THROUGH) {
+			status = NT_STATUS_OPLOCK_NOT_GRANTED;
+			goto out;
+		}
+		*statep = type;
+		status = smb_oplock_req_excl(ofile, statep);
+		goto out;
+	}
+
+	/*
+	 * Else If Type is LEVEL_TWO:
+	 * The operation MUST be failed with STATUS_OPLOCK_NOT_GRANTED under
+	 *  either of the following conditions:
+	 *	Open.Stream.ByteRangeLockList is not empty.
+	 *	Open.Mode contains either FILE_SYNCHRONOUS_IO_ALERT or
+	 *	  FILE_SYNCHRONOUS_IO_NONALERT.
+	 * Request a shared oplock according to the algorithm in
+	 * section 2.1.5.17.2, setting the algorithm's parameters as follows:
+	 *	Pass in the current Open.
+	 *	RequestedOplock = Type.
+	 *	GrantingInAck = FALSE.
+	 * The operation MUST at this point return any status code
+	 * returned by the shared oplock request algorithm.
+	 */
+	if (type == LEVEL_TWO) {
+		if (smb_lock_range_access(sr, node, 0, ~0, B_FALSE) != 0) {
+			status = NT_STATUS_OPLOCK_NOT_GRANTED;
+			goto out;
+		}
+		/* XXX: Should be a flag on the ofile. */
+		if (node->flags & NODE_FLAGS_WRITE_THROUGH) {
+			status = NT_STATUS_OPLOCK_NOT_GRANTED;
+			goto out;
+		}
+		*statep = type;
+		status = smb_oplock_req_shared(ofile, statep, B_FALSE);
+		goto out;
+	}
+
+	/*
+	 * Else If Type is LEVEL_GRANULAR:
+	 *   Sub-cases on RequestedOplockLevel (our "level")
+	 *
+	 * This is the last Type, so error on !granular and then
+	 * deal with the cache levels using one less indent.
+	 */
+	if (type != LEVEL_GRANULAR) {
+		status = NT_STATUS_INVALID_PARAMETER;
+		goto out;
+	}
+
+	switch (level) {
+
+	/*
+	 * If RequestedOplockLevel is READ_CACHING or
+	 *   (READ_CACHING|HANDLE_CACHING):
+	 *	The operation MUST be failed with STATUS_OPLOCK_NOT_GRANTED
+	 *	under either of the following conditions:
+	 *		Open.Stream.ByteRangeLockList is not empty.
+	 *		Open.Mode contains either FILE_SYNCHRONOUS_IO_ALERT or
+	 *		  FILE_SYNCHRONOUS_IO_NONALERT.
+	 *	Request a shared oplock according to the algorithm in
+	 *	section 2.1.5.17.2, setting the parameters as follows:
+	 *		Pass in the current Open.
+	 *		RequestedOplock = RequestedOplockLevel.
+	 *		GrantingInAck = FALSE.
+	 *
+	 *	The operation MUST at this point return any status code
+	 *	  returned by the shared oplock request algorithm.
+	 */
+	case READ_CACHING:
+	case (READ_CACHING|HANDLE_CACHING):
+		if (smb_lock_range_access(sr, node, 0, ~0, B_FALSE) != 0) {
+			status = NT_STATUS_OPLOCK_NOT_GRANTED;
+			goto out;
+		}
+		/* XXX: Should be a flag on the ofile. */
+		if (node->flags & NODE_FLAGS_WRITE_THROUGH) {
+			status = NT_STATUS_OPLOCK_NOT_GRANTED;
+			goto out;
+		}
+		*statep = level;
+		status = smb_oplock_req_shared(ofile, statep, B_FALSE);
+		break;
+
+	/*
+	 * Else If RequestedOplockLevel is
+	 * (READ_CACHING|WRITE_CACHING) or
+	 * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING):
+	 * If Open.Mode contains either FILE_SYNCHRONOUS_IO_ALERT or
+	 * FILE_SYNCHRONOUS_IO_NONALERT, the operation MUST be failed
+	 * with STATUS_OPLOCK_NOT_GRANTED.
+	 * Request an exclusive oplock according to the algorithm in
+	 * section 2.1.5.17.1, setting the parameters as follows:
+	 *	Pass in the current Open.
+	 *	RequestedOplock = RequestedOplockLevel.
+	 * The operation MUST at this point return any status code
+	 * returned by the exclusive oplock request algorithm.
+	 */
+	case (READ_CACHING | WRITE_CACHING):
+	case (READ_CACHING | WRITE_CACHING | HANDLE_CACHING):
+		/* XXX: Should be a flag on the ofile. */
+		if (node->flags & NODE_FLAGS_WRITE_THROUGH) {
+			status = NT_STATUS_OPLOCK_NOT_GRANTED;
+			goto out;
+		}
+		*statep = level;
+		status = smb_oplock_req_excl(ofile, statep);
+		break;
+
+	/*
+	 * Else if RequestedOplockLevel is 0 (that is, no flags):
+	 * The operation MUST return STATUS_SUCCESS at this point.
+	 */
+	case 0:
+		*statep = 0;
+		status = NT_STATUS_SUCCESS;
+		break;
+
+	/*
+	 * Else
+	 *  The operation MUST be failed with STATUS_INVALID_PARAMETER.
+	 */
+	default:
+		status = NT_STATUS_INVALID_PARAMETER;
+		break;
+	}
+
+	/* Give caller back the "Granular" bit. */
+	if (status == NT_STATUS_SUCCESS)
+		*statep |= LEVEL_GRANULAR;
+
+out:
+	mutex_exit(&node->n_oplock.ol_mutex);
+	smb_llist_exit(&node->n_ofile_list);
+
+	return (status);
+}
+
+/*
+ * 2.1.5.17.1 Algorithm to Request an Exclusive Oplock
+ *
+ * The inputs for requesting an exclusive oplock are:
+ *	Open: The Open on which the oplock is being requested.
+ *	RequestedOplock: The oplock type being requested. One of:
+ *	  LEVEL_ONE, LEVEL_BATCH, CACHE_RW, CACHE_RWH
+ *
+ * On completion, the object store MUST return:
+ *	Status: An NTSTATUS code that specifies the result.
+ *	NewOplockLevel: The type of oplock that the requested oplock has been
+ *	  broken (reduced) to.  If a failure status is returned in Status,
+ *	  the value of this field is undefined.  Valid values are as follows:
+ *		LEVEL_NONE (that is, no oplock)
+ *		LEVEL_TWO
+ *		A combination of one or more of the following flags:
+ *			READ_CACHING
+ *			HANDLE_CACHING
+ *			WRITE_CACHING
+ *	AcknowledgeRequired: A Boolean value: TRUE if the server MUST
+ *	acknowledge the oplock break; FALSE if not, as specified in
+ *	section 2.1.5.18. If a failure status is returned in Status,
+ *	the value of this field is undefined.
+ *
+ * Note: Stores NewOplockLevel in *rop
+ */
+static uint32_t
+smb_oplock_req_excl(
+    smb_ofile_t *ofile,		/* in: the "Open" */
+    uint32_t *rop)		/* in: "RequestedOplock", out:NewOplockLevel */
+{
+	smb_node_t *node = ofile->f_node;
+	smb_ofile_t *o;
+	boolean_t GrantExcl = B_FALSE;
+	uint32_t status = NT_STATUS_OPLOCK_NOT_GRANTED;
+
+	ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock));
+	ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
+
+	/*
+	 * If Open.Stream.Oplock is empty:
+	 *   Build a new Oplock object with fields initialized as follows:
+	 *	Oplock.State set to NO_OPLOCK.
+	 *	All other fields set to 0/empty.
+	 *   Store the new Oplock object in Open.Stream.Oplock.
+	 * EndIf
+	 *
+	 * Implementation specific:
+	 * Open.Stream.Oplock maps to: node->n_oplock
+	 */
+	if (node->n_oplock.ol_state == 0) {
+		node->n_oplock.ol_state = NO_OPLOCK;
+	}
+
+	/*
+	 * If Open.Stream.Oplock.State contains
+	 * LEVEL_TWO_OPLOCK or NO_OPLOCK: ...
+	 *
+	 * Per ms, this is the "If" matching the unbalalanced
+	 * "Else If" below (for which we requested clarification).
+	 */
+	if ((node->n_oplock.ol_state & (LEVEL_TWO | NO_OPLOCK)) != 0) {
+
+		/*
+		 * If Open.Stream.Oplock.State contains LEVEL_TWO_OPLOCK and
+		 * RequestedOplock contains one or more of READ_CACHING,
+		 * HANDLE_CACHING, or WRITE_CACHING, the operation MUST be
+		 * failed with Status set to STATUS_OPLOCK_NOT_GRANTED.
+		 */
+		if ((node->n_oplock.ol_state & LEVEL_TWO) != 0 &&
+		    (*rop & CACHE_RWH) != 0) {
+			status = NT_STATUS_OPLOCK_NOT_GRANTED;
+			goto out;
+		}
+
+		/*
+		 * [ from dochelp@ms ]
+		 *
+		 * By this point if there is a level II oplock present,
+		 * the caller can only be requesting an old-style oplock
+		 * because we rejected enhanced oplock requests above.
+		 * If the caller is requesting an old-style oplock our
+		 * caller already verfied that there is only one handle
+		 * open to this stream, and we've already verified that
+		 * this request is for a legacy oplock, meaning that there
+		 * can be at most one level II oplock (and no R oplocks),
+		 * and the level II oplock belongs to this handle.  Clear
+		 * the level II oplock and grant the exclusive oplock.
+		 */
+
+		/*
+		 * If Open.Stream.Oplock.State is equal to LEVEL_TWO_OPLOCK:
+		 * Remove the first Open ThisOpen from
+		 *  Open.Stream.Oplock.IIOplocks (there is supposed to be
+		 * exactly one present), and notify the server of an
+		 * oplock break according to the algorithm in section
+		 *  2.1.5.17.3, setting the algorithm's parameters as follows:
+		 *	BreakingOplockOpen = ThisOpen.
+		 *	NewOplockLevel = LEVEL_NONE.
+		 *	AcknowledgeRequired = FALSE.
+		 *	OplockCompletionStatus = STATUS_SUCCESS.
+		 * (The operation does not end at this point; this call
+		 *  to 2.1.5.17.3 completes some earlier call to 2.1.5.17.2.)
+		 *
+		 * Implementation specific:
+		 *
+		 * As explained above, the passed in ofile should be the
+		 * only open file on this node.  Out of caution, we'll
+		 * walk the ofile list as usual here, making sure there
+		 * are no LevelII oplocks remaining, as those may not
+		 * coexist with the exclusive oplock were're creating
+		 * in this call.  Also, if the passed in ofile has a
+		 * LevelII oplock, don't do an "ind break" up call on
+		 * this ofile, as that would just cause an immediate
+		 * "break to none" of the oplock we'll grant here.
+		 * If there were other ofiles with LevelII oplocks,
+		 * it would be appropriate to "ind break" those.
+		 */
+		if ((node->n_oplock.ol_state & LEVEL_TWO) != 0) {
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_II == 0)
+					continue;
+				o->f_oplock.onlist_II = B_FALSE;
+				node->n_oplock.cnt_II--;
+				ASSERT(node->n_oplock.cnt_II >= 0);
+				if (o == ofile)
+					continue;
+				DTRACE_PROBE1(unexpected, smb_ofile_t, o);
+				smb_oplock_ind_break(o,
+				    LEVEL_NONE, B_FALSE,
+				    NT_STATUS_SUCCESS);
+			}
+		}
+
+		/*
+		 * Note the spec. had an extra "EndIf" here.
+		 * Confirmed by dochelp@ms
+		 */
+
+		/*
+		 * If Open.File.OpenList contains more than one Open whose
+		 * Stream is the same as Open.Stream, and NO_OPLOCK is present
+		 * in Open.Stream.Oplock.State, the operation MUST be failed
+		 * with Status set to STATUS_OPLOCK_NOT_GRANTED.
+		 *
+		 * Implementation specific:
+		 * Allow other opens if they have the same lease ours,
+		 * so we can upgrade RH to RWH (for example). Therefore
+		 * only count opens with a different TargetOplockKey.
+		 * Also ignore "attribute-only" opens.
+		 */
+		if ((node->n_oplock.ol_state & NO_OPLOCK) != 0) {
+			FOREACH_NODE_OFILE(node, o) {
+				if (!smb_ofile_is_open(o))
+					continue;
+				if ((o->f_granted_access & FILE_DATA_ALL) == 0)
+					continue;
+				if (!CompareOplockKeys(ofile, o, 0)) {
+					status = NT_STATUS_OPLOCK_NOT_GRANTED;
+					goto out;
+				}
+			}
+		}
+
+		/*
+		 * If Open.Stream.IsDeleted is TRUE and RequestedOplock
+		 * contains HANDLE_CACHING, the operation MUST be failed
+		 * with Status set to STATUS_OPLOCK_NOT_GRANTED.
+		 */
+		if (((node->flags & NODE_FLAGS_DELETING) != 0) &&
+		    (*rop & HANDLE_CACHING) != 0) {
+			status = NT_STATUS_OPLOCK_NOT_GRANTED;
+			goto out;
+		}
+
+		/* Set GrantExclusiveOplock to TRUE. */
+		GrantExcl = B_TRUE;
+	}
+
+	/*
+	 * "Else" If (Open.Stream.Oplock.State contains one or more of
+	 * READ_CACHING, WRITE_CACHING, or HANDLE_CACHING) and
+	 * (Open.Stream.Oplock.State contains none of (BREAK_ANY)) and
+	 * (Open.Stream.Oplock.RHBreakQueue is empty):
+	 */
+	else if ((node->n_oplock.ol_state & CACHE_RWH) != 0 &&
+	    (node->n_oplock.ol_state & BREAK_ANY) == 0 &&
+	    node->n_oplock.cnt_RHBQ == 0) {
+
+		/*
+		 * This is a granular oplock and it is not breaking.
+		 */
+
+		/*
+		 * If RequestedOplock contains none of READ_CACHING,
+		 * WRITE_CACHING, or HANDLE_CACHING, the operation
+		 * MUST be failed with Status set to
+		 * STATUS_OPLOCK_NOT_GRANTED.
+		 */
+		if ((*rop & CACHE_RWH) == 0) {
+			status = NT_STATUS_OPLOCK_NOT_GRANTED;
+			goto out;
+		}
+
+		/*
+		 * If Open.Stream.IsDeleted (already checked above)
+		 */
+
+		/*
+		 * Switch (Open.Stream.Oplock.State):
+		 */
+		switch (node->n_oplock.ol_state) {
+
+		case CACHE_R:
+			/*
+			 * If RequestedOplock is neither
+			 * (READ_CACHING|WRITE_CACHING) nor
+			 * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING),
+			 * the operation MUST be failed with Status set
+			 * to STATUS_OPLOCK_NOT_GRANTED.
+			 */
+			if (*rop != CACHE_RW && *rop != CACHE_RWH) {
+				status = NT_STATUS_OPLOCK_NOT_GRANTED;
+				goto out;
+			}
+
+			/*
+			 * For each Open ThisOpen in
+			 *  Open.Stream.Oplock.ROplocks:
+			 *	If ThisOpen.TargetOplockKey !=
+			 *	Open.TargetOplockKey, the operation
+			 *	MUST be failed with Status set to
+			 *	STATUS_OPLOCK_NOT_GRANTED.
+			 * EndFor
+			 */
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_R == 0)
+					continue;
+				if (!CompareOplockKeys(ofile, o, 0)) {
+					status = NT_STATUS_OPLOCK_NOT_GRANTED;
+					goto out;
+				}
+			}
+
+			/*
+			 * For each Open o in Open.Stream.Oplock.ROplocks:
+			 *	Remove o from Open.Stream.Oplock.ROplocks.
+			 *	Notify the server of an oplock break
+			 *	according to the algorithm in section
+			 *	2.1.5.17.3, setting the algorithm's
+			 *	parameters as follows:
+			 *		BreakingOplockOpen = o.
+			 *		NewOplockLevel = RequestedOplock.
+			 *		AcknowledgeRequired = FALSE.
+			 *		OplockCompletionStatus =
+			 *		  STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE.
+			 *	(The operation does not end at this point;
+			 *	 this call to 2.1.5.17.3 completes some
+			 *	 earlier call to 2.1.5.17.2.)
+			 * EndFor
+			 *
+			 * Note: Upgrade to excl. on same lease.
+			 * Won't send a break for this.
+			 */
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_R == 0)
+					continue;
+				o->f_oplock.onlist_R = B_FALSE;
+				node->n_oplock.cnt_R--;
+				ASSERT(node->n_oplock.cnt_R >= 0);
+
+				smb_oplock_ind_break(o, *rop,
+				    B_FALSE, STATUS_NEW_HANDLE);
+			}
+			/*
+			 * Set GrantExclusiveOplock to TRUE.
+			 * EndCase // _R
+			 */
+			GrantExcl = B_TRUE;
+			break;
+
+		case CACHE_RH:
+			/*
+			 * If RequestedOplock is not
+			 * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING)
+			 * or Open.Stream.Oplock.RHBreakQueue is not empty,
+			 * the operation MUST be failed with Status set to
+			 * STATUS_OPLOCK_NOT_GRANTED.
+			 * Note: Have RHBreakQueue==0 from above.
+			 */
+			if (*rop != CACHE_RWH) {
+				status = NT_STATUS_OPLOCK_NOT_GRANTED;
+				goto out;
+			}
+
+			/*
+			 * For each Open ThisOpen in
+			 *  Open.Stream.Oplock.RHOplocks:
+			 *	If ThisOpen.TargetOplockKey !=
+			 *	Open.TargetOplockKey, the operation
+			 *	MUST be failed with Status set to
+			 *	STATUS_OPLOCK_NOT_GRANTED.
+			 * EndFor
+			 */
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_RH == 0)
+					continue;
+				if (!CompareOplockKeys(ofile, o, 0)) {
+					status = NT_STATUS_OPLOCK_NOT_GRANTED;
+					goto out;
+				}
+			}
+
+			/*
+			 * For each Open o in Open.Stream.Oplock.RHOplocks:
+			 *	Remove o from Open.Stream.Oplock.RHOplocks.
+			 *	Notify the server of an oplock break
+			 *	according to the algorithm in section
+			 *	2.1.5.17.3, setting the algorithm's
+			 *	parameters as follows:
+			 *		BreakingOplockOpen = o.
+			 *		NewOplockLevel = RequestedOplock.
+			 *		AcknowledgeRequired = FALSE.
+			 *		OplockCompletionStatus =
+			 *		  STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE.
+			 *	(The operation does not end at this point;
+			 *	 this call to 2.1.5.17.3 completes some
+			 *	 earlier call to 2.1.5.17.2.)
+			 * EndFor
+			 *
+			 * Note: Upgrade to excl. on same lease.
+			 * Won't send a break for this.
+			 */
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_RH == 0)
+					continue;
+				o->f_oplock.onlist_RH = B_FALSE;
+				node->n_oplock.cnt_RH--;
+				ASSERT(node->n_oplock.cnt_RH >= 0);
+
+				smb_oplock_ind_break(o, *rop,
+				    B_FALSE, STATUS_NEW_HANDLE);
+			}
+			/*
+			 * Set GrantExclusiveOplock to TRUE.
+			 * EndCase // _RH
+			 */
+			GrantExcl = B_TRUE;
+			break;
+
+		case (CACHE_RWH | EXCLUSIVE):
+			/*
+			 * If RequestedOplock is not
+			 * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING),
+			 * the operation MUST be failed with Status set to
+			 * STATUS_OPLOCK_NOT_GRANTED.
+			 */
+			if (*rop != CACHE_RWH) {
+				status = NT_STATUS_OPLOCK_NOT_GRANTED;
+				goto out;
+			}
+			/* Deliberate FALL-THROUGH to next Case statement. */
+			/* FALLTHROUGH */
+
+		case (CACHE_RW | EXCLUSIVE):
+			/*
+			 * If RequestedOplock is neither
+			 * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING) nor
+			 * (READ_CACHING|WRITE_CACHING), the operation MUST be
+			 * failed with Status set to STATUS_OPLOCK_NOT_GRANTED.
+			 */
+			if (*rop != CACHE_RWH && *rop != CACHE_RW) {
+				status = NT_STATUS_OPLOCK_NOT_GRANTED;
+				goto out;
+			}
+
+			o = node->n_oplock.excl_open;
+			if (o == NULL) {
+				ASSERT(0);
+				GrantExcl = B_TRUE;
+				break;
+			}
+
+			/*
+			 * If Open.TargetOplockKey !=
+			 * Open.Stream.Oplock.ExclusiveOpen.TargetOplockKey,
+			 * the operation MUST be failed with Status set to
+			 * STATUS_OPLOCK_NOT_GRANTED.
+			 */
+			if (!CompareOplockKeys(ofile, o, 0)) {
+				status = NT_STATUS_OPLOCK_NOT_GRANTED;
+				goto out;
+			}
+
+			/*
+			 * Notify the server of an oplock break according to
+			 * the algorithm in section 2.1.5.17.3, setting the
+			 * algorithm's parameters as follows:
+			 *	BreakingOplockOpen =
+			 *	  Open.Stream.Oplock.ExclusiveOpen.
+			 *	NewOplockLevel = RequestedOplock.
+			 *	AcknowledgeRequired = FALSE.
+			 *	OplockCompletionStatus =
+			 *	  STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE.
+			 * (The operation does not end at this point;
+			 *  this call to 2.1.5.17.3 completes some
+			 *  earlier call to 2.1.5.17.1.)
+			 *
+			 * Set Open.Stream.Oplock.ExclusiveOpen to NULL.
+			 * Set GrantExclusiveOplock to TRUE.
+			 *
+			 * Note: We will keep this exclusive oplock,
+			 * but move it to a new handle on this lease.
+			 * Won't send a break for this.
+			 */
+			smb_oplock_ind_break(o, *rop,
+			    B_FALSE, STATUS_NEW_HANDLE);
+			node->n_oplock.excl_open = o = NULL;
+			GrantExcl = B_TRUE;
+			break;
+
+		default:
+			/*
+			 * The operation MUST be failed with Status set to
+			 * STATUS_OPLOCK_NOT_GRANTED.
+			 */
+			status = NT_STATUS_OPLOCK_NOT_GRANTED;
+			goto out;
+
+		} /* switch n_oplock.ol_state */
+	} /* EndIf CACHE_RWH & !BREAK_ANY... */
+	else {
+		/*
+		 * The operation MUST be failed with...
+		 */
+		status = NT_STATUS_OPLOCK_NOT_GRANTED;
+		goto out;
+	}
+
+	/*
+	 * If GrantExclusiveOplock is TRUE:
+	 *
+	 * Set Open.Stream.Oplock.ExclusiveOpen = Open.
+	 * Set Open.Stream.Oplock.State =
+	 *   (RequestedOplock|EXCLUSIVE).
+	 */
+	if (GrantExcl) {
+		node->n_oplock.excl_open = ofile;
+		node->n_oplock.ol_state = *rop | EXCLUSIVE;
+
+		/*
+		 * This operation MUST be made cancelable...
+		 * This operation waits until the oplock is
+		 * broken or canceled, as specified in
+		 * section 2.1.5.17.3.
+		 *
+		 * When the operation specified in section
+		 * 2.1.5.17.3 is called, its following input
+		 * parameters are transferred to this routine
+		 * and then returned by it:
+		 *
+		 * Status is set to OplockCompletionStatus
+		 * NewOplockLevel, AcknowledgeRequired...
+		 * from the operation specified in
+		 * section 2.1.5.17.3.
+		 */
+		/* Keep *rop = ... from caller. */
+		if ((node->n_oplock.ol_state & BREAK_ANY) != 0) {
+			status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS;
+			/* Caller does smb_oplock_wait_break() */
+		} else {
+			status = NT_STATUS_SUCCESS;
+		}
+	}
+
+out:
+	if (status == NT_STATUS_OPLOCK_NOT_GRANTED)
+		*rop = LEVEL_NONE;
+
+	return (status);
+}
+
+/*
+ * 2.1.5.17.2 Algorithm to Request a Shared Oplock
+ *
+ * The inputs for requesting a shared oplock are:
+ *	Open: The Open on which the oplock is being requested.
+ *	RequestedOplock: The oplock type being requested.
+ *	GrantingInAck: A Boolean value, TRUE if this oplock is being
+ *	  requested as part of an oplock break acknowledgement,
+ *	  FALSE if not.
+ *
+ * On completion, the object store MUST return:
+ *	Status: An NTSTATUS code that specifies the result.
+ *	NewOplockLevel: The type of oplock that the requested oplock has been
+ *	  broken (reduced) to.  If a failure status is returned in Status,
+ *	  the value of this field is undefined.  Valid values are as follows:
+ *		LEVEL_NONE (that is, no oplock)
+ *		LEVEL_TWO
+ *		A combination of one or more of the following flags:
+ *			READ_CACHING
+ *			HANDLE_CACHING
+ *			WRITE_CACHING
+ *	AcknowledgeRequired: A Boolean value: TRUE if the server MUST
+ *	acknowledge the oplock break; FALSE if not, as specified in
+ *	section 2.1.5.18. If a failure status is returned in Status,
+ *	the value of this field is undefined.
+ *
+ * Note: Stores NewOplockLevel in *rop
+ */
+static uint32_t
+smb_oplock_req_shared(
+    smb_ofile_t *ofile,		/* in: the "Open" */
+    uint32_t *rop,		/* in: "RequestedOplock", out:NewOplockLevel */
+    boolean_t GrantingInAck)
+{
+	smb_node_t *node = ofile->f_node;
+	smb_ofile_t *o;
+	boolean_t OplockGranted = B_FALSE;
+	uint32_t status = NT_STATUS_OPLOCK_NOT_GRANTED;
+
+	ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock));
+	ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
+
+	/*
+	 * If Open.Stream.Oplock is empty:
+	 *   Build a new Oplock object with fields initialized as follows:
+	 *	Oplock.State set to NO_OPLOCK.
+	 *	All other fields set to 0/empty.
+	 *   Store the new Oplock object in Open.Stream.Oplock.
+	 * EndIf
+	 *
+	 * Implementation specific:
+	 * Open.Stream.Oplock maps to: node->n_oplock
+	 */
+	if (node->n_oplock.ol_state == 0) {
+		node->n_oplock.ol_state = NO_OPLOCK;
+	}
+
+	/*
+	 * If (GrantingInAck is FALSE) and (Open.Stream.Oplock.State
+	 * contains one or more of BREAK_TO_TWO, BREAK_TO_NONE,
+	 * BREAK_TO_TWO_TO_NONE, BREAK_TO_READ_CACHING,
+	 * BREAK_TO_WRITE_CACHING, BREAK_TO_HANDLE_CACHING,
+	 * BREAK_TO_NO_CACHING, or EXCLUSIVE), then:
+	 *	The operation MUST be failed with Status set to
+	 *	STATUS_OPLOCK_NOT_GRANTED.
+	 * EndIf
+	 */
+	if (GrantingInAck == B_FALSE &&
+	    (node->n_oplock.ol_state & (BREAK_ANY | EXCLUSIVE)) != 0) {
+		status = NT_STATUS_OPLOCK_NOT_GRANTED;
+		goto out;
+	}
+
+	/* Switch (RequestedOplock): */
+	switch (*rop) {
+
+	case LEVEL_TWO:
+		/*
+		 * The operation MUST be failed with Status set to
+		 * STATUS_OPLOCK_NOT_GRANTED if Open.Stream.Oplock.State
+		 * is anything other than the following:
+		 *	NO_OPLOCK
+		 *	LEVEL_TWO_OPLOCK
+		 *	READ_CACHING
+		 *	(LEVEL_TWO_OPLOCK|READ_CACHING)
+		 */
+		switch (node->n_oplock.ol_state) {
+		default:
+			status = NT_STATUS_OPLOCK_NOT_GRANTED;
+			goto out;
+		case NO_OPLOCK:
+		case LEVEL_TWO:
+		case READ_CACHING:
+		case (LEVEL_TWO | READ_CACHING):
+			break;
+		}
+		/* Deliberate FALL-THROUGH to next Case statement. */
+		/* FALLTHROUGH */
+
+	case READ_CACHING:
+		/*
+		 * The operation MUST be failed with Status set to
+		 * STATUS_OPLOCK_NOT_GRANTED if GrantingInAck is FALSE
+		 * and Open.Stream.Oplock.State is anything other than...
+		 */
+		switch (node->n_oplock.ol_state) {
+		default:
+			if (GrantingInAck == B_FALSE) {
+				status = NT_STATUS_OPLOCK_NOT_GRANTED;
+				goto out;
+			}
+			break;
+		case NO_OPLOCK:
+		case LEVEL_TWO:
+		case READ_CACHING:
+		case (LEVEL_TWO | READ_CACHING):
+		case (READ_CACHING | HANDLE_CACHING):
+		case (READ_CACHING | HANDLE_CACHING | MIXED_R_AND_RH):
+		case (READ_CACHING | HANDLE_CACHING | BREAK_TO_READ_CACHING):
+		case (READ_CACHING | HANDLE_CACHING | BREAK_TO_NO_CACHING):
+			break;
+		}
+
+		if (GrantingInAck == B_FALSE) {
+			/*
+			 * If there is an Open on
+			 * Open.Stream.Oplock.RHOplocks
+			 * whose TargetOplockKey is equal to
+			 * Open.TargetOplockKey, the operation
+			 * MUST be failed with Status set to
+			 * STATUS_OPLOCK_NOT_GRANTED.
+			 *
+			 * If there is an Open on
+			 * Open.Stream.Oplock.RHBreakQueue
+			 * whose TargetOplockKey is equal to
+			 * Open.TargetOplockKey, the operation
+			 * MUST be failed with Status set to
+			 * STATUS_OPLOCK_NOT_GRANTED.
+			 *
+			 * Implement both in one list walk.
+			 */
+			FOREACH_NODE_OFILE(node, o) {
+				if ((o->f_oplock.onlist_RH ||
+				    o->f_oplock.onlist_RHBQ) &&
+				    CompareOplockKeys(ofile, o, 0)) {
+					status = NT_STATUS_OPLOCK_NOT_GRANTED;
+					goto out;
+				}
+			}
+
+			/*
+			 * If there is an Open ThisOpen on
+			 * Open.Stream.Oplock.ROplocks whose
+			 * TargetOplockKey is equal to Open.TargetOplockKey
+			 * (there is supposed to be at most one present):
+			 *	* Remove ThisOpen from Open...ROplocks.
+			 *	* Notify the server of an oplock break
+			 *	  according to the algorithm in section
+			 *	  2.1.5.17.3, setting the algorithm's
+			 *	  parameters as follows:
+			 *		* BreakingOplockOpen = ThisOpen
+			 *		* NewOplockLevel = READ_CACHING
+			 *		* AcknowledgeRequired = FALSE
+			 *		* OplockCompletionStatus =
+			 *		  STATUS_..._NEW_HANDLE
+			 * (The operation does not end at this point;
+			 *  this call to 2.1.5.17.3 completes some
+			 *  earlier call to 2.1.5.17.2.)
+			 * EndIf
+			 *
+			 * If this SMB2 lease already has an "R" handle,
+			 * we'll update that lease locally to point to
+			 * this new handle.
+			 */
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_R == 0)
+					continue;
+				if (CompareOplockKeys(ofile, o, 0)) {
+					o->f_oplock.onlist_R = B_FALSE;
+					node->n_oplock.cnt_R--;
+					ASSERT(node->n_oplock.cnt_R >= 0);
+					smb_oplock_ind_break(o,
+					    CACHE_R, B_FALSE,
+					    STATUS_NEW_HANDLE);
+				}
+			}
+		} /* EndIf !GrantingInAck */
+
+		/*
+		 * If RequestedOplock equals LEVEL_TWO:
+		 *	Add Open to Open.Stream.Oplock.IIOplocks.
+		 * Else // RequestedOplock equals READ_CACHING:
+		 *	Add Open to Open.Stream.Oplock.ROplocks.
+		 * EndIf
+		 */
+		if (*rop == LEVEL_TWO) {
+			ofile->f_oplock.onlist_II = B_TRUE;
+			node->n_oplock.cnt_II++;
+		} else {
+			/* (*rop == READ_CACHING) */
+			if (ofile->f_oplock.onlist_R == B_FALSE) {
+				ofile->f_oplock.onlist_R = B_TRUE;
+				node->n_oplock.cnt_R++;
+			}
+		}
+
+		/*
+		 * Recompute Open.Stream.Oplock.State according to the
+		 * algorithm in section 2.1.4.13, passing Open.Stream.Oplock
+		 * as the ThisOplock parameter.
+		 * Set OplockGranted to TRUE.
+		 */
+		RecomputeOplockState(node);
+		OplockGranted = B_TRUE;
+		break;
+
+	case (READ_CACHING|HANDLE_CACHING):
+		/*
+		 * The operation MUST be failed with Status set to
+		 * STATUS_OPLOCK_NOT_GRANTED if GrantingInAck is FALSE
+		 * and Open.Stream.Oplock.State is anything other than...
+		 */
+		switch (node->n_oplock.ol_state) {
+		default:
+			if (GrantingInAck == B_FALSE) {
+				status = NT_STATUS_OPLOCK_NOT_GRANTED;
+				goto out;
+			}
+			break;
+		case NO_OPLOCK:
+		case READ_CACHING:
+		case (READ_CACHING | HANDLE_CACHING):
+		case (READ_CACHING | HANDLE_CACHING | MIXED_R_AND_RH):
+		case (READ_CACHING | HANDLE_CACHING | BREAK_TO_READ_CACHING):
+		case (READ_CACHING | HANDLE_CACHING | BREAK_TO_NO_CACHING):
+			break;
+		}
+
+		/*
+		 * If Open.Stream.IsDeleted is TRUE, the operation MUST be
+		 *  failed with Status set to STATUS_OPLOCK_NOT_GRANTED.
+		 */
+		if ((node->flags & NODE_FLAGS_DELETING) != 0) {
+			status = NT_STATUS_OPLOCK_NOT_GRANTED;
+			goto out;
+		}
+
+		if (GrantingInAck == B_FALSE) {
+			/*
+			 * If there is an Open ThisOpen on
+			 * Open.Stream.Oplock.ROplocks whose
+			 * TargetOplockKey is equal to Open.TargetOplockKey
+			 * (there is supposed to be at most one present):
+			 *	* Remove ThisOpen from Open...ROplocks.
+			 *	* Notify the server of an oplock break
+			 *	  according to the algorithm in section
+			 *	  2.1.5.17.3, setting the algorithm's
+			 *	  parameters as follows:
+			 *		* BreakingOplockOpen = ThisOpen
+			 *		* NewOplockLevel = CACHE_RH
+			 *		* AcknowledgeRequired = FALSE
+			 *		* OplockCompletionStatus =
+			 *		  STATUS_..._NEW_HANDLE
+			 * (The operation does not end at this point;
+			 *  this call to 2.1.5.17.3 completes some
+			 *  earlier call to 2.1.5.17.2.)
+			 * EndIf
+			 *
+			 * If this SMB2 lease already has an "R" handle,
+			 * we'll update that lease locally to point to
+			 * this new handle (upgrade to "RH").
+			 */
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_R == 0)
+					continue;
+				if (CompareOplockKeys(ofile, o, 0)) {
+					o->f_oplock.onlist_R = B_FALSE;
+					node->n_oplock.cnt_R--;
+					ASSERT(node->n_oplock.cnt_R >= 0);
+					smb_oplock_ind_break(o,
+					    CACHE_RH, B_FALSE,
+					    STATUS_NEW_HANDLE);
+				}
+			}
+
+			/*
+			 * If there is an Open ThisOpen on
+			 * Open.Stream.Oplock.RHOplocks whose
+			 * TargetOplockKey is equal to Open.TargetOplockKey
+			 * (there is supposed to be at most one present):
+			 *	XXX: Note, the spec. was missing a step:
+			 *	XXX: Remove the open from RHOplocks
+			 *	XXX: Confirm with MS dochelp
+			 *	* Notify the server of an oplock break
+			 *	  according to the algorithm in section
+			 *	  2.1.5.17.3, setting the algorithm's
+			 *	  parameters as follows:
+			 *		* BreakingOplockOpen = ThisOpen
+			 *		* NewOplockLevel =
+			 *		  (READ_CACHING|HANDLE_CACHING)
+			 *		* AcknowledgeRequired = FALSE
+			 *		* OplockCompletionStatus =
+			 *		  STATUS_..._NEW_HANDLE
+			 * (The operation does not end at this point;
+			 *  this call to 2.1.5.17.3 completes some
+			 *  earlier call to 2.1.5.17.2.)
+			 * EndIf
+			 *
+			 * If this SMB2 lease already has an "RH" handle,
+			 * we'll update that lease locally to point to
+			 * this new handle.
+			 */
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_RH == 0)
+					continue;
+				if (CompareOplockKeys(ofile, o, 0)) {
+					o->f_oplock.onlist_RH = B_FALSE;
+					node->n_oplock.cnt_RH--;
+					ASSERT(node->n_oplock.cnt_RH >= 0);
+					smb_oplock_ind_break(o,
+					    CACHE_RH, B_FALSE,
+					    STATUS_NEW_HANDLE);
+				}
+			}
+		} /* EndIf !GrantingInAck */
+
+		/*
+		 * Add Open to Open.Stream.Oplock.RHOplocks.
+		 */
+		if (ofile->f_oplock.onlist_RH == B_FALSE) {
+			ofile->f_oplock.onlist_RH = B_TRUE;
+			node->n_oplock.cnt_RH++;
+		}
+
+		/*
+		 * Recompute Open.Stream.Oplock.State according to the
+		 * algorithm in section 2.1.4.13, passing Open.Stream.Oplock
+		 * as the ThisOplock parameter.
+		 * Set OplockGranted to TRUE.
+		 */
+		RecomputeOplockState(node);
+		OplockGranted = B_TRUE;
+		break;
+
+	default:
+		/* No other value of RequestedOplock is possible. */
+		ASSERT(0);
+		status = NT_STATUS_OPLOCK_NOT_GRANTED;
+		goto out;
+	}  /* EndSwitch (RequestedOplock) */
+
+	/*
+	 * If OplockGranted is TRUE:
+	 * This operation MUST be made cancelable by inserting it into
+	 *   CancelableOperations.CancelableOperationList.
+	 * The operation waits until the oplock is broken or canceled,
+	 * as specified in section 2.1.5.17.3.
+	 * When the operation specified in section 2.1.5.17.3 is called,
+	 * its following input parameters are transferred to this routine
+	 * and returned by it:
+	 *	Status is set to OplockCompletionStatus from the
+	 *	  operation specified in section 2.1.5.17.3.
+	 *	NewOplockLevel is set to NewOplockLevel from the
+	 *	  operation specified in section 2.1.5.17.3.
+	 *	AcknowledgeRequired is set to AcknowledgeRequired from
+	 *	  the operation specified in section 2.1.5.17.3.
+	 * EndIf
+	 */
+	if (OplockGranted) {
+		/* Note: *rop already set. */
+		if ((node->n_oplock.ol_state & BREAK_ANY) != 0) {
+			status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS;
+			/* Caller does smb_oplock_wait_break() */
+		} else {
+			status = NT_STATUS_SUCCESS;
+		}
+	}
+
+out:
+	if (status == NT_STATUS_OPLOCK_NOT_GRANTED)
+		*rop = LEVEL_NONE;
+
+	return (status);
+}
+
+/*
+ * 2.1.5.17.3 Indicating an Oplock Break to the Server
+ * See smb_srv_oplock.c
+ */
+
+/*
+ * 2.1.5.18 Server Acknowledges an Oplock Break
+ *
+ * The server provides:
+ *	Open - The Open associated with the oplock that has broken.
+ *	Type - As part of the acknowledgement, the server indicates a
+ *	  new oplock it would like in place of the one that has broken.
+ *	  Valid values are as follows:
+ *		LEVEL_NONE
+ *		LEVEL_TWO
+ *		LEVEL_GRANULAR - If this oplock type is specified,
+ *		  the server additionally provides:
+ *	RequestedOplockLevel - A combination of zero or more of
+ *	  the following flags:
+ *		READ_CACHING
+ *		HANDLE_CACHING
+ *		WRITE_CACHING
+ *
+ * If the server requests a new oplock and it is granted, the request
+ * does not complete until the oplock is broken; the operation waits for
+ * this to happen. Processing of an oplock break is described in
+ * section 2.1.5.17.3.  Whether the new oplock is granted or not, the
+ * object store MUST return:
+ *
+ *	Status - An NTSTATUS code indicating the result of the operation.
+ *
+ * If the server requests a new oplock and it is granted, then when the
+ * oplock breaks and the request finally completes, the object store MUST
+ * additionally return:
+ *	NewOplockLevel: The type of oplock the requested oplock has
+ *	  been broken to. Valid values are as follows:
+ *		LEVEL_NONE (that is, no oplock)
+ *		LEVEL_TWO
+ *		A combination of one or more of the following flags:
+ *			READ_CACHING
+ *			HANDLE_CACHING
+ *			WRITE_CACHING
+ *	AcknowledgeRequired: A Boolean value; TRUE if the server MUST
+ *	  acknowledge the oplock break, FALSE if not, as specified in
+ *	  section 2.1.5.17.2.
+ *
+ * Note: Stores NewOplockLevel in *rop
+ */
+uint32_t
+smb_oplock_ack_break(
+    smb_request_t *sr,
+    smb_ofile_t *ofile,
+    uint32_t *rop)
+{
+	smb_node_t *node = ofile->f_node;
+	uint32_t type = *rop & OPLOCK_LEVEL_TYPE_MASK;
+	uint32_t level = *rop & OPLOCK_LEVEL_CACHE_MASK;
+	uint32_t status = NT_STATUS_SUCCESS;
+	uint32_t BreakToLevel;
+	boolean_t NewOplockGranted = B_FALSE;
+	boolean_t ReturnBreakToNone = B_FALSE;
+	boolean_t FoundMatchingRHOplock = B_FALSE;
+	int other_keys;
+
+	smb_llist_enter(&node->n_ofile_list, RW_READER);
+	mutex_enter(&node->n_oplock.ol_mutex);
+
+	/*
+	 * If Open.Stream.Oplock is empty, the operation MUST be
+	 * failed with Status set to STATUS_INVALID_OPLOCK_PROTOCOL.
+	 */
+	if (node->n_oplock.ol_state == 0) {
+		status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+		goto out;
+	}
+
+	if (type == LEVEL_NONE || type == LEVEL_TWO) {
+		/*
+		 * If Open.Stream.Oplock.ExclusiveOpen is not equal to Open,
+		 * the operation MUST be failed with Status set to
+		 * STATUS_INVALID_OPLOCK_PROTOCOL.
+		 */
+		if (node->n_oplock.excl_open != ofile) {
+			status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+			goto out;
+		}
+
+		/*
+		 * If Type is LEVEL_TWO and Open.Stream.Oplock.State
+		 * contains BREAK_TO_TWO:
+		 *	Set Open.Stream.Oplock.State to LEVEL_TWO_OPLOCK.
+		 *	Set NewOplockGranted to TRUE.
+		 */
+		if (type == LEVEL_TWO &&
+		    (node->n_oplock.ol_state & BREAK_TO_TWO) != 0) {
+			node->n_oplock.ol_state = LEVEL_TWO;
+			NewOplockGranted = B_TRUE;
+		}
+
+		/*
+		 * Else If Open.Stream.Oplock.State contains
+		 * BREAK_TO_TWO or BREAK_TO_NONE:
+		 *	Set Open.Stream.Oplock.State to NO_OPLOCK.
+		 */
+		else if ((node->n_oplock.ol_state &
+		    (BREAK_TO_TWO | BREAK_TO_NONE)) != 0) {
+			node->n_oplock.ol_state = NO_OPLOCK;
+		}
+
+		/*
+		 * Else If Open.Stream.Oplock.State contains
+		 * BREAK_TO_TWO_TO_NONE:
+		 *	Set Open.Stream.Oplock.State to NO_OPLOCK.
+		 *	Set ReturnBreakToNone to TRUE.
+		 */
+		else if ((node->n_oplock.ol_state &
+		    BREAK_TO_TWO_TO_NONE) != 0) {
+			node->n_oplock.ol_state = NO_OPLOCK;
+			ReturnBreakToNone = B_TRUE;
+		}
+
+		/*
+		 * Else
+		 *	The operation MUST be failed with Status set to
+		 *	STATUS_INVALID_OPLOCK_PROTOCOL.
+		 */
+		else {
+			status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+			goto out;
+		}
+
+		/*
+		 * For each Open WaitingOpen on Open.Stream.Oplock.WaitList:
+		 *	Indicate that the operation associated with
+		 *	  WaitingOpen can continue according to the
+		 *	  algorithm in section 2.1.4.12.1, setting
+		 *	  OpenToRelease = WaitingOpen.
+		 *	Remove WaitingOpen from Open.Stream.Oplock.WaitList.
+		 * EndFor
+		 */
+		if (node->n_oplock.waiters)
+			cv_broadcast(&node->n_oplock.WaitingOpenCV);
+
+		/*
+		 * Set Open.Stream.Oplock.ExclusiveOpen to NULL.
+		 */
+		node->n_oplock.excl_open = NULL;
+
+		if (NewOplockGranted) {
+			/*
+			 * The operation waits until the newly-granted
+			 * Level 2 oplock is broken, as specified in
+			 * section 2.1.5.17.3.
+			 *
+			 * Here we have just Ack'ed a break-to-II
+			 * so now get the level II oplock.  We also
+			 * checked for break-to-none above, so this
+			 * will not need to wait for oplock breaks.
+			 */
+			status = smb_oplock_req_shared(ofile, rop, B_TRUE);
+		}
+
+		else if (ReturnBreakToNone) {
+			/*
+			 * In this case the server was expecting the oplock
+			 * to break to Level 2, but because the oplock is
+			 * actually breaking to None (that is, no oplock),
+			 * the object store MUST indicate an oplock break
+			 * to the server according to the algorithm in
+			 * section 2.1.5.17.3, setting the algorithm's
+			 * parameters as follows:
+			 *	BreakingOplockOpen = Open.
+			 *	NewOplockLevel = LEVEL_NONE.
+			 *	AcknowledgeRequired = FALSE.
+			 *	OplockCompletionStatus = STATUS_SUCCESS.
+			 * (Because BreakingOplockOpen is equal to the
+			 * passed-in Open, the operation ends at this point.)
+			 *
+			 * It should be OK to return the reduced oplock
+			 * (*rop = LEVEL_NONE) here and avoid the need
+			 * to send another oplock break.  This is safe
+			 * because we already have an Ack of the break
+			 * to Level_II, and the additional break to none
+			 * would use AckRequired = FALSE.
+			 *
+			 * If we followed the spec here, we'd have:
+			 * smb_oplock_ind_break(ofile,
+			 *    LEVEL_NONE, B_FALSE,
+			 *    NT_STATUS_SUCCESS);
+			 * (Or smb_oplock_ind_break_in_ack...)
+			 */
+			*rop = LEVEL_NONE;	/* Reduced from L2 */
+		}
+		status = NT_STATUS_SUCCESS;
+		goto out;
+	} /* LEVEL_NONE or LEVEL_TWO */
+
+	if (type != LEVEL_GRANULAR) {
+		status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+		goto out;
+	}
+
+	/* LEVEL_GRANULAR */
+
+	/*
+	 * Let BREAK_LEVEL_MASK = (BREAK_TO_READ_CACHING |
+	 *   BREAK_TO_WRITE_CACHING | BREAK_TO_HANDLE_CACHING |
+	 *   BREAK_TO_NO_CACHING),
+	 * R_AND_RH_GRANTED = (READ_CACHING | HANDLE_CACHING |
+	 *   MIXED_R_AND_RH),
+	 * RH_GRANTED = (READ_CACHING | HANDLE_CACHING)
+	 *
+	 * (See BREAK_LEVEL_MASK in smb_oplock.h)
+	 */
+#define	RH_GRANTED		(READ_CACHING|HANDLE_CACHING)
+#define	R_AND_RH_GRANTED	(RH_GRANTED|MIXED_R_AND_RH)
+
+	/*
+	 * If there are no BREAK_LEVEL_MASK flags set, this is invalid,
+	 * unless the state is R_AND_RH_GRANTED or RH_GRANTED, in which
+	 * case we'll need to see if the RHBreakQueue is empty.
+	 */
+
+	/*
+	 * If (Open.Stream.Oplock.State does not contain any flag in
+	 * BREAK_LEVEL_MASK and
+	 *  (Open.Stream.Oplock.State != R_AND_RH_GRANTED) and
+	 *   (Open.Stream.Oplock.State != RH_GRANTED)) or
+	 *   (((Open.Stream.Oplock.State == R_AND_RH_GRANTED) or
+	 *  (Open.Stream.Oplock.State == RH_GRANTED)) and
+	 *   Open.Stream.Oplock.RHBreakQueue is empty):
+	 *	The request MUST be failed with Status set to
+	 *	  STATUS_INVALID_OPLOCK_PROTOCOL.
+	 * EndIf
+	 */
+	if ((node->n_oplock.ol_state & BREAK_LEVEL_MASK) == 0) {
+		if ((node->n_oplock.ol_state != R_AND_RH_GRANTED) &&
+		    (node->n_oplock.ol_state != RH_GRANTED)) {
+			status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+			goto out;
+		}
+		/* State is R_AND_RH_GRANTED or RH_GRANTED */
+		if (node->n_oplock.cnt_RHBQ == 0) {
+			status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+			goto out;
+		}
+	}
+
+	/*
+	 * Compute the "Break To" cache level from the
+	 * BREAK_TO_... flags
+	 */
+	switch (node->n_oplock.ol_state & BREAK_LEVEL_MASK) {
+	case (BREAK_TO_READ_CACHING | BREAK_TO_WRITE_CACHING |
+	    BREAK_TO_HANDLE_CACHING):
+		BreakToLevel = CACHE_RWH;
+		break;
+	case (BREAK_TO_READ_CACHING | BREAK_TO_WRITE_CACHING):
+		BreakToLevel = CACHE_RW;
+		break;
+	case (BREAK_TO_READ_CACHING | BREAK_TO_HANDLE_CACHING):
+		BreakToLevel = CACHE_RH;
+		break;
+	case BREAK_TO_READ_CACHING:
+		BreakToLevel = READ_CACHING;
+		break;
+	case BREAK_TO_NO_CACHING:
+	default:
+		BreakToLevel = LEVEL_NONE;
+		break;
+	}
+
+	/* Switch Open.Stream.Oplock.State */
+	switch (node->n_oplock.ol_state) {
+
+	case (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH):
+	case (READ_CACHING|HANDLE_CACHING):
+	case (READ_CACHING|HANDLE_CACHING|BREAK_TO_READ_CACHING):
+	case (READ_CACHING|HANDLE_CACHING|BREAK_TO_NO_CACHING):
+		/*
+		 * For each RHOpContext ThisContext in
+		 * Open.Stream.Oplock.RHBreakQueue:
+		 *	If ThisContext.Open equals Open:
+		 *		(see below)
+		 *
+		 * Implementation skips the list walk, because
+		 * we can get the ofile directly.
+		 */
+		if (ofile->f_oplock.onlist_RHBQ) {
+			smb_ofile_t *o;
+
+			/*
+			 * Set FoundMatchingRHOplock to TRUE.
+			 * If ThisContext.BreakingToRead is FALSE:
+			 *	If RequestedOplockLevel is not 0 and
+			 *	Open.Stream.Oplock.WaitList is not empty:
+			 *	    The object store MUST indicate an
+			 *	    oplock break to the server according to
+			 *	    the algorithm in section 2.1.5.17.3,
+			 *	    setting the algorithm's params as follows:
+			 *		BreakingOplockOpen = Open.
+			 *		NewOplockLevel = LEVEL_NONE.
+			 *		AcknowledgeRequired = TRUE.
+			 *		OplockCompletionStatus =
+			 *		  STATUS_CANNOT_GRANT_...
+			 *  (Because BreakingOplockOpen is equal to the
+			 *   passed Open, the operation ends at this point.)
+			 * EndIf
+			 */
+			FoundMatchingRHOplock = B_TRUE;
+			if (ofile->f_oplock.BreakingToRead == B_FALSE) {
+				if (level != 0 && node->n_oplock.waiters) {
+					/* The ofile stays on RHBQ */
+					smb_oplock_ind_break_in_ack(
+					    sr, ofile,
+					    LEVEL_NONE, B_TRUE);
+					status = NT_STATUS_SUCCESS;
+					goto out;
+				}
+			}
+
+			/*
+			 * Else // ThisContext.BreakingToRead is TRUE.
+			 *    If Open.Stream.Oplock.WaitList is not empty and
+			 *    (RequestedOplockLevel is CACHE_RW or CACHE_RWH:
+			 *	The object store MUST indicate an oplock
+			 *	break to the server according to the
+			 *	algorithm in section 2.1.5.17.3, setting
+			 *	the algorithm's parameters as follows:
+			 *		* BreakingOplockOpen = Open
+			 *		* NewOplockLevel = READ_CACHING
+			 *		* AcknowledgeRequired = TRUE
+			 *		* OplockCompletionStatus =
+			 *		  STATUS_CANNOT_GRANT...
+			 *	(Because BreakingOplockOpen is equal to the
+			 *	 passed-in Open, the operation ends at this
+			 *	 point.)
+			 *    EndIf
+			 * EndIf
+			 */
+			else { /* BreakingToRead is TRUE */
+				if (node->n_oplock.waiters &&
+				    (level == CACHE_RW ||
+				    level == CACHE_RWH)) {
+					/* The ofile stays on RHBQ */
+					smb_oplock_ind_break_in_ack(
+					    sr, ofile,
+					    CACHE_R, B_TRUE);
+					status = NT_STATUS_SUCCESS;
+					goto out;
+				}
+			}
+
+			/*
+			 * Remove ThisContext from Open...RHBreakQueue.
+			 */
+			ofile->f_oplock.onlist_RHBQ = B_FALSE;
+			node->n_oplock.cnt_RHBQ--;
+			ASSERT(node->n_oplock.cnt_RHBQ >= 0);
+
+			/*
+			 * The operation waiting for the Read-Handle
+			 * oplock to break can continue if there are
+			 * no more Read-Handle oplocks outstanding, or
+			 * if all the remaining Read-Handle oplocks
+			 * have the same oplock key as the waiting
+			 * operation.
+			 *
+			 * For each Open WaitingOpen on Open...WaitList:
+			 *
+			 *	* If (Open...RHBreakQueue is empty) or
+			 *	  (all RHOpContext.Open.TargetOplockKey values
+			 *	  on Open.Stream.Oplock.RHBreakQueue are
+			 *	  equal to WaitingOpen.TargetOplockKey):
+			 *		* Indicate that the operation assoc.
+			 *		  with WaitingOpen can continue
+			 *		  according to the algorithm in
+			 *		  section 2.1.4.12.1, setting
+			 *		  OpenToRelease = WaitingOpen.
+			 *		* Remove WaitingOpen from
+			 *		  Open.Stream.Oplock.WaitList.
+			 *	* EndIf
+			 * EndFor
+			 */
+			other_keys = 0;
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_RHBQ == 0)
+					continue;
+				if (!CompareOplockKeys(ofile, o, 0))
+					other_keys++;
+			}
+			if (other_keys == 0)
+				cv_broadcast(&node->n_oplock.WaitingOpenCV);
+
+			/*
+			 * If RequestedOplockLevel is 0 (that is, no flags):
+			 *	* Recompute Open.Stream.Oplock.State
+			 *	  according to the algorithm in section
+			 *	  2.1.4.13, passing Open.Stream.Oplock as
+			 *	  the ThisOplock parameter.
+			 *	* The algorithm MUST return Status set to
+			 *	  STATUS_SUCCESS at this point.
+			 */
+			if (level == 0) {
+				RecomputeOplockState(node);
+				status = NT_STATUS_SUCCESS;
+				goto out;
+			}
+
+			/*
+			 * Else If RequestedOplockLevel does not contain
+			 * WRITE_CACHING:
+			 *	* The object store MUST request a shared oplock
+			 *	  according to the algorithm in section
+			 *	  2.1.5.17.2, setting the algorithm's
+			 *	  parameters as follows:
+			 *		* Open = current Open.
+			 *		* RequestedOplock =
+			 *		  RequestedOplockLevel.
+			 *		* GrantingInAck = TRUE.
+			 *	* The operation MUST at this point return any
+			 *	  status code returned by the shared oplock
+			 *	  request algorithm.
+			 */
+			else if ((level & WRITE_CACHING) == 0) {
+				*rop = level;
+				status = smb_oplock_req_shared(
+				    ofile, rop, B_TRUE);
+				goto out;
+			}
+
+			/*
+			 * Set Open.Stream.Oplock.ExclusiveOpen to
+			 *   ThisContext.Open.
+			 * Set Open.Stream.Oplock.State to
+			 *   (RequestedOplockLevel|EXCLUSIVE).
+			 * This operation MUST be made cancelable by
+			 *   inserting it into CancelableOperations...
+			 * This operation waits until the oplock is
+			 * broken or canceled, as specified in
+			 * section 2.1.5.17.3.
+			 *
+			 * Implementation note:
+			 *
+			 * Once we assing ol_state below, there
+			 * will be no BREAK_TO_... flags set,
+			 * so no need to wait for oplock breaks.
+			 */
+			node->n_oplock.excl_open = ofile;
+			node->n_oplock.ol_state = level | EXCLUSIVE;
+			status = NT_STATUS_SUCCESS;
+		} /* onlist_RHBQ */
+		if (FoundMatchingRHOplock == B_FALSE) {
+			/* The operation MUST be failed with Status... */
+			status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+			goto out;
+		}
+		break;	/* case (READ_CACHING|HANDLE_CACHING...) */
+
+	case (READ_CACHING|WRITE_CACHING|EXCLUSIVE|BREAK_TO_READ_CACHING):
+	case (READ_CACHING|WRITE_CACHING|EXCLUSIVE|BREAK_TO_NO_CACHING):
+	case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+	    BREAK_TO_READ_CACHING|BREAK_TO_WRITE_CACHING):
+	case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+	    BREAK_TO_READ_CACHING|BREAK_TO_HANDLE_CACHING):
+	case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+	    BREAK_TO_READ_CACHING):
+	case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+	    BREAK_TO_NO_CACHING):
+		/*
+		 * If Open.Stream.Oplock.ExclusiveOpen != Open:
+		 *	* The operation MUST be failed with Status set to
+		 *	  STATUS_INVALID_OPLOCK_PROTOCOL.
+		 * EndIf
+		 */
+		if (node->n_oplock.excl_open != ofile) {
+			status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+			goto out;
+		}
+
+		/*
+		 * If Open.Stream.Oplock.WaitList is not empty and
+		 * Open.Stream.Oplock.State does not contain HANDLE_CACHING
+		 * and RequestedOplockLevel is CACHE_RWH:
+		 *	The object store MUST indicate an oplock break to
+		 *	the server according to the algorithm in section
+		 *	2.1.5.17.3, setting the algorithm's params as follows:
+		 *	* BreakingOplockOpen = Open.
+		 *	* NewOplockLevel = BreakToLevel (see above)
+		 *	* AcknowledgeRequired = TRUE.
+		 *	* OplockCompletionStatus =
+		 *	  STATUS_CANNOT_GRANT_REQUESTED_OPLOCK.
+		 *   (Because BreakingOplockOpen is equal to the passed-in
+		 *    Open, the operation ends at this point.)
+		 */
+		if (node->n_oplock.waiters &&
+		    (node->n_oplock.ol_state & HANDLE_CACHING) == 0 &&
+		    level == CACHE_RWH) {
+			smb_oplock_ind_break_in_ack(
+			    sr, ofile,
+			    BreakToLevel, B_TRUE);
+			status = NT_STATUS_SUCCESS;
+			goto out;
+		}
+
+		/*
+		 * Else If Open.Stream.IsDeleted is TRUE and
+		 * RequestedOplockLevel contains HANDLE_CACHING:
+		 */
+		else if (((node->flags & NODE_FLAGS_DELETING) != 0) &&
+		    (level & HANDLE_CACHING) != 0) {
+
+			/*
+			 * The object store MUST indicate an oplock break to
+			 * the server according to the algorithm in section
+			 * 2.1.5.17.3, setting the algorithm's params as
+			 * follows:
+			 *	* BreakingOplockOpen = Open.
+			 *	* NewOplockLevel = RequestedOplockLevel
+			 *	  without HANDLE_CACHING (for example if
+			 *	  RequestedOplockLevel is
+			 *	  (READ_CACHING|HANDLE_CACHING), then
+			 *	   NewOplockLevel would be just READ_CACHING).
+			 *	* AcknowledgeRequired = TRUE.
+			 *	* OplockCompletionStatus =
+			 *	  STATUS_CANNOT_GRANT_REQUESTED_OPLOCK.
+			 * (Because BreakingOplockOpen is equal to the
+			 *  passed-in Open, the operation ends at this point.)
+			 */
+			level &= ~HANDLE_CACHING;
+			smb_oplock_ind_break_in_ack(
+			    sr, ofile,
+			    level, B_TRUE);
+			status = NT_STATUS_SUCCESS;
+			goto out;
+		}
+
+		/*
+		 * For each Open WaitingOpen on Open.Stream.Oplock.WaitList:
+		 *	* Indicate that the operation associated with
+		 *	  WaitingOpen can continue according to the algorithm
+		 *	  in section 2.1.4.12.1, setting OpenToRelease
+		 *	  = WaitingOpen.
+		 *	* Remove WaitingOpen from Open.Stream.Oplock.WaitList.
+		 * EndFor
+		 */
+		cv_broadcast(&node->n_oplock.WaitingOpenCV);
+
+		/*
+		 * If RequestedOplockLevel does not contain WRITE_CACHING:
+		 *	* Set Open.Stream.Oplock.ExclusiveOpen to NULL.
+		 * EndIf
+		 */
+		if ((level & WRITE_CACHING) == 0) {
+			node->n_oplock.excl_open = NULL;
+		}
+
+		/*
+		 * If RequestedOplockLevel is 0 (that is, no flags):
+		 *	* Set Open.Stream.Oplock.State to NO_OPLOCK.
+		 *	* The operation returns Status set to STATUS_SUCCESS
+		 *	  at this point.
+		 */
+		if (level == 0) {
+			node->n_oplock.ol_state = NO_OPLOCK;
+			status = NT_STATUS_SUCCESS;
+			goto out;
+		}
+
+		/*
+		 * Deal with possibly still pending breaks.
+		 * Two cases: R to none, RH to R or none.
+		 *
+		 * XXX: These two missing from [MS-FSA]
+		 */
+
+		/*
+		 * Breaking R to none?  This is like:
+		 * "If BreakCacheLevel contains READ_CACHING..."
+		 * from smb_oplock_break_cmn.
+		 */
+		if (level == CACHE_R && BreakToLevel == LEVEL_NONE) {
+			smb_oplock_ind_break_in_ack(
+			    sr, ofile,
+			    LEVEL_NONE, B_FALSE);
+			node->n_oplock.ol_state = NO_OPLOCK;
+			status = NT_STATUS_SUCCESS;
+			goto out;
+		}
+
+		/*
+		 * Breaking RH to R or RH to none?  This is like:
+		 * "If BreakCacheLevel equals HANDLE_CACHING..."
+		 * from smb_oplock_break_cmn.
+		 */
+		if (level == CACHE_RH &&
+		    (BreakToLevel == CACHE_R ||
+		    BreakToLevel == LEVEL_NONE)) {
+			smb_oplock_ind_break_in_ack(
+			    sr, ofile,
+			    BreakToLevel, B_TRUE);
+
+			ofile->f_oplock.BreakingToRead =
+			    (BreakToLevel & READ_CACHING) ? 1: 0;
+
+			ASSERT(!(ofile->f_oplock.onlist_RHBQ));
+			ofile->f_oplock.onlist_RHBQ = B_TRUE;
+			node->n_oplock.cnt_RHBQ++;
+
+			RecomputeOplockState(node);
+			status = NT_STATUS_SUCCESS;
+			goto out;
+		}
+
+		/*
+		 * Else If RequestedOplockLevel does not contain WRITE_CACHING:
+		 *	* The object store MUST request a shared oplock
+		 *	  according to the algorithm in section 2.1.5.17.2,
+		 *	  setting the algorithm's parameters as follows:
+		 *		* Pass in the current Open.
+		 *		* RequestedOplock = RequestedOplockLevel.
+		 *		* GrantingInAck = TRUE.
+		 *	* The operation MUST at this point return any status
+		 *	  returned by the shared oplock request algorithm.
+		 */
+		if ((level & WRITE_CACHING) == 0) {
+			*rop = level;
+			status = smb_oplock_req_shared(ofile, rop, B_TRUE);
+			goto out;
+		}
+
+		/*
+		 * Note that because this oplock is being set up as part of
+		 * an acknowledgement of an exclusive oplock break,
+		 * Open.Stream.Oplock.ExclusiveOpen was set
+		 * at the time of the original oplock request;
+		 * it contains Open.
+		 *	* Set Open.Stream.Oplock.State to
+		 *	  (RequestedOplockLevel|EXCLUSIVE).
+		 *	* This operation MUST be made cancelable...
+		 *	* This operation waits until the oplock is broken or
+		 *	  canceled, as specified in section 2.1.5.17.3.
+		 *
+		 * Implementation notes:
+		 *
+		 * This can only be a break from RWH to RW.
+		 * The assignment of ol_state below means there will be
+		 * no BREAK_TO_... bits set, and therefore no need for
+		 * "waits until the oplock is broken" as described in
+		 * the spec for this bit of code.  Therefore, this will
+		 * return SUCCESS instead of OPLOCK_BREAK_IN_PROGRESS.
+		 */
+		node->n_oplock.ol_state = level | EXCLUSIVE;
+		status = NT_STATUS_SUCCESS;
+		break;	/* case (READ_CACHING|WRITE_CACHING|...) */
+
+	default:
+		/* The operation MUST be failed with Status */
+		status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+		break;
+
+	} /* Switch (oplock.state) */
+
+out:
+	/*
+	 * The spec. describes waiting for a break here,
+	 * but we let the caller do that (when needed) if
+	 * status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS
+	 */
+	mutex_exit(&node->n_oplock.ol_mutex);
+	smb_llist_exit(&node->n_ofile_list);
+
+	if (status == NT_STATUS_INVALID_OPLOCK_PROTOCOL)
+		*rop = LEVEL_NONE;
+
+	if (status == NT_STATUS_SUCCESS &&
+	    type == LEVEL_GRANULAR &&
+	    *rop != LEVEL_NONE)
+		*rop |= LEVEL_GRANULAR;
+
+	return (status);
+}
+
+/*
+ * 2.1.4.12 Algorithm to Check for an Oplock Break
+ *
+ * The inputs for this algorithm are:
+ *
+ * Open: The Open being used in the request calling this algorithm.
+ *
+ * Oplock: The Oplock being checked.
+ *
+ * Operation: A code describing the operation being processed.
+ *
+ * OpParams: Parameters associated with the Operation code that are
+ *   passed in from the calling request. For example, if Operation is
+ *   OPEN, as specified in section 2.1.5.1, then OpParams will have the
+ *   members DesiredAccess and CreateDisposition. Each of these is a
+ *   parameter to the open request as specified in section 2.1.5.1.
+ *   This parameter could be empty, depending on the Operation code.
+ *
+ * Flags: An optional parameter. If unspecified it is considered to
+ *   contain 0. Valid nonzero values are:
+ *	PARENT_OBJECT
+ *
+ * The algorithm uses the following local variables:
+ *
+ * Boolean values (initialized to FALSE):
+ *   BreakToTwo, BreakToNone, NeedToWait
+ *
+ * BreakCacheLevel – MAY contain 0 or a combination of one or more of
+ *   READ_CACHING, WRITE_CACHING, or HANDLE_CACHING, as specified in
+ *   section 2.1.1.10. Initialized to 0.
+ *   Note that there are only four legal nonzero combinations of flags
+ *   for BreakCacheLevel:
+ *	(READ_CACHING|WRITE_CACHING|HANDLE_CACHING)
+ *	(READ_CACHING|WRITE_CACHING)
+ *	WRITE_CACHING
+ *	HANDLE_CACHING
+ *
+ * Algorithm: (all)
+ * If Oplock is not empty and Oplock.State is not NO_OPLOCK:
+ *	If Flags contains PARENT_OBJECT:
+ *		If Operation is OPEN, CLOSE, FLUSH_DATA,
+ *		  FS_CONTROL(set_encryption) or
+ *		  SET_INFORMATION(Basic, Allocation, EoF,
+ *		  Rename, Link, Shortname, VDL):
+ *			Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING).
+ *		EndIf
+ *	Else // Normal operation (not PARENT_OBJECT)
+ *		Switch (Operation):
+ *		Case OPEN, CLOSE, ...
+ *		EndSwitch
+ *	EndIf // not parent
+ *	// Common section for all above
+ *	If BreakToTwo is TRUE:
+ *		...
+ *	Else If BreakToNone
+ *		...
+ *	EndIf
+ *	...
+ * EndIf
+ *
+ * This implementation uses separate functions for each of:
+ *	if (flags & PARENT)... else
+ *	switch (Operation)...
+ */
+
+
+/*
+ * If Flags contains PARENT_OBJECT:
+ * ...
+ * Note that this function is unusual in that the node arg is
+ * the PARENT directory node, and ofile is NOT on the ofile list
+ * of that directory but one of the nodes under it.
+ *
+ * Note that until we implement directory leases, this is a no-op.
+ */
+uint32_t
+smb_oplock_break_PARENT(smb_node_t *node, smb_ofile_t *ofile)
+{
+	uint32_t BreakCacheLevel;
+
+	/*
+	 * If Operation is OPEN, CLOSE, FLUSH_DATA,
+	 *  FS_CONTROL(set_encryption) or
+	 * SET_INFORMATION(Basic, Allocation, EoF,
+	 * Rename, Link, Shortname, VDL):
+	 *	 Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING).
+	 * EndIf
+	 */
+	BreakCacheLevel = PARENT_OBJECT |
+	    (READ_CACHING|WRITE_CACHING);
+
+	return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * Helper for the cases where section 2.1.5.1 says:
+ *
+ * If Open.Stream.Oplock is not empty and Open.Stream.Oplock.State
+ * contains BATCH_OPLOCK, the object store MUST check for an oplock
+ * break according to the algorithm in section 2.1.4.12,
+ * with input values as follows:
+ *	Open equal to this operation's Open
+ *	Oplock equal to Open.Stream.Oplock
+ *	Operation equal to "OPEN"
+ *	OpParams containing two members:
+ *      (DesiredAccess, CreateDisposition)
+ *
+ * So basically, just call smb_oplock_break_OPEN(), but
+ * only if there's a batch oplock.
+ */
+uint32_t
+smb_oplock_break_BATCH(smb_node_t *node, smb_ofile_t *ofile,
+    uint32_t DesiredAccess, uint32_t CreateDisposition)
+{
+	if ((node->n_oplock.ol_state & BATCH_OPLOCK) == 0)
+		return (0);
+
+	return (smb_oplock_break_OPEN(node, ofile,
+	    DesiredAccess, CreateDisposition));
+}
+
+/*
+ * Case OPEN, as specified in section 2.1.5.1:
+ *
+ * Note: smb_ofile_open constructs a partially complete smb_ofile_t
+ * for this call, which can be considerd a "proposed open".  This
+ * open may or may not turn into a usable open depending on what
+ * happens in the remainder of the ofile_open code path.
+ */
+uint32_t
+smb_oplock_break_OPEN(smb_node_t *node, smb_ofile_t *ofile,
+    uint32_t DesiredAccess, uint32_t CreateDisposition)
+{
+	uint32_t BreakCacheLevel = 0;
+	/* BreakToTwo, BreakToNone, NeedToWait */
+
+	/*
+	 * If OpParams.DesiredAccess contains no flags other than
+	 * FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, or SYNCHRONIZE,
+	 *   the algorithm returns at this point.
+	 * EndIf
+	 */
+	if ((DesiredAccess & ~(FILE_READ_ATTRIBUTES |
+	    FILE_WRITE_ATTRIBUTES | SYNCHRONIZE | READ_CONTROL)) == 0)
+		return (0);
+
+	/*
+	 * If OpParams.CreateDisposition is FILE_SUPERSEDE,
+	 * FILE_OVERWRITE, or FILE_OVERWRITE_IF:
+	 *	Set BreakToNone to TRUE, set BreakCacheLevel to
+	 *	   (READ_CACHING|WRITE_CACHING).
+	 * Else
+	 *	Set BreakToTwo to TRUE,
+	 *	set BreakCacheLevel to WRITE_CACHING.
+	 * EndIf
+	 */
+	if (CreateDisposition == FILE_SUPERSEDE ||
+	    CreateDisposition == FILE_OVERWRITE ||
+	    CreateDisposition == FILE_OVERWRITE_IF) {
+		BreakCacheLevel = BREAK_TO_NONE |
+		    (READ_CACHING|WRITE_CACHING);
+	} else {
+		/*
+		 * CreateDispositons: OPEN, OPEN_IF
+		 */
+		BreakCacheLevel = BREAK_TO_TWO |
+		    WRITE_CACHING;
+	}
+
+	return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * Case OPEN_BREAK_H, as specified in section 2.1.5.1:
+ *	Set BreakCacheLevel to HANDLE_CACHING.
+ * EndCase
+ */
+uint32_t
+smb_oplock_break_HANDLE(smb_node_t *node, smb_ofile_t *ofile)
+{
+	uint32_t BreakCacheLevel = HANDLE_CACHING;
+
+	return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * Case CLOSE, as specified in section 2.1.5.4:
+ *
+ * The MS-FSA spec. describes sending oplock break indications
+ * (smb_oplock_ind_break ... NT_STATUS_OPLOCK_HANDLE_CLOSED)
+ * for several cases where the ofile we're closing has some
+ * oplock grants.  We modify these slightly and use them to
+ * clear out the SMB-level oplock state.  We could probably
+ * just skip most of these, as the caller knows this handle is
+ * closing and could just discard the SMB-level oplock state.
+ * For now, keeping this close to what the spec says.
+ */
+void
+smb_oplock_break_CLOSE(smb_node_t *node, smb_ofile_t *ofile)
+{
+	smb_ofile_t *o;
+
+	if (ofile == NULL) {
+		ASSERT(0);
+		return;
+	}
+
+	smb_llist_enter(&node->n_ofile_list, RW_READER);
+	mutex_enter(&node->n_oplock.ol_mutex);
+
+	/*
+	 * If Oplock.IIOplocks is not empty:
+	 *   For each Open ThisOpen in Oplock.IIOplocks:
+	 *	If ThisOpen == Open:
+	 *		Remove ThisOpen from Oplock.IIOplocks.
+	 *		Notify the server of an oplock break according to
+	 *		  the algorithm in section 2.1.5.17.3, setting the
+	 *		  algorithm's parameters as follows:
+	 *			BreakingOplockOpen = ThisOpen.
+	 *			NewOplockLevel = LEVEL_NONE.
+	 *			AcknowledgeRequired = FALSE.
+	 *			OplockCompletionStatus = STATUS_SUCCESS.
+	 *		(The operation does not end at this point;
+	 *		 this call to 2.1.5.17.3 completes some
+	 *		 earlier call to 2.1.5.17.2.)
+	 *	EndIf
+	 *   EndFor
+	 *   Recompute Oplock.State according to the algorithm in
+	 *     section 2.1.4.13, passing Oplock as the ThisOplock parameter.
+	 * EndIf
+	 */
+	if (node->n_oplock.cnt_II > 0) {
+		o = ofile; /* No need for list walk */
+		if (o->f_oplock.onlist_II) {
+			o->f_oplock.onlist_II = B_FALSE;
+			node->n_oplock.cnt_II--;
+			ASSERT(node->n_oplock.cnt_II >= 0);
+			/*
+			 * The spec. says to do:
+			 * smb_oplock_ind_break(o,
+			 *    LEVEL_NONE, B_FALSE,
+			 *    NT_STATUS_SUCCESS);
+			 *
+			 * We'll use STATUS_OPLOCK_HANDLE_CLOSED
+			 * like all the other ind_break calls in
+			 * this function, so the SMB-level will
+			 * just clear out its oplock state.
+			 */
+			smb_oplock_ind_break(o,
+			    LEVEL_NONE, B_FALSE,
+			    NT_STATUS_OPLOCK_HANDLE_CLOSED);
+		}
+		RecomputeOplockState(node);
+	}
+
+	/*
+	 * If Oplock.ROplocks is not empty:
+	 *   For each Open ThisOpen in Oplock.ROplocks:
+	 *	If ThisOpen == Open:
+	 *		Remove ThisOpen from Oplock.ROplocks.
+	 *		Notify the server of an oplock break according to
+	 *		  the algorithm in section 2.1.5.17.3, setting the
+	 *		  algorithm's parameters as follows:
+	 *			BreakingOplockOpen = ThisOpen.
+	 *			NewOplockLevel = LEVEL_NONE.
+	 *			AcknowledgeRequired = FALSE.
+	 *			OplockCompletionStatus =
+	 *			  STATUS_OPLOCK_HANDLE_CLOSED.
+	 *		(The operation does not end at this point;
+	 *		 this call to 2.1.5.17.3 completes some
+	 *		 earlier call to 2.1.5.17.2.)
+	 *	EndIf
+	 *   EndFor
+	 *   Recompute Oplock.State according to the algorithm in
+	 *     section 2.1.4.13, passing Oplock as the ThisOplock parameter.
+	 * EndIf
+	 */
+	if (node->n_oplock.cnt_R > 0) {
+		o = ofile; /* No need for list walk */
+		if (o->f_oplock.onlist_R) {
+			o->f_oplock.onlist_R = B_FALSE;
+			node->n_oplock.cnt_R--;
+			ASSERT(node->n_oplock.cnt_R >= 0);
+
+			smb_oplock_ind_break(o,
+			    LEVEL_NONE, B_FALSE,
+			    NT_STATUS_OPLOCK_HANDLE_CLOSED);
+		}
+		RecomputeOplockState(node);
+	}
+
+	/*
+	 * If Oplock.RHOplocks is not empty:
+	 *   For each Open ThisOpen in Oplock.RHOplocks:
+	 *	If ThisOpen == Open:
+	 *		Remove ThisOpen from Oplock.RHOplocks.
+	 *		Notify the server of an oplock break according to
+	 *		  the algorithm in section 2.1.5.17.3, setting the
+	 *		  algorithm's parameters as follows:
+	 *			BreakingOplockOpen = ThisOpen.
+	 *			NewOplockLevel = LEVEL_NONE.
+	 *			AcknowledgeRequired = FALSE.
+	 *			OplockCompletionStatus =
+	 *			   STATUS_OPLOCK_HANDLE_CLOSED.
+	 *		(The operation does not end at this point;
+	 *		 this call to 2.1.5.17.3 completes some
+	 *		 earlier call to 2.1.5.17.2.)
+	 *	EndIf
+	 *   EndFor
+	 *   Recompute Oplock.State according to the algorithm in
+	 *     section 2.1.4.13, passing Oplock as the ThisOplock parameter.
+	 * EndIf
+	 */
+	if (node->n_oplock.cnt_RH > 0) {
+		o = ofile; /* No need for list walk */
+		if (o->f_oplock.onlist_RH) {
+			o->f_oplock.onlist_RH = B_FALSE;
+			node->n_oplock.cnt_RH--;
+			ASSERT(node->n_oplock.cnt_RH >= 0);
+
+			smb_oplock_ind_break(o,
+			    LEVEL_NONE, B_FALSE,
+			    NT_STATUS_OPLOCK_HANDLE_CLOSED);
+		}
+		RecomputeOplockState(node);
+	}
+
+	/*
+	 * If Oplock.RHBreakQueue is not empty:
+	 *	For each RHOpContext ThisContext in Oplock.RHBreakQueue:
+	 *		If ThisContext.Open == Open:
+	 *			Remove ThisContext from Oplock.RHBreakQueue.
+	 *		EndIf
+	 *	EndFor
+	 *	Recompute Oplock.State according to the algorithm in
+	 *	  section 2.1.4.13, passing Oplock as the ThisOplock parameter.
+	 *	For each Open WaitingOpen on Oplock.WaitList:
+	 *		If Oplock.RHBreakQueue is empty:
+	 *		(or) If the value of every
+	 *		RHOpContext.Open.TargetOplockKey
+	 *		on Oplock.RHBreakQueue is equal to
+	 *		WaitingOpen .TargetOplockKey:
+	 *			Indicate that the op. assoc. with
+	 *			WaitingOpen can continue according to
+	 *			the algorithm in section 2.1.4.12.1,
+	 *			setting OpenToRelease = WaitingOpen.
+	 *			Remove WaitingOpen from Oplock.WaitList.
+	 *		EndIf
+	 *	EndFor
+	 * EndIf
+	 */
+	if (node->n_oplock.cnt_RHBQ > 0) {
+		o = ofile; /* No need for list walk */
+		if (o->f_oplock.onlist_RHBQ) {
+			o->f_oplock.onlist_RHBQ = B_FALSE;
+			node->n_oplock.cnt_RHBQ--;
+			ASSERT(node->n_oplock.cnt_RHBQ >= 0);
+		}
+		RecomputeOplockState(node);
+		/*
+		 * We don't keep a WaitingOpen list, so just
+		 * wake them all and let them look at the
+		 * updated Oplock.RHBreakQueue
+		 */
+		cv_broadcast(&node->n_oplock.WaitingOpenCV);
+	}
+
+	/*
+	 * If Open equals Open.Oplock.ExclusiveOpen
+	 *	If Oplock.State contains none of (BREAK_ANY):
+	 *		Notify the server of an oplock break according to
+	 *		  the algorithm in section 2.1.5.17.3, setting the
+	 *		  algorithm's parameters as follows:
+	 *			BreakingOplockOpen = Oplock.ExclusiveOpen.
+	 *			NewOplockLevel = LEVEL_NONE.
+	 *			AcknowledgeRequired = FALSE.
+	 *			OplockCompletionStatus equal to:
+	 *				STATUS_OPLOCK_HANDLE_CLOSED if
+	 *				  Oplock.State contains any of
+	 *				  READ_CACHING, WRITE_CACHING, or
+	 *				  HANDLE_CACHING.
+	 *				STATUS_SUCCESS otherwise.
+	 *		(The operation does not end at this point;
+	 *		 this call to 2.1.5.17.3 completes some
+	 *		 earlier call to 2.1.5.17.1.)
+	 *	EndIf
+	 *	Set Oplock.ExclusiveOpen to NULL.
+	 *	Set Oplock.State to NO_OPLOCK.
+	 *	For each Open WaitingOpen on Oplock.WaitList:
+	 *		Indicate that the operation associated with WaitingOpen
+	 *		  can continue according to the algorithm in section
+	 *		  2.1.4.12.1, setting OpenToRelease = WaitingOpen.
+	 *		Remove WaitingOpen from Oplock.WaitList.
+	 *	EndFor
+	 * EndIf
+	 *
+	 * Modify this slightly from what the spec. says and only
+	 * up-call the break with status STATUS_OPLOCK_HANDLE_CLOSED.
+	 * The STATUS_SUCCESS case would do nothing at the SMB level,
+	 * so we'll just skip that part.
+	 */
+	if (ofile == node->n_oplock.excl_open) {
+		uint32_t level = node->n_oplock.ol_state & CACHE_RWH;
+		if (level != 0 &&
+		    (node->n_oplock.ol_state & BREAK_ANY) == 0) {
+			smb_oplock_ind_break(ofile,
+			    LEVEL_NONE, B_FALSE,
+			    NT_STATUS_OPLOCK_HANDLE_CLOSED);
+		}
+		node->n_oplock.excl_open = NULL;
+		node->n_oplock.ol_state = NO_OPLOCK;
+		cv_broadcast(&node->n_oplock.WaitingOpenCV);
+	}
+
+	/*
+	 * The CLOSE sub-case of 2.1.5.4 (separate function here)
+	 * happens to always leave BreakCacheLevel=0 (see 2.1.5.4)
+	 * so there's never a need to call smb_oplock_break_cmn()
+	 * in this function.  If that changed and we were to have
+	 * BreakCacheLevel != 0 here, then we'd need to call:
+	 * smb_oplock_break_cmn(node, ofile, BreakCacheLevel);
+	 */
+
+	if ((node->n_oplock.ol_state & BREAK_ANY) == 0)
+		cv_broadcast(&node->n_oplock.WaitingOpenCV);
+
+	mutex_exit(&node->n_oplock.ol_mutex);
+	smb_llist_exit(&node->n_ofile_list);
+}
+
+/*
+ * Case READ, as specified in section 2.1.5.2:
+ *	Set BreakToTwo to TRUE
+ *	Set BreakCacheLevel to WRITE_CACHING.
+ * EndCase
+ */
+uint32_t
+smb_oplock_break_READ(smb_node_t *node, smb_ofile_t *ofile)
+{
+	uint32_t BreakCacheLevel = BREAK_TO_TWO | WRITE_CACHING;
+
+	return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * Case FLUSH_DATA, as specified in section 2.1.5.6:
+ *	Set BreakToTwo to TRUE
+ *	Set BreakCacheLevel to WRITE_CACHING.
+ * EndCase
+ * Callers just use smb_oplock_break_READ() -- same thing.
+ */
+
+/*
+ * Case LOCK_CONTROL, as specified in section 2.1.5.7:
+ * Note: Spec does fall-through to WRITE here.
+ *
+ * Case WRITE, as specified in section 2.1.5.3:
+ *	Set BreakToNone to TRUE
+ *	Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING).
+ * EndCase
+ */
+uint32_t
+smb_oplock_break_WRITE(smb_node_t *node, smb_ofile_t *ofile)
+{
+	uint32_t BreakCacheLevel = BREAK_TO_NONE |
+	    (READ_CACHING|WRITE_CACHING);
+
+	return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * Case SET_INFORMATION, as specified in section 2.1.5.14:
+ * Switch (OpParams.FileInformationClass):
+ *	Case FileEndOfFileInformation:
+ *	Case FileAllocationInformation:
+ *		Set BreakToNone to TRUE
+ *		Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING).
+ *	EndCase
+ *	Case FileRenameInformation:
+ *	Case FileLinkInformation:
+ *	Case FileShortNameInformation:
+ *		Set BreakCacheLevel to HANDLE_CACHING.
+ *		If Oplock.State contains BATCH_OPLOCK,
+ *		  set BreakToNone to TRUE.
+ *	EndCase
+ *	Case FileDispositionInformation:
+ *		If OpParams.DeleteFile is TRUE,
+ *		Set BreakCacheLevel to HANDLE_CACHING.
+ *	EndCase
+ * EndSwitch
+ */
+uint32_t
+smb_oplock_break_SETINFO(smb_node_t *node, smb_ofile_t *ofile,
+    uint32_t InfoClass)
+{
+	uint32_t BreakCacheLevel = 0;
+
+	switch (InfoClass) {
+	case FileEndOfFileInformation:
+	case FileAllocationInformation:
+		BreakCacheLevel = BREAK_TO_NONE |
+		    (READ_CACHING|WRITE_CACHING);
+		break;
+
+	case FileRenameInformation:
+	case FileLinkInformation:
+	case FileShortNameInformation:
+		BreakCacheLevel = HANDLE_CACHING;
+		if (node->n_oplock.ol_state & BATCH_OPLOCK) {
+			BreakCacheLevel |= BREAK_TO_NONE;
+		}
+		break;
+	case FileDispositionInformation:
+		/* Only called if (OpParams.DeleteFile is TRUE) */
+		BreakCacheLevel = HANDLE_CACHING;
+		break;
+
+	}
+
+	return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * This one is not from the spec.  It appears that Windows will
+ * open a handle for an SMB1 delete call (at least internally).
+ * We don't open a handle for delete, but do want to break as if
+ * we had done, so this breaks like a combination of:
+ *	break_BATCH(... DELETE, FILE_OPEN_IF)
+ *	break_HANDLE(...)
+ */
+uint32_t
+smb_oplock_break_DELETE(smb_node_t *node, smb_ofile_t *ofile)
+{
+	uint32_t BreakCacheLevel = HANDLE_CACHING;
+
+	if ((node->n_oplock.ol_state & BATCH_OPLOCK) != 0)
+		BreakCacheLevel |= BREAK_TO_TWO;
+
+	return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * Case FS_CONTROL, as specified in section 2.1.5.9:
+ *	If OpParams.ControlCode is FSCTL_SET_ZERO_DATA:
+ *		Set BreakToNone to TRUE.
+ *		Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING).
+ *	EndIf
+ * EndCase
+ * Callers just use smb_oplock_break_WRITE() -- same thing.
+ */
+
+/*
+ * Common section for all cases above
+ * Note: When called via FEM: ofile == NULL
+ */
+static uint32_t
+smb_oplock_break_cmn(smb_node_t *node,
+    smb_ofile_t *ofile, uint32_t BreakCacheLevel)
+{
+	smb_oplock_t *nol = &node->n_oplock;
+	uint32_t CmpFlags, status;
+	boolean_t BreakToTwo, BreakToNone, NeedToWait;
+	smb_ofile_t *o = NULL;
+
+	CmpFlags = (BreakCacheLevel & PARENT_OBJECT);
+	BreakToTwo = (BreakCacheLevel & BREAK_TO_TWO) != 0;
+	BreakToNone = (BreakCacheLevel & BREAK_TO_NONE) != 0;
+	BreakCacheLevel &= (READ_CACHING | WRITE_CACHING | HANDLE_CACHING);
+	NeedToWait = B_FALSE;
+	status = NT_STATUS_SUCCESS;
+
+	smb_llist_enter(&node->n_ofile_list, RW_READER);
+	mutex_enter(&node->n_oplock.ol_mutex);
+
+	if (node->n_oplock.ol_state == 0 ||
+	    node->n_oplock.ol_state == NO_OPLOCK)
+		goto out;
+
+	if (BreakToTwo) {
+		/*
+		 * If (Oplock.State != LEVEL_TWO_OPLOCK) and
+		 *    ((Oplock.ExclusiveOpen is empty) or
+		 *     (Oplock.ExclusiveOpen.TargetOplockKey !=
+		 *      Open.TargetOplockKey)):
+		 */
+		if ((nol->ol_state != LEVEL_TWO_OPLOCK) &&
+		    (((o = nol->excl_open) == NULL) ||
+		    !CompareOplockKeys(ofile, o, CmpFlags))) {
+
+			/*
+			 * If (Oplock.State contains EXCLUSIVE) and
+			 *  (Oplock.State contains none of READ_CACHING,
+			 *   WRITE_CACHING, or HANDLE_CACHING):
+			 */
+			if ((nol->ol_state & EXCLUSIVE) != 0 &&
+			    (nol->ol_state & CACHE_RWH) == 0) {
+				/*
+				 * If Oplock.State contains none of:
+				 *	BREAK_TO_NONE,
+				 *	BREAK_TO_TWO,
+				 *	BREAK_TO_TWO_TO_NONE,
+				 *	BREAK_TO_READ_CACHING,
+				 *	BREAK_TO_WRITE_CACHING,
+				 *	BREAK_TO_HANDLE_CACHING,
+				 *	BREAK_TO_NO_CACHING:
+				 */
+				if ((nol->ol_state & BREAK_ANY) == 0) {
+
+					/*
+					 * Oplock.State MUST contain either
+					 * LEVEL_ONE_OPLOCK or BATCH_OPLOCK.
+					 * Set BREAK_TO_TWO in Oplock.State.
+					 */
+					ASSERT((nol->ol_state &
+					    (LEVEL_ONE | LEVEL_BATCH)) != 0);
+					nol->ol_state |= BREAK_TO_TWO;
+
+					/*
+					 * Notify the server of an oplock break
+					 * according to the algorithm in section
+					 * 2.1.5.17.3, setting the algorithm's
+					 * parameters as follows:
+					 *	BreakingOplockOpen =
+					 *	  Oplock.ExclusiveOpen.
+					 *	NewOplockLevel = LEVEL_TWO.
+					 *	AcknowledgeRequired = TRUE.
+					 *	Compl_Status = STATUS_SUCCESS.
+					 * (The operation does not end at this
+					 * point; this call to 2.1.5.17.3
+					 * completes some earlier call to
+					 * 2.1.5.17.1.)
+					 */
+					smb_oplock_ind_break(o,
+					    LEVEL_TWO, B_TRUE,
+					    NT_STATUS_SUCCESS);
+				}
+
+				/*
+				 * The operation that called this algorithm
+				 *  MUST be made cancelable by ...
+				 * The operation that called this algorithm
+				 *  waits until the oplock break is
+				 *  acknowledged, as specified in section
+				 *  2.1.5.18, or the operation is canceled.
+				 */
+				status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS;
+				/* Caller does smb_oplock_wait_break() */
+			}
+		}
+	} else if (BreakToNone) {
+		/*
+		 * If (Oplock.State == LEVEL_TWO_OPLOCK) or
+		 *  (Oplock.ExclusiveOpen is empty) or
+		 *  (Oplock.ExclusiveOpen.TargetOplockKey !=
+		 *   Open.TargetOplockKey):
+		 */
+		if (nol->ol_state == LEVEL_TWO_OPLOCK ||
+		    (((o = nol->excl_open) == NULL) ||
+		    !CompareOplockKeys(ofile, o, CmpFlags))) {
+
+			/*
+			 * If (Oplock.State != NO_OPLOCK) and
+			 * (Oplock.State contains neither
+			 *  WRITE_CACHING nor HANDLE_CACHING):
+			 */
+			if (nol->ol_state != NO_OPLOCK &&
+			    (nol->ol_state &
+			    (WRITE_CACHING | HANDLE_CACHING)) == 0) {
+
+				/*
+				 * If Oplock.State contains none of:
+				 *	LEVEL_TWO_OPLOCK,
+				 *	BREAK_TO_NONE,
+				 *	BREAK_TO_TWO,
+				 *	BREAK_TO_TWO_TO_NONE,
+				 *	BREAK_TO_READ_CACHING,
+				 *	BREAK_TO_WRITE_CACHING,
+				 *	BREAK_TO_HANDLE_CACHING, or
+				 *	BREAK_TO_NO_CACHING:
+				 */
+				if ((nol->ol_state &
+				    (LEVEL_TWO_OPLOCK | BREAK_ANY)) == 0) {
+
+					/*
+					 * There could be a READ_CACHING-only
+					 * oplock here. Those are broken later.
+					 *
+					 * If Oplock.State contains READ_CACHING
+					 *  go to the LeaveBreakToNone label.
+					 * Set BREAK_TO_NONE in Oplock.State.
+					 */
+					if ((nol->ol_state & READ_CACHING) != 0)
+						goto LeaveBreakToNone;
+					nol->ol_state |= BREAK_TO_NONE;
+
+					/*
+					 * Notify the server of an oplock break
+					 * according to the algorithm in section
+					 * 2.1.5.17.3, setting the algorithm's
+					 * parameters as follows:
+					 *	BreakingOplockOpen =
+					 *	  Oplock.ExclusiveOpen.
+					 *	NewOplockLevel = LEVEL_NONE.
+					 *	AcknowledgeRequired = TRUE.
+					 *	Commpl_Status = STATUS_SUCCESS.
+					 * (The operation does not end at this
+					 * point; this call to 2.1.5.17.3
+					 * completes some earlier call to
+					 * 2.1.5.17.1.)
+					 */
+					smb_oplock_ind_break(o,
+					    LEVEL_NONE, B_TRUE,
+					    NT_STATUS_SUCCESS);
+				}
+
+				/*
+				 * Else If Oplock.State equals LEVEL_TWO_OPLOCK
+				 *  or (LEVEL_TWO_OPLOCK|READ_CACHING):
+				 */
+				else if (nol->ol_state == LEVEL_TWO ||
+				    nol->ol_state == (LEVEL_TWO|READ_CACHING)) {
+
+					/*
+					 * For each Open O in Oplock.IIOplocks:
+					 *   Remove O from Oplock.IIOplocks.
+					 *   Notify the server of an oplock
+					 *    break according to the algorithm
+					 *    in section 2.1.5.17.3, setting the
+					 *    algorithm's parameters as follows:
+					 *	BreakingOplockOpen = ThisOpen.
+					 *	NewOplockLevel = LEVEL_NONE.
+					 *	AcknowledgeRequired = FALSE.
+					 *	Compl_Status = STATUS_SUCCESS.
+					 *    (The operation does not end at
+					 *    this point; this call to
+					 *    2.1.5.17.3 completes some
+					 *    earlier call to 2.1.5.17.2.)
+					 * EndFor
+					 */
+					FOREACH_NODE_OFILE(node, o) {
+						if (o->f_oplock.onlist_II == 0)
+							continue;
+						o->f_oplock.onlist_II = B_FALSE;
+						nol->cnt_II--;
+						ASSERT(nol->cnt_II >= 0);
+
+						smb_oplock_ind_break(o,
+						    LEVEL_NONE, B_FALSE,
+						    NT_STATUS_SUCCESS);
+					}
+					/*
+					 * If Oplock.State equals
+					 *  (LEVEL_TWO_OPLOCK|READ_CACHING):
+					 *	Set Oplock.State = READ_CACHING.
+					 * Else
+					 *	Set Oplock.State = NO_OPLOCK.
+					 * EndIf
+					 * Go to the LeaveBreakToNone label.
+					 */
+					if (nol->ol_state ==
+					    (LEVEL_TWO_OPLOCK | READ_CACHING)) {
+						nol->ol_state = READ_CACHING;
+					} else {
+						nol->ol_state = NO_OPLOCK;
+					}
+					goto LeaveBreakToNone;
+				}
+
+				/*
+				 * Else If Oplock.State contains BREAK_TO_TWO:
+				 *	Clear BREAK_TO_TWO from Oplock.State.
+				 *	Set BREAK_TO_TWO_TO_NONE in Oplock.State
+				 * EndIf
+				 */
+				else if (nol->ol_state & BREAK_TO_TWO) {
+					nol->ol_state &= ~BREAK_TO_TWO;
+					nol->ol_state |= BREAK_TO_TWO_TO_NONE;
+				}
+
+				/*
+				 * If Oplock.ExclusiveOpen is not empty,
+				 *  and Oplock.Excl_Open.TargetOplockKey
+				 *  equals Open.TargetOplockKey,
+				 *	 go to the LeaveBreakToNone label.
+				 */
+				if (o != NULL &&
+				    CompareOplockKeys(ofile, o, CmpFlags))
+					goto LeaveBreakToNone;
+
+				/*
+				 * The operation that called this algorithm
+				 *  MUST be made cancelable by ...
+				 * The operation that called this algorithm
+				 * waits until the opl. break is acknowledged,
+				 * as specified in section 2.1.5.18, or the
+				 * operation is canceled.
+				 */
+				status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS;
+				/* Caller does smb_oplock_wait_break() */
+			}
+		}
+	}
+
+LeaveBreakToNone:
+
+	/*
+	 * if (BreakCacheLevel != 0) and		(pp 37)
+	 * If Oplock.State contains any flags that are in BreakCacheLevel:
+	 * (Body of that "If" was here to just above the out label.)
+	 */
+	if ((nol->ol_state & BreakCacheLevel) == 0)
+		goto out;
+
+	/*
+	 * If Oplock.ExclusiveOpen is not empty, call the
+	 * algorithm in section 2.1.4.12.2, passing
+	 *	Open as the OperationOpen parameter,
+	 *	Oplock.ExclusiveOpen as the OplockOpen parameter,
+	 *	and Flags as the Flagsparameter.
+	 * If the algorithm returns TRUE:
+	 *	The algorithm returns at this point.
+	 */
+	if ((o = nol->excl_open) != NULL &&
+	    CompareOplockKeys(ofile, o, CmpFlags) == B_TRUE) {
+		status = NT_STATUS_SUCCESS;
+		goto out;
+	}
+
+	/*
+	 * Switch (Oplock.State):
+	 */
+	switch (nol->ol_state) {
+
+	case (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH):
+	case READ_CACHING:
+	case (LEVEL_TWO_OPLOCK|READ_CACHING):
+		/*
+		 * If BreakCacheLevel contains READ_CACHING:
+		 */
+		if ((BreakCacheLevel & READ_CACHING) != 0) {
+			/*
+			 * For each Open ThisOpen in Oplock.ROplocks:
+			 *   Call the algorithm in section 2.1.4.12.2, pass:
+			 *	Open as the OperationOpen parameter,
+			 *	ThisOpen as the OplockOpen parameter,
+			 *	and Flags as the Flagsparameter.
+			 *   If the algorithm returns FALSE:
+			 *	Remove ThisOpen from Oplock.ROplocks.
+			 *	Notify the server of an oplock break
+			 *	  according to the algorithm in
+			 *	  section 2.1.5.17.3, setting the
+			 *	  algorithm's parameters as follows:
+			 *		BreakingOplockOpen = ThisOpen.
+			 *		NewOplockLevel = LEVEL_NONE.
+			 *		AcknowledgeRequired = FALSE.
+			 *		Compl_Status = STATUS_SUCCESS.
+			 *	(The operation does not end at this point;
+			 *	 this call to 2.1.5.17.3 completes some
+			 *	 earlier call to 2.1.5.17.2.)
+			 *	EndIf
+			 * EndFor
+			 */
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_R == 0)
+					continue;
+				if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+					o->f_oplock.onlist_R = B_FALSE;
+					nol->cnt_R--;
+					ASSERT(nol->cnt_R >= 0);
+
+					smb_oplock_ind_break(o,
+					    LEVEL_NONE, B_FALSE,
+					    NT_STATUS_SUCCESS);
+				}
+			}
+		}
+		/*
+		 * If Oplock.State equals
+		 *  (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH):
+		 *	// Do nothing; FALL THROUGH to next Case statement.
+		 * Else
+		 *	Recompute Oplock.State according to the
+		 *	algorithm in section 2.1.4.13, passing
+		 *	Oplock as the ThisOplock parameter.
+		 * EndIf
+		 */
+		if (nol->ol_state ==
+		    (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH))
+			goto case_cache_rh;
+
+		RecomputeOplockState(node);
+		break;
+	/* EndCase	XXX Note: spec. swapped this with prev. Endif. */
+
+	case_cache_rh:
+	case (READ_CACHING|HANDLE_CACHING):
+
+		/*
+		 * If BreakCacheLevel equals HANDLE_CACHING:
+		 */
+		if (BreakCacheLevel == HANDLE_CACHING) {
+
+			/*
+			 * For each Open ThisOpen in Oplock.RHOplocks:
+			 *	If ThisOpen.OplockKey != Open.OplockKey:
+			 */
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_RH == 0)
+					continue;
+				if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+
+					/*
+					 * Remove ThisOpen from
+					 *  Oplock.RHOplocks.
+					 */
+					o->f_oplock.onlist_RH = B_FALSE;
+					nol->cnt_RH--;
+					ASSERT(nol->cnt_RH >= 0);
+
+					/*
+					 * Notify the server of an oplock break
+					 *   according to the algorithm in
+					 *   section 2.1.5.17.3, setting the
+					 *   algorithm's parameters as follows:
+					 *	BreakingOplockOpen = ThisOpen.
+					 *	NewOplockLevel = READ_CACHING.
+					 *	AcknowledgeRequired = TRUE.
+					 *	Compl_Status = STATUS_SUCCESS.
+					 * (The operation does not end at this
+					 *  point; this call to 2.1.5.17.3
+					 *  completes some earlier call to
+					 *  2.1.5.17.2.)
+					 */
+					smb_oplock_ind_break(o,
+					    READ_CACHING, B_TRUE,
+					    NT_STATUS_SUCCESS);
+
+					/*
+					 * Initialize a new RHOpContext object,
+					 *   setting its fields as follows:
+					 *	RHOpCtx.Open = ThisOpen.
+					 *	RHOpCtx.BreakingToRead = TRUE.
+					 * Add the new RHOpContext object to
+					 *    Oplock.RHBreakQueue.
+					 * Set NeedToWait to TRUE.
+					 */
+					o->f_oplock.BreakingToRead = B_TRUE;
+					ASSERT(!(o->f_oplock.onlist_RHBQ));
+					o->f_oplock.onlist_RHBQ = B_TRUE;
+					nol->cnt_RHBQ++;
+
+					NeedToWait = B_TRUE;
+				}
+			}
+		}
+
+		/*
+		 * Else If BreakCacheLevel contains both
+		 *   READ_CACHING and WRITE_CACHING:
+		 */
+		else if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) ==
+		    (READ_CACHING | WRITE_CACHING)) {
+
+			/*
+			 * For each RHOpContext ThisContext in
+			 * Oplock.RHBreakQueue:
+			 *	Call the algorithm in section 2.1.4.12.2,
+			 *	  passing Open as the OperationOpen parameter,
+			 *	  ThisContext.Open as the OplockOpen parameter,
+			 *	  and Flags as the Flags parameter.
+			 *	If the algorithm returns FALSE:
+			 *		Set ThisContext.BreakingToRead to FALSE.
+			 *		If BreakCacheLevel & HANDLE_CACHING:
+			 *			Set NeedToWait to TRUE.
+			 *		EndIf
+			 *	EndIf
+			 * EndFor
+			 */
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_RHBQ == 0)
+					continue;
+				if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+					o->f_oplock.BreakingToRead = B_FALSE;
+					if (BreakCacheLevel & HANDLE_CACHING)
+						NeedToWait = B_TRUE;
+				}
+			}
+
+			/*
+			 * For each Open ThisOpen in Oplock.RHOplocks:
+			 *	Call the algorithm in section 2.1.4.12.2,
+			 *	  passing Open as the OperationOpen parameter,
+			 *	  ThisOpen as the OplockOpen parameter, and
+			 *	  Flags as the Flagsparameter.
+			 *	If the algorithm  returns FALSE:
+			 *		Remove ThisOpen from Oplock.RHOplocks.
+			 *		Notify the server of an oplock break
+			 *		  according to the algorithm in
+			 *		  section 2.1.5.17.3, setting the
+			 *		  algorithm's parameters as follows:
+			 *			BreakingOplockOpen = ThisOpen.
+			 *			NewOplockLevel = LEVEL_NONE.
+			 *			AcknowledgeRequired = TRUE.
+			 *			Compl_Status = STATUS_SUCCESS.
+			 *		(The operation does not end at this
+			 *		 point; this call to 2.1.5.17.3
+			 *		 completes some earlier call to
+			 *		 2.1.5.17.2.)
+			 *		Initialize a new RHOpContext object,
+			 *		  setting its fields as follows:
+			 *			RHOpCtx.Open = ThisOpen.
+			 *			RHOpCtx.BreakingToRead = FALSE
+			 *		Add the new RHOpContext object to
+			 *		  Oplock.RHBreakQueue.
+			 *		If BreakCacheLevel contains
+			 *		  HANDLE_CACHING:
+			 *			Set NeedToWait to TRUE.
+			 *		EndIf
+			 *	EndIf
+			 * EndFor
+			 */
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_RH == 0)
+					continue;
+				if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+					o->f_oplock.onlist_RH = B_FALSE;
+					nol->cnt_RH--;
+					ASSERT(nol->cnt_RH >= 0);
+
+					smb_oplock_ind_break(o,
+					    LEVEL_NONE, B_TRUE,
+					    NT_STATUS_SUCCESS);
+
+					o->f_oplock.BreakingToRead = B_FALSE;
+					ASSERT(!(o->f_oplock.onlist_RHBQ));
+					o->f_oplock.onlist_RHBQ = B_TRUE;
+					nol->cnt_RHBQ++;
+
+					if (BreakCacheLevel & HANDLE_CACHING)
+						NeedToWait = B_TRUE;
+				}
+			}
+		}
+
+// If the oplock is explicitly losing HANDLE_CACHING, RHBreakQueue is
+// not empty, and the algorithm has not yet decided to wait, this operation
+// might have to wait if there is an oplock on RHBreakQueue with a
+// non-matching key. This is done because even if this operation didn't
+// cause a break of a currently-granted Read-Handle caching oplock, it
+// might have done so had a currently-breaking oplock still been granted.
+
+		/*
+		 * If (NeedToWait is FALSE) and
+		 *   (Oplock.RHBreakQueue is empty) and   (XXX: Not empty)
+		 *   (BreakCacheLevel contains HANDLE_CACHING):
+		 *	For each RHOpContext ThisContex in Oplock.RHBreakQueue:
+		 *		If ThisContext.Open.OplockKey != Open.OplockKey:
+		 *			Set NeedToWait to TRUE.
+		 *			Break out of the For loop.
+		 *		EndIf
+		 *	EndFor
+		 * EndIf
+		 * Recompute Oplock.State according to the algorithm in
+		 *   section 2.1.4.13, passing Oplock as ThisOplock.
+		 */
+		if (NeedToWait == B_FALSE &&
+		    (BreakCacheLevel & HANDLE_CACHING) != 0) {
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_RHBQ == 0)
+					continue;
+				if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+					NeedToWait = B_TRUE;
+					break;
+				}
+			}
+		}
+		RecomputeOplockState(node);
+		break;
+
+	case (READ_CACHING|HANDLE_CACHING|BREAK_TO_READ_CACHING):
+		/*
+		 * If BreakCacheLevel contains READ_CACHING:
+		 */
+		if ((BreakCacheLevel & READ_CACHING) != 0) {
+			/*
+			 * For each RHOpContext ThisContext in
+			 *  Oplock.RHBreakQueue:
+			 *	Call the algorithm in section 2.1.4.12.2,
+			 *	  passing Open = OperationOpen parameter,
+			 *	  ThisContext.Open = OplockOpen parameter,
+			 *	  and Flags as the Flags parameter.
+			 *	If the algorithm returns FALSE:
+			 *		Set ThisCtx.BreakingToRead = FALSE.
+			 *	EndIf
+			 *	Recompute Oplock.State according to the
+			 *	  algorithm in section 2.1.4.13, passing
+			 *	  Oplock as the ThisOplock parameter.
+			 * EndFor
+			 */
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_RHBQ == 0)
+					continue;
+				if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+					o->f_oplock.BreakingToRead = B_FALSE;
+				}
+			}
+			RecomputeOplockState(node);
+		}
+		/* FALLTHROUGH */
+
+	case (READ_CACHING|HANDLE_CACHING|BREAK_TO_NO_CACHING):
+		/*
+		 * If BreakCacheLevel contains HANDLE_CACHING:
+		 *	For each RHOpContext ThisContext in Oplock.RHBreakQueue:
+		 *		If ThisContext.Open.OplockKey != Open.OplockKey:
+		 *			Set NeedToWait to TRUE.
+		 *			Break out of the For loop.
+		 *		EndIf
+		 *	EndFor
+		 * EndIf
+		 */
+		if ((BreakCacheLevel & HANDLE_CACHING) != 0) {
+			FOREACH_NODE_OFILE(node, o) {
+				if (o->f_oplock.onlist_RHBQ == 0)
+					continue;
+				if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+					NeedToWait = B_TRUE;
+					break;
+				}
+			}
+		}
+		break;
+
+	case (READ_CACHING|WRITE_CACHING|EXCLUSIVE):
+		/*
+		 * If BreakCacheLevel contains both
+		 *  READ_CACHING and WRITE_CACHING:
+		 *	Notify the server of an oplock break according to
+		 *	  the algorithm in section 2.1.5.17.3, setting the
+		 *	  algorithm's parameters as follows:
+		 *		BreakingOplockOpen = Oplock.ExclusiveOpen.
+		 *		NewOplockLevel = LEVEL_NONE.
+		 *		AcknowledgeRequired = TRUE.
+		 *		OplockCompletionStatus = STATUS_SUCCESS.
+		 *	(The operation does not end at this point;
+		 *	 this call to 2.1.5.17.3 completes some
+		 *	 earlier call to 2.1.5.17.1.)
+		 *	Set Oplock.State to (READ_CACHING|WRITE_CACHING| \
+		 *			EXCLUSIVE|BREAK_TO_NO_CACHING).
+		 *	Set NeedToWait to TRUE.
+		 */
+		if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) ==
+		    (READ_CACHING | WRITE_CACHING)) {
+			o = nol->excl_open;
+			ASSERT(o != NULL);
+			smb_oplock_ind_break(o,
+			    LEVEL_NONE, B_TRUE,
+			    NT_STATUS_SUCCESS);
+
+			nol->ol_state =
+			    (READ_CACHING|WRITE_CACHING|
+			    EXCLUSIVE|BREAK_TO_NO_CACHING);
+			NeedToWait = B_TRUE;
+		}
+
+		/*
+		 * Else If BreakCacheLevel contains WRITE_CACHING:
+		 *	Notify the server of an oplock break according to
+		 *	  the algorithm in section 2.1.5.17.3, setting the
+		 *	  algorithm's parameters as follows:
+		 *		BreakingOplockOpen = Oplock.ExclusiveOpen.
+		 *		NewOplockLevel = READ_CACHING.
+		 *		AcknowledgeRequired = TRUE.
+		 *		OplockCompletionStatus = STATUS_SUCCESS.
+		 *	(The operation does not end at this point;
+		 *	 this call to 2.1.5.17.3 completes some
+		 *	 earlier call to 2.1.5.17.1.)
+		 *	Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+		 *			 EXCLUSIVE|BREAK_TO_READ_CACHING).
+		 *	Set NeedToWait to TRUE.
+		 * EndIf
+		 */
+		else if ((BreakCacheLevel & WRITE_CACHING) != 0) {
+			o = nol->excl_open;
+			ASSERT(o != NULL);
+			smb_oplock_ind_break(o,
+			    READ_CACHING, B_TRUE,
+			    NT_STATUS_SUCCESS);
+
+			nol->ol_state =
+			    (READ_CACHING|WRITE_CACHING|
+			    EXCLUSIVE|BREAK_TO_READ_CACHING);
+			NeedToWait = B_TRUE;
+		}
+		break;
+
+	case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE):
+		/*
+		 * If BreakCacheLevel equals WRITE_CACHING:
+		 *	Notify the server of an oplock break according to
+		 *	  the algorithm in section 2.1.5.17.3, setting the
+		 *	  algorithm's parameters as follows:
+		 *		BreakingOplockOpen = Oplock.ExclusiveOpen.
+		 *		NewOplockLevel = (READ_CACHING|HANDLE_CACHING).
+		 *		AcknowledgeRequired = TRUE.
+		 *		OplockCompletionStatus = STATUS_SUCCESS.
+		 *	(The operation does not end at this point;
+		 *	 this call to 2.1.5.17.3 completes some
+		 *	 earlier call to 2.1.5.17.1.)
+		 *	Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+		 *			HANDLE_CACHING|EXCLUSIVE|
+		 *			BREAK_TO_READ_CACHING|
+		 *			BREAK_TO_HANDLE_CACHING).
+		 *	Set NeedToWait to TRUE.
+		 */
+		if (BreakCacheLevel == WRITE_CACHING) {
+			o = nol->excl_open;
+			ASSERT(o != NULL);
+			smb_oplock_ind_break(o,
+			    CACHE_RH, B_TRUE,
+			    NT_STATUS_SUCCESS);
+
+			nol->ol_state =
+			    (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|
+			    EXCLUSIVE|BREAK_TO_READ_CACHING|
+			    BREAK_TO_HANDLE_CACHING);
+			NeedToWait = B_TRUE;
+		}
+
+		/*
+		 * Else If BreakCacheLevel equals HANDLE_CACHING:
+		 *	Notify the server of an oplock break according to
+		 *	  the algorithm in section 2.1.5.17.3, setting the
+		 *	  algorithm's parameters as follows:
+		 *		BreakingOplockOpen = Oplock.ExclusiveOpen.
+		 *		NewOplockLevel = (READ_CACHING|WRITE_CACHING).
+		 *		AcknowledgeRequired = TRUE.
+		 *		OplockCompletionStatus = STATUS_SUCCESS.
+		 *	(The operation does not end at this point;
+		 *	 this call to 2.1.5.17.3 completes some
+		 *	 earlier call to 2.1.5.17.1.)
+		 *	Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+		 *			HANDLE_CACHING|EXCLUSIVE|
+		 *			BREAK_TO_READ_CACHING|
+		 *			BREAK_TO_WRITE_CACHING).
+		 *	Set NeedToWait to TRUE.
+		 */
+		else if (BreakCacheLevel == HANDLE_CACHING) {
+			o = nol->excl_open;
+			ASSERT(o != NULL);
+			smb_oplock_ind_break(o,
+			    CACHE_RW, B_TRUE,
+			    NT_STATUS_SUCCESS);
+
+			nol->ol_state =
+			    (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|
+			    EXCLUSIVE|BREAK_TO_READ_CACHING|
+			    BREAK_TO_WRITE_CACHING);
+			NeedToWait = B_TRUE;
+		}
+
+		/*
+		 * Else If BreakCacheLevel contains both
+		 *  READ_CACHING and WRITE_CACHING:
+		 *	Notify the server of an oplock break according to
+		 *	  the algorithm in section 2.1.5.17.3, setting the
+		 *	  algorithm's parameters as follows:
+		 *		BreakingOplockOpen = Oplock.ExclusiveOpen.
+		 *		NewOplockLevel = LEVEL_NONE.
+		 *		AcknowledgeRequired = TRUE.
+		 *		OplockCompletionStatus = STATUS_SUCCESS.
+		 *	(The operation does not end at this point;
+		 *	 this call to 2.1.5.17.3 completes some
+		 *	 earlier call to 2.1.5.17.1.)
+		 *	Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+		 *			HANDLE_CACHING|EXCLUSIVE|
+		 *			BREAK_TO_NO_CACHING).
+		 *	Set NeedToWait to TRUE.
+		 * EndIf
+		 */
+		else if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) ==
+		    (READ_CACHING | WRITE_CACHING)) {
+			o = nol->excl_open;
+			ASSERT(o != NULL);
+			smb_oplock_ind_break(o,
+			    LEVEL_NONE, B_TRUE,
+			    NT_STATUS_SUCCESS);
+
+			nol->ol_state =
+			    (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|
+			    EXCLUSIVE|BREAK_TO_NO_CACHING);
+			NeedToWait = B_TRUE;
+		}
+		break;
+
+	case (READ_CACHING|WRITE_CACHING|EXCLUSIVE|BREAK_TO_READ_CACHING):
+		/*
+		 * If BreakCacheLevel contains READ_CACHING:
+		 *	Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+		 *			EXCLUSIVE|BREAK_TO_NO_CACHING).
+		 * EndIf
+		 * If BreakCacheLevel contains either
+		 *  READ_CACHING or WRITE_CACHING:
+		 *	Set NeedToWait to TRUE.
+		 * EndIf
+		 */
+		if ((BreakCacheLevel & READ_CACHING) != 0) {
+			nol->ol_state =
+			    (READ_CACHING|WRITE_CACHING|
+			    EXCLUSIVE|BREAK_TO_NO_CACHING);
+		}
+		if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) != 0) {
+			NeedToWait = B_TRUE;
+		}
+		break;
+
+	case (READ_CACHING|WRITE_CACHING|EXCLUSIVE|BREAK_TO_NO_CACHING):
+		/*
+		 * If BreakCacheLevel contains either
+		 *  READ_CACHING or WRITE_CACHING:
+		 *	Set NeedToWait to TRUE.
+		 * EndIf
+		 */
+		if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) != 0) {
+			NeedToWait = B_TRUE;
+		}
+		break;
+
+	case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+	    BREAK_TO_READ_CACHING|BREAK_TO_WRITE_CACHING):
+		/*
+		 * If BreakCacheLevel == WRITE_CACHING:
+		 *	Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+		 *	    HANDLE_CACHING|EXCLUSIVE|BREAK_TO_READ_CACHING).
+		 * Else If BreakCacheLevel contains both
+		 *  READ_CACHING and WRITE_CACHING:
+		 *	Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+		 *	    HANDLE_CACHING|EXCLUSIVE|BREAK_TO_NO_CACHING).
+		 * EndIf
+		 * Set NeedToWait to TRUE.
+		 */
+		if (BreakCacheLevel == WRITE_CACHING) {
+			nol->ol_state = (READ_CACHING|WRITE_CACHING|
+			    HANDLE_CACHING|EXCLUSIVE|BREAK_TO_READ_CACHING);
+		}
+		else if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) ==
+		    (READ_CACHING | WRITE_CACHING)) {
+			nol->ol_state = (READ_CACHING|WRITE_CACHING|
+			    HANDLE_CACHING|EXCLUSIVE|BREAK_TO_NO_CACHING);
+		}
+		NeedToWait = B_TRUE;
+		break;
+
+	case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+	    BREAK_TO_READ_CACHING|BREAK_TO_HANDLE_CACHING):
+		/*
+		 * If BreakCacheLevel == HANDLE_CACHING:
+		 *	Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+		 *			HANDLE_CACHING|EXCLUSIVE|
+		 *			BREAK_TO_READ_CACHING).
+		 * Else If BreakCacheLevel contains READ_CACHING:
+		 *	Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+		 *			HANDLE_CACHING|EXCLUSIVE|
+		 *			BREAK_TO_NO_CACHING).
+		 * EndIf
+		 * Set NeedToWait to TRUE.
+		 */
+		if (BreakCacheLevel == HANDLE_CACHING) {
+			nol->ol_state =
+			    (READ_CACHING|WRITE_CACHING|
+			    HANDLE_CACHING|EXCLUSIVE|
+			    BREAK_TO_READ_CACHING);
+		}
+		else if ((BreakCacheLevel & READ_CACHING) != 0) {
+			nol->ol_state =
+			    (READ_CACHING|WRITE_CACHING|
+			    HANDLE_CACHING|EXCLUSIVE|
+			    BREAK_TO_NO_CACHING);
+		}
+		NeedToWait = B_TRUE;
+		break;
+
+	case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+	    BREAK_TO_READ_CACHING):
+		/*
+		 * If BreakCacheLevel contains READ_CACHING,
+		 *	Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+		 *			HANDLE_CACHING|EXCLUSIVE|
+		 *			BREAK_TO_NO_CACHING).
+		 * EndIf
+		 * Set NeedToWait to TRUE.
+		 */
+		if ((BreakCacheLevel & READ_CACHING) != 0) {
+			nol->ol_state =
+			    (READ_CACHING|WRITE_CACHING|
+			    HANDLE_CACHING|EXCLUSIVE|
+			    BREAK_TO_NO_CACHING);
+		}
+		NeedToWait = B_TRUE;
+		break;
+
+	case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+	    BREAK_TO_NO_CACHING):
+		NeedToWait = B_TRUE;
+		break;
+
+	} /* Switch */
+
+	if (NeedToWait) {
+		/*
+		 * The operation that called this algorithm MUST be
+		 *   made cancelable by inserting it into
+		 *   CancelableOperations.CancelableOperationList.
+		 * The operation that called this algorithm waits until
+		 *   the oplock break is acknowledged, as specified in
+		 *   section 2.1.5.18, or the operation is canceled.
+		 */
+		status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS;
+		/* Caller does smb_oplock_wait_break() */
+	}
+
+out:
+	mutex_exit(&node->n_oplock.ol_mutex);
+	smb_llist_exit(&node->n_ofile_list);
+
+	return (status);
+}
+
+/*
+ * smb_oplock_move()
+ *
+ * Helper function for smb2_lease_ofile_close, where we're closing the
+ * ofile that has the oplock for a given lease, and need to move that
+ * oplock to another handle with the same lease.
+ *
+ * This is not described in [MS-FSA], so presumably Windows does this
+ * by keeping oplock objects separate from the open files (no action
+ * needed in the FSA layer).  We keep the oplock state as part of the
+ * ofile, so we need to relocate the oplock state in this case.
+ *
+ * Note that in here, we're moving state for both the FSA level and
+ * the SMB level (which is unusual) but this is the easiest way to
+ * make sure we move the state without any other effects.
+ */
+void
+smb_oplock_move(smb_node_t *node,
+    smb_ofile_t *fr_ofile, smb_ofile_t *to_ofile)
+{
+	/*
+	 * These are the two common states for an ofile with
+	 * a lease that's not the one holding the oplock.
+	 * Log if it's not either of these.
+	 */
+	static const smb_oplock_grant_t og0 = { 0 };
+	static const smb_oplock_grant_t og8 = {
+	    .og_state = OPLOCK_LEVEL_GRANULAR, 0 };
+	smb_oplock_grant_t og_tmp;
+
+	ASSERT(fr_ofile->f_node == node);
+	ASSERT(to_ofile->f_node == node);
+
+	mutex_enter(&node->n_oplock.ol_mutex);
+
+	/*
+	 * The ofile to which we're moving the oplock
+	 * should NOT have any oplock state.  However,
+	 * as long as we just swap state between the
+	 * two oplocks, we won't invalidate any of
+	 * the node's "onlist" counts etc.
+	 */
+	if (bcmp(&to_ofile->f_oplock, &og0, sizeof (og0)) != 0 &&
+	    bcmp(&to_ofile->f_oplock, &og8, sizeof (og8)) != 0) {
+#ifdef	DEBUG
+		cmn_err(CE_NOTE, "smb_oplock_move: not empty?");
+#endif
+		DTRACE_PROBE2(dst__not__empty,
+		    smb_node_t, node, smb_ofile_t, to_ofile);
+	}
+
+	og_tmp = to_ofile->f_oplock;
+	to_ofile->f_oplock = fr_ofile->f_oplock;
+	fr_ofile->f_oplock = og_tmp;
+
+	if (node->n_oplock.excl_open == fr_ofile)
+		node->n_oplock.excl_open = to_ofile;
+
+	mutex_exit(&node->n_oplock.ol_mutex);
+}
--- a/usr/src/uts/common/fs/smbsrv/smb_cmn_rename.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_cmn_rename.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,11 +20,11 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <sys/synch.h>
-#include <smbsrv/smb_kproto.h>
+#include <smbsrv/smb2_kproto.h>
 #include <smbsrv/smb_fsops.h>
 #include <sys/nbmlock.h>
 
@@ -36,6 +36,7 @@
 static int smb_rename_check_stream(smb_fqi_t *, smb_fqi_t *);
 static int smb_rename_check_attr(smb_request_t *, smb_node_t *, uint16_t);
 static int smb_rename_lookup_src(smb_request_t *);
+static uint32_t smb_rename_check_src(smb_request_t *, smb_fqi_t *);
 static void smb_rename_release_src(smb_request_t *);
 static uint32_t smb_rename_errno2status(int);
 
@@ -99,7 +100,7 @@
 	smb_node_t *tnode;
 	char *new_name, *path;
 	DWORD status;
-	int rc, count;
+	int rc;
 
 	tnode = sr->tid_tree->t_snode;
 	path = dst_fqi->fq_path.pn_path;
@@ -111,23 +112,37 @@
 
 	/*
 	 * The source node may already have been provided,
-	 * i.e. when called by SMB1/SMB2 smb_setinfo_rename.
-	 * Not provided by smb_com_rename, smb_com_nt_rename.
+	 * i.e. when called by SMB1/SMB2 smb_setinfo_rename
+	 * with an ofile.  When we have an ofile, open has
+	 * already checked for sharing violations.  For
+	 * path-based operations, do sharing check here.
 	 */
 	if (src_fqi->fq_fnode) {
-		smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
+		smb_node_ref(src_fqi->fq_dnode);
 		smb_node_ref(src_fqi->fq_fnode);
-		smb_node_ref(src_fqi->fq_dnode);
 	} else {
 		/* lookup and validate src node */
 		rc = smb_rename_lookup_src(sr);
 		if (rc != 0)
 			return (smb_rename_errno2status(rc));
+		/* Holding refs on dnode, fnode */
 	}
-
 	src_fnode = src_fqi->fq_fnode;
 	src_dnode = src_fqi->fq_dnode;
 
+	/* Break oplocks, and check share modes. */
+	status = smb_rename_check_src(sr, src_fqi);
+	if (status != NT_STATUS_SUCCESS) {
+		smb_node_release(src_fqi->fq_fnode);
+		smb_node_release(src_fqi->fq_dnode);
+		return (status);
+	}
+	/*
+	 * NB: src_fnode is now "in crit" (critical section)
+	 * as if we did smb_node_start_crit(..., RW_READER);
+	 * Call smb_rename_release_src(sr) on errors.
+	 */
+
 	/*
 	 * Find the destination dnode and last component.
 	 * May already be provided, i.e. when called via
@@ -234,24 +249,22 @@
 			return (NT_STATUS_OBJECT_NAME_COLLISION);
 		}
 
-		(void) smb_oplock_break(sr, dst_fnode,
-		    SMB_OPLOCK_BREAK_TO_NONE | SMB_OPLOCK_BREAK_BATCH);
+		status = smb_oplock_break_DELETE(dst_fnode, NULL);
+		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+			if (sr->session->dialect >= SMB_VERS_2_BASE)
+				(void) smb2sr_go_async(sr);
+			(void) smb_oplock_wait_break(dst_fnode, 0);
+			status = 0;
+		}
+		if (status != 0) {
+			smb_rename_release_src(sr);
+			smb_node_release(dst_fnode);
+			smb_node_release(dst_dnode);
+			return (status);
+		}
 
-		/*
-		 * Wait (a little) for the oplock break to be
-		 * responded to by clients closing handles.
-		 * Hold node->n_lock as reader to keep new
-		 * ofiles from showing up after we check.
-		 */
 		smb_node_rdlock(dst_fnode);
-		for (count = 0; count <= 12; count++) {
-			status = smb_node_delete_check(dst_fnode);
-			if (status != NT_STATUS_SHARING_VIOLATION)
-				break;
-			smb_node_unlock(dst_fnode);
-			delay(MSEC_TO_TICK(100));
-			smb_node_rdlock(dst_fnode);
-		}
+		status = smb_node_delete_check(dst_fnode);
 		if (status != NT_STATUS_SUCCESS) {
 			smb_node_unlock(dst_fnode);
 			smb_rename_release_src(sr);
@@ -434,23 +447,31 @@
 
 	/* The source node may already have been provided */
 	if (src_fqi->fq_fnode) {
-		smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
+		smb_node_ref(src_fqi->fq_dnode);
 		smb_node_ref(src_fqi->fq_fnode);
-		smb_node_ref(src_fqi->fq_dnode);
 	} else {
 		/* lookup and validate src node */
 		rc = smb_rename_lookup_src(sr);
 		if (rc != 0)
 			return (smb_rename_errno2status(rc));
+		/* Holding refs on dnode, fnode */
 	}
 
 	/* Not valid to create hardlink for directory */
 	if (smb_node_is_dir(src_fqi->fq_fnode)) {
-		smb_rename_release_src(sr);
+		smb_node_release(src_fqi->fq_dnode);
+		smb_node_release(src_fqi->fq_fnode);
 		return (NT_STATUS_FILE_IS_A_DIRECTORY);
 	}
 
 	/*
+	 * Unlike in rename, we will not unlink the src,
+	 * so skip the smb_rename_check_src() call, and
+	 * just "start crit" instead.
+	 */
+	smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
+
+	/*
 	 * Find the destination dnode and last component.
 	 * May already be provided, i.e. when called via
 	 * SMB1 trans2 setinfo.
@@ -510,24 +531,19 @@
 /*
  * smb_rename_lookup_src
  *
- * Lookup the src node, checking for sharing violations and
- * breaking any existing BATCH oplock.
- * Populate sr->arg.dirop.fqi
+ * Lookup the src node for a path-based link or rename.
  *
- * Upon success, the dnode and fnode will have holds and the
- * fnode will be in a critical section. These should be
- * released using smb_rename_release_src().
+ * On success, fills in sr->arg.dirop.fqi, and returns with
+ * holds on the source dnode and fnode.
  *
  * Returns errno values.
  */
 static int
 smb_rename_lookup_src(smb_request_t *sr)
 {
-	smb_node_t *src_node, *tnode;
-	DWORD status;
+	smb_node_t *tnode;
+	char *path;
 	int rc;
-	int count;
-	char *path;
 
 	smb_fqi_t *src_fqi = &sr->arg.dirop.fqi;
 
@@ -541,6 +557,7 @@
 	    &src_fqi->fq_dnode, src_fqi->fq_last_comp);
 	if (rc != 0)
 		return (rc);
+	/* hold fq_dnode */
 
 	rc = smb_fsop_lookup(sr, sr->user_cr, 0, tnode,
 	    src_fqi->fq_dnode, src_fqi->fq_last_comp, &src_fqi->fq_fnode);
@@ -548,43 +565,93 @@
 		smb_node_release(src_fqi->fq_dnode);
 		return (rc);
 	}
-	src_node = src_fqi->fq_fnode;
+	/* hold fq_dnode, fq_fnode */
 
-	rc = smb_rename_check_attr(sr, src_node, src_fqi->fq_sattr);
+	rc = smb_rename_check_attr(sr, src_fqi->fq_fnode, src_fqi->fq_sattr);
 	if (rc != 0) {
 		smb_node_release(src_fqi->fq_fnode);
 		smb_node_release(src_fqi->fq_dnode);
 		return (rc);
 	}
 
+	return (0);
+}
+
+/*
+ * smb_rename_check_src
+ *
+ * Check for sharing violations on the file we'll unlink, and
+ * break oplocks for the rename operation.  Note that we've
+ * already done oplock breaks associated with opening a handle
+ * on the file to rename.
+ *
+ * On success, returns with fnode in a critical section,
+ * as if smb_node_start_crit were called with the node.
+ * Caller should release using smb_rename_release_src().
+ */
+static uint32_t
+smb_rename_check_src(smb_request_t *sr, smb_fqi_t *src_fqi)
+{
+	smb_node_t *src_node = src_fqi->fq_fnode;
+	uint32_t status;
+
 	/*
 	 * Break BATCH oplock before ofile checks. If a client
 	 * has a file open, this will force a flush or close,
 	 * which may affect the outcome of any share checking.
+	 *
+	 * This operation may have either a handle or path for
+	 * the source node (that will be unlinked via rename).
 	 */
-	(void) smb_oplock_break(sr, src_node,
-	    SMB_OPLOCK_BREAK_TO_LEVEL_II | SMB_OPLOCK_BREAK_BATCH);
+
+	if (sr->fid_ofile != NULL) {
+		status = smb_oplock_break_SETINFO(src_node, sr->fid_ofile,
+		    FileRenameInformation);
+		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+			if (sr->session->dialect >= SMB_VERS_2_BASE)
+				(void) smb2sr_go_async(sr);
+			(void) smb_oplock_wait_break(src_node, 0);
+			status = 0;
+		}
+
+		/*
+		 * Sharing violations were checked at open time.
+		 * Just "start crit" to be consistent with the
+		 * state returned for path-based rename.
+		 */
+		smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
+		return (NT_STATUS_SUCCESS);
+	}
 
 	/*
-	 * Wait (a little) for the oplock break to be
-	 * responded to by clients closing handles.
-	 * Hold node->n_lock as reader to keep new
-	 * ofiles from showing up after we check.
+	 * This code path operates without a real open, so
+	 * break oplocks now as if we opened for delete.
+	 * Note: SMB2 does only ofile-based rename.
+	 *
+	 * Todo:  Use an "internal open" for path-based
+	 * rename and delete, then delete this code.
+	 */
+	ASSERT(sr->session->dialect < SMB_VERS_2_BASE);
+	status = smb_oplock_break_DELETE(src_node, NULL);
+	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+		(void) smb_oplock_wait_break(src_node, 0);
+	}
+
+	/*
+	 * Path-based access to the src file (no ofile)
+	 * so check for sharing violations here.
 	 */
 	smb_node_rdlock(src_node);
-	for (count = 0; count <= 12; count++) {
-		status = smb_node_rename_check(src_node);
-		if (status != NT_STATUS_SHARING_VIOLATION)
-			break;
-		smb_node_unlock(src_node);
-		delay(MSEC_TO_TICK(100));
-		smb_node_rdlock(src_node);
-	}
+	status = smb_node_rename_check(src_node);
 	if (status != NT_STATUS_SUCCESS) {
 		smb_node_unlock(src_node);
-		smb_node_release(src_fqi->fq_fnode);
-		smb_node_release(src_fqi->fq_dnode);
-		return (EPIPE); /* = ERRbadshare */
+		return (status);
+	}
+
+	status = smb_oplock_break_SETINFO(src_node, NULL,
+	    FileRenameInformation);
+	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+		(void) smb_oplock_wait_break(src_node, 0);
 	}
 
 	/*
@@ -605,15 +672,10 @@
 	status = smb_nbl_conflict(src_node, 0, UINT64_MAX, NBL_RENAME);
 	if (status != NT_STATUS_SUCCESS) {
 		smb_node_end_crit(src_node);
-		smb_node_release(src_fqi->fq_fnode);
-		smb_node_release(src_fqi->fq_dnode);
-		if (status == NT_STATUS_SHARING_VIOLATION)
-			return (EPIPE); /* = ERRbadshare */
-		return (EACCES);
 	}
 
-	/* NB: Caller expects holds on src_fqi fnode, dnode */
-	return (0);
+	/* NB: Caller expects to be "in crit" on fnode. */
+	return (status);
 }
 
 /*
--- a/usr/src/uts/common/fs/smbsrv/smb_cmn_setfile.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_cmn_setfile.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -29,7 +29,7 @@
  * SMB2 Set File Info
  */
 
-#include <smbsrv/smb_kproto.h>
+#include <smbsrv/smb2_kproto.h>
 #include <smbsrv/smb_fsops.h>
 
 /*
@@ -108,6 +108,7 @@
 	smb_attr_t *attr = &si->si_attr;
 	smb_node_t *node = si->si_node;
 	uint64_t eof;
+	uint32_t status;
 	int rc;
 
 	if (smb_mbc_decodef(&si->si_data, "q", &eof) != 0)
@@ -116,10 +117,16 @@
 	if (smb_node_is_dir(node))
 		return (NT_STATUS_INVALID_PARAMETER);
 
-	/* If opened by path, break exclusive oplock */
-	if (sr->fid_ofile == NULL)
-		(void) smb_oplock_break(sr, node,
-		    SMB_OPLOCK_BREAK_EXCLUSIVE | SMB_OPLOCK_BREAK_TO_NONE);
+	status = smb_oplock_break_SETINFO(node, sr->fid_ofile,
+	    FileEndOfFileInformation);
+	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+		if (sr->session->dialect >= SMB_VERS_2_BASE)
+			(void) smb2sr_go_async(sr);
+		(void) smb_oplock_wait_break(node, 0);
+		status = 0;
+	}
+	if (status != 0)
+		return (status);
 
 	bzero(attr, sizeof (*attr));
 	attr->sa_mask = SMB_AT_SIZE;
@@ -128,7 +135,6 @@
 	if (rc != 0)
 		return (smb_errno2status(rc));
 
-	smb_oplock_break_levelII(node);
 	return (0);
 }
 
@@ -144,6 +150,7 @@
 	smb_attr_t *attr = &si->si_attr;
 	smb_node_t *node = si->si_node;
 	uint64_t allocsz;
+	uint32_t status;
 	int rc;
 
 	if (smb_mbc_decodef(&si->si_data, "q", &allocsz) != 0)
@@ -152,10 +159,16 @@
 	if (smb_node_is_dir(node))
 		return (NT_STATUS_INVALID_PARAMETER);
 
-	/* If opened by path, break exclusive oplock */
-	if (sr->fid_ofile == NULL)
-		(void) smb_oplock_break(sr, node,
-		    SMB_OPLOCK_BREAK_EXCLUSIVE | SMB_OPLOCK_BREAK_TO_NONE);
+	status = smb_oplock_break_SETINFO(node, sr->fid_ofile,
+	    FileAllocationInformation);
+	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+		if (sr->session->dialect >= SMB_VERS_2_BASE)
+			(void) smb2sr_go_async(sr);
+		(void) smb_oplock_wait_break(node, 0);
+		status = 0;
+	}
+	if (status != 0)
+		return (status);
 
 	bzero(attr, sizeof (*attr));
 	attr->sa_mask = SMB_AT_ALLOCSZ;
@@ -164,7 +177,6 @@
 	if (rc != 0)
 		return (smb_errno2status(rc));
 
-	smb_oplock_break_levelII(node);
 	return (0);
 }
 
@@ -211,6 +223,7 @@
 	smb_node_t *node = si->si_node;
 	smb_ofile_t *of = sr->fid_ofile;
 	uint8_t		mark_delete;
+	uint32_t	status;
 	uint32_t	flags = 0;
 
 	if (smb_mbc_decodef(&si->si_data, "b", &mark_delete) != 0)
@@ -219,13 +232,27 @@
 	if ((of == NULL) || !(smb_ofile_granted_access(of) & DELETE))
 		return (NT_STATUS_ACCESS_DENIED);
 
-	if (mark_delete) {
-		if (SMB_TREE_SUPPORTS_CATIA(sr))
-			flags |= SMB_CATIA;
-		return (smb_node_set_delete_on_close(node, of->f_cr, flags));
-	} else {
+	if (mark_delete == 0) {
 		smb_node_reset_delete_on_close(node);
+		return (NT_STATUS_SUCCESS);
 	}
 
-	return (NT_STATUS_SUCCESS);
+	/*
+	 * Break any oplock handle caching.
+	 */
+	status = smb_oplock_break_SETINFO(node, of,
+	    FileDispositionInformation);
+	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+		if (sr->session->dialect >= SMB_VERS_2_BASE)
+			(void) smb2sr_go_async(sr);
+		(void) smb_oplock_wait_break(node, 0);
+		status = 0;
+	}
+	if (status != 0)
+		return (status);
+
+	if (SMB_TREE_SUPPORTS_CATIA(sr))
+		flags |= SMB_CATIA;
+
+	return (smb_node_set_delete_on_close(node, of->f_cr, flags));
 }
--- a/usr/src/uts/common/fs/smbsrv/smb_common_open.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_common_open.c	Wed Jun 12 22:46:54 2019 +0200
@@ -34,7 +34,7 @@
 #include <sys/fcntl.h>
 #include <sys/nbmlock.h>
 #include <smbsrv/string.h>
-#include <smbsrv/smb_kproto.h>
+#include <smbsrv/smb2_kproto.h>
 #include <smbsrv/smb_fsops.h>
 #include <smbsrv/smbinfo.h>
 
@@ -43,13 +43,9 @@
 static volatile uint32_t smb_fids = 0;
 #define	SMB_UNIQ_FID()	atomic_inc_32_nv(&smb_fids)
 
-static uint32_t smb_open_subr(smb_request_t *);
 extern uint32_t smb_is_executable(char *);
 static void smb_delete_new_object(smb_request_t *);
 static int smb_set_open_attributes(smb_request_t *, smb_ofile_t *);
-static void smb_open_oplock_break(smb_request_t *, smb_node_t *);
-static boolean_t smb_open_attr_only(smb_arg_open_t *);
-static boolean_t smb_open_overwrite(smb_arg_open_t *);
 
 /*
  * smb_access_generic_to_file
@@ -175,40 +171,7 @@
 }
 
 /*
- * Retry opens to avoid spurious sharing violations, due to timing
- * issues between closes and opens.  The client that already has the
- * file open may be in the process of closing it.
- */
-uint32_t
-smb_common_open(smb_request_t *sr)
-{
-	smb_arg_open_t	*parg;
-	uint32_t	status = NT_STATUS_SUCCESS;
-	int		count;
-
-	parg = kmem_alloc(sizeof (*parg), KM_SLEEP);
-	bcopy(&sr->arg.open, parg, sizeof (*parg));
-
-	for (count = 0; count <= 4; count++) {
-		if (count != 0)
-			delay(MSEC_TO_TICK(400));
-
-		status = smb_open_subr(sr);
-		if (status != NT_STATUS_SHARING_VIOLATION)
-			break;
-
-		bcopy(parg, &sr->arg.open, sizeof (*parg));
-	}
-
-	if (status == NT_STATUS_NO_SUCH_FILE)
-		status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
-
-	kmem_free(parg, sizeof (*parg));
-	return (status);
-}
-
-/*
- * smb_open_subr
+ * smb_common_open
  *
  * Notes on write-through behaviour. It looks like pre-LM0.12 versions
  * of the protocol specify the write-through mode when a file is opened,
@@ -244,18 +207,14 @@
  * 1. The creator of a readonly file can write to/modify the size of the file
  * using the original create fid, even though the file will appear as readonly
  * to all other fids and via a CIFS getattr call.
- * The readonly bit therefore cannot be set in the filesystem until the file
- * is closed (smb_ofile_close). It is accounted for via ofile and node flags.
  *
  * 2. A setinfo operation (using either an open fid or a path) to set/unset
  * readonly will be successful regardless of whether a creator of a readonly
- * file has an open fid (and has the special privilege mentioned in #1,
- * above).  I.e., the creator of a readonly fid holding that fid will no longer
- * have a special privilege.
+ * file has an open fid.
  *
  * 3. The DOS readonly bit affects only data and some metadata.
  * The following metadata can be changed regardless of the readonly bit:
- * 	- security descriptors
+ *	- security descriptors
  *	- DOS attributes
  *	- timestamps
  *
@@ -289,28 +248,38 @@
  * 4. Opening an existing file or directory
  *    The request attributes are ignored.
  */
-static uint32_t
-smb_open_subr(smb_request_t *sr)
+uint32_t
+smb_common_open(smb_request_t *sr)
 {
-	boolean_t	created = B_FALSE;
-	boolean_t	last_comp_found = B_FALSE;
-	smb_node_t	*node = NULL;
+	smb_server_t	*sv = sr->sr_server;
+	smb_tree_t	*tree = sr->tid_tree;
+	smb_node_t	*fnode = NULL;
 	smb_node_t	*dnode = NULL;
 	smb_node_t	*cur_node = NULL;
 	smb_arg_open_t	*op = &sr->sr_open;
-	int		rc;
-	smb_ofile_t	*of;
+	smb_pathname_t	*pn = &op->fqi.fq_path;
+	smb_ofile_t	*of = NULL;
 	smb_attr_t	new_attr;
+	hrtime_t	shrlock_t0;
 	int		max_requested = 0;
 	uint32_t	max_allowed;
 	uint32_t	status = NT_STATUS_SUCCESS;
 	int		is_dir;
-	smb_error_t	err;
+	int		rc;
 	boolean_t	is_stream = B_FALSE;
 	int		lookup_flags = SMB_FOLLOW_LINKS;
-	uint32_t	uniq_fid;
-	smb_pathname_t	*pn = &op->fqi.fq_path;
-	smb_server_t	*sv = sr->sr_server;
+	uint32_t	uniq_fid = 0;
+	uint16_t	tree_fid = 0;
+	boolean_t	created = B_FALSE;
+	boolean_t	last_comp_found = B_FALSE;
+	boolean_t	opening_incr = B_FALSE;
+	boolean_t	dnode_held = B_FALSE;
+	boolean_t	dnode_wlock = B_FALSE;
+	boolean_t	fnode_held = B_FALSE;
+	boolean_t	fnode_wlock = B_FALSE;
+	boolean_t	fnode_shrlk = B_FALSE;
+	boolean_t	did_open = B_FALSE;
+	boolean_t	did_break_handle = B_FALSE;
 
 	/* Get out now if we've been cancelled. */
 	mutex_enter(&sr->sr_mutex);
@@ -348,6 +317,9 @@
 		return (NT_STATUS_TOO_MANY_OPENED_FILES);
 	}
 
+	if (smb_idpool_alloc(&tree->t_fid_pool, &tree_fid))
+		return (NT_STATUS_TOO_MANY_OPENED_FILES);
+
 	/* This must be NULL at this point */
 	sr->fid_ofile = NULL;
 
@@ -372,36 +344,49 @@
 		 */
 		if ((rc = smb_threshold_enter(&sv->sv_opipe_ct)) != 0) {
 			status = RPC_NT_SERVER_TOO_BUSY;
-			return (status);
+			goto errout;
 		}
 
 		/*
-		 * No further processing for IPC, we need to either
-		 * raise an exception or return success here.
+		 * Most of IPC open is handled in smb_opipe_open()
 		 */
 		uniq_fid = SMB_UNIQ_FID();
-		status = smb_opipe_open(sr, uniq_fid);
+		op->create_options = 0;
+		of = smb_ofile_alloc(sr, op, NULL, SMB_FTYPE_MESG_PIPE,
+		    tree_fid, uniq_fid);
+		tree_fid = 0; // given to the ofile
+		status = smb_opipe_open(sr, of);
 		smb_threshold_exit(&sv->sv_opipe_ct);
-		return (status);
+		if (status != NT_STATUS_SUCCESS)
+			goto errout;
+		return (NT_STATUS_SUCCESS);
 
 	default:
-		return (NT_STATUS_BAD_DEVICE_TYPE);
+		status = NT_STATUS_BAD_DEVICE_TYPE;
+		goto errout;
 	}
 
 	smb_pathname_init(sr, pn, pn->pn_path);
-	if (!smb_pathname_validate(sr, pn))
-		return (sr->smb_error.status);
+	if (!smb_pathname_validate(sr, pn)) {
+		status = sr->smb_error.status;
+		goto errout;
+	}
 
 	if (strlen(pn->pn_path) >= SMB_MAXPATHLEN) {
-		return (NT_STATUS_OBJECT_PATH_INVALID);
+		status = NT_STATUS_OBJECT_PATH_INVALID;
+		goto errout;
 	}
 
 	if (is_dir) {
-		if (!smb_validate_dirname(sr, pn))
-			return (sr->smb_error.status);
+		if (!smb_validate_dirname(sr, pn)) {
+			status = sr->smb_error.status;
+			goto errout;
+		}
 	} else {
-		if (!smb_validate_object_name(sr, pn))
-			return (sr->smb_error.status);
+		if (!smb_validate_object_name(sr, pn)) {
+			status = sr->smb_error.status;
+			goto errout;
+		}
 	}
 
 	cur_node = op->fqi.fq_dnode ?
@@ -411,8 +396,20 @@
 	    sr->tid_tree->t_snode, cur_node, &op->fqi.fq_dnode,
 	    op->fqi.fq_last_comp);
 	if (rc != 0) {
-		return (smb_errno2status(rc));
+		status = smb_errno2status(rc);
+		goto errout;
 	}
+	dnode = op->fqi.fq_dnode;
+	dnode_held = B_TRUE;
+
+	/*
+	 * Lock the parent dir node in case another create
+	 * request to the same parent directory comes in.
+	 * Drop this once either lookup succeeds, or we've
+	 * created the object in this directory.
+	 */
+	smb_node_wrlock(dnode);
+	dnode_wlock = B_TRUE;
 
 	/*
 	 * If the access mask has only DELETE set (ignore
@@ -430,46 +427,49 @@
 
 	if (rc == 0) {
 		last_comp_found = B_TRUE;
+		fnode_held = B_TRUE;
+
 		/*
 		 * Need the DOS attributes below, where we
 		 * check the search attributes (sattr).
+		 * Also UID, for owner check below.
 		 */
-		op->fqi.fq_fattr.sa_mask = SMB_AT_DOSATTR;
+		op->fqi.fq_fattr.sa_mask = SMB_AT_DOSATTR | SMB_AT_UID;
 		rc = smb_node_getattr(sr, op->fqi.fq_fnode, zone_kcred(),
 		    NULL, &op->fqi.fq_fattr);
 		if (rc != 0) {
-			smb_node_release(op->fqi.fq_fnode);
-			smb_node_release(op->fqi.fq_dnode);
-			return (NT_STATUS_INTERNAL_ERROR);
+			status = NT_STATUS_INTERNAL_ERROR;
+			goto errout;
 		}
 	} else if (rc == ENOENT) {
 		last_comp_found = B_FALSE;
 		op->fqi.fq_fnode = NULL;
 		rc = 0;
 	} else {
-		smb_node_release(op->fqi.fq_dnode);
-		return (smb_errno2status(rc));
+		status = smb_errno2status(rc);
+		goto errout;
 	}
 
-
 	/*
 	 * The uniq_fid is a CIFS-server-wide unique identifier for an ofile
 	 * which is used to uniquely identify open instances for the
 	 * VFS share reservation and POSIX locks.
 	 */
-
 	uniq_fid = SMB_UNIQ_FID();
 
 	if (last_comp_found) {
 
-		node = op->fqi.fq_fnode;
+		smb_node_unlock(dnode);
+		dnode_wlock = B_FALSE;
+
+		fnode = op->fqi.fq_fnode;
 		dnode = op->fqi.fq_dnode;
 
-		if (!smb_node_is_file(node) && !smb_node_is_dir(node) &&
-		    !smb_node_is_symlink(node)) {
-			smb_node_release(node);
-			smb_node_release(dnode);
-			return (NT_STATUS_ACCESS_DENIED);
+		if (!smb_node_is_file(fnode) &&
+		    !smb_node_is_dir(fnode) &&
+		    !smb_node_is_symlink(fnode)) {
+			status = NT_STATUS_ACCESS_DENIED;
+			goto errout;
 		}
 
 		/*
@@ -479,18 +479,16 @@
 		 * - the target is NOT a directory and client requires that
 		 *   it MUST be.
 		 */
-		if (smb_node_is_dir(node)) {
+		if (smb_node_is_dir(fnode)) {
 			if (op->create_options & FILE_NON_DIRECTORY_FILE) {
-				smb_node_release(node);
-				smb_node_release(dnode);
-				return (NT_STATUS_FILE_IS_A_DIRECTORY);
+				status = NT_STATUS_FILE_IS_A_DIRECTORY;
+				goto errout;
 			}
 		} else {
 			if ((op->create_options & FILE_DIRECTORY_FILE) ||
 			    (op->nt_flags & NT_CREATE_FLAG_OPEN_TARGET_DIR)) {
-				smb_node_release(node);
-				smb_node_release(dnode);
-				return (NT_STATUS_NOT_A_DIRECTORY);
+				status = NT_STATUS_NOT_A_DIRECTORY;
+				goto errout;
 			}
 		}
 
@@ -498,42 +496,37 @@
 		 * No more open should be accepted when "Delete on close"
 		 * flag is set.
 		 */
-		if (node->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
-			smb_node_release(node);
-			smb_node_release(dnode);
-			return (NT_STATUS_DELETE_PENDING);
+		if (fnode->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
+			status = NT_STATUS_DELETE_PENDING;
+			goto errout;
 		}
 
 		/*
 		 * Specified file already exists so the operation should fail.
 		 */
 		if (op->create_disposition == FILE_CREATE) {
-			smb_node_release(node);
-			smb_node_release(dnode);
-			return (NT_STATUS_OBJECT_NAME_COLLISION);
+			status = NT_STATUS_OBJECT_NAME_COLLISION;
+			goto errout;
 		}
 
 		/*
 		 * Windows seems to check read-only access before file
 		 * sharing check.
 		 *
-		 * Check to see if the file is currently readonly (irrespective
+		 * Check to see if the file is currently readonly (regardless
 		 * of whether this open will make it readonly).
+		 * Readonly is ignored on directories.
 		 */
-		if (SMB_PATHFILE_IS_READONLY(sr, node)) {
-			/* Files data only */
-			if (!smb_node_is_dir(node)) {
-				if (op->desired_access & (FILE_WRITE_DATA |
-				    FILE_APPEND_DATA)) {
-					smb_node_release(node);
-					smb_node_release(dnode);
-					return (NT_STATUS_ACCESS_DENIED);
-				}
-				if (op->create_options & FILE_DELETE_ON_CLOSE) {
-					smb_node_release(node);
-					smb_node_release(dnode);
-					return (NT_STATUS_CANNOT_DELETE);
-				}
+		if (SMB_PATHFILE_IS_READONLY(sr, fnode) &&
+		    !smb_node_is_dir(fnode)) {
+			if (op->desired_access &
+			    (FILE_WRITE_DATA | FILE_APPEND_DATA)) {
+				status = NT_STATUS_ACCESS_DENIED;
+				goto errout;
+			}
+			if (op->create_options & FILE_DELETE_ON_CLOSE) {
+				status = NT_STATUS_CANNOT_DELETE;
+				goto errout;
 			}
 		}
 
@@ -543,15 +536,13 @@
 
 			if (!smb_sattr_check(op->fqi.fq_fattr.sa_dosattr,
 			    op->dattr)) {
-				smb_node_release(node);
-				smb_node_release(dnode);
-				return (NT_STATUS_ACCESS_DENIED);
+				status = NT_STATUS_ACCESS_DENIED;
+				goto errout;
 			}
 
-			if (smb_node_is_dir(node)) {
-				smb_node_release(node);
-				smb_node_release(dnode);
-				return (NT_STATUS_ACCESS_DENIED);
+			if (smb_node_is_dir(fnode)) {
+				status = NT_STATUS_ACCESS_DENIED;
+				goto errout;
 			}
 		}
 
@@ -562,58 +553,207 @@
 		    (op->create_disposition == FILE_OVERWRITE))
 			op->desired_access |= FILE_WRITE_DATA;
 
-		status = smb_fsop_access(sr, sr->user_cr, node,
+		status = smb_fsop_access(sr, sr->user_cr, fnode,
 		    op->desired_access);
-		if (status != NT_STATUS_SUCCESS) {
-			smb_node_release(node);
-			smb_node_release(dnode);
+		if (status != NT_STATUS_SUCCESS)
+			goto errout;
 
-			/* SMB1 specific? NT_STATUS_PRIVILEGE_NOT_HELD */
-			if (status == NT_STATUS_PRIVILEGE_NOT_HELD) {
-				return (status);
-			} else {
-				return (NT_STATUS_ACCESS_DENIED);
-			}
+		if (max_requested) {
+			smb_fsop_eaccess(sr, sr->user_cr, fnode, &max_allowed);
+			op->desired_access |= max_allowed;
 		}
 
-		if (max_requested) {
-			smb_fsop_eaccess(sr, sr->user_cr, node, &max_allowed);
-			op->desired_access |= max_allowed;
-		}
+		/*
+		 * File owner should always get read control + read attr.
+		 */
+		if (crgetuid(sr->user_cr) == op->fqi.fq_fattr.sa_vattr.va_uid)
+			op->desired_access |=
+			    (READ_CONTROL | FILE_READ_ATTRIBUTES);
+
 		/*
 		 * According to MS "dochelp" mail in Mar 2015, any handle
 		 * on which read or write access is granted implicitly
 		 * gets "read attributes", even if it was not requested.
-		 * This avoids unexpected access failures later that
-		 * would happen if these were not granted.
 		 */
-		if ((op->desired_access & FILE_DATA_ALL) != 0) {
-			op->desired_access |= (READ_CONTROL |
-			    FILE_READ_ATTRIBUTES);
-		}
+		if ((op->desired_access & FILE_DATA_ALL) != 0)
+			op->desired_access |= FILE_READ_ATTRIBUTES;
 
 		/*
 		 * Oplock break is done prior to sharing checks as the break
 		 * may cause other clients to close the file which would
-		 * affect the sharing checks. This may block, so set the
-		 * file opening count before oplock stuff.
+		 * affect the sharing checks, and may delete the file due to
+		 * DELETE_ON_CLOSE. This may block, so set the file opening
+		 * count before oplock stuff.
 		 */
-		smb_node_inc_opening_count(node);
-		smb_open_oplock_break(sr, node);
+		of = smb_ofile_alloc(sr, op, fnode, SMB_FTYPE_DISK,
+		    tree_fid, uniq_fid);
+		tree_fid = 0; // given to the ofile
+
+		smb_node_inc_opening_count(fnode);
+		opening_incr = B_TRUE;
 
-		smb_node_wrlock(node);
+		/*
+		 * XXX Supposed to do share access checks next.
+		 * [MS-FSA] describes that as part of access check:
+		 * 2.1.5.1.2.1 Alg... Check Access to an Existing File
+		 *
+		 * If CreateDisposition is FILE_OPEN or FILE_OPEN_IF:
+		 *   If Open.Stream.Oplock is not empty and
+		 *   Open.Stream.Oplock.State contains BATCH_OPLOCK,
+		 *   the object store MUST check for an oplock
+		 *   break according to the algorithm in section 2.1.4.12,
+		 *   with input values as follows:
+		 *	Open equal to this operation's Open
+		 *	Oplock equal to Open.Stream.Oplock
+		 *	Operation equal to "OPEN"
+		 *	OpParams containing two members:
+		 *	  DesiredAccess, CreateDisposition
+		 *
+		 * It's not clear how Windows would ask the FS layer if
+		 * the file has a BATCH oplock.  We'll use a call to the
+		 * common oplock code, which calls smb_oplock_break_OPEN
+		 * only if the oplock state contains BATCH_OPLOCK.
+		 * See: smb_oplock_break_BATCH()
+		 *
+		 * Also note: There's a nearly identical section in the
+		 * spec. at the start of the "else" part of the above
+		 * "if (disposition is overwrite, overwrite_if)" so this
+		 * section (oplock break, the share mode check, and the
+		 * next oplock_break_HANDLE) are all factored out to be
+		 * in all cases above that if/else from the spec.
+		 */
+		status = smb_oplock_break_BATCH(fnode, of,
+		    op->desired_access, op->create_disposition);
+		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+			if (sr->session->dialect >= SMB_VERS_2_BASE)
+				(void) smb2sr_go_async(sr);
+			(void) smb_oplock_wait_break(fnode, 0);
+			status = 0;
+		}
+		if (status != NT_STATUS_SUCCESS)
+			goto errout;
+
+		/*
+		 * Check for sharing violations, and if any,
+		 * do oplock break of handle caching.
+		 *
+		 * Need node_wrlock during shrlock checks,
+		 * and not locked during oplock breaks etc.
+		 */
+		shrlock_t0 = gethrtime();
+	shrlock_again:
+		smb_node_wrlock(fnode);
+		fnode_wlock = B_TRUE;
+		status = smb_fsop_shrlock(sr->user_cr, fnode, uniq_fid,
+		    op->desired_access, op->share_access);
+		smb_node_unlock(fnode);
+		fnode_wlock = B_FALSE;
 
 		/*
-		 * Check for sharing violations
+		 * [MS-FSA] "OPEN_BREAK_H"
+		 * If the (proposed) new open would violate sharing rules,
+		 * indicate an oplock break with OPEN_BREAK_H (to break
+		 * handle level caching rights) then try again.
+		 */
+		if (status == NT_STATUS_SHARING_VIOLATION &&
+		    did_break_handle == B_FALSE) {
+			did_break_handle = B_TRUE;
+
+			status = smb_oplock_break_HANDLE(fnode, of);
+			if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+				if (sr->session->dialect >= SMB_VERS_2_BASE)
+					(void) smb2sr_go_async(sr);
+				(void) smb_oplock_wait_break(fnode, 0);
+				status = 0;
+			} else {
+				/*
+				 * Even when the oplock layer does NOT
+				 * give us the special status indicating
+				 * we should wait, it may have scheduled
+				 * taskq jobs that may close handles.
+				 * Give those a chance to run before we
+				 * check again for sharing violations.
+				 */
+				delay(MSEC_TO_TICK(10));
+			}
+			if (status != NT_STATUS_SUCCESS)
+				goto errout;
+
+			goto shrlock_again;
+		}
+
+		/*
+		 * SMB1 expects a 1 sec. delay before returning a
+		 * sharing violation error.  If breaking oplocks
+		 * above took less than a sec, wait some more.
+		 * See: smbtorture base.defer_open
 		 */
-		status = smb_fsop_shrlock(sr->user_cr, node, uniq_fid,
-		    op->desired_access, op->share_access);
-		if (status == NT_STATUS_SHARING_VIOLATION) {
-			smb_node_unlock(node);
-			smb_node_dec_opening_count(node);
-			smb_node_release(node);
-			smb_node_release(dnode);
-			return (status);
+		if (status == NT_STATUS_SHARING_VIOLATION &&
+		    sr->session->dialect < SMB_VERS_2_BASE) {
+			hrtime_t t1 = shrlock_t0 + NANOSEC;
+			hrtime_t now = gethrtime();
+			if (now < t1) {
+				delay(NSEC_TO_TICK_ROUNDUP(t1 - now));
+			}
+		}
+
+		if (status != NT_STATUS_SUCCESS)
+			goto errout;
+		fnode_shrlk = B_TRUE;
+
+		/*
+		 * The [MS-FSA] spec. describes this oplock break as
+		 * part of the sharing access checks.  See:
+		 * 2.1.5.1.2.2 Algorithm to Check Sharing Access...
+		 * At the end of the share mode tests described there,
+		 * if it has not returned "sharing violation", it
+		 * specifies a call to the alg. in sec. 2.1.4.12,
+		 * that boils down to: smb_oplock_break_OPEN()
+		 */
+		status = smb_oplock_break_OPEN(fnode, of,
+		    op->desired_access,
+		    op->create_disposition);
+		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+			if (sr->session->dialect >= SMB_VERS_2_BASE)
+				(void) smb2sr_go_async(sr);
+			(void) smb_oplock_wait_break(fnode, 0);
+			status = 0;
+		}
+		if (status != NT_STATUS_SUCCESS)
+			goto errout;
+
+		if ((fnode->flags & NODE_FLAGS_DELETE_COMMITTED) != 0) {
+			/*
+			 * Breaking the oplock caused the file to be deleted,
+			 * so let's bail and pretend the file wasn't found.
+			 * Have to duplicate much of the logic found a the
+			 * "errout" label here.
+			 *
+			 * This code path is exercised by smbtorture
+			 * smb2.durable-open.delete_on_close1
+			 */
+			DTRACE_PROBE1(node_deleted, smb_node_t, fnode);
+			smb_ofile_free(of);
+			of = NULL;
+			last_comp_found = B_FALSE;
+
+			/*
+			 * Get all the holds and locks into the state
+			 * they would have if lookup had failed.
+			 */
+			fnode_shrlk = B_FALSE;
+			smb_fsop_unshrlock(sr->user_cr, fnode, uniq_fid);
+
+			opening_incr = B_FALSE;
+			smb_node_dec_opening_count(fnode);
+
+			fnode_held = B_FALSE;
+			smb_node_release(fnode);
+
+			dnode_wlock = B_TRUE;
+			smb_node_wrlock(dnode);
+
+			goto create;
 		}
 
 		/*
@@ -624,10 +764,10 @@
 		case FILE_OVERWRITE_IF:
 		case FILE_OVERWRITE:
 			op->dattr |= FILE_ATTRIBUTE_ARCHIVE;
-			/* Don't apply readonly bit until smb_ofile_close */
+			/* Don't apply readonly until smb_set_open_attributes */
 			if (op->dattr & FILE_ATTRIBUTE_READONLY) {
+				op->dattr &= ~FILE_ATTRIBUTE_READONLY;
 				op->created_readonly = B_TRUE;
-				op->dattr &= ~FILE_ATTRIBUTE_READONLY;
 			}
 
 			/*
@@ -640,31 +780,21 @@
 			new_attr.sa_dosattr = op->dattr;
 			new_attr.sa_vattr.va_size = 0;
 			new_attr.sa_mask = SMB_AT_DOSATTR | SMB_AT_SIZE;
-			rc = smb_fsop_setattr(sr, sr->user_cr, node, &new_attr);
+			rc = smb_fsop_setattr(sr, sr->user_cr, fnode,
+			    &new_attr);
 			if (rc != 0) {
-				smb_fsop_unshrlock(sr->user_cr, node, uniq_fid);
-				smb_node_unlock(node);
-				smb_node_dec_opening_count(node);
-				smb_node_release(node);
-				smb_node_release(dnode);
-				return (smb_errno2status(rc));
+				status = smb_errno2status(rc);
+				goto errout;
 			}
 
 			/*
 			 * If file is being replaced, remove existing streams
 			 */
-			if (SMB_IS_STREAM(node) == 0) {
+			if (SMB_IS_STREAM(fnode) == 0) {
 				status = smb_fsop_remove_streams(sr,
-				    sr->user_cr, node);
-				if (status != 0) {
-					smb_fsop_unshrlock(sr->user_cr, node,
-					    uniq_fid);
-					smb_node_unlock(node);
-					smb_node_dec_opening_count(node);
-					smb_node_release(node);
-					smb_node_release(dnode);
-					return (status);
-				}
+				    sr->user_cr, fnode);
+				if (status != 0)
+					goto errout;
 			}
 
 			op->action_taken = SMB_OACT_TRUNCATED;
@@ -684,6 +814,7 @@
 			break;
 		}
 	} else {
+create:
 		/* Last component was not found. */
 		dnode = op->fqi.fq_dnode;
 
@@ -692,39 +823,43 @@
 
 		if ((op->create_disposition == FILE_OPEN) ||
 		    (op->create_disposition == FILE_OVERWRITE)) {
-			smb_node_release(dnode);
-			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
+			status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+			goto errout;
 		}
 
 		if (pn->pn_fname && smb_is_invalid_filename(pn->pn_fname)) {
-			smb_node_release(dnode);
-			return (NT_STATUS_OBJECT_NAME_INVALID);
+			status = NT_STATUS_OBJECT_NAME_INVALID;
+			goto errout;
 		}
 
 		/*
 		 * Don't create in directories marked "Delete on close".
 		 */
 		if (dnode->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
-			smb_node_release(dnode);
-			return (NT_STATUS_DELETE_PENDING);
+			status = NT_STATUS_DELETE_PENDING;
+			goto errout;
 		}
 
 		/*
-		 * lock the parent dir node in case another create
-		 * request to the same parent directory comes in.
+		 * Create always sets the DOS attributes, type, and mode
+		 * in the if/else below (different for file vs directory).
+		 * Don't set the readonly bit until smb_set_open_attributes
+		 * or that would prevent this open.  Note that op->dattr
+		 * needs to be what smb_set_open_attributes will use,
+		 * except for the readonly bit.
 		 */
-		smb_node_wrlock(dnode);
-
-		/* Don't apply readonly bit until smb_ofile_close */
+		bzero(&new_attr, sizeof (new_attr));
+		new_attr.sa_mask = SMB_AT_DOSATTR | SMB_AT_TYPE | SMB_AT_MODE;
 		if (op->dattr & FILE_ATTRIBUTE_READONLY) {
 			op->dattr &= ~FILE_ATTRIBUTE_READONLY;
 			op->created_readonly = B_TRUE;
 		}
 
-		bzero(&new_attr, sizeof (new_attr));
+		/*
+		 * SMB create can specify the create time.
+		 */
 		if ((op->crtime.tv_sec != 0) &&
 		    (op->crtime.tv_sec != UINT_MAX)) {
-
 			new_attr.sa_mask |= SMB_AT_CRTIME;
 			new_attr.sa_crtime = op->crtime;
 		}
@@ -733,11 +868,12 @@
 			op->dattr |= FILE_ATTRIBUTE_ARCHIVE;
 			new_attr.sa_dosattr = op->dattr;
 			new_attr.sa_vattr.va_type = VREG;
-			new_attr.sa_vattr.va_mode = is_stream ? S_IRUSR :
-			    S_IRUSR | S_IRGRP | S_IROTH |
-			    S_IWUSR | S_IWGRP | S_IWOTH;
-			new_attr.sa_mask |=
-			    SMB_AT_DOSATTR | SMB_AT_TYPE | SMB_AT_MODE;
+			if (is_stream)
+				new_attr.sa_vattr.va_mode = S_IRUSR | S_IWUSR;
+			else
+				new_attr.sa_vattr.va_mode =
+				    S_IRUSR | S_IRGRP | S_IROTH |
+				    S_IWUSR | S_IWGRP | S_IWOTH;
 
 			/*
 			 * We set alloc_size = op->dsize later,
@@ -754,55 +890,43 @@
 
 			rc = smb_fsop_create(sr, sr->user_cr, dnode,
 			    op->fqi.fq_last_comp, &new_attr, &op->fqi.fq_fnode);
-
-			if (rc != 0) {
-				smb_node_unlock(dnode);
-				smb_node_release(dnode);
-				return (smb_errno2status(rc));
-			}
-
-			node = op->fqi.fq_fnode;
-			smb_node_inc_opening_count(node);
-			smb_node_wrlock(node);
-
-			status = smb_fsop_shrlock(sr->user_cr, node, uniq_fid,
-			    op->desired_access, op->share_access);
-
-			if (status == NT_STATUS_SHARING_VIOLATION) {
-				smb_node_unlock(node);
-				smb_node_dec_opening_count(node);
-				smb_delete_new_object(sr);
-				smb_node_release(node);
-				smb_node_unlock(dnode);
-				smb_node_release(dnode);
-				return (status);
-			}
 		} else {
 			op->dattr |= FILE_ATTRIBUTE_DIRECTORY;
 			new_attr.sa_dosattr = op->dattr;
 			new_attr.sa_vattr.va_type = VDIR;
 			new_attr.sa_vattr.va_mode = 0777;
-			new_attr.sa_mask |=
-			    SMB_AT_DOSATTR | SMB_AT_TYPE | SMB_AT_MODE;
 
 			rc = smb_fsop_mkdir(sr, sr->user_cr, dnode,
 			    op->fqi.fq_last_comp, &new_attr, &op->fqi.fq_fnode);
-			if (rc != 0) {
-				smb_node_unlock(dnode);
-				smb_node_release(dnode);
-				return (smb_errno2status(rc));
-			}
+		}
+		if (rc != 0) {
+			status = smb_errno2status(rc);
+			goto errout;
+		}
 
-			node = op->fqi.fq_fnode;
-			smb_node_inc_opening_count(node);
-			smb_node_wrlock(node);
-		}
+		smb_node_unlock(dnode);
+		dnode_wlock = B_FALSE;
 
 		created = B_TRUE;
 		op->action_taken = SMB_OACT_CREATED;
 
+		fnode = op->fqi.fq_fnode;
+		fnode_held = B_TRUE;
+
+		smb_node_inc_opening_count(fnode);
+		opening_incr = B_TRUE;
+
+		smb_node_wrlock(fnode);
+		fnode_wlock = B_TRUE;
+
+		status = smb_fsop_shrlock(sr->user_cr, fnode, uniq_fid,
+		    op->desired_access, op->share_access);
+		if (status != 0)
+			goto errout;
+		fnode_shrlk = B_TRUE;
+
 		if (max_requested) {
-			smb_fsop_eaccess(sr, sr->user_cr, node, &max_allowed);
+			smb_fsop_eaccess(sr, sr->user_cr, fnode, &max_allowed);
 			op->desired_access |= max_allowed;
 		}
 		/*
@@ -812,83 +936,80 @@
 		 * unexpected access failures later.
 		 */
 		op->desired_access |= (READ_CONTROL | FILE_READ_ATTRIBUTES);
-	}
 
-	status = NT_STATUS_SUCCESS;
-
-	of = smb_ofile_open(sr, node, op, SMB_FTYPE_DISK, uniq_fid,
-	    &err);
-	if (of == NULL) {
-		status = err.status;
+		/*
+		 * MS-FSA 2.1.5.1.1
+		 * If the Oplock member of the DirectoryStream in
+		 * Link.ParentFile.StreamList (ParentOplock) is
+		 * not empty ... oplock break on the parent...
+		 * (dnode is the parent directory)
+		 *
+		 * This compares of->ParentOplockKey with each
+		 * oplock of->TargetOplockKey and breaks...
+		 * so it's OK that we're passing an OF that's
+		 * NOT a member of dnode->n_ofile_list
+		 *
+		 * The break never blocks, so ignore the return.
+		 */
+		of = smb_ofile_alloc(sr, op, fnode, SMB_FTYPE_DISK,
+		    tree_fid, uniq_fid);
+		tree_fid = 0; // given to the ofile
+		(void) smb_oplock_break_PARENT(dnode, of);
 	}
 
 	/*
-	 * We might have blocked in smb_ofile_open long enough so a
-	 * tree disconnect might have happened.  In that case, we've
-	 * just added an ofile to a tree that's disconnecting, and
-	 * need to undo that to avoid interfering with tear-down of
-	 * the tree connection.
+	 * We might have blocked in smb_oplock_break_OPEN long enough
+	 * so a tree disconnect might have happened.  In that case,
+	 * we would be adding an ofile to a tree that's disconnecting,
+	 * which would interfere with tear-down.  If so, error out.
 	 */
-	if (status == NT_STATUS_SUCCESS &&
-	    !smb_tree_is_connected(sr->tid_tree)) {
+	if (!smb_tree_is_connected(sr->tid_tree)) {
 		status = NT_STATUS_INVALID_PARAMETER;
+		goto errout;
 	}
 
 	/*
+	 * Moved this up from smb_ofile_open()
+	 */
+	if ((rc = smb_fsop_open(fnode, of->f_mode, of->f_cr)) != 0) {
+		status = smb_errno2status(rc);
+		goto errout;
+	}
+
+	/*
+	 * Complete this open (add to ofile lists)
+	 */
+	smb_ofile_open(sr, op, of);
+	did_open = B_TRUE;
+
+	/*
 	 * This MUST be done after ofile creation, so that explicitly
-	 * set timestamps can be remembered on the ofile, and the
-	 * readonly flag will be stored "pending" on the node.
+	 * set timestamps can be remembered on the ofile, and setting
+	 * the readonly flag won't affect access via this open.
 	 */
-	if (status == NT_STATUS_SUCCESS) {
-		if ((rc = smb_set_open_attributes(sr, of)) != 0) {
-			status = smb_errno2status(rc);
-		}
-	}
-
-	if (status == NT_STATUS_SUCCESS) {
-		/*
-		 * We've already done access checks above,
-		 * and want this call to succeed even when
-		 * !(desired_access & FILE_READ_ATTRIBUTES),
-		 * so pass kcred here.
-		 */
-		op->fqi.fq_fattr.sa_mask = SMB_AT_ALL;
-		rc = smb_node_getattr(sr, node, zone_kcred(), of,
-		    &op->fqi.fq_fattr);
-		if (rc != 0) {
-			status = NT_STATUS_INTERNAL_ERROR;
-		}
+	if ((rc = smb_set_open_attributes(sr, of)) != 0) {
+		status = smb_errno2status(rc);
+		goto errout;
 	}
 
 	/*
-	 * smb_fsop_unshrlock is a no-op if node is a directory
-	 * smb_fsop_unshrlock is done in smb_ofile_close
+	 * We've already done access checks above,
+	 * and want this call to succeed even when
+	 * !(desired_access & FILE_READ_ATTRIBUTES),
+	 * so pass kcred here.
 	 */
-	if (status != NT_STATUS_SUCCESS) {
-		if (of == NULL) {
-			smb_fsop_unshrlock(sr->user_cr, node, uniq_fid);
-		} else {
-			smb_ofile_close(of, 0);
-			smb_ofile_release(of);
-		}
-		if (created)
-			smb_delete_new_object(sr);
-		smb_node_unlock(node);
-		smb_node_dec_opening_count(node);
-		smb_node_release(node);
-		if (created)
-			smb_node_unlock(dnode);
-		smb_node_release(dnode);
-		return (status);
-	}
+	op->fqi.fq_fattr.sa_mask = SMB_AT_ALL;
+	(void) smb_node_getattr(sr, fnode, zone_kcred(), of,
+	    &op->fqi.fq_fattr);
 
 	/*
 	 * Propagate the write-through mode from the open params
 	 * to the node: see the notes in the function header.
+	 * XXX: write_through should be a flag on the ofile.
 	 */
 	if (sr->sr_cfg->skc_sync_enable ||
 	    (op->create_options & FILE_WRITE_THROUGH))
-		node->flags |= NODE_FLAGS_WRITE_THROUGH;
+		fnode->flags |= NODE_FLAGS_WRITE_THROUGH;
 
 	/*
 	 * Set up the fileid and dosattr in open_param for response
@@ -903,83 +1024,62 @@
 	sr->smb_fid = of->f_fid;
 	sr->fid_ofile = of;
 
-	if (smb_node_is_file(node)) {
-		smb_oplock_acquire(sr, node, of);
+	if (smb_node_is_file(fnode)) {
 		op->dsize = op->fqi.fq_fattr.sa_vattr.va_size;
 	} else {
 		/* directory or symlink */
-		op->op_oplock_level = SMB_OPLOCK_NONE;
 		op->dsize = 0;
 	}
 
-	smb_node_dec_opening_count(node);
+	/*
+	 * Note: oplock_acquire happens in callers, because
+	 * how that happens is protocol-specific.
+	 */
 
-	smb_node_unlock(node);
-	if (created)
+	if (fnode_wlock)
+		smb_node_unlock(fnode);
+	if (opening_incr)
+		smb_node_dec_opening_count(fnode);
+	if (fnode_held)
+		smb_node_release(fnode);
+	if (dnode_wlock)
 		smb_node_unlock(dnode);
-
-	smb_node_release(node);
-	smb_node_release(dnode);
+	if (dnode_held)
+		smb_node_release(dnode);
 
 	return (NT_STATUS_SUCCESS);
-}
 
-/*
- * smb_open_oplock_break
- *
- * If the node has an ofile opened with share access none,
- * (smb_node_share_check = FALSE) only break BATCH oplock.
- * Otherwise:
- * If overwriting, break to SMB_OPLOCK_NONE, else
- * If opening for anything other than attribute access,
- * break oplock to LEVEL_II.
- */
-static void
-smb_open_oplock_break(smb_request_t *sr, smb_node_t *node)
-{
-	smb_arg_open_t	*op = &sr->sr_open;
-	uint32_t	flags = 0;
+errout:
+	if (did_open) {
+		smb_ofile_close(of, 0);
+		/* Don't also ofile_free */
+	} else if (of != NULL) {
+		smb_ofile_free(of);
+	}
 
-	if (!smb_node_share_check(node))
-		flags |= SMB_OPLOCK_BREAK_BATCH;
+	if (fnode_shrlk)
+		smb_fsop_unshrlock(sr->user_cr, fnode, uniq_fid);
 
-	if (smb_open_overwrite(op)) {
-		flags |= SMB_OPLOCK_BREAK_TO_NONE;
-		(void) smb_oplock_break(sr, node, flags);
-	} else if (!smb_open_attr_only(op)) {
-		flags |= SMB_OPLOCK_BREAK_TO_LEVEL_II;
-		(void) smb_oplock_break(sr, node, flags);
+	if (created) {
+		/* Try to roll-back create. */
+		smb_delete_new_object(sr);
 	}
-}
 
-/*
- * smb_open_attr_only
- *
- * Determine if file is being opened for attribute access only.
- * This is used to determine whether it is necessary to break
- * existing oplocks on the file.
- */
-static boolean_t
-smb_open_attr_only(smb_arg_open_t *op)
-{
-	if (((op->desired_access & ~(FILE_READ_ATTRIBUTES |
-	    FILE_WRITE_ATTRIBUTES | SYNCHRONIZE | READ_CONTROL)) == 0) &&
-	    (op->create_disposition != FILE_SUPERSEDE) &&
-	    (op->create_disposition != FILE_OVERWRITE)) {
-		return (B_TRUE);
-	}
-	return (B_FALSE);
-}
+	if (fnode_wlock)
+		smb_node_unlock(fnode);
+	if (opening_incr)
+		smb_node_dec_opening_count(fnode);
+	if (fnode_held)
+		smb_node_release(fnode);
+	if (dnode_wlock)
+		smb_node_unlock(dnode);
+	if (dnode_held)
+		smb_node_release(dnode);
 
-static boolean_t
-smb_open_overwrite(smb_arg_open_t *op)
-{
-	if ((op->create_disposition == FILE_SUPERSEDE) ||
-	    (op->create_disposition == FILE_OVERWRITE_IF) ||
-	    (op->create_disposition == FILE_OVERWRITE)) {
-		return (B_TRUE);
-	}
-	return (B_FALSE);
+	if (tree_fid != 0)
+		smb_idpool_free(&tree->t_fid_pool, tree_fid);
+
+	return (status);
 }
 
 /*
@@ -994,8 +1094,6 @@
  * - If we created_readonly, we now store the real DOS attributes
  *   (including the readonly bit) so subsequent opens will see it.
  *
- * Both are stored "pending" rather than in the file system.
- *
  * Returns: errno
  */
 static int
@@ -1034,7 +1132,7 @@
 	 * However, keep track of the fact that we modified
 	 * the file via this handle, so we can do the evil,
 	 * gratuitious mtime update on close that Windows
-	 * clients appear to expect.
+	 * clients expect.
 	 */
 	if (op->action_taken == SMB_OACT_TRUNCATED)
 		of->f_written = B_TRUE;
--- a/usr/src/uts/common/fs/smbsrv/smb_create.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_create.c	Wed Jun 12 22:46:54 2019 +0200
@@ -162,7 +162,7 @@
 	if (smb_common_create(sr) != NT_STATUS_SUCCESS)
 		return (SDRC_ERROR);
 
-	if (smbsr_encode_result(sr, 1, VAR_BCC, "bww%S", 1, sr->smb_fid,
+	if (smbsr_encode_result(sr, 1, VAR_BCC, "bww%s", 1, sr->smb_fid,
 	    VAR_BCC, sr, name))
 		return (SDRC_ERROR);
 
@@ -196,9 +196,12 @@
 	} else {
 		op->op_oplock_level = SMB_OPLOCK_NONE;
 	}
-	op->op_oplock_levelII = B_FALSE;
 
 	status = smb_common_open(sr);
+	if (status == 0 && op->op_oplock_level != SMB_OPLOCK_NONE) {
+		/* Oplock req. in op->op_oplock_level etc. */
+		smb1_oplock_acquire(sr, B_FALSE);
+	}
 
 	if (op->op_oplock_level == SMB_OPLOCK_NONE) {
 		sr->smb_flg &=
--- a/usr/src/uts/common/fs/smbsrv/smb_delete.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_delete.c	Wed Jun 12 22:46:54 2019 +0200
@@ -470,7 +470,7 @@
 static int
 smb_delete_remove_file(smb_request_t *sr, smb_error_t *err)
 {
-	int rc, count;
+	int rc;
 	uint32_t status;
 	smb_fqi_t *fqi;
 	smb_node_t *node;
@@ -484,28 +484,22 @@
 	 * has a file open, this will force a flush or close,
 	 * which may affect the outcome of any share checking.
 	 */
-	(void) smb_oplock_break(sr, node,
-	    SMB_OPLOCK_BREAK_TO_LEVEL_II | SMB_OPLOCK_BREAK_BATCH);
+	status = smb_oplock_break_DELETE(node, NULL);
+	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+		(void) smb_oplock_wait_break(node, 0);
+		status = 0;
+	}
+	if (status != 0) {
+		err->status = status;
+		return (-1);
+	}
 
-	/*
-	 * Wait (a little) for the oplock break to be
-	 * responded to by clients closing handles.
-	 * Hold node->n_lock as reader to keep new
-	 * ofiles from showing up after we check.
-	 */
 	smb_node_rdlock(node);
-	for (count = 0; count <= 12; count++) {
-		status = smb_node_delete_check(node);
-		if (status != NT_STATUS_SHARING_VIOLATION)
-			break;
+	status = smb_node_delete_check(node);
+	if (status != NT_STATUS_SUCCESS) {
 		smb_node_unlock(node);
-		delay(MSEC_TO_TICK(100));
-		smb_node_rdlock(node);
-	}
-	if (status != NT_STATUS_SUCCESS) {
 		smb_delete_error(err, NT_STATUS_SHARING_VIOLATION,
 		    ERRDOS, ERROR_SHARING_VIOLATION);
-		smb_node_unlock(node);
 		return (-1);
 	}
 
@@ -544,18 +538,12 @@
 	rc = smb_fsop_remove(sr, sr->user_cr, node->n_dnode,
 	    node->od_name, flags);
 	if (rc != 0) {
-		if (rc == ENOENT)
-			smb_delete_error(err, NT_STATUS_OBJECT_NAME_NOT_FOUND,
-			    ERRDOS, ERROR_FILE_NOT_FOUND);
-		else
-			smbsr_map_errno(rc, err);
-
-		smb_node_end_crit(node);
-		return (-1);
+		smbsr_map_errno(rc, err);
+		rc = -1;
 	}
 
 	smb_node_end_crit(node);
-	return (0);
+	return (rc);
 }
 
 
--- a/usr/src/uts/common/fs/smbsrv/smb_directory.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_directory.c	Wed Jun 12 22:46:54 2019 +0200
@@ -250,11 +250,7 @@
 	rc = smb_fsop_lookup(sr, sr->user_cr, SMB_FOLLOW_LINKS,
 	    tnode, fqi->fq_dnode, fqi->fq_last_comp, &fqi->fq_fnode);
 	if (rc != 0) {
-		if (rc == ENOENT)
-			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
-			    ERRDOS, ERROR_FILE_NOT_FOUND);
-		else
-			smbsr_errno(sr, rc);
+		smbsr_errno(sr, rc);
 		smb_node_release(fqi->fq_dnode);
 		return (SDRC_ERROR);
 	}
@@ -414,11 +410,7 @@
 	    tnode, fqi->fq_dnode, fqi->fq_last_comp, &fqi->fq_fnode);
 	smb_node_release(fqi->fq_dnode);
 	if (rc != 0) {
-		if (rc == ENOENT)
-			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
-			    ERRDOS, ERROR_PATH_NOT_FOUND);
-		else
-			smbsr_errno(sr, rc);
+		smbsr_errno(sr, rc);
 		return (SDRC_ERROR);
 	}
 
--- a/usr/src/uts/common/fs/smbsrv/smb_dispatch.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_dispatch.c	Wed Jun 12 22:46:54 2019 +0200
@@ -836,9 +836,9 @@
 	sr->sr_time_start = gethrtime();
 	if ((sdrc = (*sdd->sdt_pre_op)(sr)) == SDRC_SUCCESS)
 		sdrc = (*sdd->sdt_function)(sr);
-	(*sdd->sdt_post_op)(sr);
 
 	if (sdrc != SDRC_SR_KEPT) {
+		(*sdd->sdt_post_op)(sr);
 		smbsr_cleanup(sr);
 	}
 
@@ -906,19 +906,8 @@
 	smbsr_send_reply(sr);
 
 drop_connection:
-	if (disconnect) {
-		smb_rwx_rwenter(&session->s_lock, RW_WRITER);
-		switch (session->s_state) {
-		case SMB_SESSION_STATE_DISCONNECTED:
-		case SMB_SESSION_STATE_TERMINATED:
-			break;
-		default:
-			smb_soshutdown(session->sock);
-			session->s_state = SMB_SESSION_STATE_DISCONNECTED;
-			break;
-		}
-		smb_rwx_rwexit(&session->s_lock);
-	}
+	if (disconnect)
+		smb_session_disconnect(session);
 
 out:
 	if (sr != NULL) {
--- a/usr/src/uts/common/fs/smbsrv/smb_errno.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_errno.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -47,9 +47,9 @@
 static const struct errno2status
 smb_errno2status_map[] = {
 	{ EPERM,	NT_STATUS_ACCESS_DENIED },
-	{ ENOENT,	NT_STATUS_NO_SUCH_FILE },
-	/* NB: ESRCH is used to represent stream lookup failures. */
-	{ ESRCH,	NT_STATUS_OBJECT_NAME_NOT_FOUND },
+	{ ENOENT,	NT_STATUS_OBJECT_NAME_NOT_FOUND },
+	/* NB: ESRCH is used in rename and stream ops. */
+	{ ESRCH,	NT_STATUS_NO_SUCH_FILE },
 	{ EINTR,	NT_STATUS_CANCELLED },
 	{ EIO,		NT_STATUS_IO_DEVICE_ERROR },
 	{ ENXIO,	NT_STATUS_BAD_DEVICE_TYPE },
@@ -60,9 +60,8 @@
 	{ EACCES,	NT_STATUS_ACCESS_DENIED },
 	/* EFAULT, ENOTBLK, EBUSY */
 	{ EEXIST,	NT_STATUS_OBJECT_NAME_COLLISION },
-	{ EXDEV, 	NT_STATUS_NOT_SAME_DEVICE },
+	{ EXDEV,	NT_STATUS_NOT_SAME_DEVICE },
 	{ ENODEV,	NT_STATUS_NO_SUCH_DEVICE },
-	/* ENOTDIR should be: NT_STATUS_NOT_A_DIRECTORY, but not yet */
 	{ ENOTDIR,	NT_STATUS_OBJECT_PATH_NOT_FOUND },
 	{ EISDIR,	NT_STATUS_FILE_IS_A_DIRECTORY },
 	{ EINVAL,	NT_STATUS_INVALID_PARAMETER },
@@ -81,7 +80,7 @@
 	/* ENOMSG, EIDRM, ... */
 	{ ENOTSUP,	NT_STATUS_NOT_SUPPORTED },
 	{ EDQUOT,	NT_STATUS_DISK_FULL },
-	{ EREMOTE, 	NT_STATUS_PATH_NOT_COVERED},
+	{ EREMOTE,	NT_STATUS_PATH_NOT_COVERED},
 	{ ENAMETOOLONG,	NT_STATUS_OBJECT_NAME_INVALID },
 	{ EILSEQ,	NT_STATUS_OBJECT_NAME_INVALID },
 	{ ENOTEMPTY,	NT_STATUS_DIRECTORY_NOT_EMPTY },
--- a/usr/src/uts/common/fs/smbsrv/smb_fem.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_fem.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  * Copyright 2015 Joyent, Inc.
  */
 
@@ -81,7 +81,6 @@
     struct caller_context *);
 static int smb_fem_oplock_setattr(femarg_t *, vattr_t *, int, cred_t *,
     caller_context_t *);
-static int smb_fem_oplock_rwlock(femarg_t *, int, caller_context_t *);
 static int smb_fem_oplock_space(femarg_t *, int, flock64_t *, int,
     offset_t, cred_t *, caller_context_t *);
 static int smb_fem_oplock_vnevent(femarg_t *, vnevent_t, vnode_t *, char *,
@@ -92,13 +91,12 @@
 	VOPNAME_READ,	{ .femop_read = smb_fem_oplock_read },
 	VOPNAME_WRITE,	{ .femop_write = smb_fem_oplock_write },
 	VOPNAME_SETATTR, { .femop_setattr = smb_fem_oplock_setattr },
-	VOPNAME_RWLOCK, { .femop_rwlock = smb_fem_oplock_rwlock },
 	VOPNAME_SPACE,	{ .femop_space = smb_fem_oplock_space },
 	VOPNAME_VNEVENT, { .femop_vnevent = smb_fem_oplock_vnevent },
 	NULL, NULL
 };
 
-static int smb_fem_oplock_break(femarg_t *, caller_context_t *, uint32_t);
+static int smb_fem_oplock_wait(smb_node_t *, caller_context_t *);
 
 /*
  * smb_fem_init
@@ -428,15 +426,36 @@
     cred_t		*cr,
     caller_context_t	*ct)
 {
-	uint32_t	flags;
+	smb_node_t	*node;
+	uint32_t	status;
 	int		rc = 0;
 
 	if (ct != &smb_ct) {
-		if (mode & (FWRITE|FTRUNC))
-			flags = SMB_OPLOCK_BREAK_TO_NONE;
-		else
-			flags = SMB_OPLOCK_BREAK_TO_LEVEL_II;
-		rc = smb_fem_oplock_break(arg, ct, flags);
+		uint32_t req_acc = FILE_READ_DATA;
+		uint32_t cr_disp = FILE_OPEN_IF;
+
+		node = (smb_node_t *)(arg->fa_fnode->fn_available);
+		SMB_NODE_VALID(node);
+
+		/*
+		 * Get req_acc, cr_disp just accurate enough so
+		 * the oplock break call does the right thing.
+		 */
+		if (mode & FWRITE) {
+			req_acc = FILE_READ_DATA | FILE_WRITE_DATA;
+			cr_disp = (mode & FTRUNC) ?
+			    FILE_OVERWRITE_IF : FILE_OPEN_IF;
+		} else {
+			req_acc = FILE_READ_DATA;
+			cr_disp = FILE_OPEN_IF;
+		}
+
+		status = smb_oplock_break_OPEN(node, NULL,
+		    req_acc, cr_disp);
+		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
+			rc = smb_fem_oplock_wait(node, ct);
+		else if (status != 0)
+			rc = EIO;
 	}
 	if (rc == 0)
 		rc = vnext_open(arg, mode, cr, ct);
@@ -457,11 +476,19 @@
     cred_t		*cr,
     caller_context_t	*ct)
 {
+	smb_node_t	*node;
+	uint32_t	status;
 	int	rc = 0;
 
 	if (ct != &smb_ct) {
-		rc = smb_fem_oplock_break(arg, ct,
-		    SMB_OPLOCK_BREAK_TO_LEVEL_II);
+		node = (smb_node_t *)(arg->fa_fnode->fn_available);
+		SMB_NODE_VALID(node);
+
+		status = smb_oplock_break_READ(node, NULL);
+		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
+			rc = smb_fem_oplock_wait(node, ct);
+		else if (status != 0)
+			rc = EIO;
 	}
 	if (rc == 0)
 		rc = vnext_read(arg, uiop, ioflag, cr, ct);
@@ -482,10 +509,20 @@
     cred_t		*cr,
     caller_context_t	*ct)
 {
+	smb_node_t	*node;
+	uint32_t	status;
 	int	rc = 0;
 
-	if (ct != &smb_ct)
-		rc = smb_fem_oplock_break(arg, ct, SMB_OPLOCK_BREAK_TO_NONE);
+	if (ct != &smb_ct) {
+		node = (smb_node_t *)(arg->fa_fnode->fn_available);
+		SMB_NODE_VALID(node);
+
+		status = smb_oplock_break_WRITE(node, NULL);
+		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
+			rc = smb_fem_oplock_wait(node, ct);
+		else if (status != 0)
+			rc = EIO;
+	}
 	if (rc == 0)
 		rc = vnext_write(arg, uiop, ioflag, cr, ct);
 
@@ -500,38 +537,27 @@
     cred_t		*cr,
     caller_context_t	*ct)
 {
+	smb_node_t	*node;
+	uint32_t	status;
 	int	rc = 0;
 
-	if (ct != &smb_ct && (vap->va_mask & AT_SIZE) != 0)
-		rc = smb_fem_oplock_break(arg, ct, SMB_OPLOCK_BREAK_TO_NONE);
+	if (ct != &smb_ct && (vap->va_mask & AT_SIZE) != 0) {
+		node = (smb_node_t *)(arg->fa_fnode->fn_available);
+		SMB_NODE_VALID(node);
+
+		status = smb_oplock_break_SETINFO(node, NULL,
+		    FileEndOfFileInformation);
+		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
+			rc = smb_fem_oplock_wait(node, ct);
+		else if (status != 0)
+			rc = EIO;
+	}
 	if (rc == 0)
 		rc = vnext_setattr(arg, vap, flags, cr, ct);
 	return (rc);
 }
 
 static int
-smb_fem_oplock_rwlock(
-    femarg_t		*arg,
-    int			write_lock,
-    caller_context_t	*ct)
-{
-	uint32_t	flags;
-	int		rc = 0;
-
-	if (ct != &smb_ct) {
-		if (write_lock)
-			flags = SMB_OPLOCK_BREAK_TO_NONE;
-		else
-			flags = SMB_OPLOCK_BREAK_TO_LEVEL_II;
-		rc = smb_fem_oplock_break(arg, ct, flags);
-	}
-	if (rc == 0)
-		rc = vnext_rwlock(arg, write_lock, ct);
-
-	return (rc);
-}
-
-static int
 smb_fem_oplock_space(
     femarg_t		*arg,
     int			cmd,
@@ -541,10 +567,21 @@
     cred_t		*cr,
     caller_context_t	*ct)
 {
+	smb_node_t	*node;
+	uint32_t	status;
 	int	rc = 0;
 
-	if (ct != &smb_ct)
-		rc = smb_fem_oplock_break(arg, ct, SMB_OPLOCK_BREAK_TO_NONE);
+	if (ct != &smb_ct) {
+		node = (smb_node_t *)(arg->fa_fnode->fn_available);
+		SMB_NODE_VALID(node);
+
+		status = smb_oplock_break_SETINFO(node, NULL,
+		    FileAllocationInformation);
+		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
+			rc = smb_fem_oplock_wait(node, ct);
+		else if (status != 0)
+			rc = EIO;
+	}
 	if (rc == 0)
 		rc = vnext_space(arg, cmd, bfp, flag, offset, cr, ct);
 	return (rc);
@@ -572,28 +609,33 @@
     char		*name,
     caller_context_t	*ct)
 {
-	uint32_t	flags;
+	smb_node_t	*node;
+	uint32_t	status;
 	int		rc = 0;
 
 	if (ct != &smb_ct) {
+		node = (smb_node_t *)(arg->fa_fnode->fn_available);
+		SMB_NODE_VALID(node);
+
 		switch (vnevent) {
 		case VE_REMOVE:
 		case VE_PRE_RENAME_DEST:
 		case VE_RENAME_DEST:
-			flags = SMB_OPLOCK_BREAK_TO_NONE |
-			    SMB_OPLOCK_BREAK_BATCH;
-			rc = smb_fem_oplock_break(arg, ct, flags);
+			status = smb_oplock_break_HANDLE(node, NULL);
 			break;
 		case VE_PRE_RENAME_SRC:
 		case VE_RENAME_SRC:
-			flags = SMB_OPLOCK_BREAK_TO_LEVEL_II |
-			    SMB_OPLOCK_BREAK_BATCH;
-			rc = smb_fem_oplock_break(arg, ct, flags);
+			status = smb_oplock_break_SETINFO(node, NULL,
+			    FileRenameInformation);
 			break;
 		default:
-			rc = 0;
+			status = 0;
 			break;
 		}
+		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
+			rc = smb_fem_oplock_wait(node, ct);
+		else if (status != 0)
+			rc = EIO;
 	}
 	if (rc == 0)
 		rc = vnext_vnevent(arg, vnevent, dvp, name, ct);
@@ -601,24 +643,22 @@
 	return (rc);
 }
 
+int smb_fem_oplock_timeout = 5000; /* mSec. */
+
 static int
-smb_fem_oplock_break(femarg_t *arg, caller_context_t *ct, uint32_t flags)
+smb_fem_oplock_wait(smb_node_t *node, caller_context_t *ct)
 {
-	smb_node_t	*node;
-	int		rc;
-
-	node = (smb_node_t *)((arg)->fa_fnode->fn_available);
-	SMB_NODE_VALID(node);
+	int		rc = 0;
 
 	ASSERT(ct != &smb_ct);
 
 	if (ct && (ct->cc_flags & CC_DONTBLOCK)) {
-		flags |= SMB_OPLOCK_BREAK_NOWAIT;
-		rc = smb_oplock_break(NULL, node, flags);
-		if (rc == EAGAIN)
-			ct->cc_flags |= CC_WOULDBLOCK;
+		ct->cc_flags |= CC_WOULDBLOCK;
+		rc = EAGAIN;
 	} else {
-		rc = smb_oplock_break(NULL, node, flags);
+		(void) smb_oplock_wait_break(node,
+		    smb_fem_oplock_timeout);
+		rc = 0;
 	}
 
 	return (rc);
--- a/usr/src/uts/common/fs/smbsrv/smb_find.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_find.c	Wed Jun 12 22:46:54 2019 +0200
@@ -300,6 +300,7 @@
 				    ERRDOS, ERROR_NO_MORE_FILES);
 			return (SDRC_ERROR);
 		}
+		odid = od->d_odid;
 	} else {
 		if (smb_mbc_decodef(&sr->smb_data, "b12.wwl",
 		    &resume_char, &index, &odid, &client_key) != 0) {
@@ -455,6 +456,7 @@
 			smbsr_error(sr, status, 0, 0);
 			return (SDRC_ERROR);
 		}
+		odid = od->d_odid;
 	} else {
 		if (smb_mbc_decodef(&sr->smb_data, "b12.wwl",
 		    &resume_char, &index, &odid, &client_key) != 0) {
@@ -691,8 +693,8 @@
 		smb_name83(fileinfo.fi_shortname, name83, SMB_SHORTNAMELEN);
 
 		(void) smb_mbc_encodef(&sr->reply, "b11c.wwlbYl13c",
-		    resume_char, name83, index, od->d_odid,
-		    client_key, fileinfo.fi_dosattr & 0xff,
+		    resume_char, name83, index, od->d_odid, client_key,
+		    fileinfo.fi_dosattr & 0xff,
 		    smb_time_gmt_to_local(sr, fileinfo.fi_mtime.tv_sec),
 		    (int32_t)fileinfo.fi_size,
 		    fileinfo.fi_shortname);
--- a/usr/src/uts/common/fs/smbsrv/smb_fsops.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_fsops.c	Wed Jun 12 22:46:54 2019 +0200
@@ -820,6 +820,7 @@
 	switch (status) {
 	case 0:
 		break;
+	case NT_STATUS_OBJECT_NAME_NOT_FOUND:
 	case NT_STATUS_NO_SUCH_FILE:
 	case NT_STATUS_NOT_SUPPORTED:
 		/* No streams to remove. */
@@ -1243,25 +1244,6 @@
 		return (EACCES);
 
 	/*
-	 * The file system cannot detect pending READDONLY
-	 * (i.e. if the file has been opened readonly but
-	 * not yet closed) so we need to test READONLY here.
-	 *
-	 * Note that file handle that were opened before the
-	 * READONLY flag was set in the node (or the FS) are
-	 * immune to that change, and remain writable.
-	 */
-	if (sr && (set_attr->sa_mask & SMB_AT_SIZE)) {
-		if (sr->fid_ofile) {
-			if (SMB_OFILE_IS_READONLY(sr->fid_ofile))
-				return (EACCES);
-		} else {
-			if (SMB_PATHFILE_IS_READONLY(sr, snode))
-				return (EACCES);
-		}
-	}
-
-	/*
 	 * SMB checks access on open and retains an access granted
 	 * mask for use while the file is open.  ACL changes should
 	 * not affect access to an open file.
@@ -1351,23 +1333,6 @@
 		return (EACCES);
 
 	/*
-	 * The file system cannot detect pending READDONLY
-	 * (i.e. if the file has been opened readonly but
-	 * not yet closed) so we need to test READONLY here.
-	 *
-	 * Note that file handle that were opened before the
-	 * READONLY flag was set in the node (or the FS) are
-	 * immune to that change, and remain writable.
-	 */
-	if (sr->fid_ofile) {
-		if (SMB_OFILE_IS_READONLY(sr->fid_ofile))
-			return (EACCES);
-	} else {
-		/* This requires an open file. */
-		return (EACCES);
-	}
-
-	/*
 	 * SMB checks access on open and retains an access granted
 	 * mask for use while the file is open.  ACL changes should
 	 * not affect access to an open file.
@@ -1502,8 +1467,7 @@
 	if (SMB_TREE_IS_READONLY(sr))
 		return (EROFS);
 
-	if (SMB_OFILE_IS_READONLY(of) ||
-	    SMB_TREE_HAS_ACCESS(sr, ACE_WRITE_DATA | ACE_APPEND_DATA) == 0)
+	if (SMB_TREE_HAS_ACCESS(sr, ACE_WRITE_DATA | ACE_APPEND_DATA) == 0)
 		return (EACCES);
 
 	rc = smb_ofile_access(of, cr, FILE_WRITE_DATA);
@@ -2578,6 +2542,15 @@
 	if (access & VWRITE)
 		*eaccess |= FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES |
 		    FILE_WRITE_EA | FILE_APPEND_DATA | FILE_DELETE_CHILD;
+
+	if (access & (VREAD | VWRITE))
+		*eaccess |= SYNCHRONIZE;
+
+#ifdef	_FAKE_KERNEL
+	/* Should be: if (we are the owner)... */
+	if (access & VWRITE)
+		*eaccess |= DELETE | WRITE_DAC | WRITE_OWNER;
+#endif
 }
 
 /*
--- a/usr/src/uts/common/fs/smbsrv/smb_init.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_init.c	Wed Jun 12 22:46:54 2019 +0200
@@ -64,9 +64,6 @@
  * environment. When in doubt use 37KB.
  */
 int	smb_maxbufsize = SMB_NT_MAXBUF;
-int	smb_oplock_levelII = 1;
-int	smb_oplock_timeout = OPLOCK_STD_TIMEOUT;
-int	smb_oplock_min_timeout = OPLOCK_MIN_TIMEOUT;
 int	smb_flush_required = 1;
 int	smb_dirsymlink_enable = 1;
 int	smb_sign_debug = 0;
--- a/usr/src/uts/common/fs/smbsrv/smb_kshare.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_kshare.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  * Copyright 2017 Joyent, Inc.
  */
 
@@ -849,7 +849,7 @@
 	smb_kshare_t tmp;
 	smb_kshare_t *shr;
 	nvlist_t *smb;
-	char *csc_name = NULL;
+	char *csc_name = NULL, *strbuf = NULL;
 	int rc;
 
 	ASSERT(share);
@@ -891,6 +891,7 @@
 	    SMB_SHRF_DFSROOT);
 	tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_QUOTAS,
 	    SMB_SHRF_QUOTAS);
+	tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_FSO, SMB_SHRF_FSO);
 	tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_AUTOHOME,
 	    SMB_SHRF_AUTOHOME);
 
@@ -904,6 +905,9 @@
 		}
 	}
 
+	(void) nvlist_lookup_string(smb, SHOPT_ENCRYPT, &strbuf);
+	smb_cfg_set_require(strbuf, &tmp.shr_encrypt);
+
 	(void) nvlist_lookup_string(smb, SHOPT_CSC, &csc_name);
 	smb_kshare_csc_flags(&tmp, csc_name);
 
@@ -929,6 +933,7 @@
 	shr->shr_oemname = smb_kshare_oemname(shr->shr_name);
 	shr->shr_flags = tmp.shr_flags | smb_kshare_is_admin(shr->shr_name);
 	shr->shr_type = tmp.shr_type | smb_kshare_is_special(shr->shr_name);
+	shr->shr_encrypt = tmp.shr_encrypt;
 
 	shr->shr_uid = tmp.shr_uid;
 	shr->shr_gid = tmp.shr_gid;
--- a/usr/src/uts/common/fs/smbsrv/smb_kutil.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_kutil.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,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 <sys/param.h>
@@ -40,6 +40,7 @@
 
 #include <sys/sid.h>
 #include <sys/priv_names.h>
+#include <sys/bitmap.h>
 
 static kmem_cache_t	*smb_dtor_cache = NULL;
 
@@ -332,8 +333,22 @@
 			pool->id_pool[pool->id_idx] |= bit;
 			*id = (uint16_t)(pool->id_idx * 8 + (uint32_t)bit_idx);
 			pool->id_free_counter--;
-			pool->id_bit = bit;
-			pool->id_bit_idx = bit_idx;
+			/*
+			 * Leave position at next bit to allocate,
+			 * so we don't keep re-using the last in an
+			 * alloc/free/alloc/free sequence.  Doing
+			 * that can confuse some SMB clients.
+			 */
+			if (bit & 0x80) {
+				pool->id_bit = 1;
+				pool->id_bit_idx = 0;
+				pool->id_idx++;
+				pool->id_idx &= pool->id_idx_msk;
+			} else {
+				pool->id_bit = (bit << 1);
+				pool->id_bit_idx = bit_idx + 1;
+				/* keep id_idx */
+			}
 			mutex_exit(&pool->id_mutex);
 			return (0);
 		}
@@ -445,6 +460,16 @@
 }
 
 /*
+ * smb_llist_enter
+ * Not a macro so dtrace smbsrv:* can see it.
+ */
+void
+smb_llist_enter(smb_llist_t *ll, krw_t mode)
+{
+	rw_enter(&ll->ll_lock, mode);
+}
+
+/*
  * Post an object to the delete queue.  The delete queue will be processed
  * during list exit or list destruction.  Objects are often posted for
  * deletion during list iteration (while the list is locked) but that is
@@ -636,6 +661,16 @@
 }
 
 /*
+ * smb_slist_enter
+ * Not a macro so dtrace smbsrv:* can see it.
+ */
+void
+smb_slist_enter(smb_slist_t *sl)
+{
+	mutex_enter(&(sl)->sl_mutex);
+}
+
+/*
  * smb_slist_insert_head
  *
  * This function inserts the object passed a the beginning of the list.
@@ -799,90 +834,46 @@
 }
 
 /*
+ * smb_rwx_rwenter
+ */
+void
+smb_rwx_rwenter(smb_rwx_t *rwx, krw_t mode)
+{
+	rw_enter(&rwx->rwx_lock, mode);
+}
+
+/*
  * smb_rwx_rwexit
  */
 void
 smb_rwx_rwexit(
     smb_rwx_t	*rwx)
 {
-	if (rw_write_held(&rwx->rwx_lock)) {
-		ASSERT(rw_owner(&rwx->rwx_lock) == curthread);
-		mutex_enter(&rwx->rwx_mutex);
-		if (rwx->rwx_waiting) {
-			rwx->rwx_waiting = B_FALSE;
-			cv_broadcast(&rwx->rwx_cv);
-		}
-		mutex_exit(&rwx->rwx_mutex);
-	}
 	rw_exit(&rwx->rwx_lock);
 }
 
-/*
- * smb_rwx_rwupgrade
- */
-krw_t
-smb_rwx_rwupgrade(
-    smb_rwx_t	*rwx)
-{
-	if (rw_write_held(&rwx->rwx_lock)) {
-		ASSERT(rw_owner(&rwx->rwx_lock) == curthread);
-		return (RW_WRITER);
-	}
-	if (!rw_tryupgrade(&rwx->rwx_lock)) {
-		rw_exit(&rwx->rwx_lock);
-		rw_enter(&rwx->rwx_lock, RW_WRITER);
-	}
-	return (RW_READER);
-}
 
 /*
- * smb_rwx_rwrestore
- */
-void
-smb_rwx_rwdowngrade(
-    smb_rwx_t	*rwx,
-    krw_t	mode)
-{
-	ASSERT(rw_write_held(&rwx->rwx_lock));
-	ASSERT(rw_owner(&rwx->rwx_lock) == curthread);
-
-	if (mode == RW_WRITER) {
-		return;
-	}
-	ASSERT(mode == RW_READER);
-	mutex_enter(&rwx->rwx_mutex);
-	if (rwx->rwx_waiting) {
-		rwx->rwx_waiting = B_FALSE;
-		cv_broadcast(&rwx->rwx_cv);
-	}
-	mutex_exit(&rwx->rwx_mutex);
-	rw_downgrade(&rwx->rwx_lock);
-}
-
-/*
- * smb_rwx_wait
+ * smb_rwx_cvwait
  *
- * This function assumes the smb_rwx lock was enter in RW_READER or RW_WRITER
+ * Wait on rwx->rw_cv, dropping the rw lock and retake after wakeup.
+ * Assumes the smb_rwx lock was entered in RW_READER or RW_WRITER
  * mode. It will:
  *
  *	1) release the lock and save its current mode.
- *	2) wait until the condition variable is signaled. This can happen for
- *	   2 reasons: When a writer releases the lock or when the time out (if
- *	   provided) expires.
+ *	2) wait until the condition variable is signaled.
  *	3) re-acquire the lock in the mode saved in (1).
+ *
+ * Lock order: rwlock, mutex
  */
 int
-smb_rwx_rwwait(
+smb_rwx_cvwait(
     smb_rwx_t	*rwx,
     clock_t	timeout)
 {
 	krw_t	mode;
 	int	rc = 1;
 
-	mutex_enter(&rwx->rwx_mutex);
-	rwx->rwx_waiting = B_TRUE;
-	mutex_exit(&rwx->rwx_mutex);
-
 	if (rw_write_held(&rwx->rwx_lock)) {
 		ASSERT(rw_owner(&rwx->rwx_lock) == curthread);
 		mode = RW_WRITER;
@@ -890,16 +881,16 @@
 		ASSERT(rw_read_held(&rwx->rwx_lock));
 		mode = RW_READER;
 	}
-	rw_exit(&rwx->rwx_lock);
 
 	mutex_enter(&rwx->rwx_mutex);
-	if (rwx->rwx_waiting) {
-		if (timeout == -1) {
-			cv_wait(&rwx->rwx_cv, &rwx->rwx_mutex);
-		} else {
-			rc = cv_reltimedwait(&rwx->rwx_cv, &rwx->rwx_mutex,
-			    timeout, TR_CLOCK_TICK);
-		}
+	rw_exit(&rwx->rwx_lock);
+
+	rwx->rwx_waiting = B_TRUE;
+	if (timeout == -1) {
+		cv_wait(&rwx->rwx_cv, &rwx->rwx_mutex);
+	} else {
+		rc = cv_reltimedwait(&rwx->rwx_cv, &rwx->rwx_mutex,
+		    timeout, TR_CLOCK_TICK);
 	}
 	mutex_exit(&rwx->rwx_mutex);
 
@@ -907,6 +898,24 @@
 	return (rc);
 }
 
+/*
+ * smb_rwx_cvbcast
+ *
+ * Wake up threads waiting on rx_cv
+ * The rw lock may or may not be held.
+ * The mutex MUST NOT be held.
+ */
+void
+smb_rwx_cvbcast(smb_rwx_t *rwx)
+{
+	mutex_enter(&rwx->rwx_mutex);
+	if (rwx->rwx_waiting) {
+		rwx->rwx_waiting = B_FALSE;
+		cv_broadcast(&rwx->rwx_cv);
+	}
+	mutex_exit(&rwx->rwx_mutex);
+}
+
 /* smb_idmap_... moved to smb_idmap.c */
 
 uint64_t
@@ -1269,8 +1278,8 @@
  *
  * Returns:
  *
- * 	ENOTACTIVE	AVL is not in READY state
- * 	EEXIST		The item is already in AVL
+ *	ENOTACTIVE	AVL is not in READY state
+ *	EEXIST		The item is already in AVL
  */
 int
 smb_avl_add(smb_avl_t *avl, void *item)
@@ -1737,3 +1746,51 @@
 	cv_broadcast(&ct->ct_cond);
 	mutex_exit(&ct->ct_mutex);
 }
+
+/* taken from mod_hash_byptr */
+uint_t
+smb_hash_uint64(smb_hash_t *hash, uint64_t val)
+{
+	uint64_t k = val >> hash->rshift;
+	uint_t idx = ((uint_t)k) & (hash->num_buckets - 1);
+
+	return (idx);
+}
+
+boolean_t
+smb_is_pow2(size_t n)
+{
+	return ((n & (n - 1)) == 0);
+}
+
+smb_hash_t *
+smb_hash_create(size_t elemsz, size_t link_offset,
+    uint32_t num_buckets)
+{
+	smb_hash_t *hash = kmem_alloc(sizeof (*hash), KM_SLEEP);
+	int i;
+
+	if (!smb_is_pow2(num_buckets))
+		num_buckets = 1 << highbit(num_buckets);
+
+	hash->rshift = highbit(elemsz);
+	hash->num_buckets = num_buckets;
+	hash->buckets = kmem_zalloc(num_buckets * sizeof (smb_bucket_t),
+	    KM_SLEEP);
+	for (i = 0; i < num_buckets; i++)
+		smb_llist_constructor(&hash->buckets[i].b_list, elemsz,
+		    link_offset);
+	return (hash);
+}
+
+void
+smb_hash_destroy(smb_hash_t *hash)
+{
+	int i;
+
+	for (i = 0; i < hash->num_buckets; i++)
+		smb_llist_destructor(&hash->buckets[i].b_list);
+
+	kmem_free(hash->buckets, hash->num_buckets * sizeof (smb_bucket_t));
+	kmem_free(hash, sizeof (*hash));
+}
--- a/usr/src/uts/common/fs/smbsrv/smb_lock.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_lock.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -65,7 +65,7 @@
 uint32_t
 smb_lock_get_lock_count(smb_node_t *node, smb_ofile_t *of)
 {
-	smb_lock_t 	*lock;
+	smb_lock_t	*lock;
 	smb_llist_t	*llist;
 	uint32_t	count = 0;
 
@@ -330,8 +330,10 @@
 
 	smb_llist_exit(&node->n_lock_list);
 
-	if (result == NT_STATUS_SUCCESS)
-		smb_oplock_break_levelII(node);
+	if (result == NT_STATUS_SUCCESS) {
+		/* This revokes read cache delegations. */
+		(void) smb_oplock_break_WRITE(node, file);
+	}
 
 	return (result);
 }
@@ -345,7 +347,7 @@
  *
  * Return values
  *	NT_STATUS_SUCCESS		lock access granted.
- *	NT_STATUS_FILE_LOCK_CONFLICT 	access denied due to lock conflict.
+ *	NT_STATUS_FILE_LOCK_CONFLICT	access denied due to lock conflict.
  */
 int
 smb_lock_range_access(
@@ -415,6 +417,7 @@
 void
 smb_node_destroy_lock_by_ofile(smb_node_t *node, smb_ofile_t *file)
 {
+	cred_t		*kcr = zone_kcred();
 	smb_lock_t	*lock;
 	smb_lock_t	*nxtl;
 	list_t		destroy_list;
@@ -457,7 +460,7 @@
 		nxtl = smb_llist_next(&node->n_lock_list, lock);
 		if (lock->l_file == file) {
 			smb_llist_remove(&node->n_lock_list, lock);
-			smb_lock_posix_unlock(node, lock, file->f_user->u_cred);
+			smb_lock_posix_unlock(node, lock, kcr);
 			list_insert_tail(&destroy_list, lock);
 		}
 		lock = nxtl;
--- a/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c	Wed Jun 12 22:46:54 2019 +0200
@@ -248,11 +248,11 @@
 	unsigned short	unlock_num;	/* # unlock range structs */
 	unsigned short	lock_num;	/* # lock range structs */
 	DWORD		result;
-	int 		rc;
+	int		rc;
 	uint32_t	ltype;
+	uint32_t	status;
 	smb_ofile_t	*ofile;
 	uint16_t	tmp_pid;	/* locking uses 16-bit pids */
-	uint8_t		brk;
 	uint32_t	lrv_tot;
 	struct lreq	*lrv_ul;
 	struct lreq	*lrv_lk;
@@ -288,11 +288,16 @@
 		ltype = SMB_LOCK_TYPE_READWRITE;
 
 	if (lock_type & LOCKING_ANDX_OPLOCK_RELEASE) {
+		uint32_t NewLevel;
 		if (oplock_level == 0)
-			brk = SMB_OPLOCK_BREAK_TO_NONE;
+			NewLevel = OPLOCK_LEVEL_NONE;
 		else
-			brk = SMB_OPLOCK_BREAK_TO_LEVEL_II;
-		smb_oplock_ack(ofile->f_node, ofile, brk);
+			NewLevel = OPLOCK_LEVEL_TWO;
+		status = smb_oplock_ack_break(sr, ofile, &NewLevel);
+		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+			(void) smb_oplock_wait_break(ofile->f_node, 0);
+			status = 0;
+		}
 		if (unlock_num == 0 && lock_num == 0)
 			return (SDRC_NO_REPLY);
 	}
@@ -415,21 +420,25 @@
  * The caller will send it and free the request.
  */
 void
-smb1_oplock_break_notification(smb_request_t *sr, uint8_t brk)
+smb1_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel)
 {
 	smb_ofile_t *ofile = sr->fid_ofile;
 	uint16_t fid;
 	uint8_t lock_type;
 	uint8_t oplock_level;
 
-	switch (brk) {
+	/*
+	 * Convert internal level to SMB1
+	 */
+	switch (NewLevel) {
 	default:
 		ASSERT(0);
 		/* FALLTHROUGH */
-	case SMB_OPLOCK_BREAK_TO_NONE:
+	case OPLOCK_LEVEL_NONE:
 		oplock_level = 0;
 		break;
-	case SMB_OPLOCK_BREAK_TO_LEVEL_II:
+
+	case OPLOCK_LEVEL_TWO:
 		oplock_level = 1;
 		break;
 	}
--- a/usr/src/uts/common/fs/smbsrv/smb_mangle_name.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_mangle_name.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <sys/types.h>
@@ -351,11 +351,8 @@
  * smb_unmangle should only be called on names for which
  * smb_maybe_mangled() is true
  *
- * File systems which support VFSFT_EDIRENT_FLAGS will return the
- * directory entries as a buffer of edirent_t structure. Others will
- * return a buffer of dirent64_t structures. A union is used for the
- * the pointer into the buffer (bufptr, edp and dp).
- * The ed_name/d_name is NULL terminated by the file system.
+ * The flags arg is no longer used, but retained just to avoid
+ * changing the many callers of this function.
  *
  * Returns:
  *   0       - SUCCESS. Unmangled name is returned in namebuf.
@@ -368,21 +365,17 @@
 smb_unmangle(smb_node_t *dnode, char *name, char *namebuf,
     int buflen, uint32_t flags)
 {
-	int		err, eof, bufsize, reclen;
+	_NOTE(ARGUNUSED(flags))	// avoid changing all callers
+	int		err, eof, bufsize;
 	uint64_t	offset;
 	ino64_t		ino;
-	boolean_t	is_edp;
 	char		*namep, *buf;
 	char		shortname[SMB_SHORTNAMELEN];
 	vnode_t		*vp;
-	union {
-		char		*u_bufptr;
-		edirent_t	*u_edp;
-		dirent64_t	*u_dp;
-	} u;
-#define	bufptr		u.u_bufptr
-#define	edp		u.u_edp
-#define	dp		u.u_dp
+	char		*bufptr;
+	dirent64_t	*dp;
+	cred_t		*cr = zone_kcred();
+	int		rc = ENOENT;
 
 	if (dnode == NULL || name == NULL || namebuf == NULL || buflen == 0)
 		return (EINVAL);
@@ -394,57 +387,62 @@
 
 	vp = dnode->vp;
 	*namebuf = '\0';
-	is_edp = vfs_has_feature(vp->v_vfsp, VFSFT_DIRENTFLAGS);
 
 	buf = kmem_alloc(SMB_UNMANGLE_BUFSIZE, KM_SLEEP);
-	bufsize = SMB_UNMANGLE_BUFSIZE;
-	offset = 0;
+	bufptr = buf;
+	bufsize = 0;
+	offset = 0;	// next entry offset
+	eof = B_FALSE;
 
-	while ((err = smb_vop_readdir(vp, offset, buf, &bufsize,
-	    &eof, flags, zone_kcred())) == 0) {
-		if (bufsize == 0) {
-			err = ENOENT;
+	for (;;) {
+		/*
+		 * Read some entries, if buffer empty or
+		 * we've scanned all of it.  Flags zero
+		 * (no edirent, no ABE wanted here)
+		 */
+		if (bufsize <= 0) {
+			bufsize = SMB_UNMANGLE_BUFSIZE;
+			rc = smb_vop_readdir(vp, offset, buf,
+			    &bufsize, &eof, 0, cr);
+			if (rc != 0)
+				break; /* error */
+			if (bufsize == 0) {
+				eof = B_TRUE;
+				rc = ENOENT;
+				break;
+			}
+			bufptr = buf;
+		}
+		/* LINTED pointer alignment */
+		dp = (dirent64_t *)bufptr;
+
+		/*
+		 * Partial records are not supposed to happen,
+		 * but let's be defensive. If this happens,
+		 * restart at the current offset.
+		 */
+		bufptr += dp->d_reclen;
+		bufsize -= dp->d_reclen;
+		if (bufsize < 0)
+			continue;
+
+		offset = dp->d_off;
+		ino = dp->d_ino;
+		namep = dp->d_name;
+
+		/* skip non utf8 filename */
+		if (u8_validate(namep, strlen(namep), NULL,
+		    U8_VALIDATE_ENTIRE, &err) < 0)
+			continue;
+
+		smb_mangle(namep, ino, shortname, SMB_SHORTNAMELEN);
+		if (smb_strcasecmp(name, shortname, 0) == 0) {
+			(void) strlcpy(namebuf, namep, buflen);
+			rc = 0;
 			break;
 		}
-
-		bufptr = buf;
-		reclen = 0;
-
-		while ((bufptr += reclen) < buf + bufsize) {
-			if (is_edp) {
-				reclen = edp->ed_reclen;
-				offset = edp->ed_off;
-				ino = edp->ed_ino;
-				namep = edp->ed_name;
-			} else {
-				reclen = dp->d_reclen;
-				offset = dp->d_off;
-				ino = dp->d_ino;
-				namep = dp->d_name;
-			}
-
-			/* skip non utf8 filename */
-			if (u8_validate(namep, strlen(namep), NULL,
-			    U8_VALIDATE_ENTIRE, &err) < 0)
-				continue;
-
-			smb_mangle(namep, ino, shortname, SMB_SHORTNAMELEN);
-
-			if (smb_strcasecmp(name, shortname, 0) == 0) {
-				(void) strlcpy(namebuf, namep, buflen);
-				kmem_free(buf, SMB_UNMANGLE_BUFSIZE);
-				return (0);
-			}
-		}
-
-		if (eof) {
-			err = ENOENT;
-			break;
-		}
-
-		bufsize = SMB_UNMANGLE_BUFSIZE;
 	}
 
 	kmem_free(buf, SMB_UNMANGLE_BUFSIZE);
-	return (err);
+	return (rc);
 }
--- a/usr/src/uts/common/fs/smbsrv/smb_mbuf_marshaling.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_mbuf_marshaling.c	Wed Jun 12 22:46:54 2019 +0200
@@ -916,6 +916,23 @@
 }
 
 /*
+ * Put padding sufficient to align to A, where
+ * A is some power of 2 greater than zero.
+ */
+int
+smb_mbc_put_align(mbuf_chain_t *mbc, int align)
+{
+	int mask = align - 1;
+	int padsz;
+
+	ASSERT(align > 0 && (align & mask) == 0);
+	if ((mbc->chain_offset & mask) == 0)
+		return (0);
+	padsz = align - (mbc->chain_offset & mask);
+	return (smb_mbc_encodef(mbc, "#.", padsz));
+}
+
+/*
  * Put data into mbuf chain allocating as needed.
  * Adds room to end of mbuf chain if needed.
  */
--- a/usr/src/uts/common/fs/smbsrv/smb_negotiate.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_negotiate.c	Wed Jun 12 22:46:54 2019 +0200
@@ -403,7 +403,7 @@
 smb_sdrc_t
 smb_com_negotiate(smb_request_t *sr)
 {
-	smb_session_t 		*session = sr->session;
+	smb_session_t		*session = sr->session;
 	smb_arg_negotiate_t	*negprot = sr->sr_negprot;
 	uint16_t		secmode;
 	uint32_t		sesskey;
@@ -437,9 +437,9 @@
 		return (rc);
 	}
 
-	session->secmode = NEGOTIATE_ENCRYPT_PASSWORDS |
+	session->srv_secmode = NEGOTIATE_ENCRYPT_PASSWORDS |
 	    NEGOTIATE_USER_SECURITY;
-	secmode = session->secmode;
+	secmode = session->srv_secmode;
 	sesskey = session->sesskey;
 
 	negprot->ni_servertime.tv_sec = gethrestime_sec();
@@ -532,7 +532,7 @@
 				secmode |=
 				    NEGOTIATE_SECURITY_SIGNATURES_REQUIRED;
 
-			session->secmode = secmode;
+			session->srv_secmode = secmode;
 		}
 
 		/*
--- a/usr/src/uts/common/fs/smbsrv/smb_node.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_node.c	Wed Jun 12 22:46:54 2019 +0200
@@ -99,7 +99,6 @@
 #include <fs/fs_reparse.h>
 
 uint32_t smb_is_executable(char *);
-static void smb_node_delete_on_close(smb_node_t *);
 static void smb_node_create_audit_buf(smb_node_t *, int);
 static void smb_node_destroy_audit_buf(smb_node_t *);
 static void smb_node_audit(smb_node_t *);
@@ -190,6 +189,7 @@
 
 #ifdef DEBUG
 	for (i = 0; i <= SMBND_HASH_MASK; i++) {
+		smb_llist_t	*bucket;
 		smb_node_t	*node;
 
 		/*
@@ -205,8 +205,13 @@
 		 * smb_node_lookup() and smb_node_release(). You must track that
 		 * down.
 		 */
-		node = smb_llist_head(&smb_node_hash_table[i]);
-		ASSERT(node == NULL);
+		bucket = &smb_node_hash_table[i];
+		node = smb_llist_head(bucket);
+		while (node != NULL) {
+			cmn_err(CE_NOTE, "leaked node: 0x%p %s",
+			    (void *)node, node->od_name);
+			node = smb_llist_next(bucket, node);
+		}
 	}
 #endif
 
@@ -482,7 +487,9 @@
 			/*
 			 * Check if the file was deleted
 			 */
-			smb_node_delete_on_close(node);
+			if (node->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
+				smb_node_delete_on_close(node);
+			}
 
 			if (node->n_dnode) {
 				ASSERT(node->n_dnode->n_magic ==
@@ -507,7 +514,7 @@
 	mutex_exit(&node->n_mutex);
 }
 
-static void
+void
 smb_node_delete_on_close(smb_node_t *node)
 {
 	smb_node_t	*d_snode;
@@ -515,19 +522,23 @@
 	uint32_t	flags = 0;
 
 	d_snode = node->n_dnode;
-	if (node->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
-		node->flags &= ~NODE_FLAGS_DELETE_ON_CLOSE;
-		flags = node->n_delete_on_close_flags;
-		ASSERT(node->od_name != NULL);
+
+	ASSERT((node->flags & NODE_FLAGS_DELETE_ON_CLOSE) != 0);
+
+	node->flags &= ~NODE_FLAGS_DELETE_ON_CLOSE;
+	node->flags |= NODE_FLAGS_DELETE_COMMITTED;
+	flags = node->n_delete_on_close_flags;
+	ASSERT(node->od_name != NULL);
 
-		if (smb_node_is_dir(node))
-			rc = smb_fsop_rmdir(0, node->delete_on_close_cred,
-			    d_snode, node->od_name, flags);
-		else
-			rc = smb_fsop_remove(0, node->delete_on_close_cred,
-			    d_snode, node->od_name, flags);
-		crfree(node->delete_on_close_cred);
-	}
+	if (smb_node_is_dir(node))
+		rc = smb_fsop_rmdir(0, node->delete_on_close_cred,
+		    d_snode, node->od_name, flags);
+	else
+		rc = smb_fsop_remove(0, node->delete_on_close_cred,
+		    d_snode, node->od_name, flags);
+	crfree(node->delete_on_close_cred);
+	node->delete_on_close_cred = NULL;
+
 	if (rc != 0)
 		cmn_err(CE_WARN, "File %s could not be removed, rc=%d\n",
 		    node->od_name, rc);
@@ -600,40 +611,47 @@
  * and check for anything other than "." or ".." in the readdir buf.
  */
 static uint32_t
-smb_rmdir_possible(smb_node_t *n, uint32_t flags)
+smb_rmdir_possible(smb_node_t *n)
 {
 	ASSERT(n->vp->v_type == VDIR);
-	char buf[512]; /* Only large enough to see if the dir is empty. */
-	int eof, bsize = sizeof (buf), reclen = 0;
-	char *name;
-	boolean_t edp = vfs_has_feature(n->vp->v_vfsp, VFSFT_DIRENTFLAGS);
+	char *buf;
+	char *bufptr;
+	struct dirent64	*dp;
+	uint32_t status = NT_STATUS_SUCCESS;
+	int bsize = SMB_ODIR_BUFSIZE;
+	int eof = 0;
 
-	union {
-		char		*u_bufptr;
-		struct edirent	*u_edp;
-		struct dirent64	*u_dp;
-	} u;
-#define	bufptr	u.u_bufptr
-#define	extdp	u.u_edp
-#define	dp	u.u_dp
+	buf = kmem_alloc(SMB_ODIR_BUFSIZE, KM_SLEEP);
+
+	/* Flags zero: no edirent, no ABE wanted here */
+	if (smb_vop_readdir(n->vp, 0, buf, &bsize, &eof, 0, zone_kcred())) {
+		status = NT_STATUS_INTERNAL_ERROR;
+		goto out;
+	}
 
-	if (smb_vop_readdir(n->vp, 0, buf, &bsize, &eof, flags, zone_kcred()))
-		return (NT_STATUS_INTERNAL_ERROR);
-	if (bsize == 0)
-		return (0); /* empty dir */
 	bufptr = buf;
-	while ((bufptr += reclen) < buf + bsize) {
-		if (edp) {
-			reclen = extdp->ed_reclen;
-			name = extdp->ed_name;
-		} else {
-			reclen = dp->d_reclen;
-			name = dp->d_name;
+	while (bsize > 0) {
+		/* LINTED pointer alignment */
+		dp = (struct dirent64 *)bufptr;
+
+		bufptr += dp->d_reclen;
+		bsize  -= dp->d_reclen;
+		if (bsize < 0) {
+			/* partial record */
+			status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+			break;
 		}
-		if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
-			return (NT_STATUS_DIRECTORY_NOT_EMPTY);
+
+		if (strcmp(dp->d_name, ".") != 0 &&
+		    strcmp(dp->d_name, "..") != 0) {
+			status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+			break;
+		}
 	}
-	return (0);
+
+out:
+	kmem_free(buf, SMB_ODIR_BUFSIZE);
+	return (status);
 }
 
 /*
@@ -661,7 +679,7 @@
 	 * "File System Behavior Overview" doc section 4.3.2
 	 */
 	if (smb_node_is_dir(node)) {
-		status = smb_rmdir_possible(node, flags);
+		status = smb_rmdir_possible(node);
 		if (status != 0) {
 			return (status);
 		}
@@ -733,6 +751,10 @@
 			break;
 		default:
 			ASSERT(status == NT_STATUS_SHARING_VIOLATION);
+			DTRACE_PROBE3(conflict3,
+			    smb_ofile_t, of,
+			    uint32_t, desired_access,
+			    uint32_t, share_access);
 			smb_llist_exit(&node->n_ofile_list);
 			return (status);
 		}
@@ -765,6 +787,7 @@
 			break;
 		default:
 			ASSERT(status == NT_STATUS_SHARING_VIOLATION);
+			DTRACE_PROBE1(conflict1, smb_ofile_t, of);
 			smb_llist_exit(&node->n_ofile_list);
 			return (status);
 		}
@@ -802,6 +825,7 @@
 			break;
 		default:
 			ASSERT(status == NT_STATUS_SHARING_VIOLATION);
+			DTRACE_PROBE1(conflict1, smb_ofile_t, of);
 			smb_llist_exit(&node->n_ofile_list);
 			return (status);
 		}
@@ -1160,7 +1184,6 @@
 	node->n_refcnt = 1;
 	node->n_hash_bucket = bucket;
 	node->n_hashkey = hashkey;
-	node->n_pending_dosattr = 0;
 	node->n_open_count = 0;
 	node->n_allocsz = 0;
 	node->n_dnode = NULL;
@@ -1168,9 +1191,6 @@
 	node->delete_on_close_cred = NULL;
 	node->n_delete_on_close_flags = 0;
 	node->n_oplock.ol_fem = B_FALSE;
-	node->n_oplock.ol_xthread = NULL;
-	node->n_oplock.ol_count = 0;
-	node->n_oplock.ol_break = SMB_OPLOCK_NO_BREAK;
 
 	(void) strlcpy(node->od_name, od_name, sizeof (node->od_name));
 	if (strcmp(od_name, XATTR_DIR) == 0)
@@ -1201,8 +1221,6 @@
 	VERIFY(node->n_lock_list.ll_count == 0);
 	VERIFY(node->n_wlock_list.ll_count == 0);
 	VERIFY(node->n_ofile_list.ll_count == 0);
-	VERIFY(node->n_oplock.ol_count == 0);
-	VERIFY(node->n_oplock.ol_xthread == NULL);
 	VERIFY(node->n_oplock.ol_fem == B_FALSE);
 	VERIFY(MUTEX_NOT_HELD(&node->n_mutex));
 	VERIFY(!RW_LOCK_HELD(&node->n_lock));
@@ -1223,15 +1241,13 @@
 	bzero(node, sizeof (smb_node_t));
 
 	smb_llist_constructor(&node->n_ofile_list, sizeof (smb_ofile_t),
-	    offsetof(smb_ofile_t, f_nnd));
+	    offsetof(smb_ofile_t, f_node_lnd));
 	smb_llist_constructor(&node->n_lock_list, sizeof (smb_lock_t),
 	    offsetof(smb_lock_t, l_lnd));
 	smb_llist_constructor(&node->n_wlock_list, sizeof (smb_lock_t),
 	    offsetof(smb_lock_t, l_lnd));
-	cv_init(&node->n_oplock.ol_cv, NULL, CV_DEFAULT, NULL);
 	mutex_init(&node->n_oplock.ol_mutex, NULL, MUTEX_DEFAULT, NULL);
-	list_create(&node->n_oplock.ol_grants, sizeof (smb_oplock_grant_t),
-	    offsetof(smb_oplock_grant_t, og_lnd));
+	cv_init(&node->n_oplock.WaitingOpenCV, NULL, CV_DEFAULT, NULL);
 	rw_init(&node->n_lock, NULL, RW_DEFAULT, NULL);
 	mutex_init(&node->n_mutex, NULL, MUTEX_DEFAULT, NULL);
 	smb_node_create_audit_buf(node, kmflags);
@@ -1251,12 +1267,11 @@
 	smb_node_destroy_audit_buf(node);
 	mutex_destroy(&node->n_mutex);
 	rw_destroy(&node->n_lock);
-	cv_destroy(&node->n_oplock.ol_cv);
+	cv_destroy(&node->n_oplock.WaitingOpenCV);
 	mutex_destroy(&node->n_oplock.ol_mutex);
 	smb_llist_destructor(&node->n_lock_list);
 	smb_llist_destructor(&node->n_wlock_list);
 	smb_llist_destructor(&node->n_ofile_list);
-	list_destroy(&node->n_oplock.ol_grants);
 }
 
 /*
@@ -1382,9 +1397,9 @@
  * smb_node_file_is_readonly
  *
  * Checks if the file (which node represents) is marked readonly
- * in the filesystem. No account is taken of any pending readonly
- * in the node, which must be handled by the callers.
- * (See SMB_OFILE_IS_READONLY and SMB_PATHFILE_IS_READONLY)
+ * in the filesystem.  Note that there may be handles open with
+ * modify rights, and those continue to allow access even after
+ * the DOS read-only flag has been set in the file system.
  */
 boolean_t
 smb_node_file_is_readonly(smb_node_t *node)
@@ -1394,9 +1409,6 @@
 	if (node == NULL)
 		return (B_FALSE);	/* pipes */
 
-	if (node->n_pending_dosattr & FILE_ATTRIBUTE_READONLY)
-		return (B_TRUE);
-
 	bzero(&attr, sizeof (smb_attr_t));
 	attr.sa_mask = SMB_AT_DOSATTR;
 	(void) smb_fsop_getattr(NULL, zone_kcred(), node, &attr);
@@ -1568,40 +1580,18 @@
 		 */
 	}
 
-	/*
-	 * After this point, tmp_attr is what we will actually
-	 * store in the file system _now_, which may differ
-	 * from the callers attr and f_pending_attr w.r.t.
-	 * the DOS readonly flag etc.
-	 */
-	bcopy(attr, &tmp_attr, sizeof (tmp_attr));
-	if (attr->sa_mask & (SMB_AT_DOSATTR | SMB_AT_ALLOCSZ)) {
+	if ((attr->sa_mask & SMB_AT_ALLOCSZ) != 0) {
 		mutex_enter(&node->n_mutex);
-		if ((attr->sa_mask & SMB_AT_DOSATTR) != 0) {
-			tmp_attr.sa_dosattr &= smb_vop_dosattr_settable;
-			if (((tmp_attr.sa_dosattr &
-			    FILE_ATTRIBUTE_READONLY) != 0) &&
-			    (node->n_open_count != 0)) {
-				/* Delay setting readonly */
-				node->n_pending_dosattr =
-				    tmp_attr.sa_dosattr;
-				tmp_attr.sa_dosattr &=
-				    ~FILE_ATTRIBUTE_READONLY;
-			} else {
-				node->n_pending_dosattr = 0;
-			}
-		}
 		/*
 		 * Simulate n_allocsz persistence only while
 		 * there are opens.  See smb_node_getattr
 		 */
-		if ((attr->sa_mask & SMB_AT_ALLOCSZ) != 0 &&
-		    node->n_open_count != 0)
+		if (node->n_open_count != 0)
 			node->n_allocsz = attr->sa_allocsz;
 		mutex_exit(&node->n_mutex);
 	}
 
-	rc = smb_fsop_setattr(sr, cr, node, &tmp_attr);
+	rc = smb_fsop_setattr(sr, cr, node, attr);
 	if (rc != 0)
 		return (rc);
 
@@ -1650,21 +1640,7 @@
 
 	mutex_enter(&node->n_mutex);
 
-	/*
-	 * When there are open handles, and one of them has
-	 * set the DOS readonly flag (in n_pending_dosattr),
-	 * it will not have been stored in the file system.
-	 * In this case use n_pending_dosattr. Note that
-	 * n_pending_dosattr has only the settable bits,
-	 * (setattr masks it with smb_vop_dosattr_settable)
-	 * so we need to keep any non-settable bits we got
-	 * from the file-system above.
-	 */
 	if (attr->sa_mask & SMB_AT_DOSATTR) {
-		if (node->n_pending_dosattr) {
-			attr->sa_dosattr &= ~smb_vop_dosattr_settable;
-			attr->sa_dosattr |= node->n_pending_dosattr;
-		}
 		if (attr->sa_dosattr == 0) {
 			attr->sa_dosattr = (isdir) ?
 			    FILE_ATTRIBUTE_DIRECTORY:
--- a/usr/src/uts/common/fs/smbsrv/smb_nt_create_andx.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_nt_create_andx.c	Wed Jun 12 22:46:54 2019 +0200
@@ -290,13 +290,15 @@
 		op->fqi.fq_dnode = op->dir->f_node;
 	}
 
-	op->op_oplock_levelII = B_TRUE;
-
 	status = smb_common_open(sr);
 	if (status != NT_STATUS_SUCCESS) {
 		smbsr_status(sr, status, 0, 0);
 		return (SDRC_ERROR);
 	}
+	if (op->op_oplock_level != SMB_OPLOCK_NONE) {
+		/* Oplock req. in op->op_oplock_level etc. */
+		smb1_oplock_acquire(sr, B_TRUE);
+	}
 
 	/*
 	 * NB: after the above smb_common_open() success,
@@ -309,7 +311,7 @@
 	case STYPE_DISKTREE:
 	case STYPE_PRINTQ:
 		if (op->create_options & FILE_DELETE_ON_CLOSE)
-			smb_ofile_set_delete_on_close(of);
+			smb_ofile_set_delete_on_close(sr, of);
 		DirFlag = smb_node_is_dir(of->f_node) ? 1 : 0;
 		break;
 
--- a/usr/src/uts/common/fs/smbsrv/smb_nt_transact_create.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_nt_transact_create.c	Wed Jun 12 22:46:54 2019 +0200
@@ -201,13 +201,15 @@
 		op->fqi.fq_dnode = op->dir->f_node;
 	}
 
-	op->op_oplock_levelII = B_TRUE;
-
 	status = smb_common_open(sr);
 	if (status != NT_STATUS_SUCCESS) {
 		smbsr_status(sr, status, 0, 0);
 		return (SDRC_ERROR);
 	}
+	if (op->op_oplock_level != SMB_OPLOCK_NONE) {
+		/* Oplock req. in op->op_oplock_level etc. */
+		smb1_oplock_acquire(sr, B_TRUE);
+	}
 
 	/*
 	 * NB: after the above smb_common_open() success,
@@ -220,7 +222,7 @@
 	case STYPE_DISKTREE:
 	case STYPE_PRINTQ:
 		if (op->create_options & FILE_DELETE_ON_CLOSE)
-			smb_ofile_set_delete_on_close(of);
+			smb_ofile_set_delete_on_close(sr, of);
 		DirFlag = smb_node_is_dir(of->f_node) ? 1 : 0;
 		break;
 
--- a/usr/src/uts/common/fs/smbsrv/smb_nt_transact_ioctl.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_nt_transact_ioctl.c	Wed Jun 12 22:46:54 2019 +0200
@@ -38,10 +38,6 @@
 /*
  * This table defines the list of FSCTL values for which we'll
  * call a funtion to perform specific processing.
- *
- * Note: If support is added for FSCTL_SET_ZERO_DATA, it must break
- * any oplocks on the file to none:
- *   smb_oplock_break(sr, node, SMB_OPLOCK_BREAK_TO_NONE);
  */
 static const struct {
 	uint32_t fcode;
@@ -222,7 +218,12 @@
  * smb_nt_trans_ioctl_set_zero_data
  *
  * Check that the request is valid on the specified file.
- * The implementation is a noop.
+ * The implementation is a noop. XXX - bug!
+ * XXX: We have this in the fsclt module now.  Call that.
+ *
+ * Note: When support is added for FSCTL_SET_ZERO_DATA, it must
+ * break any oplocks on the file to none:
+ *	(void) smb_oplock_break_WRITE(node, ofile);
  */
 /* ARGSUSED */
 static uint32_t
--- a/usr/src/uts/common/fs/smbsrv/smb_nt_transact_security.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_nt_transact_security.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <smbsrv/smb_kproto.h>
@@ -371,6 +371,7 @@
 	uint32_t group_offs;
 	uint32_t sacl_offs;
 	uint32_t dacl_offs;
+	int rc;
 
 	smb_sd_init(sd, SECURITY_DESCRIPTOR_REVISION);
 
@@ -378,13 +379,15 @@
 	    mbc->chain_offset,
 	    mbc->max_bytes - mbc->chain_offset);
 
-	if (smb_mbc_decodef(&sdbuf, "b.wllll",
+	rc = smb_mbc_decodef(&sdbuf, "b.wllll",
 	    &sd->sd_revision, &sd->sd_control,
-	    &owner_offs, &group_offs, &sacl_offs, &dacl_offs))
+	    &owner_offs, &group_offs, &sacl_offs, &dacl_offs);
+
+	/* Prevent disallowed flags in smb_sd_term. */
+	sd->sd_control &= ~SE_SELF_RELATIVE;
+	if (rc != 0)
 		goto decode_error;
 
-	sd->sd_control &= ~SE_SELF_RELATIVE;
-
 	if (owner_offs != 0) {
 		if (owner_offs < SMB_SD_HDRSIZE)
 			goto decode_error;
--- a/usr/src/uts/common/fs/smbsrv/smb_odir.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_odir.c	Wed Jun 12 22:46:54 2019 +0200
@@ -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.
  */
 
 /*
@@ -264,6 +264,7 @@
 static boolean_t smb_odir_lookup_link(smb_request_t *, smb_odir_t *,
     char *, smb_node_t **);
 static boolean_t smb_odir_match_name(smb_odir_t *, smb_odirent_t *);
+static void smb_odir_delete(void *);
 
 
 /*
@@ -282,7 +283,7 @@
 	smb_tree_t	*tree;
 	smb_node_t	*dnode;
 	char		pattern[MAXNAMELEN];
-	uint16_t 	odid;
+	uint16_t	odid;
 	cred_t		*cr;
 
 	ASSERT(sr);
@@ -445,6 +446,8 @@
 void
 smb_odir_release(smb_odir_t *od)
 {
+	smb_tree_t *tree = od->d_tree;
+
 	SMB_ODIR_VALID(od);
 
 	mutex_enter(&od->d_mutex);
@@ -462,7 +465,8 @@
 		od->d_refcnt--;
 		if (od->d_refcnt == 0) {
 			od->d_state = SMB_ODIR_STATE_CLOSED;
-			smb_tree_post_odir(od->d_tree, od);
+			smb_llist_post(&tree->t_odir_list, od,
+			    smb_odir_delete);
 		}
 		break;
 	case SMB_ODIR_STATE_CLOSED:
@@ -989,7 +993,7 @@
  * Remove the odir from the tree list before freeing resources
  * associated with the odir.
  */
-void
+static void
 smb_odir_delete(void *arg)
 {
 	smb_tree_t	*tree;
@@ -1007,6 +1011,13 @@
 	atomic_dec_32(&tree->t_session->s_dir_cnt);
 	smb_llist_exit(&tree->t_odir_list);
 
+	/*
+	 * This odir is no longer on t_odir_list, however...
+	 *
+	 * This is called via smb_llist_post, which means it may run
+	 * BEFORE smb_odir_release drops d_mutex (if another thread
+	 * flushes the delete queue before we do).  Synchronize.
+	 */
 	mutex_enter(&od->d_mutex);
 	mutex_exit(&od->d_mutex);
 
@@ -1048,12 +1059,17 @@
 	dirent64_t	*dp;
 	edirent_t	*edp;
 	char		*np;
-	uint32_t	abe_flag = 0;
+	uint32_t	rddir_flags = 0;
 
 	ASSERT(MUTEX_HELD(&od->d_mutex));
 
 	bzero(odirent, sizeof (smb_odirent_t));
 
+	if (od->d_flags & SMB_ODIR_FLAG_ABE)
+		rddir_flags |= SMB_ABE;
+	if (od->d_flags & SMB_ODIR_FLAG_EDIRENT)
+		rddir_flags |= SMB_EDIRENT;
+
 	if (od->d_bufptr != NULL) {
 		if (od->d_flags & SMB_ODIR_FLAG_EDIRENT)
 			reclen = od->d_edp->ed_reclen;
@@ -1075,11 +1091,8 @@
 
 		od->d_bufsize = sizeof (od->d_buf);
 
-		if (od->d_flags & SMB_ODIR_FLAG_ABE)
-			abe_flag = SMB_ABE;
-
 		rc = smb_vop_readdir(od->d_dnode->vp, od->d_offset,
-		    od->d_buf, &od->d_bufsize, &eof, abe_flag, od->d_cred);
+		    od->d_buf, &od->d_bufsize, &eof, rddir_flags, od->d_cred);
 
 		if ((rc == 0) && (od->d_bufsize == 0))
 			rc = ENOENT;
--- a/usr/src/uts/common/fs/smbsrv/smb_ofile.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_ofile.c	Wed Jun 12 22:46:54 2019 +0200
@@ -22,7 +22,7 @@
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  * Copyright 2016 Syneto S.R.L. All rights reserved.
  * Copyright (c) 2016 by Delphix. All rights reserved.
- * Copyright 2019 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -71,22 +71,32 @@
  * ------------------
  *
  *    +-------------------------+	 T0
- *    |  SMB_OFILE_STATE_OPEN   |<----------- Creation/Allocation
- *    +-------------------------+
- *		    |
- *		    | T1
- *		    |
- *		    v
+ *    |  SMB_OFILE_STATE_OPEN   |<--+-------- Creation/Allocation
+ *    +-------------------------+   |
+ *	    |		|	    | T5
+ *	    |		|	+---------------------------+
+ *	    |		|	| SMB_OFILE_STATE_RECONNECT |
+ *	    |		|	+---------------------------+
+ *	    |		|	    ^
+ *	    |		v	    |
+ *	    |   +---------------+   |
+ *	    |   | STATE_SAVE_DH |   |
+ *	    |   | STATE_SAVING  |   |
+ *	    |   +---------------+   |
+ *	    |		|	    | T4
+ *	    | T1	| T3	+--------------------------+
+ *	    |		+------>| SMB_OFILE_STATE_ORPHANED |
+ *	    v			+--------------------------+
+ *    +-------------------------+   |		|
+ *    | SMB_OFILE_STATE_CLOSING |<--+ T6	| T7
+ *    +-------------------------+		|
+ *	    |		^			v
+ *	    | T2	| T8	+-------------------------+
+ *	    |		+-------| SMB_OFILE_STATE_EXPIRED |
+ *	    v			+-------------------------+
  *    +-------------------------+
- *    | SMB_OFILE_STATE_CLOSING |
- *    +-------------------------+
- *		    |
- *		    | T2
- *		    |
- *		    v
- *    +-------------------------+    T3
  *    | SMB_OFILE_STATE_CLOSED  |----------> Deletion/Free
- *    +-------------------------+
+ *    +-------------------------+    T9
  *
  * SMB_OFILE_STATE_OPEN
  *
@@ -94,8 +104,34 @@
  *      - The ofile is queued in the list of ofiles of its tree.
  *      - References will be given out if the ofile is looked up.
  *
+ * SMB_OFILE_STATE_SAVE_DH
+ *
+ *    Similar to state _CLOSING, but instead of deleting the ofile,
+ *    it leaves the ofile in state _ORPHANED (for later reclaim).
+ *    Will move to _SAVING after last ref, then _ORPHANED.
+ *
+ *    While in this state:
+ *	- The ofile has been marked for preservation during a
+ *	  walk of the tree ofile list to close multiple files.
+ *	- References will not be given out if the ofile is looked up,
+ *	  except for oplock break processing.
+ *	- Still affects Sharing Violation rules
+ *
+ * SMB_OFILE_STATE_SAVING
+ *
+ *    Transient state used to keep oplock break processing out
+ *    while the ofile moves to state _ORPHANED.
+ *
+ *    While in this state:
+ *	- References will not be given out if the ofile is looked up,
+ *	  except for oplock break processing.
+ *	- Still affects Sharing Violation rules
+ *
  * SMB_OFILE_STATE_CLOSING
  *
+ *    Close has been requested.  Stay in this state until the last
+ *    ref. is gone, then move to state _CLOSED
+ *
  *    While in this state:
  *      - The ofile is queued in the list of ofiles of its tree.
  *      - References will not be given out if the ofile is looked up.
@@ -109,6 +145,38 @@
  *      - References will not be given out if the ofile is looked up.
  *      - The resources associated with the ofile remain.
  *
+ * SMB_OFILE_STATE_ORPHANED
+ *
+ *    While in this state:
+ *      - The ofile is queued in the list of ofiles of its tree.
+ *      - Can be reclaimed by the original owner
+ *      - References will not be given out if the ofile is looked up.
+ *      - All the tree, user, and session "up" pointers are NULL!
+ *      - Will eventually be "expired" if not reclaimed
+ *      - Can be closed if its oplock is broken
+ *      - Still affects Sharing Violation rules
+ *
+ * SMB_OFILE_STATE_EXPIRED
+ *
+ *    While in this state:
+ *      - The ofile is queued in the list of ofiles of its tree.
+ *      - References will not be given out if the ofile is looked up.
+ *      - The ofile has not been reclaimed and will soon be closed,
+ *        due to, for example, the durable handle timer expiring, or its
+ *        oplock being broken.
+ *      - Cannot be reclaimed at this point
+ *
+ * SMB_OFILE_STATE_RECONNECT
+ *
+ *    Transient state used to keep oplock break processing out
+ *    while the ofile moves from state _ORPHANED to _OPEN.
+ *
+ *    While in this state:
+ *      - The ofile is being reclaimed; do not touch it.
+ *      - References will not be given out if the ofile is looked up.
+ *      - Still affects Sharing Violation rules
+ *	- see smb2_dh_reconnect() for which members need to be avoided
+ *
  * Transition T0
  *
  *    This transition occurs in smb_ofile_open(). A new ofile is created and
@@ -116,7 +184,9 @@
  *
  * Transition T1
  *
- *    This transition occurs in smb_ofile_close().
+ *    This transition occurs in smb_ofile_close(). Note that this only happens
+ *    when we determine that an ofile should be closed in spite of its durable
+ *    handle properties.
  *
  * Transition T2
  *
@@ -125,6 +195,50 @@
  *    transition to occur, the ofile must be in the SMB_OFILE_STATE_CLOSED
  *    state and the reference count be zero.
  *
+ * Transition T3
+ *
+ *    This transition occurs in smb_ofile_orphan_dh(). It happens during an
+ *    smb2 logoff, or during a session disconnect when certain conditions are
+ *    met. The ofile and structures above it will be kept around until the ofile
+ *    either gets reclaimed, expires after f_timeout_offset nanoseconds, or its
+ *    oplock is broken.
+ *
+ * Transition T4
+ *
+ *    This transition occurs in smb2_dh_reconnect(). An smb2 create request
+ *    with a DURABLE_HANDLE_RECONNECT(_V2) create context has been
+ *    recieved from the original owner. If leases are supported or it's
+ *    RECONNECT_V2, reconnect is subject to additional conditions. The ofile
+ *    will be unwired from the old, disconnected session, tree, and user,
+ *    and wired up to its new context.
+ *
+ * Transition T5
+ *
+ *    This transition occurs in smb2_dh_reconnect(). The ofile has been
+ *    successfully reclaimed.
+ *
+ * Transition T6
+ *
+ *    This transition occurs in smb_ofile_close(). The ofile has been orphaned
+ *    while some thread was blocked, and that thread closes the ofile. Can only
+ *    happen when the ofile is orphaned due to an SMB2 LOGOFF request.
+ *
+ * Transition T7
+ *
+ *    This transition occurs in smb_session_durable_timers() and
+ *    smb_oplock_send_brk(). The ofile will soon be closed.
+ *    In the former case, f_timeout_offset nanoseconds have passed since
+ *    the ofile was orphaned. In the latter, an oplock break occured
+ *    on the ofile while it was orphaned.
+ *
+ * Transition T8
+ *
+ *    This transition occurs in smb_ofile_close().
+ *
+ * Transition T9
+ *
+ *    This transition occurs in smb_ofile_delete().
+ *
  * Comments
  * --------
  *
@@ -142,7 +256,8 @@
  *    Rules of access to a ofile structure:
  *
  *    1) In order to avoid deadlocks, when both (mutex and lock of the ofile
- *       list) have to be entered, the lock must be entered first.
+ *       list) have to be entered, the lock must be entered first. Additionally,
+ *       f_mutex must not be held when removing the ofile from sv_persistid_ht.
  *
  *    2) All actions applied to an ofile require a reference count.
  *
@@ -162,40 +277,45 @@
  *       being queued in that list is NOT registered by incrementing the
  *       reference count.
  */
-#include <smbsrv/smb_kproto.h>
+#include <smbsrv/smb2_kproto.h>
 #include <smbsrv/smb_fsops.h>
+#include <sys/time.h>
+
+/* XXX: May need to actually assign GUIDs for these. */
+/* Don't leak object addresses */
+#define	SMB_OFILE_PERSISTID(of) \
+	((uintptr_t)&smb_cache_ofile ^ (uintptr_t)(of))
 
 static boolean_t smb_ofile_is_open_locked(smb_ofile_t *);
-static smb_ofile_t *smb_ofile_close_and_next(smb_ofile_t *);
+static void smb_ofile_delete(void *arg);
+static void smb_ofile_save_dh(void *arg);
+
 static int smb_ofile_netinfo_encode(smb_ofile_t *, uint8_t *, size_t,
     uint32_t *);
 static int smb_ofile_netinfo_init(smb_ofile_t *, smb_netfileinfo_t *);
 static void smb_ofile_netinfo_fini(smb_netfileinfo_t *);
 
 /*
- * smb_ofile_open
+ * smb_ofile_alloc
+ * Allocate an ofile and fill in it's "up" pointers, but
+ * do NOT link it into the tree's list of ofiles or the
+ * node's list of ofiles.  An ofile in this state is a
+ * "proposed" open passed to the oplock break code.
+ *
+ * If we don't get as far as smb_ofile_open with this OF,
+ * call smb_ofile_free() to free this object.
  */
 smb_ofile_t *
-smb_ofile_open(
+smb_ofile_alloc(
     smb_request_t	*sr,
-    smb_node_t		*node,
-    struct open_param	*op,
+    smb_arg_open_t	*op,
+    smb_node_t		*node, /* optional (may be NULL) */
     uint16_t		ftype,
-    uint32_t		uniqid,
-    smb_error_t		*err)
+    uint16_t		tree_fid,
+    uint32_t		uniqid)
 {
 	smb_tree_t	*tree = sr->tid_tree;
 	smb_ofile_t	*of;
-	uint16_t	fid;
-	smb_attr_t	attr;
-	int		rc;
-
-	if (smb_idpool_alloc(&tree->t_fid_pool, &fid)) {
-		err->status = NT_STATUS_TOO_MANY_OPENED_FILES;
-		err->errcls = ERRDOS;
-		err->errcode = ERROR_TOO_MANY_OPEN_FILES;
-		return (NULL);
-	}
 
 	of = kmem_cache_alloc(smb_cache_ofile, KM_SLEEP);
 	bzero(of, sizeof (smb_ofile_t));
@@ -205,9 +325,11 @@
 	list_create(&of->f_notify.nc_waiters, sizeof (smb_request_t),
 	    offsetof(smb_request_t, sr_waiters));
 
-	of->f_state = SMB_OFILE_STATE_OPEN;
+	of->f_state = SMB_OFILE_STATE_ALLOC;
 	of->f_refcnt = 1;
-	of->f_fid = fid;
+	of->f_ftype = ftype;
+	of->f_fid = tree_fid;
+	/* of->f_persistid see smb2_create */
 	of->f_uniqid = uniqid;
 	of->f_opened_by_pid = sr->smb_pid;
 	of->f_granted_access = op->desired_access;
@@ -216,127 +338,115 @@
 	of->f_cr = (op->create_options & FILE_OPEN_FOR_BACKUP_INTENT) ?
 	    smb_user_getprivcred(sr->uid_user) : sr->uid_user->u_cred;
 	crhold(of->f_cr);
-	of->f_ftype = ftype;
 	of->f_server = tree->t_server;
 	of->f_session = tree->t_session;
+	(void) memset(of->f_lock_seq, -1, SMB_OFILE_LSEQ_MAX);
+
+	of->f_mode = smb_fsop_amask_to_omode(of->f_granted_access);
+	if ((of->f_granted_access & FILE_DATA_ALL) == FILE_EXECUTE)
+		of->f_flags |= SMB_OFLAGS_EXECONLY;
 
 	/*
-	 * grab a ref for of->f_user
-	 * released in smb_ofile_delete()
+	 * In case a lease is requested, copy the lease keys now so
+	 * any oplock breaks during open don't break those on our
+	 * other handles that might have the same lease.
+	 */
+	bcopy(op->lease_key, of->TargetOplockKey, SMB_LEASE_KEY_SZ);
+	bcopy(op->parent_lease_key, of->ParentOplockKey, SMB_LEASE_KEY_SZ);
+
+	/*
+	 * grab a ref for of->f_user and of->f_tree
+	 * We know the user and tree must be "live" because
+	 * this SR holds references to them.  The node ref. is
+	 * held by our caller, until smb_ofile_open puts this
+	 * ofile on the node ofile list with smb_node_add_ofile.
 	 */
 	smb_user_hold_internal(sr->uid_user);
+	smb_tree_hold_internal(tree);
 	of->f_user = sr->uid_user;
 	of->f_tree = tree;
 	of->f_node = node;
 
-	if (ftype == SMB_FTYPE_MESG_PIPE) {
+	return (of);
+}
+
+/*
+ * smb_ofile_open
+ *
+ * Complete an open on an ofile that was previously allocated by
+ * smb_ofile_alloc, by putting it on the tree ofile list and
+ * (if it's a file) the node ofile list.
+ */
+void
+smb_ofile_open(
+    smb_request_t	*sr,
+    smb_arg_open_t	*op,
+    smb_ofile_t		*of)
+{
+	smb_tree_t	*tree = sr->tid_tree;
+	smb_node_t	*node = of->f_node;
+
+	ASSERT(of->f_state == SMB_OFILE_STATE_ALLOC);
+	of->f_state = SMB_OFILE_STATE_OPEN;
+
+	switch (of->f_ftype) {
+	case SMB_FTYPE_BYTE_PIPE:
+	case SMB_FTYPE_MESG_PIPE:
 		/* See smb_opipe_open. */
 		of->f_pipe = op->pipe;
 		smb_server_inc_pipes(of->f_server);
-	} else {
-		ASSERT(ftype == SMB_FTYPE_DISK); /* Regular file, not a pipe */
-		ASSERT(node);
-
-		/*
-		 * Note that the common open path often adds bits like
-		 * READ_CONTROL, so the logic "is this open exec-only"
-		 * needs to look at only the FILE_DATA_ALL bits.
-		 */
-		if ((of->f_granted_access & FILE_DATA_ALL) == FILE_EXECUTE)
-			of->f_flags |= SMB_OFLAGS_EXECONLY;
-
-		/*
-		 * This is an "internal" getattr because we need the
-		 * UID and DOS attributes.  Don't want to fail here
-		 * due to permissions, so use kcred.
-		 */
-		bzero(&attr, sizeof (smb_attr_t));
-		attr.sa_mask = SMB_AT_UID | SMB_AT_DOSATTR;
-		rc = smb_node_getattr(NULL, node, zone_kcred(), NULL, &attr);
-		if (rc != 0) {
-			err->status = NT_STATUS_INTERNAL_ERROR;
-			err->errcls = ERRDOS;
-			err->errcode = ERROR_INTERNAL_ERROR;
-			goto errout;
-		}
-		if (crgetuid(of->f_cr) == attr.sa_vattr.va_uid) {
-			/*
-			 * Add this bit for the file's owner even if it's not
-			 * specified in the request (Windows behavior).
-			 */
-			of->f_granted_access |= FILE_READ_ATTRIBUTES;
-		}
-
-		if (smb_node_is_file(node)) {
-			of->f_mode =
-			    smb_fsop_amask_to_omode(of->f_granted_access);
-			if (smb_fsop_open(node, of->f_mode, of->f_cr) != 0) {
-				err->status = NT_STATUS_ACCESS_DENIED;
-				err->errcls = ERRDOS;
-				err->errcode = ERROR_ACCESS_DENIED;
-				goto errout;
-			}
-		}
-
-		if (tree->t_flags & SMB_TREE_READONLY)
-			of->f_flags |= SMB_OFLAGS_READONLY;
-
-		/*
-		 * Note that if we created_readonly, that
-		 * will _not_ yet show in attr.sa_dosattr
-		 * so creating a readonly file gives the
-		 * caller a writable handle as it should.
-		 */
-		if (attr.sa_dosattr & FILE_ATTRIBUTE_READONLY)
-			of->f_flags |= SMB_OFLAGS_READONLY;
+		break;
+	case SMB_FTYPE_DISK:
+	case SMB_FTYPE_PRINTER:
+		/* Regular file, not a pipe */
+		ASSERT(node != NULL);
 
 		smb_node_inc_open_ofiles(node);
 		smb_node_add_ofile(node, of);
 		smb_node_ref(node);
 		smb_server_inc_files(of->f_server);
+		break;
+	default:
+		ASSERT(0);
 	}
 	smb_llist_enter(&tree->t_ofile_list, RW_WRITER);
 	smb_llist_insert_tail(&tree->t_ofile_list, of);
 	smb_llist_exit(&tree->t_ofile_list);
 	atomic_inc_32(&tree->t_open_files);
 	atomic_inc_32(&of->f_session->s_file_cnt);
-	return (of);
 
-errout:
-	smb_user_release(of->f_user);
-	crfree(of->f_cr);
-
-	list_destroy(&of->f_notify.nc_waiters);
-	mutex_destroy(&of->f_mutex);
-
-	of->f_magic = 0;
-	kmem_cache_free(smb_cache_ofile, of);
-
-	smb_idpool_free(&tree->t_fid_pool, fid);
-
-	return (NULL);
 }
 
 /*
  * smb_ofile_close
+ *
+ * Incoming states: (where from)
+ *   SMB_OFILE_STATE_OPEN  protocol close, smb_ofile_drop
+ *   SMB_OFILE_STATE_EXPIRED  called via smb2_dh_expire
+ *   SMB_OFILE_STATE_ORPHANED  smb2_dh_shutdown
  */
 void
 smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec)
 {
 	smb_attr_t *pa;
 	timestruc_t now;
-	uint32_t flags = 0;
 
 	SMB_OFILE_VALID(of);
 
 	mutex_enter(&of->f_mutex);
 	ASSERT(of->f_refcnt);
-	if (of->f_state != SMB_OFILE_STATE_OPEN) {
+
+	switch (of->f_state) {
+	case SMB_OFILE_STATE_OPEN:
+	case SMB_OFILE_STATE_ORPHANED:
+	case SMB_OFILE_STATE_EXPIRED:
+		of->f_state = SMB_OFILE_STATE_CLOSING;
+		mutex_exit(&of->f_mutex);
+		break;
+	default:
 		mutex_exit(&of->f_mutex);
 		return;
 	}
-	of->f_state = SMB_OFILE_STATE_CLOSING;
-	mutex_exit(&of->f_mutex);
 
 	switch (of->f_ftype) {
 	case SMB_FTYPE_BYTE_PIPE:
@@ -346,7 +456,14 @@
 		break;
 
 	case SMB_FTYPE_DISK:
-	case SMB_FTYPE_PRINTER:
+		if (of->f_persistid != 0)
+			smb_ofile_del_persistid(of);
+		if (of->f_lease != NULL)
+			smb2_lease_ofile_close(of);
+		smb_oplock_break_CLOSE(of->f_node, of);
+		/* FALLTHROUGH */
+
+	case SMB_FTYPE_PRINTER: /* or FTYPE_DISK */
 		/*
 		 * In here we make changes to of->f_pending_attr
 		 * while not holding of->f_mutex.  This is OK
@@ -374,10 +491,8 @@
 		}
 
 		if (of->f_flags & SMB_OFLAGS_SET_DELETE_ON_CLOSE) {
-			if (smb_tree_has_feature(of->f_tree,
-			    SMB_TREE_CATIA)) {
-				flags |= SMB_CATIA;
-			}
+			/* We delete using the on-disk name. */
+			uint32_t flags = SMB_CASE_SENSITIVE;
 			(void) smb_node_set_delete_on_close(of->f_node,
 			    of->f_cr, flags);
 		}
@@ -387,7 +502,6 @@
 		if (smb_node_is_file(of->f_node)) {
 			(void) smb_fsop_close(of->f_node, of->f_mode,
 			    of->f_cr);
-			smb_oplock_release(of->f_node, of);
 		} else {
 			/*
 			 * If there was an odir, close it.
@@ -416,17 +530,20 @@
 		}
 		if (smb_node_dec_open_ofiles(of->f_node) == 0) {
 			/*
-			 * Last close. The f_pending_attr has
-			 * only times (atime,ctime,mtime) so
-			 * we can borrow it to commit the
-			 * n_pending_dosattr from the node.
+			 * Last close.  If we're not deleting
+			 * the file, apply any pending attrs.
+			 * Leave allocsz zero when no open files,
+			 * just to avoid confusion, because it's
+			 * only updated when there are opens.
+			 * XXX: Just do this on _every_ close.
 			 */
-			pa->sa_dosattr =
-			    of->f_node->n_pending_dosattr;
-			if (pa->sa_dosattr != 0)
-				pa->sa_mask |= SMB_AT_DOSATTR;
-			/* Let's leave this zero when not in use. */
+			mutex_enter(&of->f_node->n_mutex);
+			if (of->f_node->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
+				smb_node_delete_on_close(of->f_node);
+				pa->sa_mask = 0;
+			}
 			of->f_node->n_allocsz = 0;
+			mutex_exit(&of->f_node->n_mutex);
 		}
 		if (pa->sa_mask != 0) {
 			/*
@@ -435,9 +552,6 @@
 			 * we pass NULL as the ofile to setattr
 			 * so it will write to the file system
 			 * and not keep anything on the ofile.
-			 * This clears n_pending_dosattr if
-			 * there are no opens, otherwise the
-			 * dosattr will be pending again.
 			 */
 			(void) smb_node_setattr(NULL, of->f_node,
 			    of->f_cr, NULL, pa);
@@ -446,13 +560,69 @@
 		smb_server_dec_files(of->f_server);
 		break;
 	}
-	atomic_dec_32(&of->f_tree->t_open_files);
+
+	/*
+	 * Keep f_state == SMB_OFILE_STATE_CLOSING
+	 * until the last ref. is dropped, in
+	 * smb_ofile_release()
+	 */
+}
+
+/*
+ * "Destructor" function for smb_ofile_close_all, and
+ * smb_ofile_close_all_by_pid, called after the llist lock
+ * for tree list has been exited.  Our job is to either
+ * close this ofile, or (if durable) set state _SAVE_DH.
+ *
+ * The next interesting thing happens when the last ref.
+ * on this ofile calls smb_ofile_release(), where we
+ * eihter delete the ofile, or (if durable) leave it
+ * in the persistid hash table for possible reclaim.
+ *
+ * This is run via smb_llist_post (after smb_llist_exit)
+ * because smb_ofile_close can block, and we'd rather not
+ * block while holding the ofile list as reader.
+ */
+static void
+smb_ofile_drop(void *arg)
+{
+	smb_ofile_t	*of = arg;
+
+	SMB_OFILE_VALID(of);
 
 	mutex_enter(&of->f_mutex);
-	ASSERT(of->f_refcnt);
-	ASSERT(of->f_state == SMB_OFILE_STATE_CLOSING);
-	of->f_state = SMB_OFILE_STATE_CLOSED;
-	mutex_exit(&of->f_mutex);
+	switch (of->f_state) {
+	case SMB_OFILE_STATE_OPEN:
+		/* DH checks under mutex. */
+		if (of->f_ftype == SMB_FTYPE_DISK &&
+		    of->dh_vers != SMB2_NOT_DURABLE &&
+		    smb_dh_should_save(of)) {
+			/*
+			 * Tell smb_ofile_release() to
+			 * make this an _ORPHANED DH.
+			 */
+			of->f_state = SMB_OFILE_STATE_SAVE_DH;
+			mutex_exit(&of->f_mutex);
+			break;
+		}
+		/* OK close it. */
+		mutex_exit(&of->f_mutex);
+		smb_ofile_close(of, 0);
+		break;
+
+	default:
+		/* Something else closed it already. */
+		mutex_exit(&of->f_mutex);
+		break;
+	}
+
+	/*
+	 * Release the ref acquired during the traversal loop.
+	 * Note that on the last ref, this ofile will be
+	 * removed from the tree list etc.
+	 * See: smb_llist_post, smb_ofile_delete
+	 */
+	smb_ofile_release(of);
 }
 
 /*
@@ -462,50 +632,35 @@
  */
 void
 smb_ofile_close_all(
-    smb_tree_t		*tree)
+    smb_tree_t		*tree,
+    uint32_t		pid)
 {
 	smb_ofile_t	*of;
+	smb_llist_t	*ll;
 
 	ASSERT(tree);
 	ASSERT(tree->t_magic == SMB_TREE_MAGIC);
 
-	smb_llist_enter(&tree->t_ofile_list, RW_READER);
-	of = smb_llist_head(&tree->t_ofile_list);
-	while (of) {
+	ll = &tree->t_ofile_list;
+
+	smb_llist_enter(ll, RW_READER);
+	for (of = smb_llist_head(ll);
+	    of != NULL;
+	    of = smb_llist_next(ll, of)) {
 		ASSERT(of->f_magic == SMB_OFILE_MAGIC);
 		ASSERT(of->f_tree == tree);
-		of = smb_ofile_close_and_next(of);
-	}
-	smb_llist_exit(&tree->t_ofile_list);
-}
-
-/*
- * smb_ofiles_close_by_pid
- *
- *
- */
-void
-smb_ofile_close_all_by_pid(
-    smb_tree_t		*tree,
-    uint16_t		pid)
-{
-	smb_ofile_t	*of;
-
-	ASSERT(tree);
-	ASSERT(tree->t_magic == SMB_TREE_MAGIC);
-
-	smb_llist_enter(&tree->t_ofile_list, RW_READER);
-	of = smb_llist_head(&tree->t_ofile_list);
-	while (of) {
-		ASSERT(of->f_magic == SMB_OFILE_MAGIC);
-		ASSERT(of->f_tree == tree);
-		if (of->f_opened_by_pid == pid) {
-			of = smb_ofile_close_and_next(of);
-		} else {
-			of = smb_llist_next(&tree->t_ofile_list, of);
+		if (pid != 0 && of->f_opened_by_pid != pid)
+			continue;
+		if (smb_ofile_hold(of)) {
+			smb_llist_post(ll, of, smb_ofile_drop);
 		}
 	}
-	smb_llist_exit(&tree->t_ofile_list);
+
+	/*
+	 * Drop the lock and process the llist dtor queue.
+	 * Calls smb_ofile_drop on ofiles that were open.
+	 */
+	smb_llist_exit(ll);
 }
 
 /*
@@ -552,6 +707,53 @@
 }
 
 /*
+ * Take a reference on an open file, in any of the states:
+ *   RECONNECT, SAVE_DH, OPEN, ORPHANED.
+ * Return TRUE if ref taken.  Used for oplock breaks.
+ *
+ * Note: When the oplock break code calls this, it holds the
+ * node ofile list lock and node oplock mutex.  When we see
+ * an ofile in states RECONNECT or SAVING, we know the ofile
+ * is gaining or losing it's tree, and that happens quickly,
+ * so we just wait for that work to finish.  However, the
+ * waiting for state transitions here means we have to be
+ * careful not to re-enter the node list lock or otherwise
+ * block on things that could cause a deadlock.  Waiting
+ * just on of->f_mutex here is OK.
+ */
+boolean_t
+smb_ofile_hold_olbrk(smb_ofile_t *of)
+{
+	boolean_t ret = B_FALSE;
+
+	ASSERT(of);
+	ASSERT(of->f_magic == SMB_OFILE_MAGIC);
+
+	mutex_enter(&of->f_mutex);
+
+again:
+	switch (of->f_state) {
+	case SMB_OFILE_STATE_RECONNECT:
+	case SMB_OFILE_STATE_SAVING:
+		cv_wait(&of->f_cv, &of->f_mutex);
+		goto again;
+
+	case SMB_OFILE_STATE_OPEN:
+	case SMB_OFILE_STATE_ORPHANED:
+	case SMB_OFILE_STATE_SAVE_DH:
+		of->f_refcnt++;
+		ret = B_TRUE;
+		break;
+
+	default:
+		break;
+	}
+	mutex_exit(&of->f_mutex);
+
+	return (ret);
+}
+
+/*
  * Take a reference on an open file.
  */
 boolean_t
@@ -577,23 +779,50 @@
  * zero and the file has been closed, post the object for deletion.
  * Object deletion is deferred to avoid modifying a list while an
  * iteration may be in progress.
+ *
+ * We're careful to avoid dropping f_session etc. until the last
+ * reference goes away.  The oplock break code depends on that
+ * not changing while it holds a ref. on an ofile.
  */
 void
 smb_ofile_release(smb_ofile_t *of)
 {
+	smb_tree_t *tree = of->f_tree;
+	boolean_t delete = B_FALSE;
+
 	SMB_OFILE_VALID(of);
 
 	mutex_enter(&of->f_mutex);
-	ASSERT(of->f_refcnt);
+	ASSERT(of->f_refcnt > 0);
 	of->f_refcnt--;
+
 	switch (of->f_state) {
 	case SMB_OFILE_STATE_OPEN:
-	case SMB_OFILE_STATE_CLOSING:
+	case SMB_OFILE_STATE_ORPHANED:
+	case SMB_OFILE_STATE_EXPIRED:
+		break;
+
+	case SMB_OFILE_STATE_SAVE_DH:
+		ASSERT(tree != NULL);
+		if (of->f_refcnt == 0) {
+			of->f_state = SMB_OFILE_STATE_SAVING;
+			smb_llist_post(&tree->t_ofile_list, of,
+			    smb_ofile_save_dh);
+		}
 		break;
 
-	case SMB_OFILE_STATE_CLOSED:
-		if (of->f_refcnt == 0)
-			smb_tree_post_ofile(of->f_tree, of);
+	case SMB_OFILE_STATE_CLOSING:
+		/* Note, tree == NULL on _ORPHANED */
+		if (of->f_refcnt == 0) {
+			of->f_state = SMB_OFILE_STATE_CLOSED;
+			if (tree == NULL) {
+				/* Skip smb_llist_post */
+				delete = B_TRUE;
+				break;
+			}
+			smb_llist_post(&tree->t_ofile_list, of,
+			    smb_ofile_delete);
+		}
 		break;
 
 	default:
@@ -601,34 +830,14 @@
 		break;
 	}
 	mutex_exit(&of->f_mutex);
-}
 
-/*
- * smb_ofile_request_complete
- *
- * During oplock acquisition, all other oplock requests on the node
- * are blocked until the acquire request completes and the response
- * is on the wire.
- * Call smb_oplock_broadcast to notify the node that the request
- * has completed.
- *
- * THIS MECHANISM RELIES ON THE FACT THAT THE OFILE IS NOT REMOVED
- * FROM THE SR UNTIL REQUEST COMPLETION (when the sr is destroyed)
- */
-void
-smb_ofile_request_complete(smb_ofile_t *of)
-{
-	SMB_OFILE_VALID(of);
-
-	switch (of->f_ftype) {
-	case SMB_FTYPE_DISK:
-		ASSERT(of->f_node);
-		smb_oplock_broadcast(of->f_node);
-		break;
-	case SMB_FTYPE_MESG_PIPE:
-		break;
-	default:
-		break;
+	/*
+	 * When we drop the last ref. on an expired DH, it's no longer
+	 * in any tree, so skip the smb_llist_post and just call
+	 * smb_ofile_delete directly.
+	 */
+	if (delete) {
+		smb_ofile_delete(of);
 	}
 }
 
@@ -673,6 +882,7 @@
 		goto out;
 	}
 
+	/* inline smb_ofile_hold() */
 	mutex_enter(&of->f_mutex);
 	if (of->f_state != SMB_OFILE_STATE_OPEN) {
 		mutex_exit(&of->f_mutex);
@@ -722,6 +932,92 @@
 	return (NULL);
 }
 
+static smb_ofile_t *
+smb_ofile_hold_cb(smb_ofile_t *of)
+{
+	smb_ofile_t *ret = of;
+
+	mutex_enter(&of->f_mutex);
+	if (of->f_state == SMB_OFILE_STATE_ORPHANED)
+		/* inline smb_ofile_hold() */
+		of->f_refcnt++;
+	else
+		ret = NULL;
+
+	mutex_exit(&of->f_mutex);
+	return (ret);
+}
+
+/*
+ * Lookup an ofile by persistent ID, and return ONLY if in state ORPHANED
+ * This is used by SMB2 create "reclaim".
+ */
+smb_ofile_t *
+smb_ofile_lookup_by_persistid(smb_request_t *sr, uint64_t persistid)
+{
+	smb_hash_t *hash;
+	smb_bucket_t *bucket;
+	smb_llist_t *ll;
+	smb_ofile_t *of;
+	uint_t idx;
+
+	hash = sr->sr_server->sv_persistid_ht;
+	idx = smb_hash_uint64(hash, persistid);
+	bucket = &hash->buckets[idx];
+	ll = &bucket->b_list;
+
+	smb_llist_enter(ll, RW_READER);
+	of = smb_llist_head(ll);
+	while (of != NULL) {
+		if (of->f_persistid == persistid)
+			break;
+		of = smb_llist_next(ll, of);
+	}
+	if (of != NULL)
+		of = smb_ofile_hold_cb(of);
+	smb_llist_exit(ll);
+
+	return (of);
+}
+
+/*
+ * Create a (unique) persistent ID for a new ofile,
+ * and add this ofile to the persistid hash table.
+ */
+void
+smb_ofile_set_persistid(smb_ofile_t *of)
+{
+	smb_hash_t *hash = of->f_server->sv_persistid_ht;
+	smb_bucket_t *bucket;
+	smb_llist_t *ll;
+	uint_t idx;
+
+	of->f_persistid = SMB_OFILE_PERSISTID(of);
+
+	idx = smb_hash_uint64(hash, of->f_persistid);
+	bucket = &hash->buckets[idx];
+	ll = &bucket->b_list;
+	smb_llist_enter(ll, RW_WRITER);
+	smb_llist_insert_tail(ll, of);
+	smb_llist_exit(ll);
+}
+
+void
+smb_ofile_del_persistid(smb_ofile_t *of)
+{
+	smb_hash_t *hash = of->f_server->sv_persistid_ht;
+	smb_bucket_t *bucket;
+	smb_llist_t *ll;
+	uint_t idx;
+
+	idx = smb_hash_uint64(hash, of->f_persistid);
+	bucket = &hash->buckets[idx];
+	ll = &bucket->b_list;
+	smb_llist_enter(ll, RW_WRITER);
+	smb_llist_remove(ll, of);
+	smb_llist_exit(ll);
+}
+
 /*
  * Disallow NetFileClose on certain ofiles to avoid side-effects.
  * Closing a tree root is not allowed: use NetSessionDel or NetShareDel.
@@ -895,12 +1191,19 @@
 static boolean_t
 smb_ofile_is_open_locked(smb_ofile_t *of)
 {
+	ASSERT(MUTEX_HELD(&of->f_mutex));
+
 	switch (of->f_state) {
 	case SMB_OFILE_STATE_OPEN:
+	case SMB_OFILE_STATE_SAVE_DH:
+	case SMB_OFILE_STATE_SAVING:
+	case SMB_OFILE_STATE_ORPHANED:
+	case SMB_OFILE_STATE_RECONNECT:
 		return (B_TRUE);
 
 	case SMB_OFILE_STATE_CLOSING:
 	case SMB_OFILE_STATE_CLOSED:
+	case SMB_OFILE_STATE_EXPIRED:
 		return (B_FALSE);
 
 	default:
@@ -910,75 +1213,105 @@
 }
 
 /*
- * This function closes the file passed in (if appropriate) and returns the
- * next open file in the list of open files of the tree of the open file passed
- * in. It requires that the list of open files of the tree be entered in
- * RW_READER mode before being called.
+ * smb_ofile_save_dh
+ *
+ * Called via smb_llist_post (after smb_llist_exit) when the last ref.
+ * on this ofile has gone, and this ofile is a "durable handle" (DH)
+ * that has state we've decided to save.
+ *
+ * This does parts of what smb_ofile_delete would do, including:
+ * remove the ofile from the tree ofile list and related.
+ *
+ * We leave the ofile in state ORPHANED, ready for reconnect
+ * or expiration via smb2_dh_expire (see smb_ofile_delete).
  */
-static smb_ofile_t *
-smb_ofile_close_and_next(smb_ofile_t *of)
+static void
+smb_ofile_save_dh(void *arg)
 {
-	smb_ofile_t	*next_of;
-	smb_tree_t	*tree;
+	smb_ofile_t	*of = (smb_ofile_t *)arg;
+	smb_tree_t	*tree = of->f_tree;
+
+	SMB_OFILE_VALID(of);
+	ASSERT(of->f_refcnt == 0);
+	ASSERT(of->f_ftype == SMB_FTYPE_DISK);
+	ASSERT(of->f_state == SMB_OFILE_STATE_SAVING);
 
-	ASSERT(of);
-	ASSERT(of->f_magic == SMB_OFILE_MAGIC);
+	atomic_dec_32(&of->f_session->s_file_cnt);
+	atomic_dec_32(&of->f_tree->t_open_files);
+	smb_llist_enter(&tree->t_ofile_list, RW_WRITER);
+	smb_llist_remove(&tree->t_ofile_list, of);
+	smb_llist_exit(&tree->t_ofile_list);
 
+	/*
+	 * This ofile is no longer on t_ofile_list, however...
+	 *
+	 * This is called via smb_llist_post, which means it may run
+	 * BEFORE smb_ofile_release drops f_mutex (if another thread
+	 * flushes the delete queue before we do).  Synchronize.
+	 */
 	mutex_enter(&of->f_mutex);
-	switch (of->f_state) {
-	case SMB_OFILE_STATE_OPEN:
-		/* The file is still open. */
-		of->f_refcnt++;
-		ASSERT(of->f_refcnt);
-		tree = of->f_tree;
-		mutex_exit(&of->f_mutex);
-		smb_llist_exit(&of->f_tree->t_ofile_list);
-		smb_ofile_close(of, 0);
-		smb_ofile_release(of);
-		smb_llist_enter(&tree->t_ofile_list, RW_READER);
-		next_of = smb_llist_head(&tree->t_ofile_list);
-		break;
-	case SMB_OFILE_STATE_CLOSING:
-	case SMB_OFILE_STATE_CLOSED:
-		/*
-		 * The ofile exists but is closed or
-		 * in the process being closed.
-		 */
-		mutex_exit(&of->f_mutex);
-		next_of = smb_llist_next(&of->f_tree->t_ofile_list, of);
-		break;
-	default:
-		ASSERT(0);
-		mutex_exit(&of->f_mutex);
-		next_of = smb_llist_next(&of->f_tree->t_ofile_list, of);
-		break;
-	}
-	return (next_of);
+	DTRACE_PROBE1(ofile__exit, smb_ofile_t, of);
+	mutex_exit(&of->f_mutex);
+
+	/*
+	 * Keep f_notify state, lease, and
+	 * keep on node ofile list.
+	 * Keep of->f_cr until reclaim.
+	 */
+
+	ASSERT(of->f_fid != 0);
+	smb_idpool_free(&tree->t_fid_pool, of->f_fid);
+	of->f_fid = 0;
+	smb_tree_release(of->f_tree);
+	of->f_tree = NULL;
+	smb_user_release(of->f_user);
+	of->f_user = NULL;
+	of->f_session = NULL;
+
+	/*
+	 * Make it "orphaned" so it can now be reclaimed.
+	 * Note that smb_ofile_hold_olbrk() may have blocked
+	 * for state SMB_OFILE_STATE_SAVING, so wake it.
+	 */
+	mutex_enter(&of->f_mutex);
+	of->dh_expire_time = gethrtime() + of->dh_timeout_offset;
+	of->f_state = SMB_OFILE_STATE_ORPHANED;
+	cv_broadcast(&of->f_cv);
+	mutex_exit(&of->f_mutex);
 }
 
 /*
  * Delete an ofile.
  *
- * Remove the ofile from the tree list before freeing resources
- * associated with the ofile.
+ * Approximately the inverse of smb_ofile_alloc()
+ * Called via smb_llist_post (after smb_llist_exit)
+ * when the last ref. on this ofile has gone.
+ *
+ * Normally,this removes the ofile from the tree list and
+ * then frees resources held on the ofile.  However, when
+ * we're expiring an orphaned durable handle, the linkage
+ * into the tree lists etc. have already been destroyed.
+ * This case is distinguished by of->f_tree == NULL.
  */
-void
+static void
 smb_ofile_delete(void *arg)
 {
-	smb_tree_t	*tree;
 	smb_ofile_t	*of = (smb_ofile_t *)arg;
+	smb_tree_t	*tree = of->f_tree;
 
 	SMB_OFILE_VALID(of);
 	ASSERT(of->f_refcnt == 0);
 	ASSERT(of->f_state == SMB_OFILE_STATE_CLOSED);
-	ASSERT(!SMB_OFILE_OPLOCK_GRANTED(of));
 
-	tree = of->f_tree;
-	smb_llist_enter(&tree->t_ofile_list, RW_WRITER);
-	smb_llist_remove(&tree->t_ofile_list, of);
-	smb_idpool_free(&tree->t_fid_pool, of->f_fid);
-	atomic_dec_32(&tree->t_session->s_file_cnt);
-	smb_llist_exit(&tree->t_ofile_list);
+	if (tree != NULL) {
+		ASSERT(of->f_user != NULL);
+		ASSERT(of->f_session != NULL);
+		atomic_dec_32(&of->f_session->s_file_cnt);
+		atomic_dec_32(&of->f_tree->t_open_files);
+		smb_llist_enter(&tree->t_ofile_list, RW_WRITER);
+		smb_llist_remove(&tree->t_ofile_list, of);
+		smb_llist_exit(&tree->t_ofile_list);
+	}
 
 	/*
 	 * Remove this ofile from the node's n_ofile_list so it
@@ -995,7 +1328,16 @@
 		smb_node_rem_ofile(of->f_node, of);
 	}
 
+	/*
+	 * This ofile is no longer on any lists, however...
+	 *
+	 * This is called via smb_llist_post, which means it may run
+	 * BEFORE smb_ofile_release drops f_mutex (if another thread
+	 * flushes the delete queue before we do).  Synchronize.
+	 */
 	mutex_enter(&of->f_mutex);
+	of->f_state = SMB_OFILE_STATE_ALLOC;
+	DTRACE_PROBE1(ofile__exit, smb_ofile_t, of);
 	mutex_exit(&of->f_mutex);
 
 	switch (of->f_ftype) {
@@ -1009,6 +1351,10 @@
 		MBC_FLUSH(&of->f_notify.nc_buffer);
 		if (of->f_odir != NULL)
 			smb_odir_release(of->f_odir);
+		if (of->f_lease != NULL) {
+			smb2_lease_rele(of->f_lease);
+			of->f_lease = NULL;
+		}
 		/* FALLTHROUGH */
 	case SMB_FTYPE_PRINTER:
 		/*
@@ -1022,11 +1368,32 @@
 		break;
 	}
 
+	smb_ofile_free(of);
+}
+
+void
+smb_ofile_free(smb_ofile_t *of)
+{
+	smb_tree_t	*tree = of->f_tree;
+
+	ASSERT(of->f_state == SMB_OFILE_STATE_ALLOC);
+
+	/* Make sure it's not in the persistid hash. */
+	ASSERT(of->f_persistid == 0);
+
+	if (tree != NULL) {
+		if (of->f_fid != 0)
+			smb_idpool_free(&tree->t_fid_pool, of->f_fid);
+		smb_tree_release(of->f_tree);
+		smb_user_release(of->f_user);
+	}
+
+	if (of->f_cr != NULL)
+		crfree(of->f_cr);
+
 	of->f_magic = (uint32_t)~SMB_OFILE_MAGIC;
 	list_destroy(&of->f_notify.nc_waiters);
 	mutex_destroy(&of->f_mutex);
-	smb_user_release(of->f_user);
-	crfree(of->f_cr);
 	kmem_cache_free(smb_cache_ofile, of);
 }
 
@@ -1082,19 +1449,21 @@
 smb_ofile_open_check(smb_ofile_t *of, uint32_t desired_access,
     uint32_t share_access)
 {
+	uint32_t ret;
+
 	ASSERT(of->f_magic == SMB_OFILE_MAGIC);
 
 	mutex_enter(&of->f_mutex);
 
-	if (of->f_state != SMB_OFILE_STATE_OPEN) {
-		mutex_exit(&of->f_mutex);
-		return (NT_STATUS_INVALID_HANDLE);
+	if (!smb_ofile_is_open_locked(of)) {
+		ret = NT_STATUS_INVALID_HANDLE;
+		goto out;
 	}
 
 	/* if it's just meta data */
 	if ((of->f_granted_access & FILE_DATA_ALL) == 0) {
-		mutex_exit(&of->f_mutex);
-		return (NT_STATUS_SUCCESS);
+		ret = NT_STATUS_SUCCESS;
+		goto out;
 	}
 
 	/*
@@ -1102,42 +1471,44 @@
 	 * open granted (desired) access
 	 */
 	if (SMB_DENY_DELETE(share_access) && (of->f_granted_access & DELETE)) {
-		mutex_exit(&of->f_mutex);
-		return (NT_STATUS_SHARING_VIOLATION);
+		ret = NT_STATUS_SHARING_VIOLATION;
+		goto out;
 	}
 
 	if (SMB_DENY_READ(share_access) &&
 	    (of->f_granted_access & (FILE_READ_DATA | FILE_EXECUTE))) {
-		mutex_exit(&of->f_mutex);
-		return (NT_STATUS_SHARING_VIOLATION);
+		ret = NT_STATUS_SHARING_VIOLATION;
+		goto out;
 	}
 
 	if (SMB_DENY_WRITE(share_access) &&
 	    (of->f_granted_access & (FILE_WRITE_DATA | FILE_APPEND_DATA))) {
-		mutex_exit(&of->f_mutex);
-		return (NT_STATUS_SHARING_VIOLATION);
+		ret = NT_STATUS_SHARING_VIOLATION;
+		goto out;
 	}
 
 	/* check requested desired access against the open share access */
 	if (SMB_DENY_DELETE(of->f_share_access) && (desired_access & DELETE)) {
-		mutex_exit(&of->f_mutex);
-		return (NT_STATUS_SHARING_VIOLATION);
+		ret = NT_STATUS_SHARING_VIOLATION;
+		goto out;
 	}
 
 	if (SMB_DENY_READ(of->f_share_access) &&
 	    (desired_access & (FILE_READ_DATA | FILE_EXECUTE))) {
-		mutex_exit(&of->f_mutex);
-		return (NT_STATUS_SHARING_VIOLATION);
+		ret = NT_STATUS_SHARING_VIOLATION;
+		goto out;
 	}
 
 	if (SMB_DENY_WRITE(of->f_share_access) &&
 	    (desired_access & (FILE_WRITE_DATA | FILE_APPEND_DATA))) {
-		mutex_exit(&of->f_mutex);
-		return (NT_STATUS_SHARING_VIOLATION);
+		ret = NT_STATUS_SHARING_VIOLATION;
+		goto out;
 	}
 
+	ret = NT_STATUS_SUCCESS;
+out:
 	mutex_exit(&of->f_mutex);
-	return (NT_STATUS_SUCCESS);
+	return (ret);
 }
 
 /*
@@ -1152,27 +1523,31 @@
 uint32_t
 smb_ofile_rename_check(smb_ofile_t *of)
 {
+	uint32_t ret;
+
 	ASSERT(of->f_magic == SMB_OFILE_MAGIC);
 
 	mutex_enter(&of->f_mutex);
 
-	if (of->f_state != SMB_OFILE_STATE_OPEN) {
-		mutex_exit(&of->f_mutex);
-		return (NT_STATUS_INVALID_HANDLE);
+	if (!smb_ofile_is_open_locked(of)) {
+		ret = NT_STATUS_INVALID_HANDLE;
+		goto out;
 	}
 
 	if ((of->f_granted_access & FILE_DATA_ALL) == 0) {
-		mutex_exit(&of->f_mutex);
-		return (NT_STATUS_SUCCESS);
+		ret = NT_STATUS_SUCCESS;
+		goto out;
 	}
 
 	if ((of->f_share_access & FILE_SHARE_DELETE) == 0) {
-		mutex_exit(&of->f_mutex);
-		return (NT_STATUS_SHARING_VIOLATION);
+		ret = NT_STATUS_SHARING_VIOLATION;
+		goto out;
 	}
 
+	ret = NT_STATUS_SUCCESS;
+out:
 	mutex_exit(&of->f_mutex);
-	return (NT_STATUS_SUCCESS);
+	return (ret);
 }
 
 /*
@@ -1196,24 +1571,28 @@
 uint32_t
 smb_ofile_delete_check(smb_ofile_t *of)
 {
+	uint32_t ret;
+
 	ASSERT(of->f_magic == SMB_OFILE_MAGIC);
 
 	mutex_enter(&of->f_mutex);
 
-	if (of->f_state != SMB_OFILE_STATE_OPEN) {
-		mutex_exit(&of->f_mutex);
-		return (NT_STATUS_INVALID_HANDLE);
+	if (!smb_ofile_is_open_locked(of)) {
+		ret = NT_STATUS_INVALID_HANDLE;
+		goto out;
 	}
 
 	if (of->f_granted_access &
 	    (FILE_READ_DATA | FILE_WRITE_DATA |
 	    FILE_APPEND_DATA | FILE_EXECUTE | DELETE)) {
-		mutex_exit(&of->f_mutex);
-		return (NT_STATUS_SHARING_VIOLATION);
+		ret = NT_STATUS_SHARING_VIOLATION;
+		goto out;
 	}
 
+	ret = NT_STATUS_SUCCESS;
+out:
 	mutex_exit(&of->f_mutex);
-	return (NT_STATUS_SUCCESS);
+	return (ret);
 }
 
 cred_t *
@@ -1236,8 +1615,21 @@
  * the fid on which the DeleteOnClose was requested.
  */
 void
-smb_ofile_set_delete_on_close(smb_ofile_t *of)
+smb_ofile_set_delete_on_close(smb_request_t *sr, smb_ofile_t *of)
 {
+	uint32_t	status;
+
+	/*
+	 * Break any oplock handle caching.
+	 */
+	status = smb_oplock_break_SETINFO(of->f_node, of,
+	    FileDispositionInformation);
+	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+		if (sr->session->dialect >= SMB_VERS_2_BASE)
+			(void) smb2sr_go_async(sr);
+		(void) smb_oplock_wait_break(of->f_node, 0);
+	}
+
 	mutex_enter(&of->f_mutex);
 	of->f_flags |= SMB_OFLAGS_SET_DELETE_ON_CLOSE;
 	mutex_exit(&of->f_mutex);
--- a/usr/src/uts/common/fs/smbsrv/smb_open_andx.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_open_andx.c	Wed Jun 12 22:46:54 2019 +0200
@@ -243,7 +243,7 @@
 {
 	struct open_param *op = &sr->arg.open;
 	smb_ofile_t *of;
-	smb_attr_t attr;
+	uint32_t mtime_sec;
 	uint32_t status;
 	uint16_t file_attr;
 	int rc;
@@ -265,18 +265,25 @@
 	} else {
 		op->op_oplock_level = SMB_OPLOCK_NONE;
 	}
-	op->op_oplock_levelII = B_FALSE;
 
 	if (smb_open_dsize_check && op->dsize > UINT_MAX) {
 		smbsr_error(sr, 0, ERRDOS, ERRbadaccess);
 		return (SDRC_ERROR);
 	}
 
+	/*
+	 * The real open call.   Note: this gets attributes into
+	 * op->fqi.fq_fattr (SMB_AT_ALL).  We need those below.
+	 */
 	status = smb_common_open(sr);
 	if (status != NT_STATUS_SUCCESS) {
 		smbsr_status(sr, status, 0, 0);
 		return (SDRC_ERROR);
 	}
+	if (op->op_oplock_level != SMB_OPLOCK_NONE) {
+		/* Oplock req. in op->op_oplock_level etc. */
+		smb1_oplock_acquire(sr, B_FALSE);
+	}
 
 	/*
 	 * NB: after the above smb_common_open() success,
@@ -291,19 +298,14 @@
 	}
 
 	file_attr = op->dattr & FILE_ATTRIBUTE_MASK;
-	bzero(&attr, sizeof (attr));
-	attr.sa_mask = SMB_AT_MTIME;
-	rc = smb_node_getattr(sr, of->f_node, of->f_cr, of, &attr);
-	if (rc != 0) {
-		smbsr_errno(sr, rc);
-		goto errout;
-	}
+	mtime_sec = smb_time_gmt_to_local(sr,
+	    op->fqi.fq_fattr.sa_vattr.va_mtime.tv_sec);
 
 	rc = smbsr_encode_result(sr, 7, 0, "bwwllww",
 	    7,
 	    sr->smb_fid,
 	    file_attr,
-	    smb_time_gmt_to_local(sr, attr.sa_vattr.va_mtime.tv_sec),
+	    mtime_sec,
 	    (uint32_t)op->dsize,
 	    op->omode,
 	    (uint16_t)0);	/* bcc */
@@ -311,7 +313,6 @@
 	if (rc == 0)
 		return (SDRC_SUCCESS);
 
-errout:
 	smb_ofile_close(of, 0);
 	return (SDRC_ERROR);
 }
@@ -349,10 +350,10 @@
 		 * The openx_flags use some "extended" flags that
 		 * happen to match some of the NtCreateX flags.
 		 */
-		if (openx_flags & NT_CREATE_FLAG_REQUEST_OPLOCK)
+		if (openx_flags & NT_CREATE_FLAG_REQUEST_OPBATCH)
+			op->op_oplock_level = SMB_OPLOCK_BATCH;
+		else if (openx_flags & NT_CREATE_FLAG_REQUEST_OPLOCK)
 			op->op_oplock_level = SMB_OPLOCK_EXCLUSIVE;
-		else if (openx_flags & NT_CREATE_FLAG_REQUEST_OPBATCH)
-			op->op_oplock_level = SMB_OPLOCK_BATCH;
 		else
 			op->op_oplock_level = SMB_OPLOCK_NONE;
 		if (openx_flags & NT_CREATE_FLAG_EXTENDED_RESPONSE)
@@ -401,8 +402,6 @@
 	if (op->omode & SMB_DA_WRITE_THROUGH)
 		op->create_options |= FILE_WRITE_THROUGH;
 
-	op->op_oplock_levelII = B_FALSE;
-
 	if (smb_open_dsize_check && op->dsize > UINT_MAX) {
 		smbsr_error(sr, 0, ERRDOS, ERRbadaccess);
 		return (SDRC_ERROR);
@@ -413,6 +412,10 @@
 		smbsr_status(sr, status, 0, 0);
 		return (SDRC_ERROR);
 	}
+	if (op->op_oplock_level != SMB_OPLOCK_NONE) {
+		/* Oplock req. in op->op_oplock_level etc. */
+		smb1_oplock_acquire(sr, B_FALSE);
+	}
 
 	/*
 	 * NB: after the above smb_common_open() success,
@@ -563,13 +566,16 @@
 	} else {
 		op->op_oplock_level = SMB_OPLOCK_NONE;
 	}
-	op->op_oplock_levelII = B_FALSE;
 
 	status = smb_common_open(sr);
 	if (status != NT_STATUS_SUCCESS) {
 		smbsr_status(sr, status, 0, 0);
 		return (SDRC_ERROR);
 	}
+	if (op->op_oplock_level != SMB_OPLOCK_NONE) {
+		/* Oplock req. in op->op_oplock_level etc. */
+		smb1_oplock_acquire(sr, B_FALSE);
+	}
 
 	if (op->op_oplock_level != SMB_OPLOCK_NONE)
 		op->action_taken |= SMB_OACT_OPLOCK;
--- a/usr/src/uts/common/fs/smbsrv/smb_opipe.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_opipe.c	Wed Jun 12 22:46:54 2019 +0200
@@ -111,9 +111,12 @@
 {
 	ksocket_t so;
 
-	if (sr->session->s_state == SMB_SESSION_STATE_DISCONNECTED &&
-	    (so = sr->cancel_arg2) != NULL) {
-		(void) ksocket_shutdown(so, SHUT_RDWR, sr->user_cr);
+	switch (sr->session->s_state) {
+	case SMB_SESSION_STATE_DISCONNECTED:
+	case SMB_SESSION_STATE_TERMINATED:
+		if ((so = sr->cancel_arg2) != NULL)
+			(void) ksocket_shutdown(so, SHUT_RDWR, sr->user_cr);
+		break;
 	}
 }
 
@@ -265,11 +268,10 @@
  * Returns 0 on success, Otherwise an NT status code.
  */
 int
-smb_opipe_open(smb_request_t *sr, uint32_t uniqid)
+smb_opipe_open(smb_request_t *sr, smb_ofile_t *ofile)
 {
 	smb_arg_open_t	*op = &sr->sr_open;
 	smb_attr_t *ap = &op->fqi.fq_fattr;
-	smb_ofile_t *ofile;
 	smb_opipe_t *opipe;
 	smb_error_t err;
 
@@ -289,18 +291,23 @@
 	}
 
 	/*
-	 * Note: If smb_ofile_open succeeds, the new ofile is
-	 * in the FID lists can can be used by I/O requests.
+	 * We might have blocked in smb_opipe_connect long enough so
+	 * a tree disconnect might have happened.  In that case, we
+	 * would be adding an ofile to a tree that's disconnecting,
+	 * which would interfere with tear-down.
 	 */
-	op->create_options = 0;
+	if (!smb_tree_is_connected(sr->tid_tree)) {
+		smb_opipe_dealloc(opipe);
+		return (NT_STATUS_NETWORK_NAME_DELETED);
+	}
+
+	/*
+	 * Note: The new opipe is given to smb_ofile_open
+	 * via op->pipe
+	 */
 	op->pipe = opipe;
-	ofile = smb_ofile_open(sr, NULL, op,
-	    SMB_FTYPE_MESG_PIPE, uniqid, &err);
+	smb_ofile_open(sr, op, ofile);
 	op->pipe = NULL;
-	if (ofile == NULL) {
-		smb_opipe_dealloc(opipe);
-		return (err.status);
-	}
 
 	/* An "up" pointer, for debug. */
 	opipe->p_ofile = ofile;
--- a/usr/src/uts/common/fs/smbsrv/smb_oplock.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_oplock.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,778 +20,139 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
- * smb_oplock_wait / smb_oplock_broadcast
- * When an oplock is being acquired, we must ensure that the acquisition
- * response is submitted to the network stack before any other operation
- * is permitted on the oplock.
- * In smb_oplock_acquire, oplock.ol_xthread is set to point to the worker
- * thread processing the command that is granting the oplock.
- * Other threads accessing the oplock will be suspended in smb_oplock_wait().
- * They will be awakened when the worker thread referenced in 'ol_xthread'
- * calls smb_oplock_broadcast().
- *
- * The purpose of this mechanism is to prevent another thread from
- * triggering an oplock break before the response conveying the grant
- * has been sent.
+ * smb1 oplock support
  */
 
 #include <smbsrv/smb_kproto.h>
-#include <sys/nbmlock.h>
 
-#define	SMB_OPLOCK_IS_EXCLUSIVE(level)		\
-	(((level) == SMB_OPLOCK_EXCLUSIVE) ||	\
-	((level) == SMB_OPLOCK_BATCH))
-
-static int smb_oplock_install_fem(smb_node_t *);
-static void smb_oplock_uninstall_fem(smb_node_t *);
-
-static void smb_oplock_wait(smb_node_t *);
-static void smb_oplock_wait_ack(smb_node_t *, uint32_t);
-static void smb_oplock_timedout(smb_node_t *);
-
-static smb_oplock_grant_t *smb_oplock_set_grant(smb_ofile_t *, uint8_t);
-void smb_oplock_clear_grant(smb_oplock_grant_t *);
-static int smb_oplock_insert_grant(smb_node_t *, smb_oplock_grant_t *);
-static void smb_oplock_remove_grant(smb_node_t *, smb_oplock_grant_t *);
-static smb_oplock_grant_t *smb_oplock_exclusive_grant(list_t *);
-static smb_oplock_grant_t *smb_oplock_get_grant(smb_oplock_t *, smb_ofile_t *);
-
-static void smb_oplock_sched_async_break(smb_oplock_grant_t *, uint8_t);
-static void smb_oplock_exec_async_break(void *);
-static void smb_oplock_break_levelII_locked(smb_node_t *);
-
-/*
- * smb_oplock_install_fem
- * Install fem monitor for cross protocol oplock breaking.
- */
-static int
-smb_oplock_install_fem(smb_node_t *node)
-{
-	ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
-
-	if (node->n_oplock.ol_fem == B_FALSE) {
-		if (smb_fem_oplock_install(node) != 0) {
-			cmn_err(CE_NOTE, "No oplock granted: "
-			    "failed to install fem monitor %s",
-			    node->vp->v_path);
-			return (-1);
-		}
-		node->n_oplock.ol_fem = B_TRUE;
-	}
-	return (0);
-}
+#define	BATCH_OR_EXCL	(OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE)
 
 /*
- * smb_oplock_uninstall_fem
- * Uninstall fem monitor for cross protocol oplock breaking.
- */
-static void
-smb_oplock_uninstall_fem(smb_node_t *node)
-{
-	ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
-
-	if (node->n_oplock.ol_fem) {
-		smb_fem_oplock_uninstall(node);
-		node->n_oplock.ol_fem = B_FALSE;
-	}
-}
-
-/*
- * This provides a way to fully disable oplocks, i.e. for testing.
- * You _really_ do _not_ want to turn this off, because if you do,
- * the clients send you very small read requests, and a _lot_ more
- * of them.  The skc_oplock_enable parameter can be used to enable
- * or disable exclusive oplocks.  Disabling that can be helpful
- * when there are clients not responding to oplock breaks.
- */
-int smb_oplocks_enabled = 1;
-
-/*
- * smb_oplock_acquire
- *
- * Attempt to acquire an oplock. Clients will request EXCLUSIVE or BATCH,
- * but might only be granted LEVEL_II or NONE.
- *
- * If oplocks are not supported on the tree, or node, grant NONE.
- * If nobody else has the file open, grant the requested level.
- * If any of the following are true, grant NONE:
- * - there is an exclusive oplock on the node
- * - op->op_oplock_levelII is B_FALSE (LEVEL_II not supported by open cmd.
- * - LEVEL_II oplocks are not supported for the session
- * - a BATCH oplock is requested on a named stream
- * - there are any range locks on the node (SMB writers)
- * Otherwise, grant LEVEL_II.
- *
- * ol->ol_xthread is set to the current thread to lock the oplock against
- * other operations until the acquire response is on the wire. When the
- * acquire response is on the wire, smb_oplock_broadcast() is called to
- * reset ol->ol_xthread and wake any waiting threads.
+ * Client has an open handle and requests an oplock.
+ * Convert SMB1 oplock request info in to internal form,
+ * call common oplock code, convert result to SMB1.
  */
 void
-smb_oplock_acquire(smb_request_t *sr, smb_node_t *node, smb_ofile_t *ofile)
+smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok)
 {
-	smb_oplock_t		*ol;
-	smb_oplock_grant_t	*og;
-	list_t			*grants;
-	smb_arg_open_t		*op;
-	smb_tree_t		*tree;
-	smb_session_t		*session;
+	smb_arg_open_t *op = &sr->arg.open;
+	smb_ofile_t *ofile = sr->fid_ofile;
+	uint32_t status;
 
-	SMB_NODE_VALID(node);
-	SMB_OFILE_VALID(ofile);
+	/* Only disk trees get oplocks. */
+	if ((sr->tid_tree->t_res_type & STYPE_MASK) != STYPE_DISKTREE) {
+		op->op_oplock_level = SMB_OPLOCK_NONE;
+		return;
+	}
+
+	if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_OPLOCKS)) {
+		op->op_oplock_level = SMB_OPLOCK_NONE;
+		return;
+	}
 
-	ASSERT(node == SMB_OFILE_GET_NODE(ofile));
-	ASSERT(RW_LOCK_HELD(&node->n_lock));
+	if (!smb_session_levelII_oplocks(sr->session))
+		level2ok = B_FALSE;
+
+	/* Common code checks file type. */
 
-	op = &sr->sr_open;
-	tree = SMB_OFILE_GET_TREE(ofile);
-	session = SMB_OFILE_GET_SESSION(ofile);
-
-	if (smb_oplocks_enabled == 0 ||
-	    (op->op_oplock_level == SMB_OPLOCK_NONE) ||
-	    ((op->op_oplock_level == SMB_OPLOCK_BATCH) &&
-	    SMB_IS_STREAM(node))) {
+	/*
+	 * SMB1: Convert to internal form.
+	 */
+	switch (op->op_oplock_level) {
+	case SMB_OPLOCK_BATCH:
+		op->op_oplock_state = OPLOCK_LEVEL_BATCH;
+		break;
+	case SMB_OPLOCK_EXCLUSIVE:
+		op->op_oplock_state = OPLOCK_LEVEL_ONE;
+		break;
+	case SMB_OPLOCK_LEVEL_II:
+		op->op_oplock_state = OPLOCK_LEVEL_TWO;
+		break;
+	case SMB_OPLOCK_NONE:
+	default:
 		op->op_oplock_level = SMB_OPLOCK_NONE;
 		return;
 	}
 
-	ol = &node->n_oplock;
-	grants = &ol->ol_grants;
-
-	mutex_enter(&ol->ol_mutex);
-	smb_oplock_wait(node);
-
 	/*
-	 * Even if there are no other opens, we might want to
-	 * grant only a Level II (shared) oplock so we avoid
-	 * ever granting exclusive oplocks.
-	 *
-	 * Borrowing the SMB_TREE_OPLOCKS flag to enable/disable
-	 * exclusive oplocks (for now).  See skc_oplock_enable,
-	 * which can now be taken as "exclusive oplock enable".
-	 * Should rename this parameter, and/or implement a new
-	 * multi-valued parameter for oplock enables.
+	 * Tree options may force shared oplocks
 	 */
-	if ((node->n_open_count > 1) ||
-	    (node->n_opening_count > 1) ||
-	    !smb_tree_has_feature(tree, SMB_TREE_OPLOCKS) ||
-	    smb_vop_other_opens(node->vp, ofile->f_mode)) {
-		/*
-		 * There are other opens.
-		 */
-		if ((!op->op_oplock_levelII) ||
-		    (!smb_session_levelII_oplocks(session)) ||
-		    (smb_oplock_exclusive_grant(grants) != NULL) ||
-		    (smb_lock_range_access(sr, node, 0, ~0, B_FALSE))) {
-			/*
-			 * LevelII (shared) oplock not allowed,
-			 * so reply with "none".
-			 */
-			op->op_oplock_level = SMB_OPLOCK_NONE;
-			mutex_exit(&ol->ol_mutex);
-			return;
-		}
-
-		op->op_oplock_level = SMB_OPLOCK_LEVEL_II;
-	}
-
-	og = smb_oplock_set_grant(ofile, op->op_oplock_level);
-	if (smb_oplock_insert_grant(node, og) != 0) {
-		smb_oplock_clear_grant(og);
-		op->op_oplock_level = SMB_OPLOCK_NONE;
-		mutex_exit(&ol->ol_mutex);
-		return;
+	if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) {
+		op->op_oplock_state = OPLOCK_LEVEL_TWO;
 	}
 
-	ol->ol_xthread = curthread;
-	mutex_exit(&ol->ol_mutex);
-}
-
-/*
- * smb_oplock_break
- *
- * Break granted oplocks according to the following rules:
- *
- * If there's an exclusive oplock granted on the node
- *  - if the BREAK_BATCH flags is specified and the oplock is not
- *    a batch oplock, no break is required.
- *  - if the session doesn't support LEVEL II oplocks, and 'brk' is
- *    BREAK_TO_LEVEL_II, do a BREAK_TO_NONE.
- *  - if the oplock is already breaking update the break level (if
- *    the requested break is to a lesser level), otherwise send an
- *    oplock break.
- *    Wait for acknowledgement of the break (unless NOWAIT flag is set)
- *
- * Otherwise:
- * If there are level II oplocks granted on the node, and the flags
- * indicate that they should be broken (BREAK_TO_NONE specified,
- * BREAK_EXCLUSIVE, BREAK_BATCH not specified) queue the levelII
- * break request for asynchronous processing.
- *
- * Returns:
- *       0 - oplock broken (or no break required)
- *  EAGAIN - oplock break request sent and would block
- *           awaiting the reponse but NOWAIT was specified
- *
- * NB: sr == NULL when called by FEM framework.
- */
-int
-smb_oplock_break(smb_request_t *sr, smb_node_t *node, uint32_t flags)
-{
-	smb_oplock_t		*ol;
-	smb_oplock_grant_t	*og;
-	smb_ofile_t		*ofile;
-	list_t			*grants;
-	uint32_t		timeout;
-	uint8_t			brk;
-
-	SMB_NODE_VALID(node);
-	ol = &node->n_oplock;
-	grants = &ol->ol_grants;
-
-	mutex_enter(&ol->ol_mutex);
-	smb_oplock_wait(node);
-
-	og = list_head(grants);
-	if (og == NULL) {
-		mutex_exit(&ol->ol_mutex);
-		return (0);
+	/*
+	 * Try exclusive first, if requested
+	 */
+	if ((op->op_oplock_state & BATCH_OR_EXCL) != 0) {
+		status = smb_oplock_request(sr, ofile,
+		    &op->op_oplock_state);
+	} else {
+		status = NT_STATUS_OPLOCK_NOT_GRANTED;
 	}
 
-	SMB_OPLOCK_GRANT_VALID(og);
-	ofile = og->og_ofile;	/* containing struct */
-
-	/* break levelII oplocks */
-	if (og->og_level == SMB_OPLOCK_LEVEL_II) {
-		mutex_exit(&ol->ol_mutex);
-
-		if ((flags & SMB_OPLOCK_BREAK_TO_NONE) &&
-		    !(flags & SMB_OPLOCK_BREAK_EXCLUSIVE) &&
-		    !(flags & SMB_OPLOCK_BREAK_BATCH))  {
-			smb_oplock_break_levelII(node);
-		}
-		return (0);
-	}
-
-	/* break exclusive oplock */
-	if ((flags & SMB_OPLOCK_BREAK_BATCH) &&
-	    (og->og_level != SMB_OPLOCK_BATCH)) {
-		mutex_exit(&ol->ol_mutex);
-		return (0);
-	}
-
-	if ((flags & SMB_OPLOCK_BREAK_TO_LEVEL_II) &&
-	    smb_session_levelII_oplocks(ofile->f_session)) {
-		brk = SMB_OPLOCK_BREAK_TO_LEVEL_II;
-	} else {
-		brk = SMB_OPLOCK_BREAK_TO_NONE;
+	/*
+	 * If exclusive failed (or tree forced shared oplocks)
+	 * and if the caller supports Level II, try shared.
+	 */
+	if (status == NT_STATUS_OPLOCK_NOT_GRANTED && level2ok) {
+		op->op_oplock_state = OPLOCK_LEVEL_TWO;
+		status = smb_oplock_request(sr, ofile,
+		    &op->op_oplock_state);
 	}
 
-	switch (ol->ol_break) {
-	case SMB_OPLOCK_NO_BREAK:
-		ol->ol_break = brk;
-		smb_oplock_sched_async_break(og, brk);
-		break;
-	case SMB_OPLOCK_BREAK_TO_LEVEL_II:
-		if (brk == SMB_OPLOCK_BREAK_TO_NONE)
-			ol->ol_break = SMB_OPLOCK_BREAK_TO_NONE;
-		break;
-	case SMB_OPLOCK_BREAK_TO_NONE:
-	default:
-		break;
-	}
-
-	if (flags & SMB_OPLOCK_BREAK_NOWAIT) {
-		mutex_exit(&ol->ol_mutex);
-		return (EAGAIN);
-	}
-
-	if (sr && (sr->uid_user == ofile->f_user)) {
-		timeout = smb_oplock_min_timeout;
-	} else {
-		timeout = smb_oplock_timeout;
-	}
-
-	mutex_exit(&ol->ol_mutex);
-	smb_oplock_wait_ack(node, timeout);
-	return (0);
-}
-
-/*
- * smb_oplock_break_levelII
- *
- * This is called after a file is modified in some way.  If there are
- * LevelII (shared) oplocks, break those to none.  If there is an
- * exclusive oplock, there can be no LevelII oplocks, so do nothing.
- *
- * LevelII (shared) oplock breaks are processed asynchronously.
- * Unlike exclusive oplock breaks, the thread initiating the break
- * is NOT blocked while the request is processed.
- *
- * There may be a thread with exclusive rights to oplock state for
- * this node (via ol_xthread in smb_oplock_wait) and if so, we must
- * avoid breaking oplocks until that's out of the way.  However, we
- * really don't want to block here, so when ol_xthread is set, we'll
- * just mark that a "break level II to none" is pending, and let the
- * exclusive thread do this work when it's done being exclusive.
- */
-void
-smb_oplock_break_levelII(smb_node_t *node)
-{
-	smb_oplock_t		*ol;
-
-	ol = &node->n_oplock;
-	mutex_enter(&ol->ol_mutex);
-
-	/* Instead of: smb_oplock_wait() ... */
-	if (ol->ol_xthread != NULL) {
-		/* Defer the call to smb_oplock_broadcast(). */
-		ol->ol_brk_pending = SMB_OPLOCK_BREAK_TO_NONE;
-	} else {
-		/* Equivalent of smb_oplock_wait() done. */
-		smb_oplock_break_levelII_locked(node);
+	/*
+	 * Either of the above may have returned the
+	 * status code that says we should wait.
+	 */
+	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+		(void) smb_oplock_wait_break(ofile->f_node, 0);
+		status = 0;
 	}
 
-	mutex_exit(&ol->ol_mutex);
-}
-
-/*
- * smb_oplock_break_levelII_locked
- * Internal helper for smb_oplock_break_levelII()
- *
- * Called with the oplock mutex already held, and _after_
- * (the equivalent of) an smb_oplock_wait().
- */
-static void
-smb_oplock_break_levelII_locked(smb_node_t *node)
-{
-	smb_oplock_t		*ol;
-	smb_oplock_grant_t	*og;
-	list_t			*grants;
-
-	ol = &node->n_oplock;
-	grants = &ol->ol_grants;
-
-	ASSERT(MUTEX_HELD(&ol->ol_mutex));
-	ASSERT(ol->ol_xthread == NULL);
-
-	while ((og = list_head(grants)) != NULL) {
-		SMB_OPLOCK_GRANT_VALID(og);
-
-		/*
-		 * If there's an exclusive oplock, there are
-		 * no LevelII oplocks, so do nothing.
-		 */
-		if (SMB_OPLOCK_IS_EXCLUSIVE(og->og_level))
-			break;
-
-		smb_oplock_sched_async_break(og, SMB_OPLOCK_BREAK_TO_NONE);
-		smb_oplock_remove_grant(node, og);
-		smb_oplock_clear_grant(og);
-	}
-}
-
-/*
- * Schedule a call to smb_session_oplock_break
- * using an smb_request on the owning session.
- */
-static void
-smb_oplock_sched_async_break(smb_oplock_grant_t *og, uint8_t brk)
-{
-	smb_request_t		*sr;
-	smb_ofile_t		*ofile;
-
 	/*
-	 * Make sure we can get a hold on the ofile.  If we can't,
-	 * the file is closing, and there's no point scheduling an
-	 * oplock break on it because the close will release the
-	 * oplock very soon. Same for the tree & user holds.
-	 *
-	 * These holds account for the pointers we copy into the
-	 * smb_request fields: fid_ofile, tid_tree, uid_user.
-	 * These holds are released via smb_request_free after
-	 * the oplock break has been sent.
+	 * Keep track of what we got (in ofile->f_oplock.og_state)
+	 * so we'll know what we had when sending a break later.
+	 * The og_dialect here is the oplock dialect, which may be
+	 * different than SMB dialect.  Pre-NT clients did not
+	 * support "Level II" oplocks.  If we're talking to a
+	 * client that didn't set the CAP_LEVEL_II_OPLOCKS in
+	 * its capabilities, let og_dialect = LANMAN2_1.
 	 */
-	ofile = og->og_ofile;	/* containing struct */
-	if (!smb_ofile_hold(ofile))
+	ofile->f_oplock.og_dialect = (level2ok) ?
+	    NT_LM_0_12 : LANMAN2_1;
+	switch (status) {
+	case NT_STATUS_SUCCESS:
+		ofile->f_oplock.og_state = op->op_oplock_state;
+		break;
+	case NT_STATUS_OPLOCK_NOT_GRANTED:
+		ofile->f_oplock.og_state = 0;
+		op->op_oplock_level = SMB_OPLOCK_NONE;
 		return;
-
-	if ((sr = smb_request_alloc(ofile->f_session, 0)) == NULL) {
-		smb_ofile_release(ofile);
+	default:
+		/* Caller did not check args sufficiently? */
+		cmn_err(CE_NOTE, "clnt %s oplock req. err 0x%x",
+		    sr->session->ip_addr_str, status);
+		ofile->f_oplock.og_state = 0;
+		op->op_oplock_level = SMB_OPLOCK_NONE;
 		return;
 	}
 
-	smb_tree_hold_internal(ofile->f_tree);
-	smb_user_hold_internal(ofile->f_user);
-
-	sr->sr_state = SMB_REQ_STATE_SUBMITTED;
-	sr->user_cr = zone_kcred();
-	sr->fid_ofile = ofile;
-	sr->tid_tree = ofile->f_tree;
-	sr->uid_user = ofile->f_user;
-
-	sr->arg.olbrk = *og; /* struct copy */
-	sr->arg.olbrk.og_breaking = brk;
-
-	(void) taskq_dispatch(
-	    sr->sr_server->sv_worker_pool,
-	    smb_oplock_exec_async_break, sr, TQ_SLEEP);
-}
-
-/*
- * smb_oplock_exec_async_break
- *
- * Called via the taskq to handle an asynchronous oplock break.
- * We have a hold on the ofile, which keeps the FID here valid.
- */
-static void
-smb_oplock_exec_async_break(void *arg)
-{
-	smb_request_t *sr = arg;
-	smb_oplock_grant_t *og = &sr->arg.olbrk;
-
-	SMB_REQ_VALID(sr);
-	SMB_OPLOCK_GRANT_VALID(og);
-
-	mutex_enter(&sr->sr_mutex);
-	sr->sr_worker = curthread;
-	sr->sr_time_active = gethrtime();
-
-	switch (sr->sr_state) {
-	case SMB_REQ_STATE_SUBMITTED:
-		sr->sr_state = SMB_REQ_STATE_ACTIVE;
-		mutex_exit(&sr->sr_mutex);
-
-		/*
-		 * This is where we actually do the deferred work
-		 * requested by smb_oplock_sched_async_break().
-		 */
-		smb_session_oplock_break(sr, og->og_breaking);
-
-		mutex_enter(&sr->sr_mutex);
-		/* FALLTHROUGH */
-
-	default: /* typically cancelled */
-		sr->sr_state = SMB_REQ_STATE_COMPLETED;
-		mutex_exit(&sr->sr_mutex);
-	}
-
-	smb_request_free(sr);
-}
-
-/*
- * smb_oplock_wait_ack
- *
- * Timed wait for an oplock break acknowledgement (or oplock release).
- */
-static void
-smb_oplock_wait_ack(smb_node_t *node, uint32_t timeout)
-{
-	smb_oplock_t	*ol;
-	clock_t		time;
-
-	ol = &node->n_oplock;
-	mutex_enter(&ol->ol_mutex);
-	time = MSEC_TO_TICK(timeout) + ddi_get_lbolt();
-
-	while (ol->ol_break != SMB_OPLOCK_NO_BREAK) {
-		if (cv_timedwait(&ol->ol_cv, &ol->ol_mutex, time) < 0) {
-			smb_oplock_timedout(node);
-			cv_broadcast(&ol->ol_cv);
-			break;
-		}
-	}
-	mutex_exit(&ol->ol_mutex);
-}
-
-/*
- * smb_oplock_timedout
- *
- * An oplock break has not been acknowledged within timeout
- * 'smb_oplock_timeout'.
- * Set oplock grant to the desired break level.
- */
-static void
-smb_oplock_timedout(smb_node_t *node)
-{
-	smb_oplock_t		*ol;
-	smb_oplock_grant_t	*og;
-	list_t			*grants;
-
-	ol = &node->n_oplock;
-	grants = &ol->ol_grants;
-
-	ASSERT(MUTEX_HELD(&ol->ol_mutex));
-
-	og = smb_oplock_exclusive_grant(grants);
-	if (og) {
-		switch (ol->ol_break) {
-		case SMB_OPLOCK_BREAK_TO_NONE:
-			og->og_level = SMB_OPLOCK_NONE;
-			smb_oplock_remove_grant(node, og);
-			smb_oplock_clear_grant(og);
-			break;
-		case SMB_OPLOCK_BREAK_TO_LEVEL_II:
-			og->og_level = SMB_OPLOCK_LEVEL_II;
-			break;
-		default:
-			SMB_PANIC();
-		}
-	}
-	ol->ol_break = SMB_OPLOCK_NO_BREAK;
-}
-
-/*
- * smb_oplock_release
- *
- * Release the oplock granted on ofile 'of'.
- * Wake any threads waiting for an oplock break acknowledgement for
- * this oplock.
- * This is called when the ofile is being closed.
- */
-void
-smb_oplock_release(smb_node_t *node, smb_ofile_t *of)
-{
-	smb_oplock_t		*ol;
-	smb_oplock_grant_t	*og;
-
-	ol = &node->n_oplock;
-	mutex_enter(&ol->ol_mutex);
-	smb_oplock_wait(node);
-
-	og = smb_oplock_get_grant(ol, of);
-	if (og) {
-		smb_oplock_remove_grant(node, og);
-		smb_oplock_clear_grant(og);
-
-		if (ol->ol_break != SMB_OPLOCK_NO_BREAK) {
-			ol->ol_break = SMB_OPLOCK_NO_BREAK;
-			cv_broadcast(&ol->ol_cv);
-		}
-	}
-
-	mutex_exit(&ol->ol_mutex);
-}
-
-/*
- * smb_oplock_ack
- *
- * Process oplock acknowledgement received for ofile 'of'.
- * - oplock.ol_break is the break level that was requested.
- * - brk is the break level being acknowledged by the client.
- *
- * Update the oplock grant level to the lesser of ol_break and brk.
- * If the grant is now SMB_OPLOCK_NONE, remove the grant from the
- * oplock's grant list and delete it.
- * If the requested break level (ol_break) was NONE and the brk is
- * LEVEL_II, send another oplock break (NONE). Do not wait for an
- * acknowledgement.
- * Wake any threads waiting for the oplock break acknowledgement.
- */
-void
-smb_oplock_ack(smb_node_t *node, smb_ofile_t *of, uint8_t brk)
-{
-	smb_oplock_t		*ol;
-	smb_oplock_grant_t	*og;
-
-	ol = &node->n_oplock;
-	mutex_enter(&ol->ol_mutex);
-	smb_oplock_wait(node);
-
-	if ((ol->ol_break == SMB_OPLOCK_NO_BREAK) ||
-	    ((og = smb_oplock_get_grant(ol, of)) == NULL)) {
-		mutex_exit(&ol->ol_mutex);
-		return;
-	}
-
-	switch (brk) {
-	case SMB_OPLOCK_BREAK_TO_NONE:
-		og->og_level = SMB_OPLOCK_NONE;
-		break;
-	case SMB_OPLOCK_BREAK_TO_LEVEL_II:
-		if (ol->ol_break == SMB_OPLOCK_BREAK_TO_LEVEL_II) {
-			og->og_level = SMB_OPLOCK_LEVEL_II;
-		} else {
-			/* SMB_OPLOCK_BREAK_TO_NONE */
-			og->og_level = SMB_OPLOCK_NONE;
-			smb_oplock_sched_async_break(og,
-			    SMB_OPLOCK_BREAK_TO_NONE);
-		}
-		break;
-	default:
-		SMB_PANIC();
-	}
-
-	if (og->og_level == SMB_OPLOCK_NONE) {
-		smb_oplock_remove_grant(node, og);
-		smb_oplock_clear_grant(og);
-	}
-
-	ol->ol_break = SMB_OPLOCK_NO_BREAK;
-	cv_broadcast(&ol->ol_cv);
-
-	mutex_exit(&ol->ol_mutex);
-}
-
-/*
- * smb_oplock_broadcast
- *
- * Called when an open with oplock request completes.
- *
- * ol->ol_xthread identifies the thread that was performing an oplock
- * acquire. Other threads may be blocked awaiting completion of the
- * acquire.
- * If the calling thread is ol_xthread, wake any waiting threads.
- */
-void
-smb_oplock_broadcast(smb_node_t *node)
-{
-	smb_oplock_t	*ol;
-
-	SMB_NODE_VALID(node);
-	ol = &node->n_oplock;
-
-	mutex_enter(&ol->ol_mutex);
-	if ((ol->ol_xthread != NULL) && (ol->ol_xthread == curthread)) {
-		ol->ol_xthread = NULL;
-		if (ol->ol_brk_pending) {
-			ol->ol_brk_pending = 0;
-			smb_oplock_break_levelII_locked(node);
-		}
-		cv_broadcast(&ol->ol_cv);
-	}
-	mutex_exit(&ol->ol_mutex);
-}
-
-/*
- * smb_oplock_wait
- *
- * Wait for the completion of an oplock acquire.
- * If ol_xthread is not NULL and doesn't contain the pointer to the
- * context of the calling thread, the caller will sleep until the
- * ol_xthread is reset to NULL (via smb_oplock_broadcast()).
- */
-static void
-smb_oplock_wait(smb_node_t *node)
-{
-	smb_oplock_t	*ol;
-
-	ol = &node->n_oplock;
-	ASSERT(MUTEX_HELD(&ol->ol_mutex));
-
-	if ((ol->ol_xthread != NULL) && (ol->ol_xthread != curthread)) {
-		while (ol->ol_xthread != NULL)
-			cv_wait(&ol->ol_cv, &ol->ol_mutex);
+	/*
+	 * Have STATUS_SUCCESS
+	 * Convert internal oplock state to SMB1
+	 */
+	if (op->op_oplock_state & OPLOCK_LEVEL_BATCH) {
+		op->op_oplock_level = SMB_OPLOCK_BATCH;
+	} else if (op->op_oplock_state & OPLOCK_LEVEL_ONE) {
+		op->op_oplock_level = SMB_OPLOCK_EXCLUSIVE;
+	} else if (op->op_oplock_state & OPLOCK_LEVEL_TWO) {
+		op->op_oplock_level = SMB_OPLOCK_LEVEL_II;
+	} else {
+		op->op_oplock_level = SMB_OPLOCK_NONE;
 	}
 }
-
-/*
- * smb_oplock_set_grant
- */
-static smb_oplock_grant_t *
-smb_oplock_set_grant(smb_ofile_t *of, uint8_t level)
-{
-	smb_oplock_grant_t	*og;
-
-	og = &of->f_oplock_grant;
-
-	og->og_magic = SMB_OPLOCK_GRANT_MAGIC;
-	og->og_breaking = 0;
-	og->og_level = level;
-	og->og_ofile = of;
-
-	return (og);
-}
-
-/*
- * smb_oplock_clear_grant
- */
-void
-smb_oplock_clear_grant(smb_oplock_grant_t *og)
-{
-	bzero(og, sizeof (smb_oplock_grant_t));
-}
-
-/*
- * smb_oplock_insert_grant
- *
- * If there are no grants in the oplock's list install the fem
- * monitor.
- * Insert the grant into the list and increment the grant count.
- */
-static int
-smb_oplock_insert_grant(smb_node_t *node, smb_oplock_grant_t *og)
-{
-	smb_oplock_t *ol = &node->n_oplock;
-
-	ASSERT(MUTEX_HELD(&ol->ol_mutex));
-
-	if (ol->ol_count == 0) {
-		if (smb_oplock_install_fem(node) != 0)
-			return (-1);
-	}
-
-	list_insert_tail(&ol->ol_grants, og);
-	++ol->ol_count;
-	return (0);
-}
-
-/*
- * smb_oplock_remove_grant
- *
- * Remove the oplock grant from the list, decrement the grant count
- * and, if there are no other grants in the list, uninstall the fem
- * monitor.
- */
-static void
-smb_oplock_remove_grant(smb_node_t *node, smb_oplock_grant_t *og)
-{
-	smb_oplock_t *ol = &node->n_oplock;
-
-	ASSERT(MUTEX_HELD(&ol->ol_mutex));
-	ASSERT(ol->ol_count > 0);
-
-	list_remove(&ol->ol_grants, og);
-	if (--ol->ol_count == 0)
-		smb_oplock_uninstall_fem(node);
-}
-
-/*
- * smb_oplock_exclusive_grant
- *
- * If an exclusive (EXCLUSIVE or BATCH) oplock grant exists,
- * return it. Otherwise return NULL.
- */
-static smb_oplock_grant_t *
-smb_oplock_exclusive_grant(list_t *grants)
-{
-	smb_oplock_grant_t	*og;
-
-	og = list_head(grants);
-	if (og) {
-		SMB_OPLOCK_GRANT_VALID(og);
-		if (SMB_OPLOCK_IS_EXCLUSIVE(og->og_level))
-			return (og);
-	}
-	return (NULL);
-}
-
-/*
- * smb_oplock_get_grant
- *
- * Find oplock grant corresponding to the specified ofile.
- */
-static smb_oplock_grant_t *
-smb_oplock_get_grant(smb_oplock_t *ol, smb_ofile_t *ofile)
-{
-	ASSERT(MUTEX_HELD(&ol->ol_mutex));
-
-	if (SMB_OFILE_OPLOCK_GRANTED(ofile))
-		return (&ofile->f_oplock_grant);
-	else
-		return (NULL);
-}
--- a/usr/src/uts/common/fs/smbsrv/smb_query_fileinfo.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_query_fileinfo.c	Wed Jun 12 22:46:54 2019 +0200
@@ -329,11 +329,7 @@
 	}
 
 	if (rc != 0) {
-		if (rc == ENOENT)
-			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
-			    ERRDOS, ERROR_FILE_NOT_FOUND);
-		else
-			smbsr_errno(sr, rc);
+		smbsr_errno(sr, rc);
 
 		kmem_free(qinfo, sizeof (smb_queryinfo_t));
 		return (-1);
@@ -654,9 +650,11 @@
 	switch (status) {
 	case 0:
 		break;
+	case NT_STATUS_OBJECT_NAME_NOT_FOUND:
 	case NT_STATUS_NO_SUCH_FILE:
 	case NT_STATUS_NOT_SUPPORTED:
 		/* No streams. */
+		status = 0;
 		done = B_TRUE;
 		break;
 	default:
--- a/usr/src/uts/common/fs/smbsrv/smb_read.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_read.c	Wed Jun 12 22:46:54 2019 +0200
@@ -385,11 +385,8 @@
  * function.  We can't move the fid lookup here because lock-and-read
  * requires the fid to do locking before attempting the read.
  *
- * Reading from a file should break oplocks on the file to LEVEL_II.
- * A call to smb_oplock_break(SMB_OPLOCK_BREAK_TO_LEVEL_II) is not
- * required as it is a no-op. If there's anything greater than a
- * LEVEL_II oplock on the file, the oplock MUST be owned by the ofile
- * on which the read is occuring and therefore would not be broken.
+ * Reading from a file does not break oplocks because any need for
+ * breaking before read is handled in open.
  *
  * Returns errno values.
  */
--- a/usr/src/uts/common/fs/smbsrv/smb_server.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_server.c	Wed Jun 12 22:46:54 2019 +0200
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 2017 by Delphix. All rights reserved.
  */
 
@@ -219,11 +219,6 @@
 #include <smbsrv/smb_door.h>
 #include <smbsrv/smb_kstat.h>
 
-typedef struct {
-	smb_listener_daemon_t	*ra_listener;
-	smb_session_t		*ra_session;
-} smb_receiver_arg_t;
-
 static void smb_server_kstat_init(smb_server_t *);
 static void smb_server_kstat_fini(smb_server_t *);
 static void smb_server_timers(smb_thread_t *, void *);
@@ -250,12 +245,25 @@
 static void smb_server_listener(smb_thread_t *, void *);
 static void smb_server_receiver(void *);
 static void smb_server_create_session(smb_listener_daemon_t *, ksocket_t);
-static void smb_server_destroy_session(smb_listener_daemon_t *,
-    smb_session_t *);
+static void smb_server_destroy_session(smb_session_t *);
 static uint16_t smb_spool_get_fid(smb_server_t *);
 static boolean_t smb_spool_lookup_doc_byfid(smb_server_t *, uint16_t,
     smb_kspooldoc_t *);
 
+/*
+ * How many "buckets" should our hash tables use?  On a "real" server,
+ * make them much larger than the number of CPUs we're likely to have.
+ * On "fksmbd" make it smaller so dtrace logs are shorter.
+ * These must be powers of two.
+ */
+#ifdef	_KERNEL
+#define	DEFAULT_HASH_NBUCKETS	256	/* real server */
+#else
+#define	DEFAULT_HASH_NBUCKETS	16	/* for "fksmbd" */
+#endif
+uint32_t SMB_OFILE_HASH_NBUCKETS = DEFAULT_HASH_NBUCKETS;
+uint32_t SMB_LEASE_HASH_NBUCKETS = DEFAULT_HASH_NBUCKETS;
+
 int smb_event_debug = 0;
 
 static smb_llist_t	smb_servers;
@@ -307,6 +315,7 @@
 	smb_codepage_init();
 	smb_mbc_init();		/* smb_mbc_cache */
 	smb_node_init();	/* smb_node_cache, lists */
+	smb2_lease_init();
 
 	smb_cache_request = kmem_cache_create("smb_request_cache",
 	    sizeof (smb_request_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
@@ -363,6 +372,7 @@
 	kmem_cache_destroy(smb_cache_event);
 	kmem_cache_destroy(smb_cache_lock);
 
+	smb2_lease_fini();
 	smb_node_fini();
 	smb_mbc_fini();
 	smb_codepage_fini();
@@ -410,6 +420,15 @@
 	cv_init(&sv->sv_cv, NULL, CV_DEFAULT, NULL);
 	cv_init(&sv->sp_info.sp_cv, NULL, CV_DEFAULT, NULL);
 
+	sv->sv_persistid_ht = smb_hash_create(sizeof (smb_ofile_t),
+	    offsetof(smb_ofile_t, f_dh_lnd), SMB_OFILE_HASH_NBUCKETS);
+
+	sv->sv_lease_ht = smb_hash_create(sizeof (smb_lease_t),
+	    offsetof(smb_lease_t, ls_lnd), SMB_LEASE_HASH_NBUCKETS);
+
+	smb_llist_constructor(&sv->sv_session_list, sizeof (smb_session_t),
+	    offsetof(smb_session_t, s_lnd));
+
 	smb_llist_constructor(&sv->sv_event_list, sizeof (smb_event_t),
 	    offsetof(smb_event_t, se_lnd));
 
@@ -510,6 +529,7 @@
 	smb_kshare_fini(sv);
 	smb_kdoor_fini(sv);
 	smb_llist_destructor(&sv->sv_event_list);
+	smb_llist_destructor(&sv->sv_session_list);
 
 	kmem_free(sv->sv_disp_stats1,
 	    SMB_COM_NUM * sizeof (smb_disp_stats_t));
@@ -521,6 +541,8 @@
 	smb_thread_destroy(&sv->si_thread_timers);
 
 	mutex_destroy(&sv->sv_mutex);
+	smb_hash_destroy(sv->sv_lease_ht);
+	smb_hash_destroy(sv->sv_persistid_ht);
 	cv_destroy(&sv->sv_cv);
 	sv->sv_magic = 0;
 	kmem_free(sv, sizeof (smb_server_t));
@@ -866,17 +888,11 @@
 
 	switch (svcenum->se_type) {
 	case SMB_SVCENUM_TYPE_USER:
-		smb_server_enum_users(&sv->sv_nbt_daemon.ld_session_list,
-		    svcenum);
-		smb_server_enum_users(&sv->sv_tcp_daemon.ld_session_list,
-		    svcenum);
+		smb_server_enum_users(&sv->sv_session_list, svcenum);
 		break;
 	case SMB_SVCENUM_TYPE_TREE:
 	case SMB_SVCENUM_TYPE_FILE:
-		smb_server_enum_trees(&sv->sv_nbt_daemon.ld_session_list,
-		    svcenum);
-		smb_server_enum_trees(&sv->sv_tcp_daemon.ld_session_list,
-		    svcenum);
+		smb_server_enum_trees(&sv->sv_session_list, svcenum);
 		break;
 	default:
 		rc = EINVAL;
@@ -894,22 +910,18 @@
 {
 	smb_llist_t	*ll;
 	smb_server_t	*sv;
-	int		nbt_cnt;
-	int		tcp_cnt;
+	int		cnt;
 	int		rc;
 
 	if ((rc = smb_server_lookup(&sv)) != 0)
 		return (rc);
 
-	ll = &sv->sv_nbt_daemon.ld_session_list;
-	nbt_cnt = smb_server_session_disconnect(ll, ioc->client, ioc->username);
-
-	ll = &sv->sv_tcp_daemon.ld_session_list;
-	tcp_cnt = smb_server_session_disconnect(ll, ioc->client, ioc->username);
+	ll = &sv->sv_session_list;
+	cnt = smb_server_session_disconnect(ll, ioc->client, ioc->username);
 
 	smb_server_release(sv);
 
-	if ((nbt_cnt == 0) && (tcp_cnt == 0))
+	if (cnt == 0)
 		return (ENOENT);
 	return (0);
 }
@@ -928,14 +940,9 @@
 	if ((rc = smb_server_lookup(&sv)) != 0)
 		return (rc);
 
-	ll = &sv->sv_nbt_daemon.ld_session_list;
+	ll = &sv->sv_session_list;
 	rc = smb_server_fclose(ll, uniqid);
 
-	if (rc == ENOENT) {
-		ll = &sv->sv_tcp_daemon.ld_session_list;
-		rc = smb_server_fclose(ll, uniqid);
-	}
-
 	smb_server_release(sv);
 	return (rc);
 }
@@ -949,8 +956,7 @@
 {
 	uint32_t	counter = 0;
 
-	counter = smb_llist_get_count(&sv->sv_nbt_daemon.ld_session_list);
-	counter += smb_llist_get_count(&sv->sv_tcp_daemon.ld_session_list);
+	counter = smb_llist_get_count(&sv->sv_session_list);
 
 	return (counter);
 }
@@ -983,7 +989,7 @@
 	mutex_exit(&sv->sv_mutex);
 
 	if ((sr = smb_request_alloc(sv->sv_session, 0)) == NULL) {
-		return (ENOMEM);
+		return (ENOTCONN);
 	}
 	sr->user_cr = zone_kcred();
 
@@ -1066,10 +1072,7 @@
 	}
 	mutex_exit(&sv->sv_mutex);
 
-	ll = &sv->sv_nbt_daemon.ld_session_list;
-	smb_server_disconnect_share(ll, sharename);
-
-	ll = &sv->sv_tcp_daemon.ld_session_list;
+	ll = &sv->sv_session_list;
 	smb_server_disconnect_share(ll, sharename);
 
 	smb_server_release(sv);
@@ -1093,12 +1096,13 @@
 		smb_rwx_rwenter(&session->s_lock, RW_READER);
 		switch (session->s_state) {
 		case SMB_SESSION_STATE_NEGOTIATED:
+			smb_rwx_rwexit(&session->s_lock);
 			smb_session_disconnect_share(session, sharename);
 			break;
 		default:
+			smb_rwx_rwexit(&session->s_lock);
 			break;
 		}
-		smb_rwx_rwexit(&session->s_lock);
 		session = smb_llist_next(ll, session);
 	}
 
@@ -1243,12 +1247,13 @@
 	ASSERT(sv != NULL);
 
 	/*
-	 * This just kills old inactive sessions.  No urgency.
-	 * The session code expects one call per minute.
+	 * This kills old inactive sessions and expired durable
+	 * handles. The session code expects one call per minute.
 	 */
 	while (smb_thread_continue_timedwait(thread, 60 /* Seconds */)) {
-		smb_session_timers(&sv->sv_nbt_daemon.ld_session_list);
-		smb_session_timers(&sv->sv_tcp_daemon.ld_session_list);
+		if (sv->sv_cfg.skc_keepalive != 0)
+			smb_session_timers(sv);
+		smb2_durable_timers(sv);
 	}
 }
 
@@ -1409,17 +1414,29 @@
 static void
 smb_server_shutdown(smb_server_t *sv)
 {
+	smb_llist_t *sl = &sv->sv_session_list;
+	smb_session_t *session;
+	clock_t	time;
+
 	SMB_SERVER_VALID(sv);
 
 	/*
 	 * Stop the listeners first, so we don't get any more
 	 * new work while we're trying to shut down.
-	 * Also disconnects all sessions under each.
 	 */
 	smb_server_listener_stop(&sv->sv_nbt_daemon);
 	smb_server_listener_stop(&sv->sv_tcp_daemon);
 	smb_thread_stop(&sv->si_thread_timers);
 
+	/* Disconnect all of the sessions */
+	smb_llist_enter(sl, RW_READER);
+	session = smb_llist_head(sl);
+	while (session != NULL) {
+		smb_session_disconnect(session);
+		session = smb_llist_next(sl, session);
+	}
+	smb_llist_exit(sl);
+
 	/*
 	 * Wake up any threads we might have blocked.
 	 * Must precede kdoor_close etc. because those will
@@ -1430,6 +1447,39 @@
 	smb_threshold_wake_all(&sv->sv_tcon_ct);
 	smb_threshold_wake_all(&sv->sv_opipe_ct);
 
+	/*
+	 * Wait for the session list to empty.
+	 * (cv_signal in smb_server_destroy_session)
+	 *
+	 * This should not take long, but if there are any leaked
+	 * references to ofiles, trees, or users, there could be a
+	 * session hanging around.  If that happens, the ll_count
+	 * never gets to zero and we'll never get the sv_signal.
+	 * Defend against that problem using timed wait, then
+	 * complain if we find sessions left over and continue
+	 * with shutdown in spite of any leaked sessions.
+	 * That's better than a server that won't reboot.
+	 */
+	time = SEC_TO_TICK(10) + ddi_get_lbolt();
+	mutex_enter(&sv->sv_mutex);
+	while (sv->sv_session_list.ll_count != 0) {
+		if (cv_timedwait(&sv->sv_cv, &sv->sv_mutex, time) < 0)
+			break;
+	}
+	mutex_exit(&sv->sv_mutex);
+#ifdef	DEBUG
+	if (sv->sv_session_list.ll_count != 0) {
+		cmn_err(CE_NOTE, "shutdown leaked sessions");
+		debug_enter("shutdown leaked sessions");
+	}
+#endif
+
+	/*
+	 * Clean out any durable handles.  After this we should
+	 * have no ofiles remaining (and no more oplock breaks).
+	 */
+	smb2_dh_shutdown(sv);
+
 	smb_kdoor_close(sv);
 #ifdef	_KERNEL
 	smb_kshare_door_fini(sv->sv_lmshrd);
@@ -1437,14 +1487,18 @@
 	sv->sv_lmshrd = NULL;
 
 	smb_export_stop(sv);
+	smb_kshare_stop(sv);
 
+	/*
+	 * Both kshare and the oplock break sub-systems may have
+	 * taskq jobs on the spcial "server" session, until we've
+	 * closed all ofiles and stopped the kshare exporter.
+	 * Now it's safe to destroy the server session, but first
+	 * wait for any requests on it to finish.  Note that for
+	 * normal sessions, this happens in smb_session_cancel,
+	 * but that's not called for the server session.
+	 */
 	if (sv->sv_session != NULL) {
-		/*
-		 * smb_kshare_export may have a request on here.
-		 * Normal sessions do this in smb_session_cancel()
-		 * but this is a "fake" session used only for the
-		 * requests used by the kshare thread(s).
-		 */
 		smb_slist_wait_for_empty(&sv->sv_session->s_req_list);
 
 		smb_session_delete(sv->sv_session);
@@ -1461,7 +1515,6 @@
 		sv->sv_worker_pool = NULL;
 	}
 
-	smb_kshare_stop(sv);
 	smb_server_fsop_stop(sv);
 }
 
@@ -1497,8 +1550,6 @@
 		    sizeof (ld->ld_sin6.sin6_addr.s6_addr));
 	}
 
-	smb_llist_constructor(&ld->ld_session_list, sizeof (smb_session_t),
-	    offsetof(smb_session_t, s_lnd));
 	smb_thread_init(&ld->ld_thread, name, smb_server_listener, ld,
 	    smbsrv_listen_pri);
 	ld->ld_magic = SMB_LISTENER_MAGIC;
@@ -1522,7 +1573,6 @@
 	SMB_LISTENER_VALID(ld);
 	ASSERT(ld->ld_so == NULL);
 	smb_thread_destroy(&ld->ld_thread);
-	smb_llist_destructor(&ld->ld_session_list);
 	ld->ld_magic = 0;
 }
 
@@ -1620,7 +1670,6 @@
 {
 	_NOTE(ARGUNUSED(thread))
 	smb_listener_daemon_t	*ld;
-	smb_session_t		*session;
 	ksocket_t		s_so;
 	int			on;
 	int			txbuf_size;
@@ -1669,14 +1718,6 @@
 		smb_server_create_session(ld, s_so);
 	}
 out:
-	/* Disconnect all the sessions this listener created. */
-	smb_llist_enter(&ld->ld_session_list, RW_READER);
-	session = smb_llist_head(&ld->ld_session_list);
-	while (session != NULL) {
-		smb_session_disconnect(session);
-		session = smb_llist_next(&ld->ld_session_list, session);
-	}
-	smb_llist_exit(&ld->ld_session_list);
 	ksocket_rele(ld->ld_so);
 }
 
@@ -1684,18 +1725,20 @@
  * smb_server_receiver
  *
  * Entry point of the receiver threads.
+ * Also does cleanup when socket disconnected.
  */
 static void
 smb_server_receiver(void *arg)
 {
-	smb_listener_daemon_t	*ld;
-	smb_session_t		*session;
+	smb_session_t	*session;
+
+	session = (smb_session_t *)arg;
 
-	ld = ((smb_receiver_arg_t *)arg)->ra_listener;
-	session = ((smb_receiver_arg_t *)arg)->ra_session;
-	smb_mem_free(arg);
+	/* We stay in here until socket disconnect. */
 	smb_session_receiver(session);
-	smb_server_destroy_session(ld, session);
+
+	ASSERT(session->s_state == SMB_SESSION_STATE_SHUTDOWN);
+	smb_server_destroy_session(session);
 }
 
 /*
@@ -1849,48 +1892,37 @@
 	smb_session_t	*sn;
 	smb_llist_t	*ulist;
 	smb_user_t	*user;
-	boolean_t	match;
 	int		count = 0;
 
 	smb_llist_enter(ll, RW_READER);
-	sn = smb_llist_head(ll);
 
-	while (sn != NULL) {
+	for (sn = smb_llist_head(ll);
+	    sn != NULL;
+	    sn = smb_llist_next(ll, sn)) {
 		SMB_SESSION_VALID(sn);
 
-		if ((*client != '\0') && (!smb_session_isclient(sn, client))) {
-			sn = smb_llist_next(ll, sn);
+		if (*client != '\0' && !smb_session_isclient(sn, client))
 			continue;
-		}
 
 		ulist = &sn->s_user_list;
 		smb_llist_enter(ulist, RW_READER);
-		user = smb_llist_head(ulist);
 
-		while (user != NULL) {
-			if (smb_user_hold(user)) {
-				match = (*name == '\0');
-				if (!match)
-					match = smb_user_namecmp(user, name);
+		for (user = smb_llist_head(ulist);
+		    user != NULL;
+		    user = smb_llist_next(ulist, user)) {
+			SMB_USER_VALID(user);
 
-				if (match) {
-					smb_llist_exit(ulist);
-					smb_user_logoff(user);
-					++count;
-					smb_user_release(user);
-					smb_llist_enter(ulist, RW_READER);
-					user = smb_llist_head(ulist);
-					continue;
-				}
+			if (*name != '\0' && !smb_user_namecmp(user, name))
+				continue;
 
+			if (smb_user_hold(user)) {
+				smb_user_logoff(user);
 				smb_user_release(user);
+				count++;
 			}
-
-			user = smb_llist_next(ulist, user);
 		}
 
 		smb_llist_exit(ulist);
-		sn = smb_llist_next(ll, sn);
 	}
 
 	smb_llist_exit(ll);
@@ -1934,6 +1966,63 @@
 	return (rc);
 }
 
+/*
+ * This is used by SMB2 session setup to logoff a previous session,
+ * so it can force a logoff that we haven't noticed yet.
+ * This is not called frequently, so we just walk the list of
+ * connections searching for the user.
+ */
+void
+smb_server_logoff_ssnid(smb_request_t *sr, uint64_t ssnid)
+{
+	smb_server_t	*sv = sr->sr_server;
+	smb_llist_t	*sess_list;
+	smb_session_t	*sess;
+
+	if (sv->sv_state != SMB_SERVER_STATE_RUNNING)
+		return;
+
+	sess_list = &sv->sv_session_list;
+	smb_llist_enter(sess_list, RW_READER);
+
+	for (sess = smb_llist_head(sess_list);
+	    sess != NULL;
+	    sess = smb_llist_next(sess_list, sess)) {
+
+		smb_user_t	*user;
+
+		SMB_SESSION_VALID(sess);
+
+		if (sess->dialect < SMB_VERS_2_BASE)
+			continue;
+
+		if (sess->s_state != SMB_SESSION_STATE_NEGOTIATED)
+			continue;
+
+		user = smb_session_lookup_ssnid(sess, ssnid);
+		if (user == NULL)
+			continue;
+
+		if (!smb_is_same_user(user->u_cred, sr->user_cr)) {
+			smb_user_release(user);
+			continue;
+		}
+
+		/* Treat this as if we lost the connection */
+		user->preserve_opens = SMB2_DH_PRESERVE_SOME;
+		smb_user_logoff(user);
+		smb_user_release(user);
+
+		/*
+		 * The above may have left work on the delete queues
+		 */
+		smb_llist_flush(&sess->s_tree_list);
+		smb_llist_flush(&sess->s_user_list);
+	}
+
+	smb_llist_exit(sess_list);
+}
+
 /* See also: libsmb smb_kmod_setcfg */
 static void
 smb_server_store_cfg(smb_server_t *sv, smb_ioc_cfg_t *ioc)
@@ -1941,10 +2030,12 @@
 	if (ioc->maxconnections == 0)
 		ioc->maxconnections = 0xFFFFFFFF;
 
-	smb_session_correct_keep_alive_values(
-	    &sv->sv_nbt_daemon.ld_session_list, ioc->keepalive);
-	smb_session_correct_keep_alive_values(
-	    &sv->sv_tcp_daemon.ld_session_list, ioc->keepalive);
+	if (ioc->encrypt == SMB_CONFIG_REQUIRED &&
+	    ioc->max_protocol < SMB_VERS_3_0) {
+		cmn_err(CE_WARN, "Server set to require encryption; "
+		    "forcing max_protocol to 3.0");
+		ioc->max_protocol = SMB_VERS_3_0;
+	}
 
 	sv->sv_cfg.skc_maxworkers = ioc->maxworkers;
 	sv->sv_cfg.skc_maxconnections = ioc->maxconnections;
@@ -1960,6 +2051,7 @@
 	sv->sv_cfg.skc_print_enable = ioc->print_enable;
 	sv->sv_cfg.skc_traverse_mounts = ioc->traverse_mounts;
 	sv->sv_cfg.skc_max_protocol = ioc->max_protocol;
+	sv->sv_cfg.skc_encrypt = ioc->encrypt;
 	sv->sv_cfg.skc_execflags = ioc->exec_flags;
 	sv->sv_cfg.skc_negtok_len = ioc->negtok_len;
 	sv->sv_cfg.skc_version = ioc->version;
@@ -2343,10 +2435,11 @@
 smb_server_create_session(smb_listener_daemon_t *ld, ksocket_t s_so)
 {
 	smb_session_t		*session;
-	smb_receiver_arg_t	*rarg;
 	taskqid_t		tqid;
+	smb_llist_t		*sl;
+	smb_server_t		*sv = ld->ld_sv;
 
-	session = smb_session_create(s_so, ld->ld_port, ld->ld_sv,
+	session = smb_session_create(s_so, ld->ld_port, sv,
 	    ld->ld_family);
 
 	if (session == NULL) {
@@ -2356,25 +2449,20 @@
 		return;
 	}
 
-	smb_llist_enter(&ld->ld_session_list, RW_WRITER);
-	smb_llist_insert_tail(&ld->ld_session_list, session);
-	smb_llist_exit(&ld->ld_session_list);
-
-	rarg = (smb_receiver_arg_t *)smb_mem_alloc(
-	    sizeof (smb_receiver_arg_t));
-	rarg->ra_listener = ld;
-	rarg->ra_session = session;
+	sl = &sv->sv_session_list;
+	smb_llist_enter(sl, RW_WRITER);
+	smb_llist_insert_tail(sl, session);
+	smb_llist_exit(sl);
 
 	/*
 	 * These taskq entries must run independently of one another,
 	 * so TQ_NOQUEUE.  TQ_SLEEP (==0) just for clarity.
 	 */
-	tqid = taskq_dispatch(ld->ld_sv->sv_receiver_pool,
-	    smb_server_receiver, rarg, TQ_NOQUEUE | TQ_SLEEP);
+	tqid = taskq_dispatch(sv->sv_receiver_pool,
+	    smb_server_receiver, session, TQ_NOQUEUE | TQ_SLEEP);
 	if (tqid == TASKQID_INVALID) {
-		smb_mem_free(rarg);
 		smb_session_disconnect(session);
-		smb_server_destroy_session(ld, session);
+		smb_server_destroy_session(session);
 		cmn_err(CE_WARN, "SMB Session: taskq_dispatch failed");
 		return;
 	}
@@ -2383,10 +2471,41 @@
 }
 
 static void
-smb_server_destroy_session(smb_listener_daemon_t *ld, smb_session_t *session)
+smb_server_destroy_session(smb_session_t *session)
 {
-	smb_llist_enter(&ld->ld_session_list, RW_WRITER);
-	smb_llist_remove(&ld->ld_session_list, session);
-	smb_llist_exit(&ld->ld_session_list);
+	smb_server_t *sv;
+	smb_llist_t *ll;
+	uint32_t count;
+
+	ASSERT(session->s_server != NULL);
+	sv = session->s_server;
+	ll = &sv->sv_session_list;
+
+	smb_llist_flush(&session->s_tree_list);
+	smb_llist_flush(&session->s_user_list);
+
+	/*
+	 * The user and tree lists should be empty now.
+	 */
+#ifdef DEBUG
+	if (session->s_user_list.ll_count != 0) {
+		cmn_err(CE_WARN, "user list not empty?");
+		debug_enter("s_user_list");
+	}
+	if (session->s_tree_list.ll_count != 0) {
+		cmn_err(CE_WARN, "tree list not empty?");
+		debug_enter("s_tree_list");
+	}
+#endif
+
+	smb_llist_enter(ll, RW_WRITER);
+	smb_llist_remove(ll, session);
+	count = ll->ll_count;
+	smb_llist_exit(ll);
+
 	smb_session_delete(session);
+	if (count == 0) {
+		/* See smb_server_shutdown */
+		cv_signal(&sv->sv_cv);
+	}
 }
--- a/usr/src/uts/common/fs/smbsrv/smb_session.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_session.c	Wed Jun 12 22:46:54 2019 +0200
@@ -74,14 +74,29 @@
     uint8_t *dst, size_t dstlen);
 static smb_tree_t *smb_session_get_tree(smb_session_t *, smb_tree_t *);
 static void smb_session_logoff(smb_session_t *);
+static void smb_session_disconnect_trees(smb_session_t	*);
 static void smb_request_init_command_mbuf(smb_request_t *sr);
 static void smb_session_genkey(smb_session_t *);
 
+/*
+ * This (legacy) code is in support of an "idle timeout" feature,
+ * which is apparently incomplete.  To complete it, we should:
+ * when the keep_alive timer expires, check whether the client
+ * has any open files, and if not then kill their session.
+ * Right now the timers are there, but nothing happens when
+ * a timer expires.
+ *
+ * Todo: complete logic to kill idle sessions.
+ *
+ * Only called when sv_cfg.skc_keepalive != 0
+ */
 void
-smb_session_timers(smb_llist_t *ll)
+smb_session_timers(smb_server_t *sv)
 {
 	smb_session_t	*session;
+	smb_llist_t	*ll;
 
+	ll = &sv->sv_session_list;
 	smb_llist_enter(ll, RW_READER);
 	session = smb_llist_head(ll);
 	while (session != NULL) {
@@ -93,44 +108,12 @@
 		if (session->keep_alive &&
 		    (session->keep_alive != (uint32_t)-1))
 			session->keep_alive--;
+
 		session = smb_llist_next(ll, session);
 	}
 	smb_llist_exit(ll);
 }
 
-void
-smb_session_correct_keep_alive_values(smb_llist_t *ll, uint32_t new_keep_alive)
-{
-	smb_session_t		*sn;
-
-	/*
-	 * Caller specifies seconds, but we track in minutes, so
-	 * convert to minutes (rounded up).
-	 */
-	new_keep_alive = (new_keep_alive + 59) / 60;
-
-	if (new_keep_alive == smb_keep_alive)
-		return;
-	/*
-	 * keep alive == 0 means do not drop connection if it's idle
-	 */
-	smb_keep_alive = (new_keep_alive) ? new_keep_alive : -1;
-
-	/*
-	 * Walk through the table and set each session to the new keep_alive
-	 * value if they have not already timed out.  Block clock interrupts.
-	 */
-	smb_llist_enter(ll, RW_READER);
-	sn = smb_llist_head(ll);
-	while (sn != NULL) {
-		SMB_SESSION_VALID(sn);
-		if (sn->keep_alive != 0)
-			sn->keep_alive = new_keep_alive;
-		sn = smb_llist_next(ll, sn);
-	}
-	smb_llist_exit(ll);
-}
-
 /*
  * Send a session message - supports SMB-over-NBT and SMB-over-TCP.
  * If an mbuf chain is provided (optional), it will be freed and
@@ -242,9 +225,9 @@
 	int			rc;
 	char			*calling_name;
 	char			*called_name;
-	char 			client_name[NETBIOS_NAME_SZ];
-	struct mbuf_chain 	mbc;
-	char 			*names = NULL;
+	char			client_name[NETBIOS_NAME_SZ];
+	struct mbuf_chain	mbc;
+	char			*names = NULL;
 	smb_wchar_t		*wbuf = NULL;
 	smb_xprt_t		hdr;
 	char *p;
@@ -491,6 +474,11 @@
  * smb_session_receiver
  *
  * Receives request from the network and dispatches them to a worker.
+ *
+ * When we receive a disconnect here, it _could_ be due to the server
+ * having initiated disconnect, in which case the session state will be
+ * SMB_SESSION_STATE_TERMINATED and we want to keep that state so later
+ * tear-down logic will know which side initiated.
  */
 void
 smb_session_receiver(smb_session_t *session)
@@ -505,7 +493,9 @@
 		rc = smb_netbios_session_request(session);
 		if (rc != 0) {
 			smb_rwx_rwenter(&session->s_lock, RW_WRITER);
-			session->s_state = SMB_SESSION_STATE_DISCONNECTED;
+			if (session->s_state != SMB_SESSION_STATE_TERMINATED)
+				session->s_state =
+				    SMB_SESSION_STATE_DISCONNECTED;
 			smb_rwx_rwexit(&session->s_lock);
 			return;
 		}
@@ -518,7 +508,8 @@
 	(void) smb_session_reader(session);
 
 	smb_rwx_rwenter(&session->s_lock, RW_WRITER);
-	session->s_state = SMB_SESSION_STATE_DISCONNECTED;
+	if (session->s_state != SMB_SESSION_STATE_TERMINATED)
+		session->s_state = SMB_SESSION_STATE_DISCONNECTED;
 	smb_rwx_rwexit(&session->s_lock);
 
 	smb_soshutdown(session->sock);
@@ -536,7 +527,7 @@
 /*
  * smb_session_disconnect
  *
- * Disconnects the session passed in.
+ * Server-initiated disconnect (i.e. server shutdown)
  */
 void
 smb_session_disconnect(smb_session_t *session)
@@ -550,8 +541,8 @@
 	case SMB_SESSION_STATE_ESTABLISHED:
 	case SMB_SESSION_STATE_NEGOTIATED:
 		smb_soshutdown(session->sock);
-		session->s_state = SMB_SESSION_STATE_DISCONNECTED;
-		_NOTE(FALLTHRU)
+		session->s_state = SMB_SESSION_STATE_TERMINATED;
+		break;
 	case SMB_SESSION_STATE_DISCONNECTED:
 	case SMB_SESSION_STATE_TERMINATED:
 		break;
@@ -622,8 +613,8 @@
 
 		/*
 		 * Allocate a request context, read the whole message.
-		 * If the request alloc fails, we've disconnected and
-		 * won't be able to send the reply anyway, so bail now.
+		 * If the request alloc fails, we've disconnected
+		 * and won't be able to send the reply anyway, so bail now.
 		 */
 		if ((sr = smb_request_alloc(session, hdr.xh_length)) == NULL)
 			break;
@@ -733,6 +724,7 @@
 
 	now = ddi_get_lbolt64();
 
+	session->s_server = sv;
 	session->s_kid = SMB_NEW_KID();
 	session->s_state = SMB_SESSION_STATE_INITIALIZED;
 	session->native_os = NATIVE_OS_UNKNOWN;
@@ -802,7 +794,6 @@
 		else
 			smb_server_inc_tcp_sess(sv);
 	}
-	session->s_server = sv;
 	smb_server_get_cfg(sv, &session->s_cfg);
 	session->s_srqueue = &sv->sv_srqueue;
 
@@ -826,6 +817,9 @@
 
 	ASSERT(session->s_magic == SMB_SESSION_MAGIC);
 
+	if (session->enc_mech != NULL)
+		smb3_encrypt_fini(session);
+
 	if (session->sign_fini != NULL)
 		session->sign_fini(session);
 
@@ -925,13 +919,23 @@
 smb_user_t *
 smb_session_lookup_uid(smb_session_t *session, uint16_t uid)
 {
-	return (smb_session_lookup_uid_st(session, uid,
+	return (smb_session_lookup_uid_st(session, 0, uid,
+	    SMB_USER_STATE_LOGGED_ON));
+}
+
+/*
+ * Find a user on the specified session by SMB2 SSNID.
+ */
+smb_user_t *
+smb_session_lookup_ssnid(smb_session_t *session, uint64_t ssnid)
+{
+	return (smb_session_lookup_uid_st(session, ssnid, 0,
 	    SMB_USER_STATE_LOGGED_ON));
 }
 
 smb_user_t *
-smb_session_lookup_uid_st(smb_session_t *session, uint16_t uid,
-    smb_user_state_t st)
+smb_session_lookup_uid_st(smb_session_t *session, uint64_t ssnid,
+    uint16_t uid, smb_user_state_t st)
 {
 	smb_user_t	*user;
 	smb_llist_t	*user_list;
@@ -941,34 +945,30 @@
 	user_list = &session->s_user_list;
 	smb_llist_enter(user_list, RW_READER);
 
-	user = smb_llist_head(user_list);
-	while (user) {
+	for (user = smb_llist_head(user_list);
+	    user != NULL;
+	    user = smb_llist_next(user_list, user)) {
+
 		SMB_USER_VALID(user);
 		ASSERT(user->u_session == session);
 
-		if (user->u_uid == uid && user->u_state == st) {
-			smb_user_hold_internal(user);
+		if (user->u_ssnid != ssnid && user->u_uid != uid)
+			continue;
+
+		mutex_enter(&user->u_mutex);
+		if (user->u_state == st) {
+			// smb_user_hold_internal(user);
+			user->u_refcnt++;
+			mutex_exit(&user->u_mutex);
 			break;
 		}
-
-		user = smb_llist_next(user_list, user);
+		mutex_exit(&user->u_mutex);
 	}
 
 	smb_llist_exit(user_list);
 	return (user);
 }
 
-void
-smb_session_post_user(smb_session_t *session, smb_user_t *user)
-{
-	SMB_USER_VALID(user);
-	ASSERT(user->u_refcnt == 0);
-	ASSERT(user->u_state == SMB_USER_STATE_LOGGED_OFF);
-	ASSERT(user->u_session == session);
-
-	smb_llist_post(&session->s_user_list, user, smb_user_delete);
-}
-
 /*
  * Find a tree by tree-id.
  */
@@ -976,7 +976,6 @@
 smb_session_lookup_tree(
     smb_session_t	*session,
     uint16_t		tid)
-
 {
 	smb_tree_t	*tree;
 
@@ -1148,8 +1147,9 @@
 			/*
 			 * smb_tree_hold() succeeded, hence we are in state
 			 * SMB_TREE_STATE_CONNECTED; schedule this tree
-			 * for asynchronous disconnect, which will fire
-			 * after we drop the llist traversal lock.
+			 * for disconnect after smb_llist_exit because
+			 * the "unmap exec" up-call can block, and we'd
+			 * rather not block with the tree list locked.
 			 */
 			smb_llist_post(tree_list, tree, smb_session_tree_dtor);
 		}
@@ -1163,11 +1163,11 @@
 /*
  * Disconnect all trees that this user has connected.
  */
-void
+static void
 smb_session_disconnect_trees(
     smb_session_t	*session)
 {
-	smb_tree_t	*tree;
+	smb_tree_t	*tree, *next_tree;
 
 	SMB_SESSION_VALID(session);
 
@@ -1176,8 +1176,9 @@
 		ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
 		ASSERT(tree->t_session == session);
 		smb_tree_disconnect(tree, B_TRUE);
+		next_tree = smb_session_get_tree(session, tree);
 		smb_tree_release(tree);
-		tree = smb_session_get_tree(session, NULL);
+		tree = next_tree;
 	}
 }
 
@@ -1206,18 +1207,6 @@
 	}
 }
 
-void
-smb_session_post_tree(smb_session_t *session, smb_tree_t *tree)
-{
-	SMB_SESSION_VALID(session);
-	SMB_TREE_VALID(tree);
-	ASSERT0(tree->t_refcnt);
-	ASSERT(tree->t_state == SMB_TREE_STATE_DISCONNECTED);
-	ASSERT(tree->t_session == session);
-
-	smb_llist_post(&session->s_tree_list, tree, smb_tree_dealloc);
-}
-
 /*
  * Get the next connected tree in the list.  A reference is taken on
  * the tree, which can be released later with smb_tree_release().
@@ -1260,44 +1249,110 @@
 
 /*
  * Logoff all users associated with the specified session.
+ *
+ * This is called for both server-initiated disconnect
+ * (SMB_SESSION_STATE_TERMINATED) and client-initiated
+ * disconnect (SMB_SESSION_STATE_DISCONNECTED).
+ * If client-initiated, save durable handles.
  */
 static void
 smb_session_logoff(smb_session_t *session)
 {
+	smb_llist_t	*ulist;
 	smb_user_t	*user;
 
 	SMB_SESSION_VALID(session);
 
-	smb_session_disconnect_trees(session);
+top:
+	ulist = &session->s_user_list;
+	smb_llist_enter(ulist, RW_READER);
 
-	smb_llist_enter(&session->s_user_list, RW_READER);
-
-	user = smb_llist_head(&session->s_user_list);
+	user = smb_llist_head(ulist);
 	while (user) {
 		SMB_USER_VALID(user);
 		ASSERT(user->u_session == session);
 
+		mutex_enter(&user->u_mutex);
 		switch (user->u_state) {
 		case SMB_USER_STATE_LOGGING_ON:
 		case SMB_USER_STATE_LOGGED_ON:
-			smb_user_hold_internal(user);
+			// smb_user_hold_internal(user);
+			user->u_refcnt++;
+			mutex_exit(&user->u_mutex);
+			if (user->u_session->s_state ==
+			    SMB_SESSION_STATE_DISCONNECTED)
+				user->preserve_opens = SMB2_DH_PRESERVE_ALL;
 			smb_user_logoff(user);
 			smb_user_release(user);
 			break;
 
 		case SMB_USER_STATE_LOGGED_OFF:
 		case SMB_USER_STATE_LOGGING_OFF:
+			mutex_exit(&user->u_mutex);
 			break;
 
 		default:
 			ASSERT(0);
+			mutex_exit(&user->u_mutex);
 			break;
 		}
 
-		user = smb_llist_next(&session->s_user_list, user);
+		user = smb_llist_next(ulist, user);
 	}
 
-	smb_llist_exit(&session->s_user_list);
+	/* Needed below (Was the list empty?) */
+	user = smb_llist_head(ulist);
+
+	smb_llist_exit(ulist);
+
+	/*
+	 * It's possible for user objects to remain due to references
+	 * obtained via smb_server_lookup_ssnid(), when an SMB2
+	 * session setup is destroying a previous session.
+	 *
+	 * Wait for user objects to clear out (last refs. go away,
+	 * then smb_user_delete takes them out of the list).  When
+	 * the last user object is removed, the session state is
+	 * set to SHUTDOWN and s_lock is signaled.
+	 *
+	 * Not all places that call smb_user_release necessarily
+	 * flush the delete queue, so after we wait for the list
+	 * to empty out, go back to the top and recheck the list
+	 * delete queue to make sure smb_user_delete happens.
+	 */
+	if (user == NULL) {
+		/* User list is empty. */
+		smb_rwx_rwenter(&session->s_lock, RW_WRITER);
+		session->s_state = SMB_SESSION_STATE_SHUTDOWN;
+		smb_rwx_rwexit(&session->s_lock);
+	} else {
+		smb_rwx_rwenter(&session->s_lock, RW_READER);
+		if (session->s_state != SMB_SESSION_STATE_SHUTDOWN) {
+			(void) smb_rwx_cvwait(&session->s_lock,
+			    MSEC_TO_TICK(200));
+			smb_rwx_rwexit(&session->s_lock);
+			goto top;
+		}
+		smb_rwx_rwexit(&session->s_lock);
+	}
+	ASSERT(session->s_state == SMB_SESSION_STATE_SHUTDOWN);
+
+	/*
+	 * User list should be empty now.
+	 */
+#ifdef	DEBUG
+	if (ulist->ll_count != 0) {
+		cmn_err(CE_WARN, "user list not empty?");
+		debug_enter("s_user_list");
+	}
+#endif
+
+	/*
+	 * User logoff happens first so we'll set preserve_opens
+	 * for client-initiated disconnect.  When that's done
+	 * there should be no trees left, but check anyway.
+	 */
+	smb_session_disconnect_trees(session);
 }
 
 /*
@@ -1400,6 +1455,7 @@
 		ASSERT(0);
 		/* FALLTHROUGH */
 	case SMB_SESSION_STATE_DISCONNECTED:
+	case SMB_SESSION_STATE_SHUTDOWN:
 	case SMB_SESSION_STATE_TERMINATED:
 		/* Disallow new requests in these states. */
 		if (sr->sr_request_buf)
@@ -1429,7 +1485,6 @@
 	ASSERT(sr->r_xa == NULL);
 
 	if (sr->fid_ofile != NULL) {
-		smb_ofile_request_complete(sr->fid_ofile);
 		smb_ofile_release(sr->fid_ofile);
 	}
 
@@ -1439,6 +1494,9 @@
 	if (sr->uid_user != NULL)
 		smb_user_release(sr->uid_user);
 
+	if (sr->tform_ssn != NULL)
+		smb_user_release(sr->tform_ssn);
+
 	/*
 	 * The above may have left work on the delete queues
 	 */
@@ -1480,10 +1538,6 @@
 {
 	SMB_SESSION_VALID(session);
 
-	/* Clients using SMB2 and later always know about oplocks. */
-	if (session->dialect > NT_LM_0_12)
-		return (B_TRUE);
-
 	/* Older clients only do Level II oplocks if negotiated. */
 	if ((session->capabilities & CAP_LEVEL_II_OPLOCKS) != 0)
 		return (B_TRUE);
@@ -1491,34 +1545,6 @@
 	return (B_FALSE);
 }
 
-/*
- * smb_session_oplock_break
- *
- * Send an oplock break request to the client,
- * recalling some cache delegation.
- */
-void
-smb_session_oplock_break(smb_request_t *sr, uint8_t brk)
-{
-	smb_session_t	*session = sr->session;
-	mbuf_chain_t	*mbc = &sr->reply;
-
-	SMB_SESSION_VALID(session);
-
-	/*
-	 * Build the break message in sr->reply and then send it.
-	 * The mbc is free'd later, in smb_request_free().
-	 */
-	mbc->max_bytes = MLEN;
-	if (session->dialect <= NT_LM_0_12) {
-		smb1_oplock_break_notification(sr, brk);
-	} else {
-		smb2_oplock_break_notification(sr, brk);
-	}
-
-	(void) smb_session_send(session, 0, mbc);
-}
-
 static void
 smb_session_genkey(smb_session_t *session)
 {
--- a/usr/src/uts/common/fs/smbsrv/smb_session_setup_andx.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_session_setup_andx.c	Wed Jun 12 22:46:54 2019 +0200
@@ -235,14 +235,17 @@
 		sr->session->smb_msg_size = sinfo->ssi_maxbufsize;
 		sr->session->smb_max_mpx = sinfo->ssi_maxmpxcount;
 		sr->session->capabilities = sinfo->ssi_capabilities;
-
-		if (!smb_oplock_levelII)
-			sr->session->capabilities &= ~CAP_LEVEL_II_OPLOCKS;
-
 		sr->session->native_os = sinfo->ssi_native_os;
 		sr->session->native_lm = sinfo->ssi_native_lm;
 	}
 
+	/* RejectUnencryptedAccess precludes SMB1 access */
+	if (sr->sr_server->sv_cfg.skc_encrypt == SMB_CONFIG_REQUIRED) {
+		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
+		    ERRDOS, ERROR_ACCESS_DENIED);
+		return (SDRC_ERROR);
+	}
+
 	/*
 	 * The "meat" of authentication happens here.
 	 */
--- a/usr/src/uts/common/fs/smbsrv/smb_set_fileinfo.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_set_fileinfo.c	Wed Jun 12 22:46:54 2019 +0200
@@ -293,12 +293,7 @@
 	kmem_free(name, MAXNAMELEN);
 
 	if (rc != 0) {
-		if (rc == ENOENT) {
-			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
-			    ERRDOS, ERROR_FILE_NOT_FOUND);
-		} else {
-			smbsr_errno(sr, rc);
-		}
+		smbsr_errno(sr, rc);
 		return (-1);
 	}
 
--- a/usr/src/uts/common/fs/smbsrv/smb_sign_kcf.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_sign_kcf.c	Wed Jun 12 22:46:54 2019 +0200
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -26,7 +26,24 @@
 #include <sys/kmem.h>
 #include <sys/crypto/api.h>
 #include <smbsrv/smb_kproto.h>
-#include <smbsrv/smb_signing.h>
+#include <smbsrv/smb_kcrypt.h>
+
+/*
+ * Common function to see if a mech is available.
+ */
+static int
+find_mech(smb_crypto_mech_t *mech, crypto_mech_name_t name)
+{
+	crypto_mech_type_t t;
+
+	t = crypto_mech2id(name);
+	if (t == CRYPTO_MECH_INVALID) {
+		cmn_err(CE_NOTE, "smb: no kcf mech: %s", name);
+		return (-1);
+	}
+	mech->cm_type = t;
+	return (0);
+}
 
 /*
  * SMB1 signing helpers:
@@ -34,22 +51,16 @@
  */
 
 int
-smb_md5_getmech(smb_sign_mech_t *mech)
+smb_md5_getmech(smb_crypto_mech_t *mech)
 {
-	crypto_mech_type_t t;
-
-	t = crypto_mech2id(SUN_CKM_MD5);
-	if (t == CRYPTO_MECH_INVALID)
-		return (-1);
-	mech->cm_type = t;
-	return (0);
+	return (find_mech(mech, SUN_CKM_MD5));
 }
 
 /*
  * Start the KCF session, load the key
  */
 int
-smb_md5_init(smb_sign_ctx_t *ctxp, smb_sign_mech_t *mech)
+smb_md5_init(smb_sign_ctx_t *ctxp, smb_crypto_mech_t *mech)
 {
 	int rv;
 
@@ -75,7 +86,12 @@
 
 	rv = crypto_digest_update(ctx, &data, 0);
 
-	return (rv == CRYPTO_SUCCESS ? 0 : -1);
+	if (rv != CRYPTO_SUCCESS) {
+		crypto_cancel_ctx(ctx);
+		return (-1);
+	}
+
+	return (0);
 }
 
 /*
@@ -104,22 +120,16 @@
  */
 
 int
-smb2_hmac_getmech(smb_sign_mech_t *mech)
+smb2_hmac_getmech(smb_crypto_mech_t *mech)
 {
-	crypto_mech_type_t t;
-
-	t = crypto_mech2id(SUN_CKM_SHA256_HMAC);
-	if (t == CRYPTO_MECH_INVALID)
-		return (-1);
-	mech->cm_type = t;
-	return (0);
+	return (find_mech(mech, SUN_CKM_SHA256_HMAC));
 }
 
 /*
  * Start the KCF session, load the key
  */
 int
-smb2_hmac_init(smb_sign_ctx_t *ctxp, smb_sign_mech_t *mech,
+smb2_hmac_init(smb_sign_ctx_t *ctxp, smb_crypto_mech_t *mech,
     uint8_t *key, size_t key_len)
 {
 	crypto_key_t ckey;
@@ -152,7 +162,12 @@
 
 	rv = crypto_mac_update(ctx, &data, 0);
 
-	return (rv == CRYPTO_SUCCESS ? 0 : -1);
+	if (rv != CRYPTO_SUCCESS) {
+		crypto_cancel_ctx(ctx);
+		return (-1);
+	}
+
+	return (0);
 }
 
 /*
@@ -178,3 +193,80 @@
 
 	return (rv == CRYPTO_SUCCESS ? 0 : -1);
 }
+
+/*
+ * SMB3 signing helpers:
+ * (getmech, init, update, final)
+ */
+
+int
+smb3_cmac_getmech(smb_crypto_mech_t *mech)
+{
+	return (find_mech(mech, SUN_CKM_AES_CMAC));
+}
+
+/*
+ * Start the KCF session, load the key
+ */
+int
+smb3_cmac_init(smb_sign_ctx_t *ctxp, smb_crypto_mech_t *mech,
+    uint8_t *key, size_t key_len)
+{
+	crypto_key_t ckey;
+	int rv;
+
+	bzero(&ckey, sizeof (ckey));
+	ckey.ck_format = CRYPTO_KEY_RAW;
+	ckey.ck_data = key;
+	ckey.ck_length = key_len * 8; /* in bits */
+
+	rv = crypto_mac_init(mech, &ckey, NULL, ctxp, NULL);
+
+	return (rv == CRYPTO_SUCCESS ? 0 : -1);
+}
+
+/*
+ * Digest one segment
+ */
+int
+smb3_cmac_update(smb_sign_ctx_t ctx, uint8_t *in, size_t len)
+{
+	crypto_data_t data;
+	int rv;
+
+	bzero(&data, sizeof (data));
+	data.cd_format = CRYPTO_DATA_RAW;
+	data.cd_length = len;
+	data.cd_raw.iov_base = (void *)in;
+	data.cd_raw.iov_len = len;
+
+	rv = crypto_mac_update(ctx, &data, 0);
+
+	if (rv != CRYPTO_SUCCESS) {
+		crypto_cancel_ctx(ctx);
+		return (-1);
+	}
+
+	return (0);
+}
+
+/*
+ * Note, the SMB2 signature is just the AES CMAC digest.
+ * (both are 16 bytes long)
+ */
+int
+smb3_cmac_final(smb_sign_ctx_t ctx, uint8_t *digest16)
+{
+	crypto_data_t out;
+	int rv;
+
+	bzero(&out, sizeof (out));
+	out.cd_format = CRYPTO_DATA_RAW;
+	out.cd_length = SMB2_SIG_SIZE;
+	out.cd_raw.iov_len = SMB2_SIG_SIZE;
+	out.cd_raw.iov_base = (void *)digest16;
+
+	rv = crypto_mac_final(ctx, &out, 0);
+
+	return (rv == CRYPTO_SUCCESS ? 0 : -1);
+}
--- a/usr/src/uts/common/fs/smbsrv/smb_signing.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_signing.c	Wed Jun 12 22:46:54 2019 +0200
@@ -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 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 /*
  * These routines provide the SMB MAC signing for the SMB server.
@@ -40,7 +40,7 @@
 
 #include <sys/uio.h>
 #include <smbsrv/smb_kproto.h>
-#include <smbsrv/smb_signing.h>
+#include <smbsrv/smb_kcrypt.h>
 #include <sys/isa_defs.h>
 #include <sys/byteorder.h>
 
@@ -104,7 +104,7 @@
 static void
 smb_sign_fini(smb_session_t *s)
 {
-	smb_sign_mech_t *mech;
+	smb_crypto_mech_t *mech;
 
 	if ((mech = s->sign_mech) != NULL) {
 		kmem_free(mech, sizeof (*mech));
@@ -119,13 +119,13 @@
  * NTLM response and store it in the signing structure.
  * This is what begins SMB signing.
  */
-int
+void
 smb_sign_begin(smb_request_t *sr, smb_token_t *token)
 {
 	smb_arg_sessionsetup_t *sinfo = sr->sr_ssetup;
 	smb_session_t *session = sr->session;
 	struct smb_sign *sign = &session->signing;
-	smb_sign_mech_t *mech;
+	smb_crypto_mech_t *mech;
 	int rc;
 
 	/*
@@ -135,7 +135,7 @@
 	 * session key, in which case: just don't sign.
 	 */
 	if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0)
-		return (0);
+		return;
 
 	/*
 	 * Session-level initialization (once per session)
@@ -148,7 +148,7 @@
 	 */
 	if (sign->mackey != NULL) {
 		smb_rwx_rwexit(&session->s_lock);
-		return (0);
+		return;
 	}
 
 	/*
@@ -160,7 +160,7 @@
 		if (rc != 0) {
 			kmem_free(mech, sizeof (*mech));
 			smb_rwx_rwexit(&session->s_lock);
-			return (rc);
+			return;
 		}
 		session->sign_mech = mech;
 		session->sign_fini = smb_sign_fini;
@@ -187,14 +187,14 @@
 	sr->reply_seqnum = 1;
 	sign->flags = 0;
 
-	if (session->secmode & NEGOTIATE_SECURITY_SIGNATURES_ENABLED) {
+	if (session->srv_secmode & NEGOTIATE_SECURITY_SIGNATURES_ENABLED) {
 		sign->flags |= SMB_SIGNING_ENABLED;
-		if (session->secmode & NEGOTIATE_SECURITY_SIGNATURES_REQUIRED)
+		if (session->srv_secmode &
+		    NEGOTIATE_SECURITY_SIGNATURES_REQUIRED)
 			sign->flags |= SMB_SIGNING_CHECK;
 	}
 
 	smb_rwx_rwexit(&session->s_lock);
-	return (0);
 }
 
 /*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,655 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+/*
+ * (SMB1/SMB2) Server-level Oplock support.
+ *
+ * Conceptually, this is a separate layer on top of the
+ * file system (FS) layer oplock code in smb_cmn_oplock.c.
+ * If these layers were more distinct, the FS layer would
+ * need to use call-back functions (installed from here)
+ * to "indicate an oplock break to the server" (see below).
+ * As these layers are all in the same kernel module, the
+ * delivery of these break indications just uses a direct
+ * function call to smb_oplock_ind_break() below.
+ *
+ * This layer is responsible for handling the break indication,
+ * which often requires scheduling a taskq job in the server,
+ * and sending an oplock break mesage to the client using
+ * the appropriate protocol for the open handle affected.
+ *
+ * The details of composing an oplock break message, the
+ * protocol-specific details of requesting an oplock, and
+ * returning that oplock to the client are in the files:
+ *  smb_oplock.c, smb2_oplock.c, smb2_lease.c
+ */
+
+#include <smbsrv/smb2_kproto.h>
+#include <smbsrv/smb_oplock.h>
+
+/*
+ * Verify relationship between BREAK_TO_... and CACHE bits,
+ * used when setting the BREAK_TO_... below.
+ */
+#if BREAK_TO_READ_CACHING != (READ_CACHING << BREAK_SHIFT)
+#error "BREAK_TO_READ_CACHING"
+#endif
+#if BREAK_TO_HANDLE_CACHING != (HANDLE_CACHING << BREAK_SHIFT)
+#error "BREAK_TO_HANDLE_CACHING"
+#endif
+#if BREAK_TO_WRITE_CACHING != (WRITE_CACHING << BREAK_SHIFT)
+#error "BREAK_TO_WRITE_CACHING"
+#endif
+#define	CACHE_RWH (READ_CACHING | WRITE_CACHING | HANDLE_CACHING)
+
+/*
+ * This is the timeout used in the thread that sends an
+ * oplock break and waits for the client to respond
+ * before it breaks the oplock locally.
+ */
+int smb_oplock_timeout_ack = 30000; /* mSec. */
+
+/*
+ * This is the timeout used in threads that have just
+ * finished some sort of oplock request and now must
+ * wait for (possibly multiple) breaks to complete.
+ * This value must be at least a couple seconds LONGER
+ * than the ack timeout above so that I/O callers won't
+ * give up waiting before the local ack timeout.
+ */
+int smb_oplock_timeout_def = 45000; /* mSec. */
+
+static void smb_oplock_async_break(void *);
+static void smb_oplock_hdl_clear(smb_ofile_t *);
+
+
+/*
+ * 2.1.5.17.3 Indicating an Oplock Break to the Server
+ *
+ * The inputs for indicating an oplock break to the server are:
+ *
+ *	BreakingOplockOpen: The Open used to request the oplock
+ *	  that is now breaking.
+ *	 NewOplockLevel: The type of oplock the requested oplock
+ *	  has been broken to.  Valid values are as follows:
+ *		LEVEL_NONE (that is, no oplock)
+ *		LEVEL_TWO
+ *		A combination of one or more of the following flags:
+ *			READ_CACHING
+ *			HANDLE_CACHING
+ *			WRITE_CACHING
+ *	AcknowledgeRequired: A Boolean value; TRUE if the server
+ *	  MUST acknowledge the oplock break, FALSE if not,
+ *	  as specified in section 2.1.5.18.
+ *	OplockCompletionStatus: The NTSTATUS code to return to the server.
+ *
+ * This algorithm simply represents the completion of an oplock request,
+ * as specified in section 2.1.5.17.1 or section 2.1.5.17.2. The server
+ * is expected to associate the return status from this algorithm with
+ * BreakingOplockOpen, which is the Open passed in when it requested
+ * the oplock that is now breaking.
+ *
+ * It is important to note that because several oplocks can be outstanding
+ * in parallel, although this algorithm represents the completion of an
+ * oplock request, it might not result in the completion of the algorithm
+ * that called it. In particular, calling this algorithm will result in
+ * completion of the caller only if BreakingOplockOpen is the same as the
+ * Open with which the calling algorithm was itself called. To mitigate
+ * confusion, each algorithm that refers to this section will specify
+ * whether that algorithm's operation terminates at that point or not.
+ *
+ * The object store MUST return OplockCompletionStatus,
+ * AcknowledgeRequired, and NewOplockLevel to the server (the algorithm is
+ * as specified in section 2.1.5.17.1 and section 2.1.5.17.2).
+ *
+ * Implementation:
+ *
+ * We use two versions of this function:
+ *	smb_oplock_ind_break_in_ack
+ *	smb_oplock_ind_break
+ *
+ * The first is used when we're handling an Oplock Break Ack.
+ * The second is used when other operations cause a break,
+ * generally in one of the smb_oplock_break_... functions.
+ *
+ * Note that these are call-back functions that may be called with the
+ * node ofile list rwlock held and the node oplock mutex entered, so
+ * these should ONLY schedule oplock break work, and MUST NOT attempt
+ * any actions that might require either of those locks.
+ */
+
+/*
+ * smb_oplock_ind_break_in_ack
+ *
+ * Variant of smb_oplock_ind_break() for the oplock Ack handler.
+ * When we need to indicate another oplock break from within the
+ * Ack handler (during the Ack. of some previous oplock break)
+ * we need to make sure this new break indication goes out only
+ * AFTER the reply to the current break ack. is sent out.
+ *
+ * In this case, we always have an SR (the break ack) so we can
+ * append the "ind break" work to the current SR and let the
+ * request hander thread do this work after the reply is sent.
+ * Note: this is always an SMB2 or later request, because this
+ * only happens for "granular" oplocks, which are SMB2-only.
+ *
+ * This is mostly the same as smb_oplock_ind_break() except:
+ * - The only CompletionStatus possible is STATUS_CANT_GRANT.
+ * - Instead of taskq_dispatch this appends the new SR to
+ *   the "post work" queue on the current SR.
+ *
+ * Note called with the node ofile list rwlock held and
+ * the oplock mutex entered.
+ */
+void
+smb_oplock_ind_break_in_ack(smb_request_t *ack_sr, smb_ofile_t *ofile,
+    uint32_t NewLevel, boolean_t AckRequired)
+{
+	smb_request_t *new_sr;
+
+	/*
+	 * This should happen only with SMB2 or later,
+	 * but in case that ever changes...
+	 */
+	if (ack_sr->session->dialect < SMB_VERS_2_BASE) {
+		smb_oplock_ind_break(ofile, NewLevel,
+		    AckRequired, STATUS_CANT_GRANT);
+		return;
+	}
+
+	/*
+	 * We're going to schedule a request that will have a
+	 * reference to this ofile. Get the hold first.
+	 */
+	if (!smb_ofile_hold_olbrk(ofile)) {
+		/* It's closing (or whatever).  Nothing to do. */
+		return;
+	}
+
+	/*
+	 * When called from Ack processing, we want to use a
+	 * request on the session doing the ack.  If we can't
+	 * allocate a request on that session (because it's
+	 * now disconnecting) just fall-back to the normal
+	 * oplock break code path which deals with that.
+	 * Once we have a request on the ack session, that
+	 * session won't go away until the request is done.
+	 */
+	new_sr = smb_request_alloc(ack_sr->session, 0);
+	if (new_sr == NULL) {
+		smb_oplock_ind_break(ofile, NewLevel,
+		    AckRequired, STATUS_CANT_GRANT);
+		smb_ofile_release(ofile);
+		return;
+	}
+
+	new_sr->sr_state = SMB_REQ_STATE_SUBMITTED;
+	new_sr->smb2_async = B_TRUE;
+	new_sr->user_cr = zone_kcred();
+	new_sr->fid_ofile = ofile;
+	/* Leave tid_tree, uid_user NULL. */
+	new_sr->arg.olbrk.NewLevel = NewLevel;
+	new_sr->arg.olbrk.AckRequired = AckRequired;
+
+	/*
+	 * Using smb2_cmd_code to indicate what to call.
+	 * work func. will call smb_oplock_send_brk
+	 */
+	new_sr->smb2_cmd_code = SMB2_OPLOCK_BREAK;
+	smb2sr_append_postwork(ack_sr, new_sr);
+}
+
+/*
+ * smb_oplock_ind_break
+ *
+ * This is the function described in [MS-FSA] 2.1.5.17.3
+ * which is called many places in the oplock break code.
+ *
+ * Schedule a request & taskq job to do oplock break work
+ * as requested by the FS-level code (smb_cmn_oplock.c).
+ *
+ * Note called with the node ofile list rwlock held and
+ * the oplock mutex entered.
+ */
+void
+smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel,
+    boolean_t AckRequired, uint32_t CompletionStatus)
+{
+	smb_server_t *sv = ofile->f_server;
+	smb_request_t *sr = NULL;
+
+	/*
+	 * See notes at smb_oplock_async_break re. CompletionStatus
+	 * Check for any invalid codes here, so assert happens in
+	 * the thread passing an unexpected value.
+	 * The real work happens in a taskq job.
+	 */
+	switch (CompletionStatus) {
+
+	case NT_STATUS_SUCCESS:
+	case STATUS_CANT_GRANT:
+		/* Send break via taskq job. */
+		break;
+
+	case STATUS_NEW_HANDLE:
+	case NT_STATUS_OPLOCK_HANDLE_CLOSED:
+		smb_oplock_hdl_clear(ofile);
+		return;
+
+	default:
+		ASSERT(0);
+		return;
+	}
+
+	/*
+	 * We're going to schedule a request that will have a
+	 * reference to this ofile. Get the hold first.
+	 */
+	if (!smb_ofile_hold_olbrk(ofile)) {
+		/* It's closing (or whatever).  Nothing to do. */
+		return;
+	}
+
+	/*
+	 * We need a request allocated on the session that owns
+	 * this ofile in order to safely send on that session.
+	 *
+	 * Note that while we hold a ref. on the ofile, it's
+	 * f_session will not change.  An ofile in state
+	 * _ORPHANED will have f_session == NULL, but the
+	 * f_session won't _change_ while we have a ref,
+	 * and won't be torn down under our feet.
+	 *
+	 * If f_session is NULL, or it's in a state that doesn't
+	 * allow new requests, use the special "server" session.
+	 */
+	if (ofile->f_session != NULL)
+		sr = smb_request_alloc(ofile->f_session, 0);
+	if (sr == NULL)
+		sr = smb_request_alloc(sv->sv_session, 0);
+
+	sr->sr_state = SMB_REQ_STATE_SUBMITTED;
+	sr->smb2_async = B_TRUE;
+	sr->user_cr = zone_kcred();
+	sr->fid_ofile = ofile;
+	/* Leave tid_tree, uid_user NULL. */
+	sr->arg.olbrk.NewLevel = NewLevel;
+	sr->arg.olbrk.AckRequired = AckRequired;
+	sr->smb2_status = CompletionStatus;
+
+	(void) taskq_dispatch(
+	    sv->sv_worker_pool,
+	    smb_oplock_async_break, sr, TQ_SLEEP);
+}
+
+/*
+ * smb_oplock_async_break
+ *
+ * Called via the taskq to handle an asynchronous oplock break.
+ * We have a hold on the ofile, which will be released in
+ * smb_request_free (via sr->fid_ofile)
+ *
+ * Note we have: sr->uid_user == NULL, sr->tid_tree == NULL.
+ * Nothing called here needs those.
+ *
+ * Note that NewLevel as provided by the FS up-call does NOT
+ * include the GRANULAR flag.  The SMB level is expected to
+ * keep track of how each oplock was acquired (by lease or
+ * traditional oplock request) and put the GRANULAR flag
+ * back into the oplock state when calling down to the
+ * FS-level code.  Also note that the lease break message
+ * carries only the cache flags, not the GRANULAR flag.
+ */
+static void
+smb_oplock_async_break(void *arg)
+{
+	smb_request_t	*sr = arg;
+	uint32_t	CompletionStatus;
+
+	SMB_REQ_VALID(sr);
+
+	CompletionStatus = sr->smb2_status;
+	sr->smb2_status = NT_STATUS_SUCCESS;
+
+	mutex_enter(&sr->sr_mutex);
+	sr->sr_worker = curthread;
+	sr->sr_state = SMB_REQ_STATE_ACTIVE;
+	mutex_exit(&sr->sr_mutex);
+
+	/*
+	 * Note that the CompletionStatus from the FS level
+	 * (smb_cmn_oplock.c) encodes what kind of action we
+	 * need to take at the SMB level.
+	 */
+	switch (CompletionStatus) {
+
+	case STATUS_CANT_GRANT:
+	case NT_STATUS_SUCCESS:
+		smb_oplock_send_brk(sr);
+		break;
+
+	default:
+		/* Checked by caller. */
+		ASSERT(0);
+		break;
+	}
+
+	sr->sr_state = SMB_REQ_STATE_COMPLETED;
+	smb_request_free(sr);
+}
+
+#ifdef DEBUG
+int smb_oplock_debug_wait = 0;
+#endif
+
+/*
+ * Send an oplock break over the wire, or if we can't,
+ * then process the oplock break locally.
+ *
+ * Note that we have sr->fid_ofile here but all the other
+ * normal sr members are NULL:  uid_user, tid_tree.
+ * Also sr->session may or may not be the same session as
+ * the ofile came from (ofile->f_session) depending on
+ * whether this is a "live" open or an orphaned DH,
+ * where ofile->f_session will be NULL.
+ *
+ * Given that we don't always have a session, we determine
+ * the oplock type (lease etc) from f_oplock.og_dialect.
+ */
+void
+smb_oplock_send_brk(smb_request_t *sr)
+{
+	smb_ofile_t	*ofile;
+	smb_lease_t	*lease;
+	uint32_t	NewLevel;
+	boolean_t	AckReq;
+	uint32_t	status;
+	int		rc;
+
+	ofile = sr->fid_ofile;
+	NewLevel = sr->arg.olbrk.NewLevel;
+	AckReq = sr->arg.olbrk.AckRequired;
+	lease = ofile->f_lease;
+
+	/*
+	 * Build the break message in sr->reply.
+	 * It's free'd in smb_request_free().
+	 * Also updates the lease and NewLevel.
+	 */
+	sr->reply.max_bytes = MLEN;
+	if (ofile->f_oplock.og_dialect >= SMB_VERS_2_BASE) {
+		if (lease != NULL) {
+			/*
+			 * Oplock state has changed, so
+			 * update the epoch.
+			 */
+			mutex_enter(&lease->ls_mutex);
+			lease->ls_epoch++;
+			mutex_exit(&lease->ls_mutex);
+
+			/* Note, needs "old" state in og_state */
+			smb2_lease_break_notification(sr,
+			    (NewLevel & CACHE_RWH), AckReq);
+			NewLevel |= OPLOCK_LEVEL_GRANULAR;
+		} else {
+			smb2_oplock_break_notification(sr, NewLevel);
+		}
+	} else {
+		/*
+		 * SMB1 clients should only get Level II oplocks if they
+		 * set the capability indicating they know about them.
+		 */
+		if (NewLevel == OPLOCK_LEVEL_TWO &&
+		    ofile->f_oplock.og_dialect < NT_LM_0_12)
+			NewLevel = OPLOCK_LEVEL_NONE;
+		smb1_oplock_break_notification(sr, NewLevel);
+	}
+
+	/*
+	 * Keep track of what we last sent to the client,
+	 * preserving the GRANULAR flag (if a lease).
+	 * If we're expecting an ACK, set og_breaking
+	 * (and maybe lease->ls_breaking) so we can
+	 * later find the ofile with breaks pending.
+	 */
+	if (AckReq) {
+		uint32_t BreakTo;
+
+		if (lease != NULL) {
+			BreakTo = (NewLevel & CACHE_RWH) << BREAK_SHIFT;
+			if (BreakTo == 0)
+				BreakTo = BREAK_TO_NO_CACHING;
+			lease->ls_breaking = BreakTo;
+		} else {
+			if ((NewLevel & LEVEL_TWO_OPLOCK) != 0)
+				BreakTo = BREAK_TO_TWO;
+			else
+				BreakTo = BREAK_TO_NONE;
+		}
+		/* Will update og_state in ack. */
+		ofile->f_oplock.og_breaking = BreakTo;
+	} else {
+		if (lease != NULL)
+			lease->ls_state = NewLevel & CACHE_RWH;
+		ofile->f_oplock.og_state = NewLevel;
+	}
+
+	/*
+	 * Try to send the break message to the client.
+	 * When we get to multi-channel, this is supposed to
+	 * try to send on every channel before giving up.
+	 */
+	if (sr->session == ofile->f_session)
+		rc = smb_session_send(sr->session, 0, &sr->reply);
+	else
+		rc = ENOTCONN;
+
+	if (rc == 0) {
+		/*
+		 * OK, we were able to send the break message.
+		 * If no ack. required, we're done.
+		 */
+		if (!AckReq)
+			return;
+
+		/*
+		 * We're expecting an ACK.  Wait in this thread
+		 * so we can log clients that don't respond.
+		 *
+		 * If debugging, may want to break after a
+		 * short wait to look into why we might be
+		 * holding up progress.  (i.e. locks?)
+		 */
+#ifdef DEBUG
+		if (smb_oplock_debug_wait > 0) {
+			status = smb_oplock_wait_break(ofile->f_node,
+			    smb_oplock_debug_wait);
+			if (status == 0)
+				return;
+			cmn_err(CE_NOTE, "clnt %s oplock break wait debug",
+			    sr->session->ip_addr_str);
+			debug_enter("oplock_wait");
+		}
+#endif
+		status = smb_oplock_wait_break(ofile->f_node,
+		    smb_oplock_timeout_ack);
+		if (status == 0)
+			return;
+
+		cmn_err(CE_NOTE, "clnt %s oplock break timeout",
+		    sr->session->ip_addr_str);
+		DTRACE_PROBE1(break_timeout, smb_ofile_t, ofile);
+
+		/*
+		 * Will do local ack below.  Note, after timeout,
+		 * do a break to none or "no caching" regardless
+		 * of what the passed in cache level was.
+		 * That means: clear all except GRANULAR.
+		 */
+		NewLevel &= OPLOCK_LEVEL_GRANULAR;
+	} else {
+		/*
+		 * We were unable to send the oplock break request.
+		 * Generally, that means we have no connection to this
+		 * client right now, and this ofile will have state
+		 * SMB_OFILE_STATE_ORPHANED.  We either close the handle
+		 * or break the oplock locally, in which case the client
+		 * gets the updated oplock state when they reconnect.
+		 * Decide whether to keep or close.
+		 *
+		 * Relevant [MS-SMB2] sections:
+		 *
+		 * 3.3.4.6 Object Store Indicates an Oplock Break
+		 * If Open.Connection is NULL, Open.IsResilient is FALSE,
+		 * Open.IsDurable is FALSE and Open.IsPersistent is FALSE,
+		 * the server SHOULD close the Open as specified in...
+		 *
+		 * 3.3.4.7 Object Store Indicates a Lease Break
+		 * If Open.Connection is NULL, the server MUST close the
+		 * Open as specified in ... for the following cases:
+		 * - Open.IsResilient is FALSE, Open.IsDurable is FALSE,
+		 *   and Open.IsPersistent is FALSE.
+		 * - Lease.BreakToLeaseState does not contain
+		 *   ...HANDLE_CACHING and Open.IsDurable is TRUE.
+		 * If Lease.LeaseOpens is empty, (... local ack to "none").
+		 */
+
+		/*
+		 * See similar logic in smb_dh_should_save
+		 */
+		switch (ofile->dh_vers) {
+		case SMB2_RESILIENT:
+			break;			/* keep DH */
+
+		case SMB2_DURABLE_V2:
+			if (ofile->dh_persist)
+				break;		/* keep DH */
+			/* FALLTHROUGH */
+		case SMB2_DURABLE_V1:
+			/* IS durable (v1 or v2) */
+			if ((NewLevel & (OPLOCK_LEVEL_BATCH |
+			    OPLOCK_LEVEL_CACHE_HANDLE)) != 0)
+				break;		/* keep DH */
+			/* FALLTHROUGH */
+		case SMB2_NOT_DURABLE:
+		default:
+			smb_ofile_close(ofile, 0);
+			return;
+		}
+		/* Keep this ofile (durable handle). */
+
+		if (!AckReq) {
+			/* Nothing more to do. */
+			return;
+		}
+	}
+
+	/*
+	 * We get here after either an oplock break ack timeout,
+	 * or a send failure for a durable handle type that we
+	 * preserve rather than just close.  Do local ack.
+	 */
+	ofile->f_oplock.og_breaking = 0;
+	if (lease != NULL)
+		lease->ls_breaking = 0;
+
+	status = smb_oplock_ack_break(sr, ofile, &NewLevel);
+	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+		/* Not expecting this status return. */
+		cmn_err(CE_NOTE, "clnt local oplock ack wait?");
+		(void) smb_oplock_wait_break(ofile->f_node,
+		    smb_oplock_timeout_ack);
+		status = 0;
+	}
+	if (status != 0) {
+		cmn_err(CE_NOTE, "clnt local oplock ack, "
+		    "status=0x%x", status);
+	}
+
+	/* Update og_state as if we heard from the client. */
+	ofile->f_oplock.og_state = NewLevel;
+	if (lease != NULL) {
+		lease->ls_state = NewLevel & CACHE_RWH;
+	}
+}
+
+/*
+ * See: NT_STATUS_OPLOCK_HANDLE_CLOSED above,
+ * and: STATUS_NEW_HANDLE
+ *
+ * The FS-level oplock layer calls this to update the
+ * SMB-level state when a handle loses its oplock.
+ */
+static void
+smb_oplock_hdl_clear(smb_ofile_t *ofile)
+{
+	smb_lease_t *lease = ofile->f_lease;
+
+	if (lease != NULL) {
+		if (lease->ls_oplock_ofile == ofile) {
+			/* Last close on the lease. */
+			lease->ls_oplock_ofile = NULL;
+		}
+	}
+	ofile->f_oplock.og_state = 0;
+	ofile->f_oplock.og_breaking = 0;
+}
+
+/*
+ * Wait up to "timeout" mSec. for the current oplock "breaking" flags
+ * to be cleared (by smb_oplock_ack_break or smb_oplock_break_CLOSE).
+ *
+ * Callers of the above public oplock functions:
+ *	smb_oplock_request()
+ *	smb_oplock_ack_break()
+ *	smb_oplock_break_OPEN() ...
+ * check for return status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS
+ * and call this function to wait for the break to complete.
+ *
+ * Most callers should use this default timeout, which they get
+ * by passing zero as the timeout arg.  This include places where
+ * we're about to do something that invalidates some cache.
+ */
+uint32_t
+smb_oplock_wait_break(smb_node_t *node, int timeout)  /* mSec. */
+{
+	smb_oplock_t	*ol;
+	clock_t		time, rv;
+	uint32_t	status = 0;
+
+	if (timeout == 0)
+		timeout = smb_oplock_timeout_def;
+
+	SMB_NODE_VALID(node);
+	ol = &node->n_oplock;
+
+	mutex_enter(&ol->ol_mutex);
+	time = MSEC_TO_TICK(timeout) + ddi_get_lbolt();
+
+	while ((ol->ol_state & BREAK_ANY) != 0) {
+		ol->waiters++;
+		rv = cv_timedwait(&ol->WaitingOpenCV,
+		    &ol->ol_mutex, time);
+		ol->waiters--;
+		if (rv < 0) {
+			status = NT_STATUS_CANNOT_BREAK_OPLOCK;
+			break;
+		}
+	}
+
+	mutex_exit(&ol->ol_mutex);
+
+	return (status);
+}
--- a/usr/src/uts/common/fs/smbsrv/smb_trans2_find.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_trans2_find.c	Wed Jun 12 22:46:54 2019 +0200
@@ -344,7 +344,8 @@
 	if (count == 0) {
 		smb_odir_close(od);
 		smb_odir_release(od);
-		smbsr_errno(sr, ENOENT);
+		smbsr_status(sr, NT_STATUS_NO_SUCH_FILE,
+		    ERRDOS, ERROR_FILE_NOT_FOUND);
 		return (SDRC_ERROR);
 	}
 
--- a/usr/src/uts/common/fs/smbsrv/smb_tree.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_tree.c	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 2016 by Delphix. All rights reserved.
  */
 
@@ -122,10 +122,14 @@
  *
  * Transition T2
  *
+ *    This transition occurs in smb_tree_disconnect()
+ *
+ * Transition T3
+ *
  *    This transition occurs in smb_tree_release(). The resources associated
  *    with the tree are freed as well as the tree structure. For the transition
- *    to occur, the tree must be in the SMB_TREE_STATE_DISCONNECTED state and
- *    the reference count be zero.
+ *    to occur, the tree must be in the SMB_TREE_STATE_DISCONNECTED and the
+ *    reference count must be zero.
  *
  * Comments
  * --------
@@ -144,7 +148,11 @@
  *    Rules of access to a tree structure:
  *
  *    1) In order to avoid deadlocks, when both (mutex and lock of the user
- *       list) have to be entered, the lock must be entered first.
+ *       list) have to be entered, the lock must be entered first. Additionally,
+ *       when both the (mutex and lock of the ofile list) have to be entered,
+ *       the mutex must be entered first. However, the ofile list lock must NOT
+ *       be dropped while the mutex is held in such a way that the ofile deleteq
+ *       is flushed.
  *
  *    2) All actions applied to a tree require a reference count.
  *
@@ -178,8 +186,8 @@
 uint32_t	smb_tree_connect_ipc(smb_request_t *, smb_arg_tcon_t *);
 static smb_tree_t *smb_tree_alloc(smb_request_t *, const smb_kshare_t *,
     smb_node_t *, uint32_t, uint32_t);
+static void smb_tree_dealloc(void *);
 static boolean_t smb_tree_is_connected_locked(smb_tree_t *);
-static boolean_t smb_tree_is_disconnected(smb_tree_t *);
 static char *smb_tree_get_sharename(char *);
 static int smb_tree_getattr(const smb_kshare_t *, smb_node_t *, smb_tree_t *);
 static void smb_tree_get_volname(vfs_t *, smb_tree_t *);
@@ -252,6 +260,25 @@
 	tcon->name = name;
 	sr->sr_tcon.si = si;
 
+	/*
+	 * [MS-SMB2] 3.3.5.7 Receiving an SMB2 TREE_CONNECT Request
+	 *
+	 * If we support 3.x, RejectUnencryptedAccess is TRUE,
+	 * if Tcon.EncryptData is TRUE or global EncryptData is TRUE,
+	 * and the connection doesn't support encryption,
+	 * return ACCESS_DENIED.
+	 *
+	 * If RejectUnencryptedAccess is TRUE, we force max_protocol
+	 * to at least 3.0. Additionally, if the tree requires encryption,
+	 * we don't care what we support, we still enforce encryption.
+	 */
+	if ((sr->sr_server->sv_cfg.skc_encrypt == SMB_CONFIG_REQUIRED ||
+	    si->shr_encrypt == SMB_CONFIG_REQUIRED) &&
+	    (sr->session->srv_cap & SMB2_CAP_ENCRYPTION) == 0) {
+		status = NT_STATUS_ACCESS_DENIED;
+		goto out;
+	}
+
 	switch (si->shr_type & STYPE_MASK) {
 	case STYPE_DISKTREE:
 		status = smb_tree_connect_disk(sr, &sr->sr_tcon);
@@ -267,6 +294,7 @@
 		break;
 	}
 
+out:
 	smb_kshare_release(sr->sr_server, si);
 	sr->sr_tcon.si = NULL;
 
@@ -297,7 +325,7 @@
 			/*
 			 * The files opened under this tree are closed.
 			 */
-			smb_ofile_close_all(tree);
+			smb_ofile_close_all(tree, 0);
 			/*
 			 * The directories opened under this tree are closed.
 			 */
@@ -371,44 +399,34 @@
 {
 	SMB_TREE_VALID(tree);
 
-	mutex_enter(&tree->t_mutex);
-	ASSERT(tree->t_refcnt);
-	tree->t_refcnt--;
-
 	/* flush the ofile and odir lists' delete queues */
 	smb_llist_flush(&tree->t_ofile_list);
 	smb_llist_flush(&tree->t_odir_list);
 
-	if (smb_tree_is_disconnected(tree) && (tree->t_refcnt == 0))
-		smb_session_post_tree(tree->t_session, tree);
+	mutex_enter(&tree->t_mutex);
+	ASSERT(tree->t_refcnt);
+	tree->t_refcnt--;
+
+	switch (tree->t_state) {
+	case SMB_TREE_STATE_DISCONNECTED:
+		if (tree->t_refcnt == 0) {
+			smb_session_t *ssn = tree->t_session;
+			tree->t_state = SMB_TREE_STATE_DISCONNECTED;
+			smb_llist_post(&ssn->s_tree_list, tree,
+			    smb_tree_dealloc);
+		}
+		break;
+	case SMB_TREE_STATE_CONNECTED:
+	case SMB_TREE_STATE_DISCONNECTING:
+		break;
+	default:
+		ASSERT(0);
+		break;
+	}
 
 	mutex_exit(&tree->t_mutex);
 }
 
-void
-smb_tree_post_ofile(smb_tree_t *tree, smb_ofile_t *of)
-{
-	SMB_TREE_VALID(tree);
-	SMB_OFILE_VALID(of);
-	ASSERT(of->f_refcnt == 0);
-	ASSERT(of->f_state == SMB_OFILE_STATE_CLOSED);
-	ASSERT(of->f_tree == tree);
-
-	smb_llist_post(&tree->t_ofile_list, of, smb_ofile_delete);
-}
-
-void
-smb_tree_post_odir(smb_tree_t *tree, smb_odir_t *od)
-{
-	SMB_TREE_VALID(tree);
-	SMB_ODIR_VALID(od);
-	ASSERT(od->d_refcnt == 0);
-	ASSERT(od->d_state == SMB_ODIR_STATE_CLOSED);
-	ASSERT(od->d_tree == tree);
-
-	smb_llist_post(&tree->t_odir_list, od, smb_odir_delete);
-}
-
 /*
  * Close ofiles and odirs that match pid.
  */
@@ -420,7 +438,7 @@
 	ASSERT(tree);
 	ASSERT(tree->t_magic == SMB_TREE_MAGIC);
 
-	smb_ofile_close_all_by_pid(tree, pid);
+	smb_ofile_close_all(tree, pid);
 	smb_tree_close_odirs(tree, pid);
 }
 
@@ -484,6 +502,11 @@
 	ASSERT(tree);
 	ASSERT(tree->t_magic == SMB_TREE_MAGIC);
 
+	/*
+	 * Note that ORPHANED ofiles aren't fclosable, as they have
+	 * no session, user, or tree by which they might be found.
+	 * They will eventually expire.
+	 */
 	if ((of = smb_ofile_lookup_by_uniqid(tree, uniqid)) == NULL)
 		return (ENOENT);
 
@@ -650,7 +673,7 @@
 	smb_user_t		*user = sr->uid_user;
 	smb_node_t		*dnode = NULL;
 	smb_node_t		*snode = NULL;
-	smb_kshare_t 		*si = tcon->si;
+	smb_kshare_t		*si = tcon->si;
 	char			*service = tcon->service;
 	char			last_component[MAXNAMELEN];
 	smb_tree_t		*tree;
@@ -772,7 +795,7 @@
 	smb_user_t		*user = sr->uid_user;
 	smb_node_t		*dnode = NULL;
 	smb_node_t		*snode = NULL;
-	smb_kshare_t 		*si = tcon->si;
+	smb_kshare_t		*si = tcon->si;
 	char			*service = tcon->service;
 	char			last_component[MAXNAMELEN];
 	smb_tree_t		*tree;
@@ -922,7 +945,7 @@
 	}
 
 	smb_llist_constructor(&tree->t_ofile_list, sizeof (smb_ofile_t),
-	    offsetof(smb_ofile_t, f_lnd));
+	    offsetof(smb_ofile_t, f_tree_lnd));
 
 	smb_llist_constructor(&tree->t_odir_list, sizeof (smb_odir_t),
 	    offsetof(smb_odir_t, d_lnd));
@@ -968,7 +991,7 @@
  * Remove the tree from the user's tree list before freeing resources
  * associated with the tree.
  */
-void
+static void
 smb_tree_dealloc(void *arg)
 {
 	smb_session_t	*session;
@@ -985,6 +1008,13 @@
 	atomic_dec_32(&session->s_tree_cnt);
 	smb_llist_exit(&session->s_tree_list);
 
+	/*
+	 * This tree is no longer on s_tree_list, however...
+	 *
+	 * This is called via smb_llist_post, which means it may run
+	 * BEFORE smb_tree_release drops t_mutex (if another thread
+	 * flushes the delete queue before we do).  Synchronize.
+	 */
 	mutex_enter(&tree->t_mutex);
 	mutex_exit(&tree->t_mutex);
 
@@ -1019,7 +1049,7 @@
 	case SMB_TREE_STATE_DISCONNECTING:
 	case SMB_TREE_STATE_DISCONNECTED:
 		/*
-		 * The tree exists but being diconnected or destroyed.
+		 * The tree exists but is being disconnected or destroyed.
 		 */
 		return (B_FALSE);
 
@@ -1030,27 +1060,6 @@
 }
 
 /*
- * Determine whether or not a tree is disconnected.
- * This function must be called with the tree mutex held.
- */
-static boolean_t
-smb_tree_is_disconnected(smb_tree_t *tree)
-{
-	switch (tree->t_state) {
-	case SMB_TREE_STATE_DISCONNECTED:
-		return (B_TRUE);
-
-	case SMB_TREE_STATE_CONNECTED:
-	case SMB_TREE_STATE_DISCONNECTING:
-		return (B_FALSE);
-
-	default:
-		ASSERT(0);
-		return (B_FALSE);
-	}
-}
-
-/*
  * Return a pointer to the share name within a share resource path.
  *
  * The share path may be a Uniform Naming Convention (UNC) string
@@ -1090,6 +1099,7 @@
 smb_tree_getattr(const smb_kshare_t *si, smb_node_t *node, smb_tree_t *tree)
 {
 	vfs_t *vfsp = SMB_NODE_VFS(node);
+	smb_cfg_val_t srv_encrypt;
 
 	ASSERT(vfsp);
 
@@ -1099,6 +1109,19 @@
 	smb_tree_get_volname(vfsp, tree);
 	smb_tree_get_flags(si, vfsp, tree);
 
+	srv_encrypt = tree->t_session->s_server->sv_cfg.skc_encrypt;
+	if (tree->t_session->dialect >= SMB_VERS_3_0) {
+		if (si->shr_encrypt == SMB_CONFIG_REQUIRED ||
+		    srv_encrypt == SMB_CONFIG_REQUIRED)
+			tree->t_encrypt = SMB_CONFIG_REQUIRED;
+		else if (si->shr_encrypt == SMB_CONFIG_ENABLED ||
+		    srv_encrypt == SMB_CONFIG_ENABLED)
+			tree->t_encrypt = SMB_CONFIG_ENABLED;
+		else
+			tree->t_encrypt = SMB_CONFIG_DISABLED;
+	} else
+		tree->t_encrypt = SMB_CONFIG_DISABLED;
+
 	VFS_RELE(vfsp);
 	return (0);
 }
@@ -1172,6 +1195,9 @@
 	if (si->shr_flags & SMB_SHRF_ABE)
 		flags |= SMB_TREE_ABE;
 
+	if (si->shr_flags & SMB_SHRF_FSO)
+		flags |= SMB_TREE_FORCE_L2_OPLOCK;
+
 	if (ssn->s_cfg.skc_oplock_enable) {
 		/* if 'smb' zfs property: oplocks=enabled */
 		flags |= SMB_TREE_OPLOCKS;
--- a/usr/src/uts/common/fs/smbsrv/smb_user.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_user.c	Wed Jun 12 22:46:54 2019 +0200
@@ -181,7 +181,9 @@
  *    Rules of access to a user structure:
  *
  *    1) In order to avoid deadlocks, when both (mutex and lock of the session
- *       list) have to be entered, the lock must be entered first.
+ *       list) have to be entered, the lock must be entered first. Additionally,
+ *       one may NOT flush the deleteq of either the tree list or the ofile list
+ *       while the user mutex is held.
  *
  *    2) All actions applied to a user require a reference count.
  *
@@ -208,23 +210,36 @@
 
 #define	ADMINISTRATORS_SID	"S-1-5-32-544"
 
+/* Don't leak object addresses */
+#define	SMB_USER_SSNID(u) \
+	((uintptr_t)&smb_cache_user ^ (uintptr_t)(u))
+
+static void smb_user_delete(void *);
 static int smb_user_enum_private(smb_user_t *, smb_svcenum_t *);
 static void smb_user_auth_logoff(smb_user_t *);
 static void smb_user_logoff_tq(void *);
 
-
 /*
  * Create a new user.
+ *
+ * For SMB2 and later, session IDs (u_ssnid) need to be unique among all
+ * current and "recent" sessions.  The session ID is derived from the
+ * address of the smb_user object (obscured by XOR with a constant).
+ * This adds a 3-bit generation number in the low bits, incremented
+ * when we allocate an smb_user_t from its kmem cache, so it can't
+ * be confused with a (recent) previous incarnation of this object.
  */
 smb_user_t *
 smb_user_new(smb_session_t *session)
 {
 	smb_user_t	*user;
+	uint_t		gen;	// generation (low 3 bits of ssnid)
 
 	ASSERT(session);
 	ASSERT(session->s_magic == SMB_SESSION_MAGIC);
 
 	user = kmem_cache_alloc(smb_cache_user, KM_SLEEP);
+	gen = (user->u_ssnid + 1) & 7;
 	bzero(user, sizeof (smb_user_t));
 
 	user->u_refcnt = 1;
@@ -234,6 +249,7 @@
 
 	if (smb_idpool_alloc(&session->s_uid_pool, &user->u_uid))
 		goto errout;
+	user->u_ssnid = SMB_USER_SSNID(user) + gen;
 
 	mutex_init(&user->u_mutex, NULL, MUTEX_DEFAULT, NULL);
 	user->u_state = SMB_USER_STATE_LOGGING_ON;
@@ -317,8 +333,11 @@
 /*
  * smb_user_logoff
  *
- * Change the user state and disconnect trees.
+ * Change the user state to "logging off" and disconnect trees.
  * The user list must not be entered or modified here.
+ *
+ * We remain in state "logging off" until the last ref. is gone,
+ * then smb_user_release takes us to state "logged off".
  */
 void
 smb_user_logoff(
@@ -337,8 +356,15 @@
 		user->u_authsock = NULL;
 		tmo = user->u_auth_tmo;
 		user->u_auth_tmo = NULL;
-		user->u_state = SMB_USER_STATE_LOGGED_OFF;
-		smb_server_dec_users(user->u_server);
+		user->u_state = SMB_USER_STATE_LOGGING_OFF;
+		mutex_exit(&user->u_mutex);
+
+		/* Timeout callback takes u_mutex. See untimeout(9f) */
+		if (tmo != NULL)
+			(void) untimeout(tmo);
+		/* This close can block, so not under the mutex. */
+		if (authsock != NULL)
+			smb_authsock_close(user, authsock);
 		break;
 
 	case SMB_USER_STATE_LOGGED_ON:
@@ -350,29 +376,18 @@
 		mutex_exit(&user->u_mutex);
 		smb_session_disconnect_owned_trees(user->u_session, user);
 		smb_user_auth_logoff(user);
-		mutex_enter(&user->u_mutex);
-		user->u_state = SMB_USER_STATE_LOGGED_OFF;
-		smb_server_dec_users(user->u_server);
 		break;
 
 	case SMB_USER_STATE_LOGGED_OFF:
 	case SMB_USER_STATE_LOGGING_OFF:
+		mutex_exit(&user->u_mutex);
 		break;
 
 	default:
 		ASSERT(0);
+		mutex_exit(&user->u_mutex);
 		break;
 	}
-	mutex_exit(&user->u_mutex);
-
-	/* Timeout callback takes u_mutex. See untimeout(9f) */
-	if (tmo != NULL)
-		(void) untimeout(tmo);
-
-	/* This close can block, so not under the mutex. */
-	if (authsock != NULL) {
-		smb_authsock_close(user, authsock);
-	}
 }
 
 /*
@@ -419,23 +434,32 @@
 smb_user_release(
     smb_user_t		*user)
 {
-	ASSERT(user->u_magic == SMB_USER_MAGIC);
+	smb_session_t *ssn = user->u_session;
+
+	SMB_USER_VALID(user);
+
+	/* flush the tree list delete queue */
+	smb_llist_flush(&ssn->s_tree_list);
 
 	mutex_enter(&user->u_mutex);
 	ASSERT(user->u_refcnt);
 	user->u_refcnt--;
 
 	switch (user->u_state) {
-	case SMB_USER_STATE_LOGGED_OFF:
-		if (user->u_refcnt == 0)
-			smb_session_post_user(user->u_session, user);
+	case SMB_USER_STATE_LOGGING_OFF:
+		if (user->u_refcnt == 0) {
+			smb_session_t *ssn = user->u_session;
+			user->u_state = SMB_USER_STATE_LOGGED_OFF;
+			smb_llist_post(&ssn->s_user_list, user,
+			    smb_user_delete);
+		}
 		break;
 
 	case SMB_USER_STATE_LOGGING_ON:
 	case SMB_USER_STATE_LOGGED_ON:
-	case SMB_USER_STATE_LOGGING_OFF:
 		break;
 
+	case SMB_USER_STATE_LOGGED_OFF:
 	default:
 		ASSERT(0);
 		break;
@@ -626,11 +650,12 @@
  * Remove the user from the session's user list before freeing resources
  * associated with the user.
  */
-void
+static void
 smb_user_delete(void *arg)
 {
 	smb_session_t	*session;
 	smb_user_t	*user = (smb_user_t *)arg;
+	uint32_t	ucount;
 
 	SMB_USER_VALID(user);
 	ASSERT(user->u_refcnt == 0);
@@ -639,11 +664,28 @@
 	ASSERT(user->u_auth_tmo == NULL);
 
 	session = user->u_session;
+
+	smb_server_dec_users(session->s_server);
 	smb_llist_enter(&session->s_user_list, RW_WRITER);
 	smb_llist_remove(&session->s_user_list, user);
 	smb_idpool_free(&session->s_uid_pool, user->u_uid);
+	ucount = smb_llist_get_count(&session->s_user_list);
 	smb_llist_exit(&session->s_user_list);
 
+	if (ucount == 0) {
+		smb_rwx_rwenter(&session->s_lock, RW_WRITER);
+		session->s_state = SMB_SESSION_STATE_SHUTDOWN;
+		smb_rwx_cvbcast(&session->s_lock);
+		smb_rwx_rwexit(&session->s_lock);
+	}
+
+	/*
+	 * This user is no longer on s_user_list, however...
+	 *
+	 * This is called via smb_llist_post, which means it may run
+	 * BEFORE smb_user_release drops u_mutex (if another thread
+	 * flushes the delete queue before we do).  Synchronize.
+	 */
 	mutex_enter(&user->u_mutex);
 	mutex_exit(&user->u_mutex);
 
@@ -777,7 +819,6 @@
 	info->ui_native_os = session->native_os;
 	info->ui_ipaddr = session->ipaddr;
 	info->ui_numopens = session->s_file_cnt;
-	info->ui_smb_uid = user->u_uid;
 	info->ui_logon_time = user->u_logon_time;
 	info->ui_flags = user->u_flags;
 	info->ui_posix_uid = crgetuid(user->u_cred);
@@ -811,11 +852,34 @@
 	bzero(info, sizeof (smb_netuserinfo_t));
 }
 
+/*
+ * Tell smbd this user is going away so it can clean up their
+ * audit session, autohome dir, etc.
+ *
+ * Note that when we're shutting down, smbd will already have set
+ * smbd.s_shutting_down and therefore will ignore door calls.
+ * Skip this during shutdown to reduce upcall noise.
+ */
 static void
 smb_user_auth_logoff(smb_user_t *user)
 {
-	uint32_t audit_sid = user->u_audit_sid;
+	smb_server_t *sv = user->u_server;
+	uint32_t audit_sid;
 
-	(void) smb_kdoor_upcall(user->u_server, SMB_DR_USER_AUTH_LOGOFF,
+	if (sv->sv_state != SMB_SERVER_STATE_RUNNING)
+		return;
+
+	audit_sid = user->u_audit_sid;
+	(void) smb_kdoor_upcall(sv, SMB_DR_USER_AUTH_LOGOFF,
 	    &audit_sid, xdr_uint32_t, NULL, NULL);
 }
+
+boolean_t
+smb_is_same_user(cred_t *cr1, cred_t *cr2)
+{
+	ksid_t *ks1 = crgetsid(cr1, KSID_USER);
+	ksid_t *ks2 = crgetsid(cr2, KSID_USER);
+
+	return (ks1->ks_rid == ks2->ks_rid &&
+	    strcmp(ks1->ks_domain->kd_name, ks2->ks_domain->kd_name) == 0);
+}
--- a/usr/src/uts/common/fs/smbsrv/smb_vops.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_vops.c	Wed Jun 12 22:46:54 2019 +0200
@@ -1006,7 +1006,8 @@
 	if (vp->v_type != VDIR)
 		return (ENOTDIR);
 
-	if (vfs_has_feature(vp->v_vfsp, VFSFT_DIRENTFLAGS)) {
+	if ((rddir_flag & SMB_EDIRENT) != 0 &&
+	    vfs_has_feature(vp->v_vfsp, VFSFT_DIRENTFLAGS)) {
 		flags |= V_RDDIR_ENTFLAGS;
 		rdirent_size = sizeof (edirent_t);
 	} else {
--- a/usr/src/uts/common/fs/smbsrv/smb_write.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/fs/smbsrv/smb_write.c	Wed Jun 12 22:46:54 2019 +0200
@@ -528,8 +528,8 @@
 		 */
 		ofile->f_written = B_TRUE;
 
-		if (!smb_node_is_dir(node))
-			smb_oplock_break_levelII(node);
+		/* This revokes read cache delegations. */
+		(void) smb_oplock_break_WRITE(node, ofile);
 
 		param->rw_count = lcount;
 		break;
--- a/usr/src/uts/common/smb/ntstatus.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smb/ntstatus.h	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #ifndef _SMB_NTSTATUS_H
@@ -30,9 +30,7 @@
 /*
  * This file defines the list of Win32 status codes. If you need
  * a status code that is defined in the [MS-ERREF] document but
- * is not listed here, please add it to the file.  This file is
- * compatible with the Windows DDK file inc/ntstatus.h
- * Please preserve this compatibility.
+ * is not listed here, please add it to the file.
  *
  * Be careful not to confuse error codes with status codes. The error
  * codes are listed in nterror.h.  Some mappings between NT status
@@ -71,16 +69,6 @@
 #endif
 
 /*
- * XXX: Some temporary left-overs from the old ntstatus.h
- * Should eliminate uses of these macros when convenient.
- */
-/* This used to OR in the severity bits. */
-#define	NT_SC_ERROR(S)		(S)
-/* This used to mask off the severity bits. */
-#define	NT_SC_VALUE(S)		(S)
-/* XXX end of temporary left-overs. */
-
-/*
  * One non-NT macro added for getting the severity value
  * from a given NT status code.  Evaluates to one of the
  * SEVERITY values defined below.
@@ -101,6 +89,10 @@
 #define	NT_STATUS_SEVERITY_WARNING		2	/* 0x80000000 */
 #define	NT_STATUS_SEVERITY_ERROR		3	/* 0xC0000000 */
 
+/*
+ * Please keep the following defines sorted by number.
+ */
+
 #define	NT_STATUS_SUCCESS				0x00000000
 
 /* Facility OS (0x..00....) */
@@ -137,6 +129,17 @@
 #define	NT_STATUS_NOTHING_TO_TERMINATE			0x00000122
 #define	NT_STATUS_PROCESS_NOT_IN_JOB			0x00000123
 #define	NT_STATUS_PROCESS_IN_JOB			0x00000124
+#define	NT_STATUS_VOLSNAP_HIBERNATE_READY		0x00000125
+#define	NT_STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY	0x00000126
+#define	NT_STATUS_INTERRUPT_VECTOR_ALREADY_CONNECTED	0x00000127
+#define	NT_STATUS_INTERRUPT_STILL_CONNECTED		0x00000128
+#define	NT_STATUS_PROCESS_CLONED			0x00000129
+#define	NT_STATUS_FILE_LOCKED_WITH_ONLY_READERS		0x0000012A
+#define	NT_STATUS_FILE_LOCKED_WITH_WRITERS		0x0000012B
+#define	NT_STATUS_RESOURCEMANAGER_READ_ONLY		0x00000202
+#define	NT_STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE		0x00000215
+#define	NT_STATUS_OPLOCK_HANDLE_CLOSED			0x00000216
+#define	NT_STATUS_WAIT_FOR_OPLOCK			0x00000367
 
 /* All severity 1 (informational) */
 #define	NT_STATUS_OBJECT_NAME_EXISTS			0x40000000
@@ -183,6 +186,21 @@
 #define	NT_STATUS_MP_PROCESSOR_MISMATCH			0x40000029
 #define	NT_STATUS_HIBERNATED				0x4000002A
 #define	NT_STATUS_RESUME_HIBERNATION			0x4000002B
+#define	NT_STATUS_FIRMWARE_UPDATED			0x4000002C
+#define	NT_STATUS_DRIVERS_LEAKING_LOCKED_PAGES		0x4000002D
+#define	NT_STATUS_MESSAGE_RETRIEVED			0x4000002E
+#define	NT_STATUS_SYSTEM_POWERSTATE_TRANSITION		0x4000002F
+#define	NT_STATUS_ALPC_CHECK_COMPLETION_LIST		0x40000030
+#define	NT_STATUS_SYSTEM_POWERSTATE_COMPLEX_TRANSITION	0x40000031
+#define	NT_STATUS_ACCESS_AUDIT_BY_POLICY		0x40000032
+#define	NT_STATUS_ABANDON_HIBERFILE			0x40000033
+#define	NT_STATUS_BIZRULES_NOT_ENABLED			0x40000034
+#define	NT_STATUS_WAKE_SYSTEM				0x40000294
+#define	NT_STATUS_DS_SHUTTING_DOWN			0x40000370
+
+/* Facility RPC Runtime (0x..02....) */
+#define	RPC_NT_UUID_LOCAL_ONLY				0x40020056
+#define	RPC_NT_SEND_INCOMPLETE				0x400200AF
 
 /* All severity 2 (warning) */
 #define	NT_STATUS_GUARD_PAGE_VIOLATION			0x80000001
@@ -223,8 +241,18 @@
 #define	NT_STATUS_CLEANER_CARTRIDGE_INSTALLED		0x80000027
 #define	NT_STATUS_PLUGPLAY_QUERY_VETOED			0x80000028
 #define	NT_STATUS_UNWIND_CONSOLIDATE			0x80000029
+#define	NT_STATUS_REGISTRY_HIVE_RECOVERED		0x8000002A
+#define	NT_STATUS_DLL_MIGHT_BE_INSECURE			0x8000002B
+#define	NT_STATUS_DLL_MIGHT_BE_INCOMPATIBLE		0x8000002C
+#define	NT_STATUS_STOPPED_ON_SYMLINK			0x8000002D
+#define	NT_STATUS_CANNOT_GRANT_REQUESTED_OPLOCK		0x8000002E
+#define	NT_STATUS_DEVICE_REQUIRES_CLEANING		0x80000288
+#define	NT_STATUS_DEVICE_DOOR_OPEN			0x80000289
+#define	NT_STATUS_DATA_LOST_REPAIR			0x80000803
 
-/* Mostly severity 3 (error) - but NOT all! */
+/* facility cluster (0x8013....) etc, not used */
+
+/* All severity 3 (error) */
 #define	NT_STATUS_UNSUCCESSFUL				0xC0000001
 #define	NT_STATUS_NOT_IMPLEMENTED			0xC0000002
 #define	NT_STATUS_INVALID_INFO_CLASS			0xC0000003
@@ -494,14 +522,7 @@
 #define	NT_STATUS_NO_GUID_TRANSLATION			0xC000010C
 #define	NT_STATUS_CANNOT_IMPERSONATE			0xC000010D
 #define	NT_STATUS_IMAGE_ALREADY_LOADED			0xC000010E
-#define	NT_STATUS_ABIOS_NOT_PRESENT			0xC000010F
-#define	NT_STATUS_ABIOS_LID_NOT_EXIST			0xC0000110
-#define	NT_STATUS_ABIOS_LID_ALREADY_OWNED		0xC0000111
-#define	NT_STATUS_ABIOS_NOT_LID_OWNER			0xC0000112
-#define	NT_STATUS_ABIOS_INVALID_COMMAND			0xC0000113
-#define	NT_STATUS_ABIOS_INVALID_LID			0xC0000114
-#define	NT_STATUS_ABIOS_SELECTOR_NOT_AVAILABLE		0xC0000115
-#define	NT_STATUS_ABIOS_INVALID_SELECTOR		0xC0000116
+/* Old: NT_STATUS_ABIOS_... 0xC000010F - 0xC0000116 */
 #define	NT_STATUS_NO_LDT				0xC0000117
 #define	NT_STATUS_INVALID_LDT_SIZE			0xC0000118
 #define	NT_STATUS_INVALID_LDT_OFFSET			0xC0000119
@@ -632,8 +653,16 @@
 #define	NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT		0xC000019A
 #define	NT_STATUS_DOMAIN_TRUST_INCONSISTENT		0xC000019B
 #define	NT_STATUS_FS_DRIVER_REQUIRED			0xC000019C
+#define	NT_STATUS_IMAGE_ALREADY_LOADED_AS_DLL		0xC000019D
+/* Was: NT_STATUS_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING */
+#define	NT_STATUS_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_SETTING	0xC000019E
 #define	NT_STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME	0xC000019F
+#define	NT_STATUS_SECURITY_STREAM_IS_INCONSISTENT	0xC00001A0
 #define	NT_STATUS_INVALID_LOCK_RANGE			0xC00001A1
+#define	NT_STATUS_INVALID_ACE_CONDITION			0xC00001A2
+#define	NT_STATUS_IMAGE_SUBSYSTEM_NOT_PRESENT		0xC00001A3
+#define	NT_STATUS_NOTIFICATION_GUID_ALREADY_DEFINED	0xC00001A4
+#define	NT_STATUS_NETWORK_OPEN_RESTRICTION		0xC0000201
 #define	NT_STATUS_NO_USER_SESSION_KEY			0xC0000202
 #define	NT_STATUS_USER_SESSION_DELETED			0xC0000203
 #define	NT_STATUS_RESOURCE_LANG_NOT_FOUND		0xC0000204
@@ -754,8 +783,8 @@
 #define	NT_STATUS_ILLEGAL_ELEMENT_ADDRESS		0xC0000285
 #define	NT_STATUS_MAGAZINE_NOT_PRESENT			0xC0000286
 #define	NT_STATUS_REINITIALIZATION_NEEDED		0xC0000287
-#define	NT_STATUS_DEVICE_REQUIRES_CLEANING		0x80000288
-#define	NT_STATUS_DEVICE_DOOR_OPEN			0x80000289
+/*	NT_STATUS_DEVICE_REQUIRES_CLEANING		0x80000288 */
+/*	NT_STATUS_DEVICE_DOOR_OPEN			0x80000289 */
 #define	NT_STATUS_ENCRYPTION_FAILED			0xC000028A
 #define	NT_STATUS_DECRYPTION_FAILED			0xC000028B
 #define	NT_STATUS_RANGE_NOT_FOUND			0xC000028C
@@ -766,7 +795,7 @@
 #define	NT_STATUS_FILE_NOT_ENCRYPTED			0xC0000291
 #define	NT_STATUS_NOT_EXPORT_FORMAT			0xC0000292
 #define	NT_STATUS_FILE_ENCRYPTED			0xC0000293
-#define	NT_STATUS_WAKE_SYSTEM				0x40000294
+/*	NT_STATUS_WAKE_SYSTEM				0x40000294 */
 #define	NT_STATUS_WMI_GUID_NOT_FOUND			0xC0000295
 #define	NT_STATUS_WMI_INSTANCE_NOT_FOUND		0xC0000296
 #define	NT_STATUS_WMI_ITEMID_NOT_FOUND			0xC0000297
@@ -904,7 +933,7 @@
 #define	NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER	0xC0000364
 #define	NT_STATUS_FAILED_DRIVER_ENTRY			0xC0000365
 #define	NT_STATUS_DEVICE_ENUMERATION_ERROR		0xC0000366
-#define	NT_STATUS_WAIT_FOR_OPLOCK			0x00000367
+/*	NT_STATUS_WAIT_FOR_OPLOCK			0x00000367 */
 #define	NT_STATUS_MOUNT_POINT_NOT_RESOLVED		0xC0000368
 #define	NT_STATUS_INVALID_DEVICE_OBJECT_PARAMETER	0xC0000369
 #define	NT_STATUS_MCA_OCCURED				0xC000036A
@@ -913,7 +942,11 @@
 #define	NT_STATUS_DRIVER_DATABASE_ERROR			0xC000036D
 #define	NT_STATUS_SYSTEM_HIVE_TOO_LARGE			0xC000036E
 #define	NT_STATUS_INVALID_IMPORT_OF_NON_DLL		0xC000036F
-#define	NT_STATUS_DS_SHUTTING_DOWN			0x40000370
+/*	NT_STATUS_DS_SHUTTING_DOWN			0x40000370 */
+#define	NT_STATUS_NO_SECRETS				0xC0000371
+#define	NT_STATUS_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY	0xC0000372
+#define	NT_STATUS_FAILED_STACK_SWITCH			0xC0000373
+#define	NT_STATUS_HEAP_CORRUPTION			0xC0000374
 #define	NT_STATUS_SMARTCARD_WRONG_PIN			0xC0000380
 #define	NT_STATUS_SMARTCARD_CARD_BLOCKED		0xC0000381
 #define	NT_STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED	0xC0000382
@@ -929,8 +962,153 @@
 #define	NT_STATUS_PKINIT_CLIENT_FAILURE			0xC000038C
 #define	NT_STATUS_SMARTCARD_CERT_EXPIRED		0xC000038D
 #define	NT_STATUS_DRIVER_FAILED_PRIOR_UNLOAD		0xC000038E
+#define	NT_STATUS_SMARTCARD_SILENT_CONTEXT		0xC000038F
+#define	NT_STATUS_PER_USER_TRUST_QUOTA_EXCEEDED		0xC0000401
+#define	NT_STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED		0xC0000402
+#define	NT_STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED	0xC0000403
+#define	NT_STATUS_DS_NAME_NOT_UNIQUE			0xC0000404
+#define	NT_STATUS_DS_DUPLICATE_ID_FOUND			0xC0000405
+#define	NT_STATUS_DS_GROUP_CONVERSION_ERROR		0xC0000406
+#define	NT_STATUS_VOLSNAP_PREPARE_HIBERNATE		0xC0000407
+#define	NT_STATUS_USER2USER_REQUIRED			0xC0000408
+#define	NT_STATUS_STACK_BUFFER_OVERRUN			0xC0000409
+#define	NT_STATUS_NO_S4U_PROT_SUPPORT			0xC000040A
+#define	NT_STATUS_CROSSREALM_DELEGATION_FAILURE		0xC000040B
+#define	NT_STATUS_REVOCATION_OFFLINE_KDC		0xC000040C
+#define	NT_STATUS_ISSUING_CA_UNTRUSTED_KDC		0xC000040D
+#define	NT_STATUS_KDC_CERT_EXPIRED			0xC000040E
+#define	NT_STATUS_KDC_CERT_REVOKED			0xC000040F
+#define	NT_STATUS_PARAMETER_QUOTA_EXCEEDED		0xC0000410
+#define	NT_STATUS_HIBERNATION_FAILURE			0xC0000411
+#define	NT_STATUS_DELAY_LOAD_FAILED			0xC0000412
+#define	NT_STATUS_AUTHENTICATION_FIREWALL_FAILED	0xC0000413
+#define	NT_STATUS_VDM_DISALLOWED			0xC0000414
+#define	NT_STATUS_HUNG_DISPLAY_DRIVER_THREAD		0xC0000415
+/* Was: NT_STATUS_INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE */
+#define	NT_STATUS_INSUFFICIENT_RESOURCE_FOR_SHARED_SECTION_SIZE 0xC0000416
+#define	NT_STATUS_INVALID_CRUNTIME_PARAMETER		0xC0000417
+#define	NT_STATUS_NTLM_BLOCKED				0xC0000418
+#define	NT_STATUS_DS_SRC_SID_EXISTS_IN_FOREST		0xC0000419
+#define	NT_STATUS_DS_DOMAIN_NAME_EXISTS_IN_FOREST	0xC000041A
+#define	NT_STATUS_DS_FLAT_NAME_EXISTS_IN_FOREST		0xC000041B
+#define	NT_STATUS_INVALID_USER_PRINCIPAL_NAME		0xC000041C
+#define	NT_STATUS_ASSERTION_FAILURE			0xC0000420
+#define	NT_STATUS_VERIFIER_STOP				0xC0000421
+#define	NT_STATUS_CALLBACK_POP_STACK			0xC0000423
+#define	NT_STATUS_INCOMPATIBLE_DRIVER_BLOCKED		0xC0000424
+#define	NT_STATUS_HIVE_UNLOADED				0xC0000425
+#define	NT_STATUS_COMPRESSION_DISABLED			0xC0000426
+#define	NT_STATUS_FILE_SYSTEM_LIMITATION		0xC0000427
+#define	NT_STATUS_INVALID_IMAGE_HASH			0xC0000428
+#define	NT_STATUS_NOT_CAPABLE				0xC0000429
+#define	NT_STATUS_REQUEST_OUT_OF_SEQUENCE		0xC000042A
+#define	NT_STATUS_IMPLEMENTATION_LIMIT			0xC000042B
+#define	NT_STATUS_ELEVATION_REQUIRED			0xC000042C
+#define	NT_STATUS_NO_SECURITY_CONTEXT			0xC000042D
+#define	NT_STATUS_PKU2U_CERT_FAILURE			0xC000042E
+#define	NT_STATUS_BEYOND_VDL				0xC0000432
+#define	NT_STATUS_ENCOUNTERED_WRITE_IN_PROGRESS		0xC0000433
+#define	NT_STATUS_PTE_CHANGED				0xC0000434
+#define	NT_STATUS_PURGE_FAILED				0xC0000435
+#define	NT_STATUS_CRED_REQUIRES_CONFIRMATION		0xC0000440
+#define	NT_STATUS_CS_ENCRYPTION_INVALID_SERVER_RESPONSE	0xC0000441
+#define	NT_STATUS_CS_ENCRYPTION_UNSUPPORTED_SERVER	0xC0000442
+#define	NT_STATUS_CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE	0xC0000443
+#define	NT_STATUS_CS_ENCRYPTION_NEW_ENCRYPTED_FILE	0xC0000444
+#define	NT_STATUS_CS_ENCRYPTION_FILE_NOT_CSE		0xC0000445
+#define	NT_STATUS_INVALID_LABEL				0xC0000446
+#define	NT_STATUS_DRIVER_PROCESS_TERMINATED		0xC0000450
+#define	NT_STATUS_AMBIGUOUS_SYSTEM_DEVICE		0xC0000451
+#define	NT_STATUS_SYSTEM_DEVICE_NOT_FOUND		0xC0000452
+#define	NT_STATUS_RESTART_BOOT_APPLICATION		0xC0000453
+#define	NT_STATUS_INSUFFICIENT_NVRAM_RESOURCES		0xC0000454
+#define	NT_STATUS_NO_RANGES_PROCESSED			0xC0000460
+#define	NT_STATUS_DEVICE_FEATURE_NOT_SUPPORTED		0xC0000463
+#define	NT_STATUS_DEVICE_UNREACHABLE			0xC0000464
+#define	NT_STATUS_INVALID_TOKEN				0xC0000465
+#define	NT_STATUS_SERVER_UNAVAILABLE			0xC0000466
+#define	NT_STATUS_FILE_NOT_AVAILABLE			0xC0000467
+
+#define	NT_STATUS_INVALID_TASK_NAME			0xC0000500
+#define	NT_STATUS_INVALID_TASK_INDEX			0xC0000501
+#define	NT_STATUS_THREAD_ALREADY_IN_TASK		0xC0000502
+#define	NT_STATUS_CALLBACK_BYPASS			0xC0000503
+#define	NT_STATUS_FAIL_FAST_EXCEPTION			0xC0000602
+#define	NT_STATUS_IMAGE_CERT_REVOKED			0xC0000603
+#define	NT_STATUS_PORT_CLOSED				0xC0000700
+#define	NT_STATUS_MESSAGE_LOST				0xC0000701
+#define	NT_STATUS_INVALID_MESSAGE			0xC0000702
+#define	NT_STATUS_REQUEST_CANCELED			0xC0000703
+#define	NT_STATUS_RECURSIVE_DISPATCH			0xC0000704
+#define	NT_STATUS_LPC_RECEIVE_BUFFER_EXPECTED		0xC0000705
+#define	NT_STATUS_LPC_INVALID_CONNECTION_USAGE		0xC0000706
+#define	NT_STATUS_LPC_REQUESTS_NOT_ALLOWED		0xC0000707
+#define	NT_STATUS_RESOURCE_IN_USE			0xC0000708
+#define	NT_STATUS_HARDWARE_MEMORY_ERROR			0xC0000709
+#define	NT_STATUS_THREADPOOL_HANDLE_EXCEPTION		0xC000070A
+#define	NT_STATUS_THREADPOOL_SET_EVENT_ON_COMPLETION_FAILED	0xC000070B
+#define	NT_STATUS_THREADPOOL_RELEASE_SEMAPHORE_ON_COMPLETION_FAILED 0xC000070C
+#define	NT_STATUS_THREADPOOL_RELEASE_MUTEX_ON_COMPLETION_FAILED 0xC000070D
+#define	NT_STATUS_THREADPOOL_FREE_LIBRARY_ON_COMPLETION_FAILED  0xC000070E
+#define	NT_STATUS_THREADPOOL_RELEASED_DURING_OPERATION	0xC000070F
+#define	NT_STATUS_CALLBACK_RETURNED_WHILE_IMPERSONATING	0xC0000710
+#define	NT_STATUS_APC_RETURNED_WHILE_IMPERSONATING	0xC0000711
+#define	NT_STATUS_PROCESS_IS_PROTECTED			0xC0000712
+#define	NT_STATUS_MCA_EXCEPTION				0xC0000713
+#define	NT_STATUS_CERTIFICATE_MAPPING_NOT_UNIQUE	0xC0000714
+#define	NT_STATUS_SYMLINK_CLASS_DISABLED		0xC0000715
+#define	NT_STATUS_INVALID_IDN_NORMALIZATION		0xC0000716
+#define	NT_STATUS_NO_UNICODE_TRANSLATION		0xC0000717
+#define	NT_STATUS_ALREADY_REGISTERED			0xC0000718
+#define	NT_STATUS_CONTEXT_MISMATCH			0xC0000719
+#define	NT_STATUS_PORT_ALREADY_HAS_COMPLETION_LIST	0xC000071A
+#define	NT_STATUS_CALLBACK_RETURNED_THREAD_PRIORITY	0xC000071B
+#define	NT_STATUS_INVALID_THREAD			0xC000071C
+#define	NT_STATUS_CALLBACK_RETURNED_TRANSACTION		0xC000071D
+#define	NT_STATUS_CALLBACK_RETURNED_LDR_LOCK		0xC000071E
+#define	NT_STATUS_CALLBACK_RETURNED_LANG		0xC000071F
+#define	NT_STATUS_CALLBACK_RETURNED_PRI_BACK		0xC0000720
+#define	NT_STATUS_CALLBACK_RETURNED_THREAD_AFFINITY	0xC0000721
+#define	NT_STATUS_DISK_REPAIR_DISABLED			0xC0000800
+#define	NT_STATUS_DS_DOMAIN_RENAME_IN_PROGRESS		0xC0000801
+#define	NT_STATUS_DISK_QUOTA_EXCEEDED			0xC0000802
+#define	NT_STATUS_CONTENT_BLOCKED			0xC0000804
+#define	NT_STATUS_BAD_CLUSTERS				0xC0000805
+#define	NT_STATUS_VOLUME_DIRTY				0xC0000806
+#define	NT_STATUS_FILE_CHECKED_OUT			0xC0000901
+#define	NT_STATUS_CHECKOUT_REQUIRED			0xC0000902
+#define	NT_STATUS_BAD_FILE_TYPE				0xC0000903
+#define	NT_STATUS_FILE_TOO_LARGE			0xC0000904
+#define	NT_STATUS_FORMS_AUTH_REQUIRED			0xC0000905
+#define	NT_STATUS_VIRUS_INFECTED			0xC0000906
+#define	NT_STATUS_VIRUS_DELETED				0xC0000907
+#define	NT_STATUS_BAD_MCFG_TABLE			0xC0000908
+#define	NT_STATUS_CANNOT_BREAK_OPLOCK			0xC0000909
+
 #define	NT_STATUS_WOW_ASSERTION				0xC0009898
 
+#define	NT_STATUS_INVALID_SIGNATURE			0xC000A000
+#define	NT_STATUS_HMAC_NOT_SUPPORTED			0xC000A001
+#define	NT_STATUS_IPSEC_QUEUE_OVERFLOW			0xC000A010
+#define	NT_STATUS_ND_QUEUE_OVERFLOW			0xC000A011
+#define	NT_STATUS_HOPLIMIT_EXCEEDED			0xC000A012
+#define	NT_STATUS_PROTOCOL_NOT_SUPPORTED		0xC000A013
+#define	NT_STATUS_LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED	0xC000A080
+#define	NT_STATUS_LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR	0xC000A081
+#define	NT_STATUS_LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR	0xC000A082
+#define	NT_STATUS_XML_PARSE_ERROR			0xC000A083
+#define	NT_STATUS_XMLDSIG_ERROR				0xC000A084
+#define	NT_STATUS_WRONG_COMPARTMENT			0xC000A085
+#define	NT_STATUS_AUTHIP_FAILURE			0xC000A086
+#define	NT_STATUS_DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS	0xC000A087
+#define	NT_STATUS_DS_OID_NOT_FOUND			0xC000A088
+#define	NT_STATUS_HASH_NOT_SUPPORTED			0xC000A100
+#define	NT_STATUS_HASH_NOT_PRESENT			0xC000A101
+#define	NT_STATUS_OFFLOAD_READ_FLT_NOT_SUPPORTED	0xC000A2A1
+#define	NT_STATUS_OFFLOAD_WRITE_FLT_NOT_SUPPORTED	0xC000A2A2
+#define	NT_STATUS_OFFLOAD_READ_FILE_NOT_SUPPORTED	0xC000A2A3
+#define	NT_STATUS_OFFLOAD_WRITE_FILE_NOT_SUPPORTED	0xC000A2A4
+
 /* Facility Debugger (0x..01....) not used */
 
 /* Facility RPC Runtime (0x..02....) */
@@ -1015,13 +1193,13 @@
 #define	RPC_NT_UNSUPPORTED_AUTHN_LEVEL			0xC0020053
 #define	RPC_NT_NO_PRINC_NAME				0xC0020054
 #define	RPC_NT_NOT_RPC_ERROR				0xC0020055
-#define	RPC_NT_UUID_LOCAL_ONLY				0x40020056
+/*	RPC_NT_UUID_LOCAL_ONLY				0x40020056 */
 #define	RPC_NT_SEC_PKG_ERROR				0xC0020057
 #define	RPC_NT_NOT_CANCELLED				0xC0020058
 #define	RPC_NT_INVALID_ASYNC_HANDLE			0xC0020062
 #define	RPC_NT_INVALID_ASYNC_CALL			0xC0020063
 #define	RPC_NT_PROXY_ACCESS_DENIED			0xC0020064
-#define	RPC_NT_SEND_INCOMPLETE				0x400200AF
+/*	RPC_NT_SEND_INCOMPLETE				0x400200AF */
 
 /* Facility RPC Stubs (0x..03....) */
 #define	RPC_NT_NO_MORE_ENTRIES				0xC0030001
--- a/usr/src/uts/common/smbsrv/Makefile	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smbsrv/Makefile	Wed Jun 12 22:46:54 2019 +0200
@@ -46,12 +46,13 @@
 	smb_idmap.h		\
 	smb_inet.h		\
 	smb_ioctl.h		\
+	smb_kcrypt.h		\
 	smb_kproto.h		\
 	smb_kstat.h		\
 	smb_ktypes.h		\
+	smb_oplock.h		\
 	smb_privilege.h		\
 	smb_share.h		\
-	smb_signing.h		\
 	smb_token.h		\
 	smb_vops.h		\
 	smb_xdr.h		\
--- a/usr/src/uts/common/smbsrv/ntifs.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smbsrv/ntifs.h	Wed Jun 12 22:46:54 2019 +0200
@@ -22,7 +22,7 @@
  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  *
- * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 2016 by Delphix. All rights reserved.
  */
 
@@ -198,6 +198,25 @@
 #define	FILE_VALID_SET_FLAGS			0x00000036
 
 /*
+ * "Granular" oplock flags; [MS-FSA], WinDDK/ntifs.h
+ * Same as smb2.h SMB2_LEASE_...
+ */
+#define	OPLOCK_LEVEL_CACHE_READ			0x01
+#define	OPLOCK_LEVEL_CACHE_HANDLE		0x02
+#define	OPLOCK_LEVEL_CACHE_WRITE		0x04
+#define	OPLOCK_LEVEL_CACHE_MASK			0x07
+
+/*
+ * [MS-FSA] oplock types (also "levels")
+ */
+#define	OPLOCK_LEVEL_NONE			0
+#define	OPLOCK_LEVEL_TWO			0x100
+#define	OPLOCK_LEVEL_ONE			0x200
+#define	OPLOCK_LEVEL_BATCH			0x400
+#define	OPLOCK_LEVEL_GRANULAR			0x800
+#define	OPLOCK_LEVEL_TYPE_MASK			0xf00
+
+/*
  * Define the file information class values used by the NT DDK and HAL.
  */
 typedef enum _FILE_INFORMATION_CLASS {
@@ -368,7 +387,7 @@
  *		If this flag is not set,
  *		the ACE is an effective ACE which controls access to the object
  *		to which it is attached.
- * 		Both effective and inherit-only ACEs can be inherited
+ *		Both effective and inherit-only ACEs can be inherited
  *		depending on the state of the other inheritance flags.
  *
  * INHERITED_ACE: Windows 2000/XP: Indicates that the ACE was inherited.
@@ -559,10 +578,10 @@
 typedef struct smb_sd {
 	uint8_t		sd_revision;
 	uint16_t	sd_control;
-	smb_sid_t 	*sd_owner;	/* SID file owner */
-	smb_sid_t 	*sd_group;	/* SID group (for POSIX) */
-	smb_acl_t 	*sd_sacl;	/* ACL System (audits) */
-	smb_acl_t 	*sd_dacl;	/* ACL Discretionary (perm) */
+	smb_sid_t	*sd_owner;	/* SID file owner */
+	smb_sid_t	*sd_group;	/* SID group (for POSIX) */
+	smb_acl_t	*sd_sacl;	/* ACL System (audits) */
+	smb_acl_t	*sd_dacl;	/* ACL Discretionary (perm) */
 } smb_sd_t;
 
 /*
--- a/usr/src/uts/common/smbsrv/smb.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smbsrv/smb.h	Wed Jun 12 22:46:54 2019 +0200
@@ -213,6 +213,17 @@
 	FILE_RESERVE_OPFILTER))
 
 /*
+ * Oplocks levels as expressed in the SMB procotol, i.e.
+ * in nt_create_andx and nt_transact_create responses.
+ * The FS-level oplock interface flags are in ntifs.h
+ * (See OPLOCK_LEVEL_...)
+ */
+#define	SMB_OPLOCK_NONE		0
+#define	SMB_OPLOCK_EXCLUSIVE	1
+#define	SMB_OPLOCK_BATCH	2
+#define	SMB_OPLOCK_LEVEL_II	3
+
+/*
  * Define the filter flags for NtNotifyChangeDirectoryFile
  */
 #define	FILE_NOTIFY_CHANGE_FILE_NAME	0x00000001
--- a/usr/src/uts/common/smbsrv/smb2.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smbsrv/smb2.h	Wed Jun 12 22:46:54 2019 +0200
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #ifndef _SMB_SMB2_H
@@ -22,6 +22,13 @@
 
 #define	SMB2_PROTOCOL_ID	{ 0xFE, 'S', 'M', 'B' }
 #define	SMB2_HDR_SIZE	64
+#define	SMB3_TFORM_HDR_SIZE	52
+
+/*
+ * Protocol ID as a 32-bit little-endian integer.
+ */
+#define	SMB2_PROTOCOL_MAGIC	0x424d53fe
+#define	SMB3_ENCRYPTED_MAGIC	0x424d53fd
 
 /*
  * SMB2 header command codes.
@@ -149,6 +156,11 @@
 #define	SMB2_SESSION_FLAG_ENCRYPT_DATA	0x0004
 
 /*
+ * Client wants to bind an existing session to a new connection
+ */
+#define	SMB2_SESSION_FLAG_BINDING	0x01
+
+/*
  * SMB2 Tree connect, disconnect
  */
 
@@ -185,13 +197,29 @@
  * SMB2 Create (open)
  */
 
-/* SMB2 requested oplock levels */
+/*
+ * SMB2 requested oplock levels
+ * Corresponds to ntifs.h OPLOCK_LEVEL_... but NOT the same!
+ */
 #define	SMB2_OPLOCK_LEVEL_NONE				0x00
 #define	SMB2_OPLOCK_LEVEL_II				0x01
 #define	SMB2_OPLOCK_LEVEL_EXCLUSIVE			0x08
 #define	SMB2_OPLOCK_LEVEL_BATCH				0x09
 #define	SMB2_OPLOCK_LEVEL_LEASE				0xFF
 
+/*
+ * SMB2 create request lease "type"
+ * Note: Same as ntifs.h OPLOCK_LEVEL_CACHE...
+ */
+#define	SMB2_LEASE_NONE					0x00
+#define	SMB2_LEASE_READ_CACHING				0x01
+#define	SMB2_LEASE_HANDLE_CACHING			0x02
+#define	SMB2_LEASE_WRITE_CACHING			0x04
+
+/* SMB2 create lease flags */
+#define	SMB2_LEASE_FLAG_BREAK_IN_PROGRESS		0x00000002
+#define	SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET		0x00000004
+
 /* SMB2 impersonation levels */
 #define	SMB2_IMPERSONATION_ANONYMOUS			0x00
 #define	SMB2_IMPERSONATION_IDENTIFICATION		0x01
@@ -257,6 +285,22 @@
  * on disk. No data is passed to the server by the client.
  */
 
+#define	SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2	0x44483251 /* ("DH2Q") */
+/*
+ * The client is requesting the open to be durable.
+ * This value is only supported for the SMB 3.x dialect family.
+ */
+
+#define	SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2	0x44483243 /* ("DH2C") */
+/*
+ * The client is requesting to reconnect to a
+ * durable open after being disconnected.
+ * This value is only supported for the SMB 3.x dialect family.
+ */
+
+#define	SMB2_DHANDLE_FLAG_PERSISTENT	0x00000002
+/* A persistent handle is requested. */
+
 #define	SMB2_CREATE_REQUEST_LEASE		0x52714c73 /* ("RqLs") */
 /*
  * The client is requesting that the server return a lease.
@@ -268,19 +312,10 @@
  * Client is MacOS X looking for MacOS-specific extensions.
  */
 
-/* SMB2 create request lease */
-#define	SMB2_LEASE_NONE				0x00
-#define	SMB2_LEASE_READ_CACHING			0x01
-#define	SMB2_LEASE_HANDLE_CACHING		0x02
-#define	SMB2_LEASE_WRITE_CACHING		0x04
-
-/* SMB2 lease break notification flags */
-#define	SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED  0x01
-
 /*
  * SMB2 Close
  */
-#define	SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB 	0x0001
+#define	SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB	0x0001
 
 /*
  * SMB2 Write
@@ -329,7 +364,7 @@
 /*
  * SMB2 Ioctl Request
  */
-#define	SMB2_0_IOCTL_IS_FSCTL 		0x00000001
+#define	SMB2_0_IOCTL_IS_FSCTL		0x00000001
 
 
 /*
@@ -369,6 +404,9 @@
  */
 #define	SMB2_WATCH_TREE			0x00000001
 
+/* SMB2 Oplock Break: lease break notification flags */
+#define	SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED  0x01
+
 #ifdef __cplusplus
 }
 #endif
--- a/usr/src/uts/common/smbsrv/smb2_aapl.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smbsrv/smb2_aapl.h	Wed Jun 12 22:46:54 2019 +0200
@@ -22,8 +22,8 @@
  */
 
 /*
- * This content was published as: smb-759.0/kernel/netsmb/smb_2.h
- * in http://opensource.apple.com/source/smb/smb-759.0.tar.gz
+ * This file contains excerpts of content published under:
+ * http://opensource.apple.com/source/smb/smb-759.40.1.1
  */
 
 #ifndef	_SMB2AAPL_H
@@ -111,10 +111,22 @@
 /* Define Volume Capabilities bitmap */
 enum {
 	kAAPL_SUPPORT_RESOLVE_ID = 0x01,
-	kAAPL_CASE_SENSITIVE = 0x02
+	kAAPL_CASE_SENSITIVE = 0x02,
+	kAAPL_SUPPORTS_FULL_SYNC = 0x04
 };
 
 /*
+ * kAAPL_SUPPORTS_FULL_SYNC - Full Sync Request
+ * If the volume supports Full Sync, then when a F_FULLSYNC is done on the
+ * client side, the client will flush its buffers and then a SMB Flush Request
+ * with Reserved1 (uint16_t) set to 0xFFFF will be sent to the server. The
+ * server should flush all its buffer for that file and then call the
+ * filesystem to perform a F_FULLSYNC on that file.
+ * Refer to "man fsync" and "man fcntl" in OS X for more information on
+ * F_FULLSYNC
+ */
+
+/*
  * Resolve ID Request
  *
  *	uint32_t command_code = kAAPL_RESOLVE_ID;
--- a/usr/src/uts/common/smbsrv/smb2_kproto.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smbsrv/smb2_kproto.h	Wed Jun 12 22:46:54 2019 +0200
@@ -28,6 +28,14 @@
 extern uint32_t smb2_max_trans;
 
 extern int smb2_aapl_use_file_ids;
+extern uint32_t smb2_dh_def_timeout;
+extern uint32_t smb2_dh_max_timeout;
+extern uint32_t smb2_res_def_timeout;
+extern uint32_t smb2_res_max_timeout;
+extern int smb2_enable_dh;
+
+#define	SMB3_CLIENT_ENCRYPTS(sr) \
+	((sr->session->capabilities & SMB2_CAP_ENCRYPTION) != 0)
 
 void	smb2_dispatch_stats_init(smb_server_t *);
 void	smb2_dispatch_stats_fini(smb_server_t *);
@@ -36,9 +44,14 @@
 
 int	smb2sr_newrq(smb_request_t *);
 void	smb2sr_work(smb_request_t *);
+uint32_t smb2sr_go_async(smb_request_t *);
+void smb2sr_append_postwork(smb_request_t *, smb_request_t *);
 
 int smb2_decode_header(smb_request_t *);
 int smb2_encode_header(smb_request_t *, boolean_t);
+int smb3_decode_tform_header(smb_request_t *);
+int smb3_encode_tform_header(smb_request_t *, struct mbuf_chain *mbc);
+
 void smb2_send_reply(smb_request_t *);
 void smb2sr_put_error(smb_request_t *, uint32_t);
 void smb2sr_put_error_data(smb_request_t *, uint32_t, mbuf_chain_t *);
@@ -48,7 +61,13 @@
 /* SMB2 signing routines - smb2_signing.c */
 int smb2_sign_check_request(smb_request_t *);
 void smb2_sign_reply(smb_request_t *);
+void smb2_sign_init_mech(smb_session_t *);
 
+int smb3_encrypt_sr(smb_request_t *, struct mbuf_chain *, struct mbuf_chain *);
+int smb3_decrypt_sr(smb_request_t *);
+int smb3_encrypt_init_mech(smb_session_t *s);
+
+uint32_t smb2_fsctl_resiliency(smb_request_t *, smb_fsctl_t *);
 uint32_t smb2_fsctl_vneginfo(smb_request_t *, smb_fsctl_t *);
 
 smb_sdrc_t smb2_negotiate(smb_request_t *);
@@ -70,6 +89,7 @@
 smb_sdrc_t smb2_query_info(smb_request_t *);
 smb_sdrc_t smb2_set_info(smb_request_t *);
 smb_sdrc_t smb2_oplock_break_ack(smb_request_t *);
+smb_sdrc_t smb2_lease_break_ack(smb_request_t *);
 
 int smb2_newrq_negotiate(smb_request_t *);
 int smb2_newrq_cancel(smb_request_t *);
@@ -92,7 +112,20 @@
 uint32_t smb2_setinfo_sec(smb_request_t *, smb_setinfo_t *, uint32_t);
 uint32_t smb2_setinfo_quota(smb_request_t *, smb_setinfo_t *);
 
-void smb2sr_finish_async(smb_request_t *);
+void smb2_oplock_acquire(smb_request_t *sr);
+void smb2_oplock_reconnect(smb_request_t *sr);
+void smb2_lease_acquire(smb_request_t *sr);
+uint32_t smb2_lease_create(smb_request_t *sr);
+void smb2_lease_rele(smb_lease_t *);
+void smb2_lease_init(void);
+void smb2_lease_fini(void);
+void smb2_lease_ofile_close(smb_ofile_t *);
+
+void smb2_durable_timers(smb_server_t *);
+
+uint32_t smb2_dh_reconnect(smb_request_t *);
+boolean_t smb_dh_should_save(smb_ofile_t *);
+extern void smb2_dh_shutdown(smb_server_t *);
 
 #ifdef	__cplusplus
 }
--- a/usr/src/uts/common/smbsrv/smb_fsops.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smbsrv/smb_fsops.h	Wed Jun 12 22:46:54 2019 +0200
@@ -22,7 +22,7 @@
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  *
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #ifndef _SMBSRV_SMB_FSOPS_H
@@ -123,6 +123,7 @@
 #define	SMB_CATIA		0x00000004
 #define	SMB_ABE			0x00000008
 #define	SMB_CASE_SENSITIVE	0x00000010
+#define	SMB_EDIRENT		0x00000020
 
 /*
  * Increased MAXPATHLEN for SMB.  Essentially, we want to allow a
--- a/usr/src/uts/common/smbsrv/smb_ioctl.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smbsrv/smb_ioctl.h	Wed Jun 12 22:46:54 2019 +0200
@@ -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 2016 Nexenta Systems, Inc.  All rights reserved.
  * Copyright 2017 Joyent, Inc.
  */
 
@@ -70,7 +70,7 @@
 
 typedef	struct {
 	smb_ioc_header_t hdr;
-	int32_t 	offset;
+	int32_t		offset;
 } smb_ioc_gmt_t;
 
 typedef struct smb_ioc_share {
@@ -172,6 +172,7 @@
 	int32_t		print_enable;
 	int32_t		traverse_mounts;
 	uint32_t	max_protocol;
+	uint32_t	encrypt;
 	uint32_t	exec_flags;
 	uint32_t	negtok_len;
 	smb_version_t	version;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/smbsrv/smb_kcrypt.h	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,117 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+#ifndef _SMB_KCRYPT_H_
+#define	_SMB_KCRYPT_H_
+
+/*
+ * SMB signing routines used in {smb,smb2}_signing.c
+ * Two implementations of these (kernel/user) in:
+ *	uts/common/fs/smbsrv/smb_sign_kcf.c
+ *	lib/smbsrv/libfksmbsrv/common/fksmb_sign_pkcs.c
+ */
+
+#ifdef	_KERNEL
+#include <sys/crypto/api.h>
+#else
+#include <security/cryptoki.h>
+#include <security/pkcs11.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define	MD5_DIGEST_LENGTH	16	/* MD5 digest length in bytes */
+#define	SHA256_DIGEST_LENGTH	32	/* SHA256 digest length in bytes */
+#define	SMB2_SIG_SIZE		16
+#define	SMB2_KEYLEN		16
+#define	SMB3_KEYLEN		16	/* AES-128 keys */
+
+#ifdef	_KERNEL
+/* KCF variant */
+typedef crypto_mechanism_t	smb_crypto_mech_t;
+typedef crypto_context_t	smb_sign_ctx_t;
+typedef struct smb3_enc_ctx {
+	crypto_context_t ctx;
+	crypto_data_t output;
+	size_t len;
+} smb3_enc_ctx_t;
+typedef CK_AES_CCM_PARAMS	smb3_crypto_param_t;
+#else	/* _KERNEL */
+/* PKCS11 variant */
+typedef CK_MECHANISM		smb_crypto_mech_t;
+typedef CK_SESSION_HANDLE	smb_sign_ctx_t;
+typedef struct smb_enc_ctx {
+	CK_SESSION_HANDLE ctx;
+	uint8_t *output;
+	CK_ULONG len;
+} smb3_enc_ctx_t;
+/*
+ * CCM in PKCS has not been implemented.
+ * We just need an opaque type with space to refer to.
+ */
+typedef struct pkcs_ccm_param {
+	uint8_t buf[100];
+} smb3_crypto_param_t;
+#endif	/* _KERNEL */
+
+/*
+ * SMB signing routines used in smb_signing.c
+ */
+int smb_md5_getmech(smb_crypto_mech_t *);
+int smb_md5_init(smb_sign_ctx_t *, smb_crypto_mech_t *);
+int smb_md5_update(smb_sign_ctx_t, void *, size_t);
+int smb_md5_final(smb_sign_ctx_t, uint8_t *);
+
+/*
+ * SMB2/3 signing routines used in smb2_signing.c
+ * Two implementations of these (kernel/user) in:
+ *	uts/common/fs/smbsrv/smb2_sign_kcf.c
+ *	lib/smbsrv/libfksmbsrv/common/fksmb_sign_pkcs.c
+ */
+
+int smb2_hmac_getmech(smb_crypto_mech_t *);
+int smb2_hmac_init(smb_sign_ctx_t *, smb_crypto_mech_t *, uint8_t *, size_t);
+int smb2_hmac_update(smb_sign_ctx_t, uint8_t *, size_t);
+int smb2_hmac_final(smb_sign_ctx_t, uint8_t *);
+
+int smb3_cmac_getmech(smb_crypto_mech_t *);
+int smb3_cmac_init(smb_sign_ctx_t *, smb_crypto_mech_t *, uint8_t *, size_t);
+int smb3_cmac_update(smb_sign_ctx_t, uint8_t *, size_t);
+int smb3_cmac_final(smb_sign_ctx_t, uint8_t *);
+
+int smb3_do_kdf(void *, void *, size_t, uint8_t *, uint32_t);
+
+int smb3_encrypt_getmech(smb_crypto_mech_t *);
+void smb3_crypto_init_param(smb3_crypto_param_t *, uint8_t *, size_t,
+    uint8_t *, size_t, size_t);
+
+int smb3_encrypt_init(smb3_enc_ctx_t *, smb_crypto_mech_t *,
+    smb3_crypto_param_t *, uint8_t *, size_t, uint8_t *, size_t);
+int smb3_encrypt_update(smb3_enc_ctx_t *, uint8_t *, size_t);
+int smb3_encrypt_final(smb3_enc_ctx_t *, uint8_t *);
+void smb3_encrypt_cancel(smb3_enc_ctx_t *);
+
+int smb3_decrypt_init(smb3_enc_ctx_t *, smb_crypto_mech_t *,
+    smb3_crypto_param_t *, uint8_t *, size_t);
+int smb3_decrypt_update(smb3_enc_ctx_t *, uint8_t *, size_t);
+int smb3_decrypt_final(smb3_enc_ctx_t *, uint8_t *, size_t);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif /* _SMB_KCRYPT_H_ */
--- a/usr/src/uts/common/smbsrv/smb_kproto.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smbsrv/smb_kproto.h	Wed Jun 12 22:46:54 2019 +0200
@@ -38,6 +38,7 @@
 
 #include <sys/types.h>
 #include <sys/param.h>
+#include <sys/varargs.h>
 #include <sys/systm.h>
 #include <sys/debug.h>
 #include <sys/kmem.h>
@@ -58,9 +59,6 @@
 extern	int smb_maxbufsize;
 extern	int smb_flush_required;
 extern	int smb_dirsymlink_enable;
-extern	int smb_oplock_levelII;
-extern	int smb_oplock_timeout;
-extern	int smb_oplock_min_timeout;
 extern	int smb_shortnames;
 extern	int smb_sign_debug;
 extern	uint_t smb_audit_flags;
@@ -254,23 +252,43 @@
  * Logging functions
  */
 void smb_log_flush(void);
-void smb_correct_keep_alive_values(uint32_t new_keep_alive);
 void smb_close_all_connections(void);
 
 int smb_net_id(uint32_t);
 
 /*
- * oplock functions - node operations
+ * Common oplock functions
  */
-void smb_oplock_acquire(smb_request_t *sr, smb_node_t *, smb_ofile_t *);
-void smb_oplock_release(smb_node_t *, smb_ofile_t *);
-int smb_oplock_break(smb_request_t *, smb_node_t *, uint32_t);
-void smb_oplock_break_levelII(smb_node_t *);
-void smb_oplock_ack(smb_node_t *, smb_ofile_t *, uint8_t);
-void smb_oplock_broadcast(smb_node_t *);
+uint32_t smb_oplock_request(smb_request_t *, smb_ofile_t *, uint32_t *);
+uint32_t smb_oplock_ack_break(smb_request_t *, smb_ofile_t *, uint32_t *);
+uint32_t smb_oplock_break_PARENT(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_OPEN(smb_node_t *, smb_ofile_t *,
+    uint32_t DesiredAccess, uint32_t CreateDisposition);
+uint32_t smb_oplock_break_BATCH(smb_node_t *, smb_ofile_t *,
+    uint32_t DesiredAccess, uint32_t CreateDisposition);
+uint32_t smb_oplock_break_HANDLE(smb_node_t *, smb_ofile_t *);
+void smb_oplock_break_CLOSE(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_READ(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_WRITE(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_SETINFO(smb_node_t *,
+    smb_ofile_t *ofile, uint32_t InfoClass);
+uint32_t smb_oplock_break_DELETE(smb_node_t *, smb_ofile_t *);
 
-void smb1_oplock_break_notification(smb_request_t *, uint8_t);
-void smb2_oplock_break_notification(smb_request_t *, uint8_t);
+void smb_oplock_move(smb_node_t *, smb_ofile_t *, smb_ofile_t *);
+
+/*
+ * Protocol-specific oplock functions
+ * (and "server-level" functions)
+ */
+void smb1_oplock_acquire(smb_request_t *, boolean_t);
+void smb1_oplock_break_notification(smb_request_t *, uint32_t);
+void smb2_oplock_break_notification(smb_request_t *, uint32_t);
+void smb2_lease_break_notification(smb_request_t *, uint32_t, boolean_t);
+void smb_oplock_ind_break(smb_ofile_t *, uint32_t, boolean_t, uint32_t);
+void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *,
+    uint32_t, boolean_t);
+void smb_oplock_send_brk(smb_request_t *);
+uint32_t smb_oplock_wait_break(smb_node_t *, int);
 
 /*
  * range lock functions - node operations
@@ -310,6 +328,7 @@
 
 uint32_t smb_common_create(smb_request_t *);
 uint32_t smb_common_open(smb_request_t *);
+
 int smb_common_write(smb_request_t *, smb_rw_param_t *);
 
 void smb_pathname_init(smb_request_t *, smb_pathname_t *, char *);
@@ -325,9 +344,6 @@
 
 void	sshow_distribution_info(char *);
 
-uint32_t smb2sr_go_async(smb_request_t *sr,
-	smb_sdrc_t (*async_func)(smb_request_t *));
-
 void	smb_dispatch_stats_init(smb_server_t *);
 void	smb_dispatch_stats_fini(smb_server_t *);
 void	smb_dispatch_stats_update(smb_server_t *,
@@ -370,6 +386,7 @@
 int	smb_mbc_poke(mbuf_chain_t *, int, const char *, ...);
 int	smb_mbc_put_mem(mbuf_chain_t *, void *, int);
 int	smb_mbc_copy(mbuf_chain_t *, const mbuf_chain_t *, int, int);
+int	smb_mbc_put_align(mbuf_chain_t *, int);
 
 void	smbsr_encode_header(smb_request_t *sr, int wct,
 		    int bcc, const char *fmt, ...);
@@ -396,7 +413,7 @@
  * SMB RPC interface
  */
 void smb_opipe_dealloc(smb_opipe_t *);
-int smb_opipe_open(smb_request_t *, uint32_t);
+int smb_opipe_open(smb_request_t *, smb_ofile_t *);
 void smb_opipe_close(smb_ofile_t *);
 int smb_opipe_read(smb_request_t *, struct uio *);
 int smb_opipe_write(smb_request_t *, struct uio *);
@@ -435,6 +452,8 @@
 int smb_server_sharevp(smb_server_t *, const char *, vnode_t **);
 int smb_server_unshare(const char *);
 
+void smb_server_logoff_ssnid(smb_request_t *, uint64_t);
+
 void smb_server_get_cfg(smb_server_t *, smb_kmod_cfg_t *);
 
 int smb_server_spooldoc(smb_ioc_spooldoc_t *);
@@ -515,6 +534,7 @@
     smb_ofile_t *, smb_attr_t *);
 uint32_t smb_node_set_delete_on_close(smb_node_t *, cred_t *, uint32_t);
 void smb_node_reset_delete_on_close(smb_node_t *);
+void smb_node_delete_on_close(smb_node_t *);
 boolean_t smb_node_file_is_readonly(smb_node_t *);
 int smb_node_getpath(smb_node_t *, vnode_t *, char *, uint32_t);
 int smb_node_getmntpath(smb_node_t *, char *, uint32_t);
@@ -563,12 +583,14 @@
 unsigned short smb_worker_getnum();
 
 /* SMB signing routines smb_signing.c */
-int smb_sign_begin(smb_request_t *, smb_token_t *);
+void smb_sign_begin(smb_request_t *, smb_token_t *);
 int smb_sign_check_request(smb_request_t *);
 int smb_sign_check_secondary(smb_request_t *, unsigned int);
 void smb_sign_reply(smb_request_t *, mbuf_chain_t *);
 /* SMB2, but here because it's called from common code. */
-int smb2_sign_begin(smb_request_t *, smb_token_t *);
+void smb2_sign_begin(smb_request_t *, smb_token_t *);
+void smb3_encrypt_begin(smb_request_t *, smb_token_t *);
+void smb3_encrypt_fini(smb_session_t *);
 
 boolean_t smb_sattr_check(uint16_t, uint16_t);
 
@@ -586,20 +608,21 @@
  * session functions (file smb_session.c)
  */
 smb_session_t *smb_session_create(ksocket_t, uint16_t, smb_server_t *, int);
+smb_session_t *smb_server_find_session_byptr(smb_server_t *, void *);
+
 void smb_session_receiver(smb_session_t *);
 void smb_session_disconnect(smb_session_t *);
-void smb_session_timers(smb_llist_t *);
+void smb_session_timers(smb_server_t *);
 void smb_session_delete(smb_session_t *session);
 void smb_session_cancel_requests(smb_session_t *, smb_tree_t *,
     smb_request_t *);
 void smb_session_config(smb_session_t *session);
 void smb_session_disconnect_from_share(smb_llist_t *, char *);
 smb_user_t *smb_session_dup_user(smb_session_t *, char *, char *);
+smb_user_t *smb_session_lookup_ssnid(smb_session_t *, uint64_t);
 smb_user_t *smb_session_lookup_uid(smb_session_t *, uint16_t);
-smb_user_t *smb_session_lookup_uid_st(smb_session_t *session,
-    uint16_t uid, smb_user_state_t st);
-void smb_session_post_user(smb_session_t *, smb_user_t *);
-void smb_session_post_tree(smb_session_t *, smb_tree_t *);
+smb_user_t *smb_session_lookup_uid_st(smb_session_t *,
+    uint64_t, uint16_t, smb_user_state_t);
 smb_tree_t *smb_session_lookup_tree(smb_session_t *, uint16_t);
 smb_tree_t *smb_session_lookup_share(smb_session_t *, const char *,
     smb_tree_t *);
@@ -607,12 +630,10 @@
     smb_tree_t *);
 void smb_session_close_pid(smb_session_t *, uint32_t);
 void smb_session_disconnect_owned_trees(smb_session_t *, smb_user_t *);
-void smb_session_disconnect_trees(smb_session_t *);
 void smb_session_disconnect_share(smb_session_t *, const char *);
 void smb_session_getclient(smb_session_t *, char *, size_t);
 boolean_t smb_session_isclient(smb_session_t *, const char *);
 void smb_session_correct_keep_alive_values(smb_llist_t *, uint32_t);
-void smb_session_oplock_break(smb_request_t *, uint8_t);
 int smb_session_send(smb_session_t *, uint8_t type, mbuf_chain_t *);
 int smb_session_xprt_gethdr(smb_session_t *, smb_xprt_t *);
 boolean_t smb_session_oplocks_enable(smb_session_t *);
@@ -628,19 +649,20 @@
  */
 smb_ofile_t *smb_ofile_lookup_by_fid(smb_request_t *, uint16_t);
 smb_ofile_t *smb_ofile_lookup_by_uniqid(smb_tree_t *, uint32_t);
+smb_ofile_t *smb_ofile_lookup_by_persistid(smb_request_t *, uint64_t);
 boolean_t smb_ofile_disallow_fclose(smb_ofile_t *);
-smb_ofile_t *smb_ofile_open(smb_request_t *, smb_node_t *,
-    smb_arg_open_t *, uint16_t, uint32_t, smb_error_t *);
+smb_ofile_t *smb_ofile_alloc(smb_request_t *, smb_arg_open_t *, smb_node_t *,
+    uint16_t, uint16_t, uint32_t);
+void smb_ofile_open(smb_request_t *, smb_arg_open_t *, smb_ofile_t *);
 void smb_ofile_close(smb_ofile_t *, int32_t);
-void smb_ofile_delete(void *);
+void smb_ofile_free(smb_ofile_t *);
 uint32_t smb_ofile_access(smb_ofile_t *, cred_t *, uint32_t);
 int smb_ofile_seek(smb_ofile_t *, ushort_t, int32_t, uint32_t *);
 void smb_ofile_flush(smb_request_t *, smb_ofile_t *);
 boolean_t smb_ofile_hold(smb_ofile_t *);
+boolean_t smb_ofile_hold_olbrk(smb_ofile_t *);
 void smb_ofile_release(smb_ofile_t *);
-void smb_ofile_request_complete(smb_ofile_t *);
-void smb_ofile_close_all(smb_tree_t *);
-void smb_ofile_close_all_by_pid(smb_tree_t *, uint16_t);
+void smb_ofile_close_all(smb_tree_t *, uint32_t);
 void smb_ofile_set_flags(smb_ofile_t *, uint32_t);
 boolean_t smb_ofile_is_open(smb_ofile_t *);
 int smb_ofile_enum(smb_ofile_t *, smb_svcenum_t *);
@@ -649,10 +671,12 @@
 uint32_t smb_ofile_delete_check(smb_ofile_t *);
 boolean_t smb_ofile_share_check(smb_ofile_t *);
 cred_t *smb_ofile_getcred(smb_ofile_t *);
-void smb_ofile_set_delete_on_close(smb_ofile_t *);
+void smb_ofile_set_delete_on_close(smb_request_t *, smb_ofile_t *);
 void smb_delayed_write_timer(smb_llist_t *);
 void smb_ofile_set_quota_resume(smb_ofile_t *, char *);
 void smb_ofile_get_quota_resume(smb_ofile_t *, char *, int);
+void smb_ofile_del_persistid(smb_ofile_t *);
+void smb_ofile_set_persistid(smb_ofile_t *);
 
 #define	SMB_OFILE_GET_SESSION(of)	((of)->f_session)
 #define	SMB_OFILE_GET_TREE(of)		((of)->f_tree)
@@ -673,7 +697,6 @@
 void smb_odir_close(smb_odir_t *);
 boolean_t smb_odir_hold(smb_odir_t *);
 void smb_odir_release(smb_odir_t *);
-void smb_odir_delete(void *);
 
 int smb_odir_read(smb_request_t *, smb_odir_t *,
     smb_odirent_t *, boolean_t *);
@@ -694,7 +717,6 @@
 int smb_user_logon(smb_user_t *, cred_t *,
     char *, char *, uint32_t, uint32_t, uint32_t);
 void smb_user_logoff(smb_user_t *);
-void smb_user_delete(void *);
 void smb_user_auth_tmo(void *);
 
 boolean_t smb_user_is_admin(smb_user_t *);
@@ -711,15 +733,13 @@
 smb_token_t *smb_get_token(smb_session_t *, smb_logon_t *);
 cred_t *smb_cred_create(smb_token_t *);
 void smb_user_setcred(smb_user_t *, cred_t *, uint32_t);
+boolean_t smb_is_same_user(cred_t *, cred_t *);
 
 /*
  * SMB tree functions (file smb_tree.c)
  */
 uint32_t smb_tree_connect(smb_request_t *);
 void smb_tree_disconnect(smb_tree_t *, boolean_t);
-void smb_tree_dealloc(void *);
-void smb_tree_post_ofile(smb_tree_t *, smb_ofile_t *);
-void smb_tree_post_odir(smb_tree_t *, smb_odir_t *);
 void smb_tree_close_pid(smb_tree_t *, uint32_t);
 boolean_t smb_tree_has_feature(smb_tree_t *, uint_t);
 int smb_tree_enum(smb_tree_t *, smb_svcenum_t *);
@@ -729,7 +749,6 @@
 void smb_tree_release(smb_tree_t *);
 smb_odir_t *smb_tree_lookup_odir(smb_request_t *, uint16_t);
 boolean_t smb_tree_is_connected(smb_tree_t *);
-#define	SMB_TREE_GET_TID(tree)		((tree)->t_tid)
 
 smb_xa_t *smb_xa_create(smb_session_t *session, smb_request_t *sr,
     uint32_t total_parameter_count, uint32_t total_data_count,
@@ -773,6 +792,7 @@
 void	smb_llist_fini(void);
 void	smb_llist_constructor(smb_llist_t *, size_t, size_t);
 void	smb_llist_destructor(smb_llist_t *);
+void	smb_llist_enter(smb_llist_t *, krw_t);
 void	smb_llist_exit(smb_llist_t *);
 void	smb_llist_post(smb_llist_t *, void *, smb_dtorproc_t);
 void	smb_llist_flush(smb_llist_t *);
@@ -781,7 +801,6 @@
 void	smb_llist_remove(smb_llist_t *ll, void *obj);
 int	smb_llist_upgrade(smb_llist_t *ll);
 uint32_t smb_llist_get_count(smb_llist_t *ll);
-#define	smb_llist_enter(ll, mode)	rw_enter(&(ll)->ll_lock, mode)
 #define	smb_llist_head(ll)		list_head(&(ll)->ll_list)
 #define	smb_llist_next(ll, obj)		list_next(&(ll)->ll_list, obj)
 int	smb_account_connected(smb_user_t *user);
@@ -795,22 +814,19 @@
 void	smb_slist_insert_tail(smb_slist_t *sl, void *obj);
 void	smb_slist_remove(smb_slist_t *sl, void *obj);
 void	smb_slist_wait_for_empty(smb_slist_t *sl);
+void	smb_slist_enter(smb_slist_t *sl);
 void	smb_slist_exit(smb_slist_t *sl);
 uint32_t smb_slist_move_tail(list_t *lst, smb_slist_t *sl);
 void    smb_slist_obj_move(smb_slist_t *dst, smb_slist_t *src, void *obj);
-#define	smb_slist_enter(sl)		mutex_enter(&(sl)->sl_mutex)
 #define	smb_slist_head(sl)		list_head(&(sl)->sl_list)
 #define	smb_slist_next(sl, obj)		list_next(&(sl)->sl_list, obj)
 
 void    smb_rwx_init(smb_rwx_t *rwx);
 void    smb_rwx_destroy(smb_rwx_t *rwx);
-#define	smb_rwx_rwenter(rwx, mode)	rw_enter(&(rwx)->rwx_lock, mode)
+void	smb_rwx_rwenter(smb_rwx_t *rwx, krw_t);
 void    smb_rwx_rwexit(smb_rwx_t *rwx);
-int	smb_rwx_rwwait(smb_rwx_t *rwx, clock_t timeout);
-#define	smb_rwx_xenter(rwx)		mutex_enter(&(rwx)->rwx_mutex)
-#define	smb_rwx_xexit(rwx)		mutex_exit(&(rwx)->rwx_mutex)
-krw_t   smb_rwx_rwupgrade(smb_rwx_t *rwx);
-void    smb_rwx_rwdowngrade(smb_rwx_t *rwx, krw_t mode);
+int	smb_rwx_cvwait(smb_rwx_t *rwx, clock_t timeout);
+void    smb_rwx_cvbcast(smb_rwx_t *rwx);
 
 void	smb_thread_init(smb_thread_t *, char *, smb_thread_ep_t,
 		void *, pri_t);
@@ -918,6 +934,11 @@
 void smb_threshold_exit(smb_cmd_threshold_t *);
 void smb_threshold_wake_all(smb_cmd_threshold_t *);
 
+/* SMB hash function prototypes */
+smb_hash_t *smb_hash_create(size_t, size_t, uint32_t num_buckets);
+void smb_hash_destroy(smb_hash_t *);
+uint_t smb_hash_uint64(smb_hash_t *, uint64_t);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/usr/src/uts/common/smbsrv/smb_ktypes.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smbsrv/smb_ktypes.h	Wed Jun 12 22:46:54 2019 +0200
@@ -391,6 +391,17 @@
 	boolean_t	ll_flushing;
 } smb_llist_t;
 
+typedef struct smb_bucket {
+	smb_llist_t	b_list;
+	uint32_t	b_max_seen;
+} smb_bucket_t;
+
+typedef struct smb_hash {
+	uint32_t	rshift;
+	uint32_t	num_buckets;
+	smb_bucket_t	*buckets;
+} smb_hash_t;
+
 typedef struct smb_slist {
 	kmutex_t	sl_mutex;
 	kcondvar_t	sl_cv;
@@ -565,66 +576,58 @@
 
 #define	MBC_ROOM_FOR(b, n) (((b)->chain_offset + (n)) <= (b)->max_bytes)
 
-#define	OPLOCK_MIN_TIMEOUT	(5 * 1000)
-#define	OPLOCK_STD_TIMEOUT	(30 * 1000)
-
 /*
- * Oplock break flags:
- * SMB_OPLOCK_BREAK_EXCLUSIVE - only break exclusive oplock
- * (type SMB_OPLOCK_EXCLUSIVE or SMB_OPLOCK_BATCH)
- * SMB_OPLOCK_BREAK_BATCH - only break exclusive BATCH oplock
- * SMB_OPLOCK_BREAK_NOWAIT - do not wait for oplock break ack
+ * Per smb_node oplock state
  */
-#define	SMB_OPLOCK_NO_BREAK		0x00
-#define	SMB_OPLOCK_BREAK_TO_NONE	0x01
-#define	SMB_OPLOCK_BREAK_TO_LEVEL_II	0x02
-#define	SMB_OPLOCK_BREAK_EXCLUSIVE	0x04
-#define	SMB_OPLOCK_BREAK_BATCH		0x08
-#define	SMB_OPLOCK_BREAK_NOWAIT		0x10
-
-/*
- * Oplocks levels are defined to match the levels in the SMB
- * protocol (nt_create_andx / nt_transact_create) and should
- * not be changed
- */
-#define	SMB_OPLOCK_NONE		0
-#define	SMB_OPLOCK_EXCLUSIVE	1
-#define	SMB_OPLOCK_BATCH	2
-#define	SMB_OPLOCK_LEVEL_II	3
-
 typedef struct smb_oplock {
 	kmutex_t		ol_mutex;
-	kcondvar_t		ol_cv;
-	kthread_t		*ol_xthread;
 	boolean_t		ol_fem;		/* fem monitor installed? */
-	uint8_t			ol_brk_pending;
-	uint8_t			ol_break;
-	uint32_t		ol_count;	/* number of grants */
-	list_t			ol_grants;	/* list of smb_oplock_grant_t */
+	struct smb_ofile	*excl_open;
+	uint32_t		ol_state;
+	int32_t			cnt_II;
+	int32_t			cnt_R;
+	int32_t			cnt_RH;
+	int32_t			cnt_RHBQ;
+	int32_t			waiters;
+	kcondvar_t		WaitingOpenCV;
 } smb_oplock_t;
 
-#define	SMB_OPLOCK_GRANT_MAGIC	0x4F4C4B47	/* OLKG */
-#define	SMB_OPLOCK_GRANT_VALID(p) \
-	ASSERT((p)->og_magic == SMB_OPLOCK_GRANT_MAGIC)
-#define	SMB_OFILE_OPLOCK_GRANTED(p) \
-	((p)->f_oplock_grant.og_magic == SMB_OPLOCK_GRANT_MAGIC)
+/*
+ * Per smb_ofile oplock state
+ */
 typedef struct smb_oplock_grant {
-	list_node_t		og_lnd;
-	uint32_t		og_magic;
-	uint8_t			og_breaking;
-	uint8_t			og_level;
-	struct smb_ofile	*og_ofile;
+	/* smb protocol-level state */
+	uint32_t		og_state;	/* latest sent to client */
+	uint32_t		og_breaking;	/* BREAK_TO... flags */
+	uint16_t		og_dialect;	/* how to send breaks */
+	/* File-system level state */
+	uint8_t			onlist_II;
+	uint8_t			onlist_R;
+	uint8_t			onlist_RH;
+	uint8_t			onlist_RHBQ;
+	uint8_t			BreakingToRead;
 } smb_oplock_grant_t;
 
-#define	SMB_OPLOCK_BREAK_MAGIC	0x4F4C4B42	/* OLKB */
-#define	SMB_OPLOCK_BREAK_VALID(p) \
-	ASSERT((p)->ob_magic == SMB_OPLOCK_BREAK_MAGIC)
-typedef struct smb_oplock_break {
-	list_node_t	ob_lnd;
-	uint32_t	ob_magic;
-	struct smb_node	*ob_node;
-} smb_oplock_break_t;
+#define	SMB_LEASE_KEY_SZ	16
 
+typedef struct smb_lease {
+	list_node_t		ls_lnd;		/* sv_lease_ht */
+	kmutex_t		ls_mutex;
+	smb_llist_t		*ls_bucket;
+	struct smb_node		*ls_node;
+	/*
+	 * With a lease, just one ofile has the oplock.
+	 * This (used only for comparison) identifies which.
+	 */
+	void			*ls_oplock_ofile;
+	uint32_t		ls_refcnt;
+	uint32_t		ls_state;
+	uint32_t		ls_breaking;	/* BREAK_TO... flags */
+	uint16_t		ls_epoch;
+	uint16_t		ls_version;
+	uint8_t			ls_key[SMB_LEASE_KEY_SZ];
+	uint8_t			ls_clnt[SMB_LEASE_KEY_SZ];
+} smb_lease_t;
 
 #define	SMB_VFS_MAGIC	0x534D4256	/* 'SMBV' */
 
@@ -667,7 +670,6 @@
 	/* If entering both, go in order n_lock_list, n_wlock_list */
 	smb_llist_t		n_lock_list;	/* active locks */
 	smb_llist_t		n_wlock_list;	/* waiting locks */
-	uint32_t		n_pending_dosattr;
 	volatile int		flags;
 	u_offset_t		n_allocsz;
 	uint32_t		n_fcn_count;
@@ -687,6 +689,7 @@
 #define	NODE_FLAGS_SYSTEM		0x00008000
 #define	NODE_FLAGS_WRITE_THROUGH	0x00100000
 #define	NODE_XATTR_DIR			0x01000000
+#define	NODE_FLAGS_DELETE_COMMITTED	0x20000000
 #define	NODE_FLAGS_DELETE_ON_CLOSE	0x40000000
 #define	NODE_FLAGS_EXECUTABLE		0x80000000
 
@@ -716,6 +719,7 @@
 	char		*shr_access_rw;
 	avl_node_t	shr_link;
 	kmutex_t	shr_mutex;
+	smb_cfg_val_t	shr_encrypt; /* Share.EncryptData */
 } smb_kshare_t;
 
 
@@ -812,15 +816,39 @@
 #define	SMB_SIGNING_CHECK	2
 
 /*
+ * Locking notes:
+ * If you hold the mutex/lock on an object, don't flush the deleteq
+ * of the objects directly below it in the logical hierarchy
+ * (i.e. via smb_llist_exit()). I.e. don't drop s_tree_list when
+ * you hold u_mutex, because deleted trees need u_mutex to
+ * lower the refcnt.
+ *
+ * Note that this also applies to u_mutex and t_ofile_list.
+ */
+
+/*
+ * The "session" object.
+ *
+ * Note that the smb_session_t object here corresponds to what MS-SMB2
+ * calls a "connection".  Adding to the confusion, what MS calls a
+ * "session" corresponds to our smb_user_t (below).
+ */
+
+/*
  * Session State Machine
  * ---------------------
  *
- * +-----------------------------+	     +------------------------------+
- * | SMB_SESSION_STATE_CONNECTED |           | SMB_SESSION_STATE_TERMINATED |
- * +-----------------------------+           +------------------------------+
- *		T0|					     ^
- *		  +--------------------+		     |T5
- *		  v		       |T4                   |
+ *
+ * +-----------------------------+	    +----------------------------+
+ * | SMB_SESSION_STATE_CONNECTED |	    | SMB_SESSION_STATE_SHUTDOWN |
+ * +-----------------------------+	    +----------------------------+
+ *		  |					     ^
+ *		  |					     |T6
+ *		  |			    +------------------------------+
+ *		  |			    | SMB_SESSION_STATE_TERMINATED |
+ *		T0|			    +------------------------------+
+ *		  +--------------------+		     ^
+ *		  v		       |T4                   |T5
  * +-------------------------------+   |    +--------------------------------+
  * | SMB_SESSION_STATE_ESTABLISHED |---+--->| SMB_SESSION_STATE_DISCONNECTED |
  * +-------------------------------+        +--------------------------------+
@@ -856,6 +884,10 @@
  *
  *
  *
+ * Transition T6
+ *
+ *
+ *
  */
 #define	SMB_SESSION_MAGIC	0x53455353	/* 'SESS' */
 #define	SMB_SESSION_VALID(p)	\
@@ -870,6 +902,7 @@
 	SMB_SESSION_STATE_ESTABLISHED,
 	SMB_SESSION_STATE_NEGOTIATED,
 	SMB_SESSION_STATE_TERMINATED,
+	SMB_SESSION_STATE_SHUTDOWN,
 	SMB_SESSION_STATE_SENTINEL
 } smb_session_state_t;
 
@@ -906,9 +939,15 @@
 	uint16_t		s_max_credits;
 
 	uint32_t		capabilities;
+	uint32_t		srv_cap;
 
 	struct smb_sign		signing;	/* SMB1 */
 	void			*sign_mech;	/* mechanism info */
+	void			*enc_mech;
+
+	/* SMB2/SMB3 signing support */
+	int			(*sign_calc)(struct smb_request *,
+					struct mbuf_chain *, uint8_t *);
 	void			(*sign_fini)(struct smb_session *);
 
 	ksocket_t		sock;
@@ -925,11 +964,13 @@
 	volatile uint32_t	s_file_cnt;
 	volatile uint32_t	s_dir_cnt;
 
-	uint16_t		secmode;
+	uint16_t		cli_secmode;
+	uint16_t		srv_secmode;
 	uint32_t		sesskey;
 	uint32_t		challenge_len;
 	unsigned char		challenge_key[SMB_CHALLENGE_SZ];
 	int64_t			activity_timestamp;
+
 	/*
 	 * Maximum negotiated buffer sizes between SMB client and server
 	 * in SMB_SESSION_SETUP_ANDX
@@ -942,14 +983,22 @@
 	uint64_t		start_time;
 	unsigned char		MAC_key[44];
 	char			ip_addr_str[INET6_ADDRSTRLEN];
-	char			clnt_uuid[16];
+	uint8_t			clnt_uuid[16];
 	char 			workstation[SMB_PI_MAX_HOST];
 } smb_session_t;
 
+/*
+ * The "user" object.
+ *
+ * Note that smb_user_t object here corresponds to what MS-SMB2 calls
+ * a "session".  (Our smb_session_t is something else -- see above).
+ */
+
 #define	SMB_USER_MAGIC 0x55534552	/* 'USER' */
 #define	SMB_USER_VALID(u)	\
     ASSERT(((u) != NULL) && ((u)->u_magic == SMB_USER_MAGIC))
 
+/* These flags are all <= 0x00000010 */
 #define	SMB_USER_FLAG_GUEST			SMB_ATF_GUEST
 #define	SMB_USER_FLAG_ANON			SMB_ATF_ANON
 #define	SMB_USER_FLAG_ADMIN			SMB_ATF_ADMIN
@@ -964,7 +1013,9 @@
 #define	SMB_USER_PRIV_RESTORE		0x00000004
 #define	SMB_USER_PRIV_SECURITY		0x00000008
 
-
+/*
+ * See the long "User State Machine" comment in smb_user.c
+ */
 typedef enum {
 	SMB_USER_STATE_LOGGING_ON = 0,
 	SMB_USER_STATE_LOGGED_ON,
@@ -973,6 +1024,12 @@
 	SMB_USER_STATE_SENTINEL
 } smb_user_state_t;
 
+typedef enum {
+	SMB2_DH_PRESERVE_NONE = 0,
+	SMB2_DH_PRESERVE_SOME,
+	SMB2_DH_PRESERVE_ALL
+} smb_preserve_type_t;
+
 typedef struct smb_user {
 	list_node_t		u_lnd;
 	uint32_t		u_magic;
@@ -991,14 +1048,23 @@
 	cred_t			*u_cred;
 	cred_t			*u_privcred;
 
+	uint64_t		u_ssnid;	/* unique server-wide */
 	uint32_t		u_refcnt;
 	uint32_t		u_flags;
+	smb_preserve_type_t	preserve_opens;
 	uint32_t		u_privileges;
-	uint16_t		u_uid;
+	uint16_t		u_uid;		/* unique per-session */
 	uint32_t		u_audit_sid;
 
 	uint32_t		u_sign_flags;
 	struct smb_key		u_sign_key;	/* SMB2 signing */
+
+	struct smb_key		u_enc_key;
+	struct smb_key		u_dec_key;
+	volatile uint64_t	u_nonce_cnt;
+	uint8_t			u_nonce_fixed[4];
+	uint64_t		u_salt;
+	smb_cfg_val_t		u_encrypt;
 } smb_user_t;
 
 #define	SMB_TREE_MAGIC			0x54524545	/* 'TREE' */
@@ -1028,7 +1094,12 @@
 #define	SMB_TREE_DFSROOT		0x00020000
 #define	SMB_TREE_SPARSE			0x00040000
 #define	SMB_TREE_TRAVERSE_MOUNTS	0x00080000
+#define	SMB_TREE_FORCE_L2_OPLOCK	0x00100000
+/* Note: SMB_TREE_... in the mdb module too. */
 
+/*
+ * See the long "Tree State Machine" comment in smb_tree.c
+ */
 typedef enum {
 	SMB_TREE_STATE_CONNECTED = 0,
 	SMB_TREE_STATE_DISCONNECTING,
@@ -1071,6 +1142,7 @@
 	uint32_t		t_execflags;
 	time_t			t_connect_time;
 	volatile uint32_t	t_open_files;
+	smb_cfg_val_t		t_encrypt; /* Share.EncryptData */
 } smb_tree_t;
 
 #define	SMB_TREE_VFS(tree)	((tree)->t_snode->vp->v_vfsp)
@@ -1118,16 +1190,6 @@
 	smb_tree_has_feature((sr)->tid_tree, SMB_TREE_TRAVERSE_MOUNTS))
 
 /*
- * SMB_OFILE_IS_READONLY reflects whether an ofile is readonly or not.
- * The macro takes into account read-only settings in any of:
- * the tree, the node (pending) and the file-system object.
- * all of this is evaluated in smb_ofile_open() and after that
- * we can just test the f_flags & SMB_OFLAGS_READONLY
- */
-#define	SMB_OFILE_IS_READONLY(of)	\
-	((of)->f_flags & SMB_OFLAGS_READONLY)
-
-/*
  * SMB_PATHFILE_IS_READONLY indicates whether or not a file is
  * readonly when the caller has a path rather than an ofile.
  */
@@ -1260,7 +1322,7 @@
  *   will be set for the file node upon close.
  */
 
-#define	SMB_OFLAGS_READONLY		0x0001
+/*	SMB_OFLAGS_READONLY		0x0001 (obsolete) */
 #define	SMB_OFLAGS_EXECONLY		0x0002
 #define	SMB_OFLAGS_SET_DELETE_ON_CLOSE	0x0004
 #define	SMB_OFLAGS_LLF_POS_VALID	0x0008
@@ -1269,16 +1331,40 @@
 #define	SMB_OFILE_VALID(p)	\
     ASSERT((p != NULL) && ((p)->f_magic == SMB_OFILE_MAGIC))
 
+/*
+ * This is the size of the per-handle "Lock Sequence" array.
+ * See LockSequenceIndex in [MS-SMB2] 2.2.26, and smb2_lock.c
+ */
+#define	SMB_OFILE_LSEQ_MAX		64
+
+/* {arg_open,ofile}->dh_vers values */
 typedef enum {
-	SMB_OFILE_STATE_OPEN = 0,
+	SMB2_NOT_DURABLE = 0,
+	SMB2_DURABLE_V1,
+	SMB2_DURABLE_V2,
+	SMB2_RESILIENT,
+} smb_dh_vers_t;
+
+/*
+ * See the long "Ofile State Machine" comment in smb_ofile.c
+ */
+typedef enum {
+	SMB_OFILE_STATE_ALLOC = 0,
+	SMB_OFILE_STATE_OPEN,
+	SMB_OFILE_STATE_SAVE_DH,
+	SMB_OFILE_STATE_SAVING,
 	SMB_OFILE_STATE_CLOSING,
 	SMB_OFILE_STATE_CLOSED,
+	SMB_OFILE_STATE_ORPHANED,
+	SMB_OFILE_STATE_RECONNECT,
+	SMB_OFILE_STATE_EXPIRED,
 	SMB_OFILE_STATE_SENTINEL
 } smb_ofile_state_t;
 
 typedef struct smb_ofile {
-	list_node_t		f_lnd;	/* t_ofile_list */
-	list_node_t		f_nnd;	/* n_ofile_list */
+	list_node_t		f_tree_lnd;	/* t_ofile_list */
+	list_node_t		f_node_lnd;	/* n_ofile_list */
+	list_node_t		f_dh_lnd;	/* sv_persistid_ht */
 	uint32_t		f_magic;
 	kmutex_t		f_mutex;
 	smb_ofile_state_t	f_state;
@@ -1291,6 +1377,13 @@
 	smb_odir_t		*f_odir;
 	smb_opipe_t		*f_pipe;
 
+	kcondvar_t		f_cv;
+	/*
+	 * Note: f_persistid == 0 means this ofile has no persistid
+	 * (same interpretation at the protocol level).  IFF non-zero,
+	 * this ofile is linked in the sv_persistid_ht hash table.
+	 */
+	uint64_t		f_persistid;
 	uint32_t		f_uniqid;
 	uint32_t		f_refcnt;
 	uint64_t		f_seek_pos;
@@ -1307,9 +1400,20 @@
 	pid_t			f_pid;
 	smb_attr_t		f_pending_attr;
 	boolean_t		f_written;
+	smb_oplock_grant_t	f_oplock;
+	uint8_t			TargetOplockKey[SMB_LEASE_KEY_SZ];
+	uint8_t			ParentOplockKey[SMB_LEASE_KEY_SZ];
+	struct smb_lease	*f_lease;
+
+	smb_notify_t		f_notify;
+
+	smb_dh_vers_t		dh_vers;
+	hrtime_t		dh_timeout_offset; /* time offset for timeout */
+	hrtime_t		dh_expire_time; /* time the handle expires */
+	boolean_t		dh_persist;
+	uint8_t			dh_create_guid[16];
 	char			f_quota_resume[SMB_SID_STRSZ];
-	smb_oplock_grant_t	f_oplock_grant;
-	smb_notify_t		f_notify;
+	uint8_t			f_lock_seq[SMB_OFILE_LSEQ_MAX];
 } smb_ofile_t;
 
 typedef struct smb_fileinfo {
@@ -1505,8 +1609,21 @@
 	smb_opipe_t	*pipe;	/* for smb_opipe_open */
 	struct smb_sd	*sd;	/* for NTTransactCreate */
 	void		*create_ctx;
+
 	uint8_t		op_oplock_level;	/* requested/granted level */
-	boolean_t	op_oplock_levelII;	/* TRUE if levelII supported */
+	uint32_t	op_oplock_state;	/* internal type+level */
+	uint32_t	lease_state;		/* SMB2_LEASE_... */
+	uint32_t	lease_flags;
+	uint16_t	lease_epoch;
+	uint16_t	lease_version;		/* 1 or 2 */
+	uint8_t		lease_key[SMB_LEASE_KEY_SZ];	/* from client */
+	uint8_t		parent_lease_key[SMB_LEASE_KEY_SZ]; /* for V2 */
+
+	smb_dh_vers_t	dh_vers;
+	smb2fid_t	dh_fileid;		/* for durable reconnect */
+	uint8_t		create_guid[16];
+	uint32_t	dh_v2_flags;
+	uint32_t	dh_timeout;
 } smb_arg_open_t;
 
 typedef struct smb_arg_lock {
@@ -1515,7 +1632,10 @@
 	uint32_t	lseq;
 } smb_arg_lock_t;
 
-struct smb_async_req;
+typedef struct smb_arg_olbrk {
+	uint32_t	NewLevel;
+	boolean_t	AckRequired;
+} smb_arg_olbrk_t;
 
 /*
  * SMB Request State Machine
@@ -1668,6 +1788,9 @@
 	void			(*cancel_method)(struct smb_request *);
 	void			*cancel_arg2;
 
+	/* Queue used by smb_request_append_postwork. */
+	struct smb_request	*sr_postwork;
+
 	list_node_t		sr_waiters;	/* smb_notify.c */
 
 	/* Info from session service header */
@@ -1698,7 +1821,7 @@
 	unsigned char		smb_sig[8];	/* signiture */
 	uint16_t		smb_tid;	/* tree id #  */
 	uint32_t		smb_pid;	/* caller's process id # */
-	uint16_t		smb_uid;	/* user id # */
+	uint16_t		smb_uid;	/* local (smb1) user id # */
 	uint16_t		smb_mid;	/* mutiplex id #  */
 	unsigned char		smb_wct;	/* count of parameter words */
 	uint16_t		smb_bcc;	/* data byte count */
@@ -1728,12 +1851,21 @@
 	uint64_t		smb2_first_msgid;
 	/* uint32_t		smb2_pid; use smb_pid */
 	/* uint32_t		smb2_tid; use smb_tid */
-	/* uint64_t		smb2_ssnid; use smb_uid */
-	unsigned char		smb2_sig[16];	/* signiture */
+	uint64_t		smb2_ssnid;	/* See u_ssnid */
+	uint8_t			smb2_sig[16];	/* signature */
 
+	/*
+	 * SMB3 transform header fields. [MS-SMB2 2.2.41]
+	 */
+	uint64_t		smb3_tform_ssnid;
+	smb_user_t		*tform_ssn;
+	uint32_t		msgsize;
+	uint8_t			nonce[16];
+
+	boolean_t		encrypted;
+
+	boolean_t		smb2_async;
 	uint64_t		smb2_async_id;
-	struct smb2_async_req	*sr_async_req;
-
 	/* Parameters */
 	struct mbuf_chain	smb_vwv;	/* variable width value */
 
@@ -1764,8 +1896,8 @@
 		smb_arg_dirop_t		dirop;
 		smb_arg_open_t		open;
 		smb_arg_lock_t		lock;
+		smb_arg_olbrk_t		olbrk;	/* for async oplock break */
 		smb_rw_param_t		*rw;
-		smb_oplock_grant_t	olbrk;	/* for async oplock break */
 		int32_t			timestamp;
 	} arg;
 } smb_request_t;
@@ -1896,7 +2028,6 @@
 	int			ld_family;
 	struct sockaddr_in	ld_sin;
 	struct sockaddr_in6	ld_sin6;
-	smb_llist_t		ld_session_list;
 } smb_listener_daemon_t;
 
 #define	SMB_SSETUP_CMD			"authentication"
@@ -1957,6 +2088,9 @@
 	krwlock_t		sv_cfg_lock;
 	smb_kmod_cfg_t		sv_cfg;
 	smb_session_t		*sv_session;
+	smb_llist_t		sv_session_list;
+	smb_hash_t		*sv_persistid_ht;
+	smb_hash_t		*sv_lease_ht;
 
 	struct smb_export	sv_export;
 	struct __door_handle	*sv_lmshrd;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/smbsrv/smb_oplock.h	Wed Jun 12 22:46:54 2019 +0200
@@ -0,0 +1,252 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+#ifndef _SMBSRV_SMB_OPLOCK_H
+#define	_SMBSRV_SMB_OPLOCK_H
+
+#include <smbsrv/ntifs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * 2.1.1.10 Per Oplock
+ *
+ *
+ * ExclusiveOpen: The Open used to request the opportunistic lock.
+ *
+ * IIOplocks: A list of zero or more Opens used to request a LEVEL_TWO
+ *  opportunistic lock, as specified in section 2.1.5.17.1.
+ *
+ * ROplocks: A list of zero or more Opens used to request a LEVEL_GRANULAR
+ *  (RequestedOplockLevel: READ_CACHING) opportunistic lock, as specified in
+ *   section 2.1.5.17.1.
+ *
+ * RHOplocks: A list of zero or more Opens used to request a LEVEL_GRANULAR
+ *  (RequestedOplockLevel: (READ_CACHING|HANDLE_CACHING)) opportunistic lock,
+ *  as specified in section 2.1.5.17.1.
+ *
+ * RHBreakQueue: A list of zero or more RHOpContext objects. This queue is
+ *  used to track (READ_CACHING|HANDLE_CACHING) oplocks as they are breaking.
+ *
+ * WaitList: A list of zero or more Opens belonging to operations that are
+ *  waiting for an oplock to break, as specified in section 2.1.4.12.
+ *
+ * State: The current state of the oplock, expressed as a combination of
+ *  one or more flags. Valid flags are:
+ *	[ As follows;  Re-ordered a bit from the spec. ]
+ */
+
+/*
+ * READ_CACHING - Indicates that this Oplock represents an oplock
+ * that provides caching of reads; this provides the SMB 2.1 read
+ * caching lease, as described in [MS-SMB2] section 2.2.13.2.8.
+ */
+#define	READ_CACHING	OPLOCK_LEVEL_CACHE_READ		/* 1 */
+
+/*
+ * HANDLE_CACHING - Indicates that this Oplock represents an oplock
+ * that provides caching of handles; this provides the SMB 2.1 handle
+ * caching lease, as described in [MS-SMB2] section 2.2.13.2.8.
+ */
+#define	HANDLE_CACHING	OPLOCK_LEVEL_CACHE_HANDLE	/* 2 */
+
+/*
+ * WRITE_CACHING - Indicates that this Oplock represents an oplock
+ * that provides caching of writes; this provides the SMB 2.1 write
+ * caching lease, as described in [MS-SMB2] section 2.2.13.2.8.
+ */
+#define	WRITE_CACHING	OPLOCK_LEVEL_CACHE_WRITE	/* 4 */
+
+/*
+ * EXCLUSIVE - Indicates that this Oplock represents an oplock that
+ * can be held by exactly one client at a time. This flag always appears
+ * in combination with other flags that indicate the actual oplock level.
+ * For example, (READ_CACHING|WRITE_CACHING|EXCLUSIVE) represents a
+ * read caching and write caching oplock, which can be held by only
+ * one client at a time.
+ */
+#define	EXCLUSIVE			0x00000010
+
+/*
+ * MIXED_R_AND_RH - Always appears together with READ_CACHING and
+ * HANDLE_CACHING.  Indicates that this Oplock represents an oplock
+ * on which at least one client has been granted a read caching oplock,
+ * and at least one other client has been granted a read caching and
+ * handle caching oplock.
+ */
+#define	MIXED_R_AND_RH			0x00000020
+
+/*
+ * LEVEL_TWO_OPLOCK - Indicates that this Oplock represents a
+ * Level 2 (also called Shared) oplock.
+ * Corresponds to SMB2_OPLOCK_LEVEL_II
+ */
+#define	LEVEL_TWO_OPLOCK	OPLOCK_LEVEL_TWO	/* 0x100 */
+
+/*
+ * LEVEL_ONE_OPLOCK - Indicates that this Oplock represents a
+ * Level 1 (also called Exclusive) oplock.
+ * Corresponds to SMB2_OPLOCK_LEVEL_EXCLUSIVE
+ */
+#define	LEVEL_ONE_OPLOCK	OPLOCK_LEVEL_ONE	/* 0x200 */
+
+/*
+ * BATCH_OPLOCK - Indicates that this Oplock represents a Batch oplock.
+ * Corresponds to SMB2_OPLOCK_LEVEL_BATCH
+ */
+#define	BATCH_OPLOCK		OPLOCK_LEVEL_BATCH	/* 0x400 */
+
+/* Note: ntifs.h		OPLOCK_LEVEL_GRANULAR	   0x800 */
+
+/*
+ * Note that the oplock leasing implementation uses this shift
+ * to convert (i.e.) CACHE_READ to BREAK_TO_READ_CACHING etc.
+ * This relationship is checked in smb_srv_oplock.c
+ */
+#define	BREAK_SHIFT 16
+
+/*
+ * BREAK_TO_READ_CACHING - Indicates that this Oplock represents an
+ * oplock that is currently breaking to an oplock that provides
+ * caching of reads; the oplock has broken but the break has not yet
+ * been acknowledged.
+ */
+#define	BREAK_TO_READ_CACHING		0x00010000
+
+/*
+ * BREAK_TO_HANDLE_CACHING - Indicates that this Oplock represents an
+ * oplock that is currently breaking to an oplock that provides
+ * caching of handles; the oplock has broken but the break has not yet
+ * been acknowledged.  Note: == (CACHE_HANDLE << BREAK_SHIFT)
+ */
+#define	BREAK_TO_HANDLE_CACHING		0x00020000
+
+/*
+ * BREAK_TO_WRITE_CACHING - Indicates that this Oplock represents an
+ * oplock that is currently breaking to an oplock that provides
+ * caching of writes; the oplock has broken but the break has
+ * not yet been acknowledged.
+ */
+#define	BREAK_TO_WRITE_CACHING		0x00040000
+
+/*
+ * BREAK_TO_NO_CACHING - Indicates that this Oplock represents an
+ * oplock that is currently breaking to None (that is, no oplock);
+ * the oplock has broken but the break has not yet been acknowledged.
+ */
+#define	BREAK_TO_NO_CACHING		0x00080000
+
+/*
+ * BREAK_TO_TWO - Indicates that this Oplock represents an oplock
+ * that is currently breaking from either Level 1 or Batch to Level 2;
+ * the oplock has broken but the break has not yet been acknowledged.
+ */
+#define	BREAK_TO_TWO			0x00100000
+
+/*
+ * BREAK_TO_NONE - Indicates that this Oplock represents an oplock
+ * that is currently breaking from either Level 1 or Batch to None
+ * (that is, no oplock); the oplock has broken but the break has
+ * not yet been acknowledged.
+ */
+#define	BREAK_TO_NONE			0x00200000
+
+/*
+ * BREAK_TO_TWO_TO_NONE - Indicates that this Oplock represents an
+ * oplock that is currently breaking from either Level 1 or Batch to
+ * None (that is, no oplock), and was previously breaking from Level 1
+ *  or Batch to Level 2; the oplock has broken but the break has
+ * not yet been acknowledged.
+ */
+#define	BREAK_TO_TWO_TO_NONE		0x00400000
+
+/*
+ * NO_OPLOCK - Indicates that this Oplock does not represent a
+ * currently granted or breaking oplock. This is semantically
+ * equivalent to the Oplock object being entirely absent from a
+ * Stream. This flag always appears alone.
+ * Note we also have OPLOCK_LEVEL_NONE == 0 from ntifs.h
+ */
+#define	NO_OPLOCK			0x10000000
+
+/*
+ * An internal flag, non-overlapping wth other oplock flags,
+ * used only in smb_cmn_oplock.c (and here only to make clear
+ * that it does not overlap with an other flags above).
+ */
+#define	PARENT_OBJECT			0x40000000
+
+/*
+ * Also not in the spec, but convenient
+ */
+#define	BREAK_LEVEL_MASK (\
+	BREAK_TO_READ_CACHING |\
+	BREAK_TO_WRITE_CACHING |\
+	BREAK_TO_HANDLE_CACHING |\
+	BREAK_TO_NO_CACHING)
+
+#define	BREAK_ANY (\
+	BREAK_LEVEL_MASK |\
+	BREAK_TO_TWO |\
+	BREAK_TO_NONE |\
+	BREAK_TO_TWO_TO_NONE)
+
+
+/*
+ * Convenience macro to walk ofiles on a give node.
+ * Used as follows:
+ *	FOREACH_NODE_OFILE(node, o) { muck_with(o); }
+ */
+#define	FOREACH_NODE_OFILE(node, o)	for \
+	(o = smb_llist_head(&node->n_ofile_list); \
+	o != NULL; \
+	o = smb_llist_next(&node->n_ofile_list, o))
+
+/*
+ * Some short-hand names used in the oplock code.
+ */
+
+#define	STATUS_NEW_HANDLE	NT_STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE
+#define	STATUS_CANT_GRANT	NT_STATUS_CANNOT_GRANT_REQUESTED_OPLOCK
+
+typedef enum oplock_type {
+	LEVEL_NONE = OPLOCK_LEVEL_NONE,
+	LEVEL_TWO = OPLOCK_LEVEL_TWO,
+	LEVEL_ONE = OPLOCK_LEVEL_ONE,
+	LEVEL_BATCH = OPLOCK_LEVEL_BATCH,
+	LEVEL_GRANULAR = OPLOCK_LEVEL_GRANULAR
+} oplock_type_t;
+
+typedef enum oplock_cache_level {
+	CACHE_R =	READ_CACHING,
+
+	CACHE_RH =	READ_CACHING |
+			HANDLE_CACHING,
+
+	CACHE_RW =	READ_CACHING |
+			WRITE_CACHING,
+
+	CACHE_RWH =	READ_CACHING |
+			WRITE_CACHING |
+			HANDLE_CACHING,
+} oplock_cache_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SMBSRV_SMB_OPLOCK_H */
--- a/usr/src/uts/common/smbsrv/smb_share.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smbsrv/smb_share.h	Wed Jun 12 22:46:54 2019 +0200
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 2016 by Delphix. All rights reserved.
  */
 
@@ -34,6 +34,7 @@
 #include <smbsrv/string.h>
 #include <smbsrv/smb_inet.h>
 #include <smbsrv/hash_table.h>
+#include <smbsrv/smbinfo.h>
 
 #if !defined(_KERNEL) && !defined(_FAKE_KERNEL)
 #include <libshare.h>
@@ -76,7 +77,13 @@
  *
  * quotas		SMB quotas presented & supported (T/F)
  *
- * next three properties use access-list a al NFS
+ * encrypt		Controls SMB3 encryption per-share.
+ *	disabled	Server does not tell the client to encrypt requests.
+ *	enabled		Server asks, but does not require, that the client
+ *			encrypt its requests.
+ *	required	Server denies unencrypted share access.
+ *
+ * next three properties use access-list a la NFS
  *
  * ro			list of hosts that will have read-only access
  * rw			list of hosts that will have read/write access
@@ -94,6 +101,8 @@
 #define	SHOPT_DFSROOT		"dfsroot"
 #define	SHOPT_DESCRIPTION	"description"
 #define	SHOPT_QUOTAS		"quotas"
+#define	SHOPT_FSO		"fso"	/* Force Shared Oplocks */
+#define	SHOPT_ENCRYPT		"encrypt"
 #define	SHOPT_AUTOHOME		"Autohome"
 
 #define	SMB_DEFAULT_SHARE_GROUP	"smb"
@@ -174,7 +183,8 @@
 #define	SMB_SHRF_ACC_RW		0x0400
 #define	SMB_SHRF_ACC_ALL	0x0F00
 
-#define	SMB_SHRF_QUOTAS		0x1000
+#define	SMB_SHRF_QUOTAS		0x1000	/* Enable SMB Quotas */
+#define	SMB_SHRF_FSO		0x2000	/* Force Shared Oplocks */
 
 /*
  * Runtime flags
@@ -206,6 +216,7 @@
 	char		shr_access_none[MAXPATHLEN];
 	char		shr_access_ro[MAXPATHLEN];
 	char		shr_access_rw[MAXPATHLEN];
+	smb_cfg_val_t	shr_encrypt;
 } smb_share_t;
 
 typedef struct smb_shriter {
--- a/usr/src/uts/common/smbsrv/smb_signing.h	Wed Jun 05 14:38:04 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/*
- * This file and its contents are supplied under the terms of the
- * Common Development and Distribution License ("CDDL"), version 1.0.
- * You may only use this file in accordance with the terms of version
- * 1.0 of the CDDL.
- *
- * A full copy of the text of the CDDL should have accompanied this
- * source.  A copy of the CDDL is also available via the Internet at
- * http://www.illumos.org/license/CDDL.
- */
-
-/*
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
- */
-
-#ifndef _SMB_SIGNING_H_
-#define	_SMB_SIGNING_H_
-
-/*
- * SMB signing routines used in {smb,smb2}_signing.c
- * Two implementations of these (kernel/user) in:
- *	uts/common/fs/smbsrv/smb_sign_kcf.c
- *	lib/smbsrv/libfksmbsrv/common/fksmb_sign_pkcs.c
- */
-
-#ifdef	_KERNEL
-#include <sys/crypto/api.h>
-#else
-#include <security/cryptoki.h>
-#include <security/pkcs11.h>
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define	MD5_DIGEST_LENGTH	16	/* MD5 digest length in bytes */
-#define	SHA256_DIGEST_LENGTH	32	/* SHA256 digest length in bytes */
-#define	SMB2_SIG_SIZE		16
-
-#ifdef	_KERNEL
-/* KCF variant */
-typedef crypto_mechanism_t	smb_sign_mech_t;
-typedef crypto_context_t	smb_sign_ctx_t;
-#else	/* _KERNEL */
-/* PKCS11 variant */
-typedef CK_MECHANISM		smb_sign_mech_t;
-typedef CK_SESSION_HANDLE	smb_sign_ctx_t;
-#endif	/* _KERNEL */
-
-/*
- * SMB signing routines used in smb_signing.c
- */
-
-int smb_md5_getmech(smb_sign_mech_t *);
-int smb_md5_init(smb_sign_ctx_t *, smb_sign_mech_t *);
-int smb_md5_update(smb_sign_ctx_t, void *, size_t);
-int smb_md5_final(smb_sign_ctx_t, uint8_t *);
-
-/*
- * SMB2 signing routines used in smb2_signing.c
- */
-
-int smb2_hmac_getmech(smb_sign_mech_t *);
-int smb2_hmac_init(smb_sign_ctx_t *, smb_sign_mech_t *, uint8_t *, size_t);
-int smb2_hmac_update(smb_sign_ctx_t, uint8_t *, size_t);
-int smb2_hmac_final(smb_sign_ctx_t, uint8_t *);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* _SMB_SIGNING_H_ */
--- a/usr/src/uts/common/smbsrv/smb_xdr.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smbsrv/smb_xdr.h	Wed Jun 12 22:46:54 2019 +0200
@@ -127,7 +127,6 @@
  */
 typedef struct smb_netuserinfo {
 	uint64_t	ui_session_id;
-	uint16_t	ui_smb_uid;
 	uint16_t	ui_domain_len;
 	char		*ui_domain;
 	uint16_t	ui_account_len;
--- a/usr/src/uts/common/smbsrv/smbinfo.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/smbsrv/smbinfo.h	Wed Jun 12 22:46:54 2019 +0200
@@ -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 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #ifndef	_SMBSRV_SMBINFO_H
@@ -131,6 +131,14 @@
 	uint32_t	sv_platform_id;
 } smb_version_t;
 
+typedef enum smb_cfg_val {
+	SMB_CONFIG_DISABLED = 0,
+	SMB_CONFIG_ENABLED = 1,
+	SMB_CONFIG_REQUIRED = 2
+} smb_cfg_val_t;
+
+void smb_cfg_set_require(const char *, smb_cfg_val_t *);
+
 /* See also: smb_ioc_cfg_t */
 typedef struct smb_kmod_cfg {
 	uint32_t skc_maxworkers;
@@ -147,6 +155,7 @@
 	int32_t skc_print_enable;
 	int32_t skc_traverse_mounts;
 	uint32_t skc_max_protocol;	/* SMB_VERS_... */
+	smb_cfg_val_t skc_encrypt; /* EncryptData and RejectUnencryptedAccess */
 	uint32_t skc_execflags;
 	uint32_t skc_negtok_len;
 	smb_version_t skc_version;
--- a/usr/src/uts/common/sys/bitmap.h	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/common/sys/bitmap.h	Wed Jun 12 22:46:54 2019 +0200
@@ -26,6 +26,7 @@
 
 /*
  * Copyright (c) 2014 by Delphix. All rights reserved.
+ * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
  * Copyright 2017 RackTop Systems.
  */
 
--- a/usr/src/uts/intel/smbsrv/Makefile	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/intel/smbsrv/Makefile	Wed Jun 12 22:46:54 2019 +0200
@@ -22,7 +22,7 @@
 # Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-# Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 #
 # Copyright (c) 2018, Joyent, Inc.
 
--- a/usr/src/uts/sparc/smbsrv/Makefile	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/sparc/smbsrv/Makefile	Wed Jun 12 22:46:54 2019 +0200
@@ -22,7 +22,7 @@
 # Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-# Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 #
 
 #
--- a/usr/src/uts/sun4/os/mp_startup.c	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/sun4/os/mp_startup.c	Wed Jun 12 22:46:54 2019 +0200
@@ -26,6 +26,7 @@
 
 /*
  * Copyright 2019 Joyent, Inc.
+ * Copyright 2019 Peter Tribble.
  */
 
 #include <sys/sysmacros.h>
@@ -167,7 +168,7 @@
 
 	cp = cpu[cpuid];
 
-	if (!(cpu->cpu_flags & CPU_ENABLE))
+	if (!(cp->cpu_flags & CPU_ENABLE))
 		ncpus_intr_enabled++;
 
 	cp->cpu_flags |= CPU_RUNNING | CPU_ENABLE | CPU_EXISTS;
--- a/usr/src/uts/sun4u/sys/Makefile	Wed Jun 05 14:38:04 2019 +0200
+++ b/usr/src/uts/sun4u/sys/Makefile	Wed Jun 12 22:46:54 2019 +0200
@@ -48,7 +48,6 @@
 	errclassify.h		\
 	fcode.h			\
 	fc_plat.h		\
-	ht.h			\
 	idprom.h		\
 	intr.h			\
 	intreg.h		\
@@ -58,6 +57,7 @@
 	nexusdebug.h		\
 	prom_debug.h		\
 	scb.h			\
+	smt.h			\
 	sun4asi.h		\
 	tod.h			\
 	trapstat.h		\