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