194047d49SGordon Ross /*
294047d49SGordon Ross  * This file and its contents are supplied under the terms of the
394047d49SGordon Ross  * Common Development and Distribution License ("CDDL"), version 1.0.
494047d49SGordon Ross  * You may only use this file in accordance with the terms of version
594047d49SGordon Ross  * 1.0 of the CDDL.
694047d49SGordon Ross  *
794047d49SGordon Ross  * A full copy of the text of the CDDL should have accompanied this
894047d49SGordon Ross  * source.  A copy of the CDDL is also available via the Internet at
994047d49SGordon Ross  * http://www.illumos.org/license/CDDL.
1094047d49SGordon Ross  */
1194047d49SGordon Ross 
1294047d49SGordon Ross /*
13a9931e68SGordon Ross  * Copyright 2021 Tintri by DDN, Inc.  All rights reserved.
14*6f8336c5SGordon Ross  * Copyright 2021 RackTop Systems, Inc.
1594047d49SGordon Ross  */
1694047d49SGordon Ross 
1794047d49SGordon Ross /*
1894047d49SGordon Ross  * Dispatch function for SMB2_OPLOCK_BREAK
1994047d49SGordon Ross  */
2094047d49SGordon Ross 
2194047d49SGordon Ross #include <smbsrv/smb2_kproto.h>
2294047d49SGordon Ross #include <smbsrv/smb_oplock.h>
2394047d49SGordon Ross 
2494047d49SGordon Ross /* StructSize for the two "break" message formats. */
2594047d49SGordon Ross #define	SSZ_OPLOCK	24
2694047d49SGordon Ross #define	SSZ_LEASE_ACK	36
2794047d49SGordon Ross #define	SSZ_LEASE_BRK	44
2894047d49SGordon Ross 
2994047d49SGordon Ross #define	NODE_FLAGS_DELETING	(NODE_FLAGS_DELETE_ON_CLOSE |\
3094047d49SGordon Ross 				NODE_FLAGS_DELETE_COMMITTED)
3194047d49SGordon Ross 
3294047d49SGordon Ross static const char lease_zero[UUID_LEN] = { 0 };
3394047d49SGordon Ross 
3494047d49SGordon Ross static kmem_cache_t	*smb_lease_cache = NULL;
3594047d49SGordon Ross 
3694047d49SGordon Ross void
3794047d49SGordon Ross smb2_lease_init()
3894047d49SGordon Ross {
3994047d49SGordon Ross 	if (smb_lease_cache != NULL)
4094047d49SGordon Ross 		return;
4194047d49SGordon Ross 
4294047d49SGordon Ross 	smb_lease_cache = kmem_cache_create("smb_lease_cache",
4394047d49SGordon Ross 	    sizeof (smb_lease_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
4494047d49SGordon Ross }
4594047d49SGordon Ross 
4694047d49SGordon Ross void
4794047d49SGordon Ross smb2_lease_fini()
4894047d49SGordon Ross {
4994047d49SGordon Ross 	if (smb_lease_cache != NULL) {
5094047d49SGordon Ross 		kmem_cache_destroy(smb_lease_cache);
5194047d49SGordon Ross 		smb_lease_cache = NULL;
5294047d49SGordon Ross 	}
5394047d49SGordon Ross }
5494047d49SGordon Ross 
5594047d49SGordon Ross static void
5694047d49SGordon Ross smb2_lease_hold(smb_lease_t *ls)
5794047d49SGordon Ross {
5894047d49SGordon Ross 	mutex_enter(&ls->ls_mutex);
5994047d49SGordon Ross 	ls->ls_refcnt++;
6094047d49SGordon Ross 	mutex_exit(&ls->ls_mutex);
6194047d49SGordon Ross }
6294047d49SGordon Ross 
6394047d49SGordon Ross void
6494047d49SGordon Ross smb2_lease_rele(smb_lease_t *ls)
6594047d49SGordon Ross {
6694047d49SGordon Ross 	smb_llist_t *bucket;
6794047d49SGordon Ross 
6894047d49SGordon Ross 	mutex_enter(&ls->ls_mutex);
6994047d49SGordon Ross 	ls->ls_refcnt--;
7094047d49SGordon Ross 	if (ls->ls_refcnt != 0) {
7194047d49SGordon Ross 		mutex_exit(&ls->ls_mutex);
7294047d49SGordon Ross 		return;
7394047d49SGordon Ross 	}
7494047d49SGordon Ross 	mutex_exit(&ls->ls_mutex);
7594047d49SGordon Ross 
7694047d49SGordon Ross 	/*
7794047d49SGordon Ross 	 * Get the list lock, then re-check the refcnt
7894047d49SGordon Ross 	 * and if it's still zero, unlink & destroy.
7994047d49SGordon Ross 	 */
8094047d49SGordon Ross 	bucket = ls->ls_bucket;
8194047d49SGordon Ross 	smb_llist_enter(bucket, RW_WRITER);
8294047d49SGordon Ross 
8394047d49SGordon Ross 	mutex_enter(&ls->ls_mutex);
8494047d49SGordon Ross 	if (ls->ls_refcnt == 0)
8594047d49SGordon Ross 		smb_llist_remove(bucket, ls);
8694047d49SGordon Ross 	mutex_exit(&ls->ls_mutex);
8794047d49SGordon Ross 
8894047d49SGordon Ross 	if (ls->ls_refcnt == 0) {
8994047d49SGordon Ross 		mutex_destroy(&ls->ls_mutex);
9094047d49SGordon Ross 		kmem_cache_free(smb_lease_cache, ls);
9194047d49SGordon Ross 	}
9294047d49SGordon Ross 
9394047d49SGordon Ross 	smb_llist_exit(bucket);
9494047d49SGordon Ross }
9594047d49SGordon Ross 
9694047d49SGordon Ross /*
9794047d49SGordon Ross  * Compute a hash from a uuid
9894047d49SGordon Ross  * Based on mod_hash_bystr()
9994047d49SGordon Ross  */
10094047d49SGordon Ross static uint_t
10194047d49SGordon Ross smb_hash_uuid(const uint8_t *uuid)
10294047d49SGordon Ross {
10394047d49SGordon Ross 	char *k = (char *)uuid;
10494047d49SGordon Ross 	uint_t hash = 0;
10594047d49SGordon Ross 	uint_t g;
10694047d49SGordon Ross 	int i;
10794047d49SGordon Ross 
10894047d49SGordon Ross 	ASSERT(k);
10994047d49SGordon Ross 	for (i = 0; i < UUID_LEN; i++) {
11094047d49SGordon Ross 		hash = (hash << 4) + k[i];
11194047d49SGordon Ross 		if ((g = (hash & 0xf0000000)) != 0) {
11294047d49SGordon Ross 			hash ^= (g >> 24);
11394047d49SGordon Ross 			hash ^= g;
11494047d49SGordon Ross 		}
11594047d49SGordon Ross 	}
11694047d49SGordon Ross 	return (hash);
11794047d49SGordon Ross }
11894047d49SGordon Ross 
11994047d49SGordon Ross /*
12094047d49SGordon Ross  * Add or update a lease table entry for a new ofile.
12194047d49SGordon Ross  * (in the per-session lease table)
12294047d49SGordon Ross  * See [MS-SMB2] 3.3.5.9.8
12394047d49SGordon Ross  * Handling the SMB2_CREATE_REQUEST_LEASE Create Context
12494047d49SGordon Ross  */
12594047d49SGordon Ross uint32_t
1268d94f651SGordon Ross smb2_lease_create(smb_request_t *sr, uint8_t *clnt)
12794047d49SGordon Ross {
12894047d49SGordon Ross 	smb_arg_open_t *op = &sr->arg.open;
12994047d49SGordon Ross 	uint8_t *key = op->lease_key;
13094047d49SGordon Ross 	smb_ofile_t *of = sr->fid_ofile;
13194047d49SGordon Ross 	smb_hash_t *ht = sr->sr_server->sv_lease_ht;
13294047d49SGordon Ross 	smb_llist_t *bucket;
13394047d49SGordon Ross 	smb_lease_t *lease;
13494047d49SGordon Ross 	smb_lease_t *newlease;
13594047d49SGordon Ross 	size_t hashkey;
13694047d49SGordon Ross 	uint32_t status = NT_STATUS_INVALID_PARAMETER;
13794047d49SGordon Ross 
13894047d49SGordon Ross 	if (bcmp(key, lease_zero, UUID_LEN) == 0)
13994047d49SGordon Ross 		return (status);
14094047d49SGordon Ross 
14194047d49SGordon Ross 	/*
14294047d49SGordon Ross 	 * Find or create, and add a ref for the new ofile.
14394047d49SGordon Ross 	 */
14494047d49SGordon Ross 	hashkey = smb_hash_uuid(key);
14594047d49SGordon Ross 	hashkey &= (ht->num_buckets - 1);
14694047d49SGordon Ross 	bucket = &ht->buckets[hashkey].b_list;
14794047d49SGordon Ross 
14894047d49SGordon Ross 	newlease = kmem_cache_alloc(smb_lease_cache, KM_SLEEP);
14994047d49SGordon Ross 	bzero(newlease, sizeof (smb_lease_t));
15094047d49SGordon Ross 	mutex_init(&newlease->ls_mutex, NULL, MUTEX_DEFAULT, NULL);
15194047d49SGordon Ross 	newlease->ls_bucket = bucket;
15294047d49SGordon Ross 	newlease->ls_node = of->f_node;
15394047d49SGordon Ross 	newlease->ls_refcnt = 1;
15494047d49SGordon Ross 	newlease->ls_epoch = op->lease_epoch;
15594047d49SGordon Ross 	newlease->ls_version = op->lease_version;
15694047d49SGordon Ross 	bcopy(key, newlease->ls_key, UUID_LEN);
15794047d49SGordon Ross 	bcopy(clnt, newlease->ls_clnt, UUID_LEN);
15894047d49SGordon Ross 
15994047d49SGordon Ross 	smb_llist_enter(bucket, RW_WRITER);
16094047d49SGordon Ross 	for (lease = smb_llist_head(bucket); lease != NULL;
16194047d49SGordon Ross 	    lease = smb_llist_next(bucket, lease)) {
16294047d49SGordon Ross 		/*
16394047d49SGordon Ross 		 * Looking for this lease ID, on a node
16494047d49SGordon Ross 		 * that's not being deleted.
16594047d49SGordon Ross 		 */
16694047d49SGordon Ross 		if (bcmp(lease->ls_key, key, UUID_LEN) == 0 &&
16794047d49SGordon Ross 		    bcmp(lease->ls_clnt, clnt, UUID_LEN) == 0 &&
16894047d49SGordon Ross 		    (lease->ls_node->flags & NODE_FLAGS_DELETING) == 0)
16994047d49SGordon Ross 			break;
17094047d49SGordon Ross 	}
17194047d49SGordon Ross 	if (lease != NULL) {
17294047d49SGordon Ross 		/*
17394047d49SGordon Ross 		 * Found existing lease.  Make sure it refers to
17494047d49SGordon Ross 		 * the same node...
17594047d49SGordon Ross 		 */
17694047d49SGordon Ross 		if (lease->ls_node == of->f_node) {
17794047d49SGordon Ross 			smb2_lease_hold(lease);
17894047d49SGordon Ross 		} else {
17994047d49SGordon Ross 			/* Same lease ID, different node! */
18094047d49SGordon Ross #ifdef DEBUG
18194047d49SGordon Ross 			cmn_err(CE_NOTE, "new lease on node %p (%s) "
18294047d49SGordon Ross 			    "conflicts with existing node %p (%s)",
18394047d49SGordon Ross 			    (void *) of->f_node,
18494047d49SGordon Ross 			    of->f_node->od_name,
18594047d49SGordon Ross 			    (void *) lease->ls_node,
18694047d49SGordon Ross 			    lease->ls_node->od_name);
18794047d49SGordon Ross #endif
18894047d49SGordon Ross 			DTRACE_PROBE2(dup_lease, smb_request_t, sr,
18994047d49SGordon Ross 			    smb_lease_t, lease);
19094047d49SGordon Ross 			lease = NULL; /* error */
19194047d49SGordon Ross 		}
19294047d49SGordon Ross 	} else {
19394047d49SGordon Ross 		lease = newlease;
19494047d49SGordon Ross 		smb_llist_insert_head(bucket, lease);
19594047d49SGordon Ross 		newlease = NULL; /* don't free */
19694047d49SGordon Ross 	}
19794047d49SGordon Ross 	smb_llist_exit(bucket);
19894047d49SGordon Ross 
19994047d49SGordon Ross 	if (newlease != NULL) {
20094047d49SGordon Ross 		mutex_destroy(&newlease->ls_mutex);
20194047d49SGordon Ross 		kmem_cache_free(smb_lease_cache, newlease);
20294047d49SGordon Ross 	}
20394047d49SGordon Ross 
20494047d49SGordon Ross 	if (lease != NULL) {
20594047d49SGordon Ross 		of->f_lease = lease;
20694047d49SGordon Ross 		status = NT_STATUS_SUCCESS;
20794047d49SGordon Ross 	}
20894047d49SGordon Ross 
20994047d49SGordon Ross 	return (status);
21094047d49SGordon Ross }
21194047d49SGordon Ross 
21294047d49SGordon Ross /*
21394047d49SGordon Ross  * Find the lease for a given: client_uuid, lease_key
21494047d49SGordon Ross  * Returns the lease with a new ref.
21594047d49SGordon Ross  */
21694047d49SGordon Ross smb_lease_t *
21794047d49SGordon Ross smb2_lease_lookup(smb_server_t *sv, uint8_t *clnt_uuid, uint8_t *lease_key)
21894047d49SGordon Ross {
21994047d49SGordon Ross 	smb_hash_t *ht = sv->sv_lease_ht;
22094047d49SGordon Ross 	smb_llist_t *bucket;
22194047d49SGordon Ross 	smb_lease_t *lease;
22294047d49SGordon Ross 	size_t hashkey;
22394047d49SGordon Ross 
22494047d49SGordon Ross 	hashkey = smb_hash_uuid(lease_key);
22594047d49SGordon Ross 	hashkey &= (ht->num_buckets - 1);
22694047d49SGordon Ross 	bucket = &ht->buckets[hashkey].b_list;
22794047d49SGordon Ross 
22894047d49SGordon Ross 	smb_llist_enter(bucket, RW_READER);
22994047d49SGordon Ross 	lease = smb_llist_head(bucket);
23094047d49SGordon Ross 	while (lease != NULL) {
23194047d49SGordon Ross 		if (bcmp(lease->ls_key, lease_key, UUID_LEN) == 0 &&
23294047d49SGordon Ross 		    bcmp(lease->ls_clnt, clnt_uuid, UUID_LEN) == 0) {
23394047d49SGordon Ross 			smb2_lease_hold(lease);
23494047d49SGordon Ross 			break;
23594047d49SGordon Ross 		}
23694047d49SGordon Ross 		lease = smb_llist_next(bucket, lease);
23794047d49SGordon Ross 	}
23894047d49SGordon Ross 	smb_llist_exit(bucket);
23994047d49SGordon Ross 
24094047d49SGordon Ross 	return (lease);
24194047d49SGordon Ross }
24294047d49SGordon Ross 
24394047d49SGordon Ross /*
24494047d49SGordon Ross  * Find an smb_ofile_t in the current tree that shares the
245*6f8336c5SGordon Ross  * specified lease and holds the oplock for the lease.
24694047d49SGordon Ross  * If lease not found, NT_STATUS_OBJECT_NAME_NOT_FOUND.
247*6f8336c5SGordon Ross  * If no ofile (on the lease) holds the oplock, NT_STATUS_UNSUCCESSFUL.
24894047d49SGordon Ross  * On success, ofile (held) in sr->fid_ofile.
24994047d49SGordon Ross  */
25094047d49SGordon Ross static uint32_t
251*6f8336c5SGordon Ross find_oplock_ofile(smb_request_t *sr, uint8_t *lease_key)
25294047d49SGordon Ross {
25394047d49SGordon Ross 	smb_tree_t	*tree = sr->tid_tree;
25494047d49SGordon Ross 	smb_lease_t	*lease;
25594047d49SGordon Ross 	smb_llist_t	*of_list;
25694047d49SGordon Ross 	smb_ofile_t	*o;
25794047d49SGordon Ross 	uint32_t	status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
25894047d49SGordon Ross 
25994047d49SGordon Ross 	SMB_TREE_VALID(tree);
26094047d49SGordon Ross 	of_list = &tree->t_ofile_list;
26194047d49SGordon Ross 
26294047d49SGordon Ross 	smb_llist_enter(of_list, RW_READER);
26394047d49SGordon Ross 	for (o = smb_llist_head(of_list); o != NULL;
26494047d49SGordon Ross 	    o = smb_llist_next(of_list, o)) {
26594047d49SGordon Ross 
26694047d49SGordon Ross 		ASSERT(o->f_magic == SMB_OFILE_MAGIC);
26794047d49SGordon Ross 		ASSERT(o->f_tree == tree);
26894047d49SGordon Ross 
269*6f8336c5SGordon Ross 		DTRACE_PROBE1(every_ofile, smb_ofile_t *, o);
27094047d49SGordon Ross 		if ((lease = o->f_lease) == NULL)
27194047d49SGordon Ross 			continue; // no lease
27294047d49SGordon Ross 
27394047d49SGordon Ross 		if (bcmp(lease->ls_key, lease_key, UUID_LEN) != 0)
27494047d49SGordon Ross 			continue; // wrong lease
27594047d49SGordon Ross 
27694047d49SGordon Ross 		/*
27794047d49SGordon Ross 		 * Now we know the lease exists, so if we don't
278*6f8336c5SGordon Ross 		 * find the ofile that has the oplock, return:
27994047d49SGordon Ross 		 */
28094047d49SGordon Ross 		status = NT_STATUS_UNSUCCESSFUL;
28194047d49SGordon Ross 
282*6f8336c5SGordon Ross 		DTRACE_PROBE2(lease_ofile, smb_lease_t *, lease,
283*6f8336c5SGordon Ross 		    smb_ofile_t *, o);
284*6f8336c5SGordon Ross 		if (lease->ls_oplock_ofile != o)
285*6f8336c5SGordon Ross 			continue; // not oplock holder
28694047d49SGordon Ross 
287*6f8336c5SGordon Ross 		/* Found the ofile holding the oplock */
28894047d49SGordon Ross 		if (smb_ofile_hold(o)) {
28994047d49SGordon Ross 			sr->fid_ofile = o;
29094047d49SGordon Ross 			status = NT_STATUS_SUCCESS;
29194047d49SGordon Ross 			break;
29294047d49SGordon Ross 		}
29394047d49SGordon Ross 	}
29494047d49SGordon Ross 	smb_llist_exit(of_list);
29594047d49SGordon Ross 
29694047d49SGordon Ross 	return (status);
29794047d49SGordon Ross }
29894047d49SGordon Ross 
29994047d49SGordon Ross /*
30094047d49SGordon Ross  * This is called by smb2_oplock_break_ack when the struct size
30194047d49SGordon Ross  * indicates this is a lease break (SZ_LEASE).  See:
30294047d49SGordon Ross  * [MS-SMB2] 3.3.5.22.2 Processing a Lease Acknowledgment
30394047d49SGordon Ross  */
30494047d49SGordon Ross smb_sdrc_t
30594047d49SGordon Ross smb2_lease_break_ack(smb_request_t *sr)
30694047d49SGordon Ross {
30794047d49SGordon Ross 	smb_lease_t *lease;
30894047d49SGordon Ross 	smb_ofile_t *ofile;
30994047d49SGordon Ross 	uint8_t LeaseKey[UUID_LEN];
31094047d49SGordon Ross 	uint32_t LeaseState;
31194047d49SGordon Ross 	uint32_t LeaseBreakTo;
31294047d49SGordon Ross 	uint32_t status;
31394047d49SGordon Ross 	int rc = 0;
31494047d49SGordon Ross 
31594047d49SGordon Ross 	if (sr->session->dialect < SMB_VERS_2_1)
31694047d49SGordon Ross 		return (SDRC_ERROR);
31794047d49SGordon Ross 
31894047d49SGordon Ross 	/*
31994047d49SGordon Ross 	 * Decode an SMB2 Lease Acknowldgement
32094047d49SGordon Ross 	 * [MS-SMB2] 2.2.24.2
32194047d49SGordon Ross 	 * Note: Struct size decoded by caller.
32294047d49SGordon Ross 	 */
32394047d49SGordon Ross 	rc = smb_mbc_decodef(
32494047d49SGordon Ross 	    &sr->smb_data, "6.#cl8.",
32594047d49SGordon Ross 	    /* reserved		  6. */
32694047d49SGordon Ross 	    UUID_LEN,		/* # */
32794047d49SGordon Ross 	    LeaseKey,		/* c */
32894047d49SGordon Ross 	    &LeaseState);	/* l */
32994047d49SGordon Ross 	    /* duration		  8. */
33094047d49SGordon Ross 	if (rc != 0)
33194047d49SGordon Ross 		return (SDRC_ERROR);
33294047d49SGordon Ross 
333*6f8336c5SGordon Ross 	status = find_oplock_ofile(sr, LeaseKey);
33494047d49SGordon Ross 
33594047d49SGordon Ross 	DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr);
33694047d49SGordon Ross 	if (status != 0)
33794047d49SGordon Ross 		goto errout;
33894047d49SGordon Ross 
33994047d49SGordon Ross 	/* Success, so have sr->fid_ofile and lease */
34094047d49SGordon Ross 	ofile = sr->fid_ofile;
34194047d49SGordon Ross 	lease = ofile->f_lease;
34294047d49SGordon Ross 
343*6f8336c5SGordon Ross 	/* Either this ACK is unsolicited, or we timed out waiting. */
344*6f8336c5SGordon Ross 	if (lease->ls_breaking == 0) {
345*6f8336c5SGordon Ross 		status = NT_STATUS_UNSUCCESSFUL;
346*6f8336c5SGordon Ross 		goto errout;
347*6f8336c5SGordon Ross 	}
348*6f8336c5SGordon Ross 
34994047d49SGordon Ross 	/*
35094047d49SGordon Ross 	 * Process the lease break ack.
35194047d49SGordon Ross 	 *
35294047d49SGordon Ross 	 * If the new LeaseState has any bits in excess of
35394047d49SGordon Ross 	 * the lease state we sent in the break, error...
35494047d49SGordon Ross 	 */
35594047d49SGordon Ross 	LeaseBreakTo = (lease->ls_breaking >> BREAK_SHIFT) &
35694047d49SGordon Ross 	    OPLOCK_LEVEL_CACHE_MASK;
35794047d49SGordon Ross 	if ((LeaseState & ~LeaseBreakTo) != 0) {
35894047d49SGordon Ross 		status = NT_STATUS_REQUEST_NOT_ACCEPTED;
35994047d49SGordon Ross 		goto errout;
36094047d49SGordon Ross 	}
36194047d49SGordon Ross 
36294047d49SGordon Ross 	lease->ls_breaking = 0;
36394047d49SGordon Ross 
36494047d49SGordon Ross 	LeaseState |= OPLOCK_LEVEL_GRANULAR;
36594047d49SGordon Ross 	status = smb_oplock_ack_break(sr, ofile, &LeaseState);
36694047d49SGordon Ross 	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
36794047d49SGordon Ross 		(void) smb2sr_go_async(sr);
36894047d49SGordon Ross 		(void) smb_oplock_wait_break(ofile->f_node, 0);
36994047d49SGordon Ross 		status = NT_STATUS_SUCCESS;
37094047d49SGordon Ross 	}
37194047d49SGordon Ross 
372*6f8336c5SGordon Ross 	lease->ls_state = LeaseState & OPLOCK_LEVEL_CACHE_MASK;
37394047d49SGordon Ross 
37494047d49SGordon Ross errout:
37594047d49SGordon Ross 	sr->smb2_status = status;
37694047d49SGordon Ross 	DTRACE_SMB2_DONE(op__OplockBreak, smb_request_t *, sr);
37794047d49SGordon Ross 	if (status) {
37894047d49SGordon Ross 		smb2sr_put_error(sr, status);
37994047d49SGordon Ross 		return (SDRC_SUCCESS);
38094047d49SGordon Ross 	}
38194047d49SGordon Ross 
38294047d49SGordon Ross 	/*
38394047d49SGordon Ross 	 * Encode an SMB2 Lease Ack. response
38494047d49SGordon Ross 	 * [MS-SMB2] 2.2.25.2
38594047d49SGordon Ross 	 */
38694047d49SGordon Ross 	LeaseState &= OPLOCK_LEVEL_CACHE_MASK;
38794047d49SGordon Ross 	(void) smb_mbc_encodef(
38894047d49SGordon Ross 	    &sr->reply, "w6.#cl8.",
38994047d49SGordon Ross 	    SSZ_LEASE_ACK,	/* w */
39094047d49SGordon Ross 	    /* reserved		  6. */
39194047d49SGordon Ross 	    UUID_LEN,		/* # */
39294047d49SGordon Ross 	    LeaseKey,		/* c */
39394047d49SGordon Ross 	    LeaseState);	/* l */
39494047d49SGordon Ross 	    /* duration		  8. */
39594047d49SGordon Ross 
39694047d49SGordon Ross 	return (SDRC_SUCCESS);
39794047d49SGordon Ross 
39894047d49SGordon Ross }
39994047d49SGordon Ross 
40094047d49SGordon Ross /*
40194047d49SGordon Ross  * Compose an SMB2 Lease Break Notification packet, including
40294047d49SGordon Ross  * the SMB2 header and everything, in sr->reply.
40394047d49SGordon Ross  * The caller will send it and free the request.
40494047d49SGordon Ross  *
40594047d49SGordon Ross  * [MS-SMB2] 2.2.23.2 Lease Break Notification
40694047d49SGordon Ross  */
40794047d49SGordon Ross void
40894047d49SGordon Ross smb2_lease_break_notification(smb_request_t *sr, uint32_t NewLevel,
40994047d49SGordon Ross     boolean_t AckReq)
41094047d49SGordon Ross {
411*6f8336c5SGordon Ross 	smb_lease_t *ls = sr->fid_ofile->f_lease;
41294047d49SGordon Ross 	uint32_t oldcache;
41394047d49SGordon Ross 	uint32_t newcache;
41494047d49SGordon Ross 	uint16_t Epoch;
41594047d49SGordon Ross 	uint16_t Flags;
41694047d49SGordon Ross 
41794047d49SGordon Ross 	/*
41894047d49SGordon Ross 	 * Convert internal level to SMB2
41994047d49SGordon Ross 	 */
420*6f8336c5SGordon Ross 	oldcache = ls->ls_state & OPLOCK_LEVEL_CACHE_MASK;
42194047d49SGordon Ross 	newcache = NewLevel & OPLOCK_LEVEL_CACHE_MASK;
42294047d49SGordon Ross 	if (ls->ls_version < 2)
42394047d49SGordon Ross 		Epoch = 0;
42494047d49SGordon Ross 	else
42594047d49SGordon Ross 		Epoch = ls->ls_epoch;
42694047d49SGordon Ross 
42794047d49SGordon Ross 	/*
42894047d49SGordon Ross 	 * SMB2 Header
42994047d49SGordon Ross 	 */
43094047d49SGordon Ross 	sr->smb2_cmd_code = SMB2_OPLOCK_BREAK;
43194047d49SGordon Ross 	sr->smb2_hdr_flags = SMB2_FLAGS_SERVER_TO_REDIR;
43294047d49SGordon Ross 	sr->smb_tid = 0;
43394047d49SGordon Ross 	sr->smb_pid = 0;
43494047d49SGordon Ross 	sr->smb2_ssnid = 0;
43594047d49SGordon Ross 	sr->smb2_messageid = UINT64_MAX;
43694047d49SGordon Ross 	(void) smb2_encode_header(sr, B_FALSE);
43794047d49SGordon Ross 
43894047d49SGordon Ross 	/*
43994047d49SGordon Ross 	 * SMB2 Oplock Break, variable part
44094047d49SGordon Ross 	 *
44194047d49SGordon Ross 	 * [MS-SMB2] says the current lease state preceeds the
44294047d49SGordon Ross 	 * new lease state, but that looks like an error...
44394047d49SGordon Ross 	 */
44494047d49SGordon Ross 	Flags = AckReq ? SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED : 0;
44594047d49SGordon Ross 	(void) smb_mbc_encodef(
44694047d49SGordon Ross 	    &sr->reply, "wwl#cll4.4.4.",
44794047d49SGordon Ross 	    SSZ_LEASE_BRK,		/* w */
44894047d49SGordon Ross 	    Epoch,			/* w */
44994047d49SGordon Ross 	    Flags,			/* l */
45094047d49SGordon Ross 	    SMB_LEASE_KEY_SZ,		/* # */
45194047d49SGordon Ross 	    ls->ls_key,			/* c */
45294047d49SGordon Ross 	    oldcache,		/* cur.st  l */
45394047d49SGordon Ross 	    newcache);		/* new.st  l */
45494047d49SGordon Ross 	    /* reserved (4.4.4.) */
45594047d49SGordon Ross }
45694047d49SGordon Ross 
45794047d49SGordon Ross /*
45894047d49SGordon Ross  * Client has an open handle and requests a lease.
45994047d49SGordon Ross  * Convert SMB2 lease request info in to internal form,
46094047d49SGordon Ross  * call common oplock code, convert result to SMB2.
46194047d49SGordon Ross  *
46294047d49SGordon Ross  * If necessary, "go async" here.
46394047d49SGordon Ross  */
46494047d49SGordon Ross void
46594047d49SGordon Ross smb2_lease_acquire(smb_request_t *sr)
46694047d49SGordon Ross {
46794047d49SGordon Ross 	smb_arg_open_t *op = &sr->arg.open;
46894047d49SGordon Ross 	smb_ofile_t *ofile = sr->fid_ofile;
46994047d49SGordon Ross 	smb_lease_t *lease = ofile->f_lease;
47094047d49SGordon Ross 	uint32_t status = NT_STATUS_OPLOCK_NOT_GRANTED;
47194047d49SGordon Ross 	uint32_t have, want; /* lease flags */
47294047d49SGordon Ross 	boolean_t NewGrant = B_FALSE;
47394047d49SGordon Ross 
47494047d49SGordon Ross 	/* Only disk trees get oplocks. */
47594047d49SGordon Ross 	ASSERT((sr->tid_tree->t_res_type & STYPE_MASK) == STYPE_DISKTREE);
47694047d49SGordon Ross 
47794047d49SGordon Ross 	/*
47894047d49SGordon Ross 	 * Only plain files (for now).
47994047d49SGordon Ross 	 * Later, test SMB2_CAP_DIRECTORY_LEASING
48094047d49SGordon Ross 	 */
48194047d49SGordon Ross 	if (!smb_node_is_file(ofile->f_node)) {
48294047d49SGordon Ross 		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
48394047d49SGordon Ross 		return;
48494047d49SGordon Ross 	}
48594047d49SGordon Ross 
48694047d49SGordon Ross 	if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_OPLOCKS)) {
48794047d49SGordon Ross 		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
48894047d49SGordon Ross 		return;
48994047d49SGordon Ross 	}
49094047d49SGordon Ross 
49194047d49SGordon Ross 	/*
49294047d49SGordon Ross 	 * SMB2: Convert to internal form.
49394047d49SGordon Ross 	 * Caller should have setup the lease.
49494047d49SGordon Ross 	 */
49594047d49SGordon Ross 	ASSERT(op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE);
49694047d49SGordon Ross 	ASSERT(lease != NULL);
49794047d49SGordon Ross 	if (lease == NULL) {
49894047d49SGordon Ross 		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
49994047d49SGordon Ross 		return;
50094047d49SGordon Ross 	}
50194047d49SGordon Ross 	op->op_oplock_state = OPLOCK_LEVEL_GRANULAR |
50294047d49SGordon Ross 	    (op->lease_state & CACHE_RWH);
50394047d49SGordon Ross 
50494047d49SGordon Ross 	/*
50594047d49SGordon Ross 	 * Tree options may force shared oplocks,
50694047d49SGordon Ross 	 * in which case we reduce the request.
50794047d49SGordon Ross 	 */
50894047d49SGordon Ross 	if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) {
50994047d49SGordon Ross 		op->op_oplock_state &= ~WRITE_CACHING;
51094047d49SGordon Ross 	}
51194047d49SGordon Ross 
51294047d49SGordon Ross 	/*
51394047d49SGordon Ross 	 * Disallow downgrade
51494047d49SGordon Ross 	 *
51594047d49SGordon Ross 	 * Note that open with a lease is not allowed to turn off
51694047d49SGordon Ross 	 * any cache rights.  If the client tries to "downgrade",
51794047d49SGordon Ross 	 * any bits, just return the existing lease cache bits.
51894047d49SGordon Ross 	 */
51994047d49SGordon Ross 	have = lease->ls_state & CACHE_RWH;
52094047d49SGordon Ross 	want = op->op_oplock_state & CACHE_RWH;
52194047d49SGordon Ross 	if ((have & ~want) != 0) {
52294047d49SGordon Ross 		op->op_oplock_state = have |
52394047d49SGordon Ross 		    OPLOCK_LEVEL_GRANULAR;
52494047d49SGordon Ross 		goto done;
52594047d49SGordon Ross 	}
52694047d49SGordon Ross 
52794047d49SGordon Ross 	/*
52894047d49SGordon Ross 	 * Handle oplock requests in three parts:
52994047d49SGordon Ross 	 *	a: Requests with WRITE_CACHING
53094047d49SGordon Ross 	 *	b: Requests with HANDLE_CACHING
53194047d49SGordon Ross 	 *	c: Requests with READ_CACHING
53294047d49SGordon Ross 	 * reducing the request before b and c.
53394047d49SGordon Ross 	 *
53494047d49SGordon Ross 	 * In each: first check if the lease grants the
53594047d49SGordon Ross 	 * (possibly reduced) request, in which case we
53694047d49SGordon Ross 	 * leave the lease unchanged and return what's
53794047d49SGordon Ross 	 * granted by the lease.  Otherwise, try to get
53894047d49SGordon Ross 	 * the oplock, and if the succeeds, wait for any
53994047d49SGordon Ross 	 * breaks, update the lease, and return.
54094047d49SGordon Ross 	 */
54194047d49SGordon Ross 
54294047d49SGordon Ross 	/*
54394047d49SGordon Ross 	 * Try exclusive (request is RW or RWH)
54494047d49SGordon Ross 	 */
54594047d49SGordon Ross 	if ((op->op_oplock_state & WRITE_CACHING) != 0) {
54694047d49SGordon Ross 		want = op->op_oplock_state & CACHE_RWH;
54794047d49SGordon Ross 		if (have == want)
54894047d49SGordon Ross 			goto done;
54994047d49SGordon Ross 
55094047d49SGordon Ross 		status = smb_oplock_request(sr, ofile,
55194047d49SGordon Ross 		    &op->op_oplock_state);
55294047d49SGordon Ross 		if (status == NT_STATUS_SUCCESS ||
55394047d49SGordon Ross 		    status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
55494047d49SGordon Ross 			NewGrant = B_TRUE;
55594047d49SGordon Ross 			goto done;
55694047d49SGordon Ross 		}
55794047d49SGordon Ross 
55894047d49SGordon Ross 		/*
55994047d49SGordon Ross 		 * We did not get the exclusive oplock.
56094047d49SGordon Ross 		 *
56194047d49SGordon Ross 		 * There are odd rules about lease upgrade.
56294047d49SGordon Ross 		 * If the existing lease grants R and the
56394047d49SGordon Ross 		 * client fails to upgrade it to "RWH"
56494047d49SGordon Ross 		 * (presumably due to handle conflicts)
56594047d49SGordon Ross 		 * then just return the existing lease,
56694047d49SGordon Ross 		 * even though upgrade to RH would work.
56794047d49SGordon Ross 		 */
56894047d49SGordon Ross 		if (have != 0) {
56994047d49SGordon Ross 			op->op_oplock_state = have |
57094047d49SGordon Ross 			    OPLOCK_LEVEL_GRANULAR;
57194047d49SGordon Ross 			goto done;
57294047d49SGordon Ross 		}
57394047d49SGordon Ross 
57494047d49SGordon Ross 		/*
57594047d49SGordon Ross 		 * Keep trying without write.
57694047d49SGordon Ross 		 * Need to re-init op_oplock_state
57794047d49SGordon Ross 		 */
57894047d49SGordon Ross 		op->op_oplock_state = OPLOCK_LEVEL_GRANULAR |
57994047d49SGordon Ross 		    (op->lease_state & CACHE_RH);
58094047d49SGordon Ross 	}
58194047d49SGordon Ross 
58294047d49SGordon Ross 	/*
58394047d49SGordon Ross 	 * Try shared ("RH")
58494047d49SGordon Ross 	 */
58594047d49SGordon Ross 	if ((op->op_oplock_state & HANDLE_CACHING) != 0) {
58694047d49SGordon Ross 		want = op->op_oplock_state & CACHE_RWH;
58794047d49SGordon Ross 		if (have == want)
58894047d49SGordon Ross 			goto done;
58994047d49SGordon Ross 
59094047d49SGordon Ross 		status = smb_oplock_request(sr, ofile,
59194047d49SGordon Ross 		    &op->op_oplock_state);
59294047d49SGordon Ross 		if (status == NT_STATUS_SUCCESS ||
59394047d49SGordon Ross 		    status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
59494047d49SGordon Ross 			NewGrant = B_TRUE;
59594047d49SGordon Ross 			goto done;
59694047d49SGordon Ross 		}
59794047d49SGordon Ross 
59894047d49SGordon Ross 		/*
59994047d49SGordon Ross 		 * We did not get "RH", probably because
60094047d49SGordon Ross 		 * ther were (old style) Level II oplocks.
60194047d49SGordon Ross 		 * Continue, try for just read.
60294047d49SGordon Ross 		 */
60394047d49SGordon Ross 		op->op_oplock_state = OPLOCK_LEVEL_GRANULAR |
60494047d49SGordon Ross 		    (op->lease_state & CACHE_R);
60594047d49SGordon Ross 	}
60694047d49SGordon Ross 
60794047d49SGordon Ross 	/*
60894047d49SGordon Ross 	 * Try shared ("R")
60994047d49SGordon Ross 	 */
61094047d49SGordon Ross 	if ((op->op_oplock_state & READ_CACHING) != 0) {
61194047d49SGordon Ross 		want = op->op_oplock_state & CACHE_RWH;
61294047d49SGordon Ross 		if (have == want)
61394047d49SGordon Ross 			goto done;
61494047d49SGordon Ross 
61594047d49SGordon Ross 		status = smb_oplock_request(sr, ofile,
61694047d49SGordon Ross 		    &op->op_oplock_state);
61794047d49SGordon Ross 		if (status == NT_STATUS_SUCCESS ||
61894047d49SGordon Ross 		    status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
61994047d49SGordon Ross 			NewGrant = B_TRUE;
62094047d49SGordon Ross 			goto done;
62194047d49SGordon Ross 		}
62294047d49SGordon Ross 
62394047d49SGordon Ross 		/*
62494047d49SGordon Ross 		 * We did not get "R".
62594047d49SGordon Ross 		 * Fall into "none".
62694047d49SGordon Ross 		 */
62794047d49SGordon Ross 	}
62894047d49SGordon Ross 
62994047d49SGordon Ross 	/*
63094047d49SGordon Ross 	 * None of the above were able to get an oplock.
63194047d49SGordon Ross 	 * The lease has no caching rights, and we didn't
63294047d49SGordon Ross 	 * add any in this request.  Return it as-is.
63394047d49SGordon Ross 	 */
63494047d49SGordon Ross 	op->op_oplock_state = OPLOCK_LEVEL_GRANULAR;
63594047d49SGordon Ross 
63694047d49SGordon Ross done:
63794047d49SGordon Ross 	if (NewGrant) {
63894047d49SGordon Ross 		/*
63994047d49SGordon Ross 		 * After a new oplock grant, the status return
64094047d49SGordon Ross 		 * may indicate we need to wait for breaks.
64194047d49SGordon Ross 		 */
64294047d49SGordon Ross 		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
64394047d49SGordon Ross 			(void) smb2sr_go_async(sr);
64494047d49SGordon Ross 			(void) smb_oplock_wait_break(ofile->f_node, 0);
64594047d49SGordon Ross 			status = NT_STATUS_SUCCESS;
64694047d49SGordon Ross 		}
64794047d49SGordon Ross 		ASSERT(status == NT_STATUS_SUCCESS);
64894047d49SGordon Ross 
64994047d49SGordon Ross 		/*
650*6f8336c5SGordon Ross 		 * Keep track of what we got (in lease->ls_state)
651*6f8336c5SGordon Ross 		 * so we'll know what we last told the client.
65294047d49SGordon Ross 		 */
65394047d49SGordon Ross 		mutex_enter(&lease->ls_mutex);
65494047d49SGordon Ross 		lease->ls_state = op->op_oplock_state & CACHE_RWH;
65594047d49SGordon Ross 		lease->ls_epoch++;
65694047d49SGordon Ross 		mutex_exit(&lease->ls_mutex);
65794047d49SGordon Ross 	}
65894047d49SGordon Ross 
65994047d49SGordon Ross 	/*
66094047d49SGordon Ross 	 * Convert internal oplock state to SMB2
66194047d49SGordon Ross 	 */
66294047d49SGordon Ross 	op->op_oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
66394047d49SGordon Ross 	op->lease_state = lease->ls_state & CACHE_RWH;
66494047d49SGordon Ross 	op->lease_flags = (lease->ls_breaking != 0) ?
66594047d49SGordon Ross 	    SMB2_LEASE_FLAG_BREAK_IN_PROGRESS : 0;
66694047d49SGordon Ross 	op->lease_epoch = lease->ls_epoch;
66794047d49SGordon Ross 	op->lease_version = lease->ls_version;
66894047d49SGordon Ross }
66994047d49SGordon Ross 
67094047d49SGordon Ross /*
67194047d49SGordon Ross  * This ofile has a lease and is about to close.
67294047d49SGordon Ross  * Called by smb_ofile_close when there's a lease.
67394047d49SGordon Ross  *
67494047d49SGordon Ross  * With leases, just one ofile on a lease owns the oplock.
67594047d49SGordon Ross  * If an ofile with a lease is closed and it's the one that
67694047d49SGordon Ross  * owns the oplock, try to move the oplock to another ofile
67794047d49SGordon Ross  * on the same lease.
67894047d49SGordon Ross  */
67994047d49SGordon Ross void
68094047d49SGordon Ross smb2_lease_ofile_close(smb_ofile_t *ofile)
68194047d49SGordon Ross {
68294047d49SGordon Ross 	smb_node_t *node = ofile->f_node;
68394047d49SGordon Ross 	smb_lease_t *lease = ofile->f_lease;
68494047d49SGordon Ross 	smb_ofile_t *o;
68594047d49SGordon Ross 
6861f0845f1SGordon Ross 	ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock));
6871f0845f1SGordon Ross 	ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
6881f0845f1SGordon Ross 
68994047d49SGordon Ross 	/*
69094047d49SGordon Ross 	 * If this ofile was not the oplock owner for this lease,
69194047d49SGordon Ross 	 * we can leave things as they are.
69294047d49SGordon Ross 	 */
69394047d49SGordon Ross 	if (lease->ls_oplock_ofile != ofile)
69494047d49SGordon Ross 		return;
69594047d49SGordon Ross 
69694047d49SGordon Ross 	/*
69794047d49SGordon Ross 	 * Find another ofile to which we can move the oplock.
69894047d49SGordon Ross 	 * The ofile must be open and allow a new ref.
69994047d49SGordon Ross 	 */
70094047d49SGordon Ross 	FOREACH_NODE_OFILE(node, o) {
70194047d49SGordon Ross 		if (o == ofile)
70294047d49SGordon Ross 			continue;
70394047d49SGordon Ross 		if (o->f_lease != lease)
70494047d49SGordon Ross 			continue;
705*6f8336c5SGordon Ross 		if (o->f_oplock_closing)
7061f0845f1SGordon Ross 			continue;
70794047d49SGordon Ross 		/* If we can get a hold, use this ofile. */
708a9931e68SGordon Ross 		if (smb_ofile_hold(o))
70994047d49SGordon Ross 			break;
71094047d49SGordon Ross 	}
71194047d49SGordon Ross 	if (o == NULL) {
71294047d49SGordon Ross 		/* Normal for last close on a lease. */
713*6f8336c5SGordon Ross 		lease->ls_oplock_ofile = NULL;
71494047d49SGordon Ross 		return;
71594047d49SGordon Ross 	}
71694047d49SGordon Ross 	smb_oplock_move(node, ofile, o);
717*6f8336c5SGordon Ross 	lease->ls_oplock_ofile = o;
71894047d49SGordon Ross 
71994047d49SGordon Ross 	smb_ofile_release(o);
72094047d49SGordon Ross }
721