1*94047d49SGordon Ross /*
2*94047d49SGordon Ross  * This file and its contents are supplied under the terms of the
3*94047d49SGordon Ross  * Common Development and Distribution License ("CDDL"), version 1.0.
4*94047d49SGordon Ross  * You may only use this file in accordance with the terms of version
5*94047d49SGordon Ross  * 1.0 of the CDDL.
6*94047d49SGordon Ross  *
7*94047d49SGordon Ross  * A full copy of the text of the CDDL should have accompanied this
8*94047d49SGordon Ross  * source.  A copy of the CDDL is also available via the Internet at
9*94047d49SGordon Ross  * http://www.illumos.org/license/CDDL.
10*94047d49SGordon Ross  */
11*94047d49SGordon Ross 
12*94047d49SGordon Ross /*
13*94047d49SGordon Ross  * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
14*94047d49SGordon Ross  */
15*94047d49SGordon Ross 
16*94047d49SGordon Ross /*
17*94047d49SGordon Ross  * Dispatch function for SMB2_OPLOCK_BREAK
18*94047d49SGordon Ross  */
19*94047d49SGordon Ross 
20*94047d49SGordon Ross #include <smbsrv/smb2_kproto.h>
21*94047d49SGordon Ross #include <smbsrv/smb_oplock.h>
22*94047d49SGordon Ross 
23*94047d49SGordon Ross /* StructSize for the two "break" message formats. */
24*94047d49SGordon Ross #define	SSZ_OPLOCK	24
25*94047d49SGordon Ross #define	SSZ_LEASE_ACK	36
26*94047d49SGordon Ross #define	SSZ_LEASE_BRK	44
27*94047d49SGordon Ross 
28*94047d49SGordon Ross #define	NODE_FLAGS_DELETING	(NODE_FLAGS_DELETE_ON_CLOSE |\
29*94047d49SGordon Ross 				NODE_FLAGS_DELETE_COMMITTED)
30*94047d49SGordon Ross 
31*94047d49SGordon Ross static const char lease_zero[UUID_LEN] = { 0 };
32*94047d49SGordon Ross 
33*94047d49SGordon Ross static kmem_cache_t	*smb_lease_cache = NULL;
34*94047d49SGordon Ross 
35*94047d49SGordon Ross void
36*94047d49SGordon Ross smb2_lease_init()
37*94047d49SGordon Ross {
38*94047d49SGordon Ross 	if (smb_lease_cache != NULL)
39*94047d49SGordon Ross 		return;
40*94047d49SGordon Ross 
41*94047d49SGordon Ross 	smb_lease_cache = kmem_cache_create("smb_lease_cache",
42*94047d49SGordon Ross 	    sizeof (smb_lease_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
43*94047d49SGordon Ross }
44*94047d49SGordon Ross 
45*94047d49SGordon Ross void
46*94047d49SGordon Ross smb2_lease_fini()
47*94047d49SGordon Ross {
48*94047d49SGordon Ross 	if (smb_lease_cache != NULL) {
49*94047d49SGordon Ross 		kmem_cache_destroy(smb_lease_cache);
50*94047d49SGordon Ross 		smb_lease_cache = NULL;
51*94047d49SGordon Ross 	}
52*94047d49SGordon Ross }
53*94047d49SGordon Ross 
54*94047d49SGordon Ross static void
55*94047d49SGordon Ross smb2_lease_hold(smb_lease_t *ls)
56*94047d49SGordon Ross {
57*94047d49SGordon Ross 	mutex_enter(&ls->ls_mutex);
58*94047d49SGordon Ross 	ls->ls_refcnt++;
59*94047d49SGordon Ross 	mutex_exit(&ls->ls_mutex);
60*94047d49SGordon Ross }
61*94047d49SGordon Ross 
62*94047d49SGordon Ross void
63*94047d49SGordon Ross smb2_lease_rele(smb_lease_t *ls)
64*94047d49SGordon Ross {
65*94047d49SGordon Ross 	smb_llist_t *bucket;
66*94047d49SGordon Ross 
67*94047d49SGordon Ross 	mutex_enter(&ls->ls_mutex);
68*94047d49SGordon Ross 	ls->ls_refcnt--;
69*94047d49SGordon Ross 	if (ls->ls_refcnt != 0) {
70*94047d49SGordon Ross 		mutex_exit(&ls->ls_mutex);
71*94047d49SGordon Ross 		return;
72*94047d49SGordon Ross 	}
73*94047d49SGordon Ross 	mutex_exit(&ls->ls_mutex);
74*94047d49SGordon Ross 
75*94047d49SGordon Ross 	/*
76*94047d49SGordon Ross 	 * Get the list lock, then re-check the refcnt
77*94047d49SGordon Ross 	 * and if it's still zero, unlink & destroy.
78*94047d49SGordon Ross 	 */
79*94047d49SGordon Ross 	bucket = ls->ls_bucket;
80*94047d49SGordon Ross 	smb_llist_enter(bucket, RW_WRITER);
81*94047d49SGordon Ross 
82*94047d49SGordon Ross 	mutex_enter(&ls->ls_mutex);
83*94047d49SGordon Ross 	if (ls->ls_refcnt == 0)
84*94047d49SGordon Ross 		smb_llist_remove(bucket, ls);
85*94047d49SGordon Ross 	mutex_exit(&ls->ls_mutex);
86*94047d49SGordon Ross 
87*94047d49SGordon Ross 	if (ls->ls_refcnt == 0) {
88*94047d49SGordon Ross 		mutex_destroy(&ls->ls_mutex);
89*94047d49SGordon Ross 		kmem_cache_free(smb_lease_cache, ls);
90*94047d49SGordon Ross 	}
91*94047d49SGordon Ross 
92*94047d49SGordon Ross 	smb_llist_exit(bucket);
93*94047d49SGordon Ross }
94*94047d49SGordon Ross 
95*94047d49SGordon Ross /*
96*94047d49SGordon Ross  * Compute a hash from a uuid
97*94047d49SGordon Ross  * Based on mod_hash_bystr()
98*94047d49SGordon Ross  */
99*94047d49SGordon Ross static uint_t
100*94047d49SGordon Ross smb_hash_uuid(const uint8_t *uuid)
101*94047d49SGordon Ross {
102*94047d49SGordon Ross 	char *k = (char *)uuid;
103*94047d49SGordon Ross 	uint_t hash = 0;
104*94047d49SGordon Ross 	uint_t g;
105*94047d49SGordon Ross 	int i;
106*94047d49SGordon Ross 
107*94047d49SGordon Ross 	ASSERT(k);
108*94047d49SGordon Ross 	for (i = 0; i < UUID_LEN; i++) {
109*94047d49SGordon Ross 		hash = (hash << 4) + k[i];
110*94047d49SGordon Ross 		if ((g = (hash & 0xf0000000)) != 0) {
111*94047d49SGordon Ross 			hash ^= (g >> 24);
112*94047d49SGordon Ross 			hash ^= g;
113*94047d49SGordon Ross 		}
114*94047d49SGordon Ross 	}
115*94047d49SGordon Ross 	return (hash);
116*94047d49SGordon Ross }
117*94047d49SGordon Ross 
118*94047d49SGordon Ross /*
119*94047d49SGordon Ross  * Add or update a lease table entry for a new ofile.
120*94047d49SGordon Ross  * (in the per-session lease table)
121*94047d49SGordon Ross  * See [MS-SMB2] 3.3.5.9.8
122*94047d49SGordon Ross  * Handling the SMB2_CREATE_REQUEST_LEASE Create Context
123*94047d49SGordon Ross  */
124*94047d49SGordon Ross uint32_t
125*94047d49SGordon Ross smb2_lease_create(smb_request_t *sr)
126*94047d49SGordon Ross {
127*94047d49SGordon Ross 	smb_arg_open_t *op = &sr->arg.open;
128*94047d49SGordon Ross 	uint8_t *key = op->lease_key;
129*94047d49SGordon Ross 	uint8_t *clnt = sr->session->clnt_uuid;
130*94047d49SGordon Ross 	smb_ofile_t *of = sr->fid_ofile;
131*94047d49SGordon Ross 	smb_hash_t *ht = sr->sr_server->sv_lease_ht;
132*94047d49SGordon Ross 	smb_llist_t *bucket;
133*94047d49SGordon Ross 	smb_lease_t *lease;
134*94047d49SGordon Ross 	smb_lease_t *newlease;
135*94047d49SGordon Ross 	size_t hashkey;
136*94047d49SGordon Ross 	uint32_t status = NT_STATUS_INVALID_PARAMETER;
137*94047d49SGordon Ross 
138*94047d49SGordon Ross 	if (bcmp(key, lease_zero, UUID_LEN) == 0)
139*94047d49SGordon Ross 		return (status);
140*94047d49SGordon Ross 
141*94047d49SGordon Ross 	/*
142*94047d49SGordon Ross 	 * Find or create, and add a ref for the new ofile.
143*94047d49SGordon Ross 	 */
144*94047d49SGordon Ross 	hashkey = smb_hash_uuid(key);
145*94047d49SGordon Ross 	hashkey &= (ht->num_buckets - 1);
146*94047d49SGordon Ross 	bucket = &ht->buckets[hashkey].b_list;
147*94047d49SGordon Ross 
148*94047d49SGordon Ross 	newlease = kmem_cache_alloc(smb_lease_cache, KM_SLEEP);
149*94047d49SGordon Ross 	bzero(newlease, sizeof (smb_lease_t));
150*94047d49SGordon Ross 	mutex_init(&newlease->ls_mutex, NULL, MUTEX_DEFAULT, NULL);
151*94047d49SGordon Ross 	newlease->ls_bucket = bucket;
152*94047d49SGordon Ross 	newlease->ls_node = of->f_node;
153*94047d49SGordon Ross 	newlease->ls_refcnt = 1;
154*94047d49SGordon Ross 	newlease->ls_epoch = op->lease_epoch;
155*94047d49SGordon Ross 	newlease->ls_version = op->lease_version;
156*94047d49SGordon Ross 	bcopy(key, newlease->ls_key, UUID_LEN);
157*94047d49SGordon Ross 	bcopy(clnt, newlease->ls_clnt, UUID_LEN);
158*94047d49SGordon Ross 
159*94047d49SGordon Ross 	smb_llist_enter(bucket, RW_WRITER);
160*94047d49SGordon Ross 	for (lease = smb_llist_head(bucket); lease != NULL;
161*94047d49SGordon Ross 	    lease = smb_llist_next(bucket, lease)) {
162*94047d49SGordon Ross 		/*
163*94047d49SGordon Ross 		 * Looking for this lease ID, on a node
164*94047d49SGordon Ross 		 * that's not being deleted.
165*94047d49SGordon Ross 		 */
166*94047d49SGordon Ross 		if (bcmp(lease->ls_key, key, UUID_LEN) == 0 &&
167*94047d49SGordon Ross 		    bcmp(lease->ls_clnt, clnt, UUID_LEN) == 0 &&
168*94047d49SGordon Ross 		    (lease->ls_node->flags & NODE_FLAGS_DELETING) == 0)
169*94047d49SGordon Ross 			break;
170*94047d49SGordon Ross 	}
171*94047d49SGordon Ross 	if (lease != NULL) {
172*94047d49SGordon Ross 		/*
173*94047d49SGordon Ross 		 * Found existing lease.  Make sure it refers to
174*94047d49SGordon Ross 		 * the same node...
175*94047d49SGordon Ross 		 */
176*94047d49SGordon Ross 		if (lease->ls_node == of->f_node) {
177*94047d49SGordon Ross 			smb2_lease_hold(lease);
178*94047d49SGordon Ross 		} else {
179*94047d49SGordon Ross 			/* Same lease ID, different node! */
180*94047d49SGordon Ross #ifdef DEBUG
181*94047d49SGordon Ross 			cmn_err(CE_NOTE, "new lease on node %p (%s) "
182*94047d49SGordon Ross 			    "conflicts with existing node %p (%s)",
183*94047d49SGordon Ross 			    (void *) of->f_node,
184*94047d49SGordon Ross 			    of->f_node->od_name,
185*94047d49SGordon Ross 			    (void *) lease->ls_node,
186*94047d49SGordon Ross 			    lease->ls_node->od_name);
187*94047d49SGordon Ross #endif
188*94047d49SGordon Ross 			DTRACE_PROBE2(dup_lease, smb_request_t, sr,
189*94047d49SGordon Ross 			    smb_lease_t, lease);
190*94047d49SGordon Ross 			lease = NULL; /* error */
191*94047d49SGordon Ross 		}
192*94047d49SGordon Ross 	} else {
193*94047d49SGordon Ross 		lease = newlease;
194*94047d49SGordon Ross 		smb_llist_insert_head(bucket, lease);
195*94047d49SGordon Ross 		newlease = NULL; /* don't free */
196*94047d49SGordon Ross 	}
197*94047d49SGordon Ross 	smb_llist_exit(bucket);
198*94047d49SGordon Ross 
199*94047d49SGordon Ross 	if (newlease != NULL) {
200*94047d49SGordon Ross 		mutex_destroy(&newlease->ls_mutex);
201*94047d49SGordon Ross 		kmem_cache_free(smb_lease_cache, newlease);
202*94047d49SGordon Ross 	}
203*94047d49SGordon Ross 
204*94047d49SGordon Ross 	if (lease != NULL) {
205*94047d49SGordon Ross 		of->f_lease = lease;
206*94047d49SGordon Ross 		status = NT_STATUS_SUCCESS;
207*94047d49SGordon Ross 	}
208*94047d49SGordon Ross 
209*94047d49SGordon Ross 	return (status);
210*94047d49SGordon Ross }
211*94047d49SGordon Ross 
212*94047d49SGordon Ross /*
213*94047d49SGordon Ross  * Find the lease for a given: client_uuid, lease_key
214*94047d49SGordon Ross  * Returns the lease with a new ref.
215*94047d49SGordon Ross  */
216*94047d49SGordon Ross smb_lease_t *
217*94047d49SGordon Ross smb2_lease_lookup(smb_server_t *sv, uint8_t *clnt_uuid, uint8_t *lease_key)
218*94047d49SGordon Ross {
219*94047d49SGordon Ross 	smb_hash_t *ht = sv->sv_lease_ht;
220*94047d49SGordon Ross 	smb_llist_t *bucket;
221*94047d49SGordon Ross 	smb_lease_t *lease;
222*94047d49SGordon Ross 	size_t hashkey;
223*94047d49SGordon Ross 
224*94047d49SGordon Ross 	hashkey = smb_hash_uuid(lease_key);
225*94047d49SGordon Ross 	hashkey &= (ht->num_buckets - 1);
226*94047d49SGordon Ross 	bucket = &ht->buckets[hashkey].b_list;
227*94047d49SGordon Ross 
228*94047d49SGordon Ross 	smb_llist_enter(bucket, RW_READER);
229*94047d49SGordon Ross 	lease = smb_llist_head(bucket);
230*94047d49SGordon Ross 	while (lease != NULL) {
231*94047d49SGordon Ross 		if (bcmp(lease->ls_key, lease_key, UUID_LEN) == 0 &&
232*94047d49SGordon Ross 		    bcmp(lease->ls_clnt, clnt_uuid, UUID_LEN) == 0) {
233*94047d49SGordon Ross 			smb2_lease_hold(lease);
234*94047d49SGordon Ross 			break;
235*94047d49SGordon Ross 		}
236*94047d49SGordon Ross 		lease = smb_llist_next(bucket, lease);
237*94047d49SGordon Ross 	}
238*94047d49SGordon Ross 	smb_llist_exit(bucket);
239*94047d49SGordon Ross 
240*94047d49SGordon Ross 	return (lease);
241*94047d49SGordon Ross }
242*94047d49SGordon Ross 
243*94047d49SGordon Ross /*
244*94047d49SGordon Ross  * Find an smb_ofile_t in the current tree that shares the
245*94047d49SGordon Ross  * specified lease and has some oplock breaking flags set.
246*94047d49SGordon Ross  * If lease not found, NT_STATUS_OBJECT_NAME_NOT_FOUND.
247*94047d49SGordon Ross  * If ofile not breaking NT_STATUS_UNSUCCESSFUL.
248*94047d49SGordon Ross  * On success, ofile (held) in sr->fid_ofile.
249*94047d49SGordon Ross  */
250*94047d49SGordon Ross static uint32_t
251*94047d49SGordon Ross find_breaking_ofile(smb_request_t *sr, uint8_t *lease_key)
252*94047d49SGordon Ross {
253*94047d49SGordon Ross 	smb_tree_t	*tree = sr->tid_tree;
254*94047d49SGordon Ross 	smb_lease_t	*lease;
255*94047d49SGordon Ross 	smb_llist_t	*of_list;
256*94047d49SGordon Ross 	smb_ofile_t	*o;
257*94047d49SGordon Ross 	uint32_t	status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
258*94047d49SGordon Ross 
259*94047d49SGordon Ross 	SMB_TREE_VALID(tree);
260*94047d49SGordon Ross 	of_list = &tree->t_ofile_list;
261*94047d49SGordon Ross 
262*94047d49SGordon Ross 	smb_llist_enter(of_list, RW_READER);
263*94047d49SGordon Ross 	for (o = smb_llist_head(of_list); o != NULL;
264*94047d49SGordon Ross 	    o = smb_llist_next(of_list, o)) {
265*94047d49SGordon Ross 
266*94047d49SGordon Ross 		ASSERT(o->f_magic == SMB_OFILE_MAGIC);
267*94047d49SGordon Ross 		ASSERT(o->f_tree == tree);
268*94047d49SGordon Ross 
269*94047d49SGordon Ross 		if ((lease = o->f_lease) == NULL)
270*94047d49SGordon Ross 			continue; // no lease
271*94047d49SGordon Ross 
272*94047d49SGordon Ross 		if (bcmp(lease->ls_key, lease_key, UUID_LEN) != 0)
273*94047d49SGordon Ross 			continue; // wrong lease
274*94047d49SGordon Ross 
275*94047d49SGordon Ross 		/*
276*94047d49SGordon Ross 		 * Now we know the lease exists, so if we don't
277*94047d49SGordon Ross 		 * find an ofile with breaking flags, return:
278*94047d49SGordon Ross 		 */
279*94047d49SGordon Ross 		status = NT_STATUS_UNSUCCESSFUL;
280*94047d49SGordon Ross 
281*94047d49SGordon Ross 		if (o->f_oplock.og_breaking == 0)
282*94047d49SGordon Ross 			continue; // not breaking
283*94047d49SGordon Ross 
284*94047d49SGordon Ross 		/* Found breaking ofile. */
285*94047d49SGordon Ross 		if (smb_ofile_hold(o)) {
286*94047d49SGordon Ross 			sr->fid_ofile = o;
287*94047d49SGordon Ross 			status = NT_STATUS_SUCCESS;
288*94047d49SGordon Ross 			break;
289*94047d49SGordon Ross 		}
290*94047d49SGordon Ross 	}
291*94047d49SGordon Ross 	smb_llist_exit(of_list);
292*94047d49SGordon Ross 
293*94047d49SGordon Ross 	return (status);
294*94047d49SGordon Ross }
295*94047d49SGordon Ross 
296*94047d49SGordon Ross /*
297*94047d49SGordon Ross  * This is called by smb2_oplock_break_ack when the struct size
298*94047d49SGordon Ross  * indicates this is a lease break (SZ_LEASE).  See:
299*94047d49SGordon Ross  * [MS-SMB2] 3.3.5.22.2 Processing a Lease Acknowledgment
300*94047d49SGordon Ross  */
301*94047d49SGordon Ross smb_sdrc_t
302*94047d49SGordon Ross smb2_lease_break_ack(smb_request_t *sr)
303*94047d49SGordon Ross {
304*94047d49SGordon Ross 	smb_lease_t *lease;
305*94047d49SGordon Ross 	smb_ofile_t *ofile;
306*94047d49SGordon Ross 	uint8_t LeaseKey[UUID_LEN];
307*94047d49SGordon Ross 	uint32_t LeaseState;
308*94047d49SGordon Ross 	uint32_t LeaseBreakTo;
309*94047d49SGordon Ross 	uint32_t status;
310*94047d49SGordon Ross 	int rc = 0;
311*94047d49SGordon Ross 
312*94047d49SGordon Ross 	if (sr->session->dialect < SMB_VERS_2_1)
313*94047d49SGordon Ross 		return (SDRC_ERROR);
314*94047d49SGordon Ross 
315*94047d49SGordon Ross 	/*
316*94047d49SGordon Ross 	 * Decode an SMB2 Lease Acknowldgement
317*94047d49SGordon Ross 	 * [MS-SMB2] 2.2.24.2
318*94047d49SGordon Ross 	 * Note: Struct size decoded by caller.
319*94047d49SGordon Ross 	 */
320*94047d49SGordon Ross 	rc = smb_mbc_decodef(
321*94047d49SGordon Ross 	    &sr->smb_data, "6.#cl8.",
322*94047d49SGordon Ross 	    /* reserved		  6. */
323*94047d49SGordon Ross 	    UUID_LEN,		/* # */
324*94047d49SGordon Ross 	    LeaseKey,		/* c */
325*94047d49SGordon Ross 	    &LeaseState);	/* l */
326*94047d49SGordon Ross 	    /* duration		  8. */
327*94047d49SGordon Ross 	if (rc != 0)
328*94047d49SGordon Ross 		return (SDRC_ERROR);
329*94047d49SGordon Ross 
330*94047d49SGordon Ross 	status = find_breaking_ofile(sr, LeaseKey);
331*94047d49SGordon Ross 
332*94047d49SGordon Ross 	DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr);
333*94047d49SGordon Ross 	if (status != 0)
334*94047d49SGordon Ross 		goto errout;
335*94047d49SGordon Ross 
336*94047d49SGordon Ross 	/* Success, so have sr->fid_ofile and lease */
337*94047d49SGordon Ross 	ofile = sr->fid_ofile;
338*94047d49SGordon Ross 	lease = ofile->f_lease;
339*94047d49SGordon Ross 
340*94047d49SGordon Ross 	/*
341*94047d49SGordon Ross 	 * Process the lease break ack.
342*94047d49SGordon Ross 	 *
343*94047d49SGordon Ross 	 * If the new LeaseState has any bits in excess of
344*94047d49SGordon Ross 	 * the lease state we sent in the break, error...
345*94047d49SGordon Ross 	 */
346*94047d49SGordon Ross 	LeaseBreakTo = (lease->ls_breaking >> BREAK_SHIFT) &
347*94047d49SGordon Ross 	    OPLOCK_LEVEL_CACHE_MASK;
348*94047d49SGordon Ross 	if ((LeaseState & ~LeaseBreakTo) != 0) {
349*94047d49SGordon Ross 		status = NT_STATUS_REQUEST_NOT_ACCEPTED;
350*94047d49SGordon Ross 		goto errout;
351*94047d49SGordon Ross 	}
352*94047d49SGordon Ross 
353*94047d49SGordon Ross 	ofile->f_oplock.og_breaking = 0;
354*94047d49SGordon Ross 	lease->ls_breaking = 0;
355*94047d49SGordon Ross 
356*94047d49SGordon Ross 	LeaseState |= OPLOCK_LEVEL_GRANULAR;
357*94047d49SGordon Ross 	status = smb_oplock_ack_break(sr, ofile, &LeaseState);
358*94047d49SGordon Ross 	if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
359*94047d49SGordon Ross 		(void) smb2sr_go_async(sr);
360*94047d49SGordon Ross 		(void) smb_oplock_wait_break(ofile->f_node, 0);
361*94047d49SGordon Ross 		status = NT_STATUS_SUCCESS;
362*94047d49SGordon Ross 	}
363*94047d49SGordon Ross 
364*94047d49SGordon Ross 	ofile->f_oplock.og_state = LeaseState;
365*94047d49SGordon Ross 	lease->ls_state = LeaseState &
366*94047d49SGordon Ross 	    OPLOCK_LEVEL_CACHE_MASK;
367*94047d49SGordon Ross 
368*94047d49SGordon Ross errout:
369*94047d49SGordon Ross 	sr->smb2_status = status;
370*94047d49SGordon Ross 	DTRACE_SMB2_DONE(op__OplockBreak, smb_request_t *, sr);
371*94047d49SGordon Ross 	if (status) {
372*94047d49SGordon Ross 		smb2sr_put_error(sr, status);
373*94047d49SGordon Ross 		return (SDRC_SUCCESS);
374*94047d49SGordon Ross 	}
375*94047d49SGordon Ross 
376*94047d49SGordon Ross 	/*
377*94047d49SGordon Ross 	 * Encode an SMB2 Lease Ack. response
378*94047d49SGordon Ross 	 * [MS-SMB2] 2.2.25.2
379*94047d49SGordon Ross 	 */
380*94047d49SGordon Ross 	LeaseState &= OPLOCK_LEVEL_CACHE_MASK;
381*94047d49SGordon Ross 	(void) smb_mbc_encodef(
382*94047d49SGordon Ross 	    &sr->reply, "w6.#cl8.",
383*94047d49SGordon Ross 	    SSZ_LEASE_ACK,	/* w */
384*94047d49SGordon Ross 	    /* reserved		  6. */
385*94047d49SGordon Ross 	    UUID_LEN,		/* # */
386*94047d49SGordon Ross 	    LeaseKey,		/* c */
387*94047d49SGordon Ross 	    LeaseState);	/* l */
388*94047d49SGordon Ross 	    /* duration		  8. */
389*94047d49SGordon Ross 
390*94047d49SGordon Ross 	return (SDRC_SUCCESS);
391*94047d49SGordon Ross 
392*94047d49SGordon Ross }
393*94047d49SGordon Ross 
394*94047d49SGordon Ross /*
395*94047d49SGordon Ross  * Compose an SMB2 Lease Break Notification packet, including
396*94047d49SGordon Ross  * the SMB2 header and everything, in sr->reply.
397*94047d49SGordon Ross  * The caller will send it and free the request.
398*94047d49SGordon Ross  *
399*94047d49SGordon Ross  * [MS-SMB2] 2.2.23.2 Lease Break Notification
400*94047d49SGordon Ross  */
401*94047d49SGordon Ross void
402*94047d49SGordon Ross smb2_lease_break_notification(smb_request_t *sr, uint32_t NewLevel,
403*94047d49SGordon Ross     boolean_t AckReq)
404*94047d49SGordon Ross {
405*94047d49SGordon Ross 	smb_ofile_t *ofile = sr->fid_ofile;
406*94047d49SGordon Ross 	smb_oplock_grant_t *og = &ofile->f_oplock;
407*94047d49SGordon Ross 	smb_lease_t *ls = ofile->f_lease;
408*94047d49SGordon Ross 	uint32_t oldcache;
409*94047d49SGordon Ross 	uint32_t newcache;
410*94047d49SGordon Ross 	uint16_t Epoch;
411*94047d49SGordon Ross 	uint16_t Flags;
412*94047d49SGordon Ross 
413*94047d49SGordon Ross 	/*
414*94047d49SGordon Ross 	 * Convert internal level to SMB2
415*94047d49SGordon Ross 	 */
416*94047d49SGordon Ross 	oldcache = og->og_state & OPLOCK_LEVEL_CACHE_MASK;
417*94047d49SGordon Ross 	newcache = NewLevel & OPLOCK_LEVEL_CACHE_MASK;
418*94047d49SGordon Ross 	if (ls->ls_version < 2)
419*94047d49SGordon Ross 		Epoch = 0;
420*94047d49SGordon Ross 	else
421*94047d49SGordon Ross 		Epoch = ls->ls_epoch;
422*94047d49SGordon Ross 
423*94047d49SGordon Ross 	/*
424*94047d49SGordon Ross 	 * SMB2 Header
425*94047d49SGordon Ross 	 */
426*94047d49SGordon Ross 	sr->smb2_cmd_code = SMB2_OPLOCK_BREAK;
427*94047d49SGordon Ross 	sr->smb2_hdr_flags = SMB2_FLAGS_SERVER_TO_REDIR;
428*94047d49SGordon Ross 	sr->smb_tid = 0;
429*94047d49SGordon Ross 	sr->smb_pid = 0;
430*94047d49SGordon Ross 	sr->smb2_ssnid = 0;
431*94047d49SGordon Ross 	sr->smb2_messageid = UINT64_MAX;
432*94047d49SGordon Ross 	(void) smb2_encode_header(sr, B_FALSE);
433*94047d49SGordon Ross 
434*94047d49SGordon Ross 	/*
435*94047d49SGordon Ross 	 * SMB2 Oplock Break, variable part
436*94047d49SGordon Ross 	 *
437*94047d49SGordon Ross 	 * [MS-SMB2] says the current lease state preceeds the
438*94047d49SGordon Ross 	 * new lease state, but that looks like an error...
439*94047d49SGordon Ross 	 */
440*94047d49SGordon Ross 	Flags = AckReq ? SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED : 0;
441*94047d49SGordon Ross 	(void) smb_mbc_encodef(
442*94047d49SGordon Ross 	    &sr->reply, "wwl#cll4.4.4.",
443*94047d49SGordon Ross 	    SSZ_LEASE_BRK,		/* w */
444*94047d49SGordon Ross 	    Epoch,			/* w */
445*94047d49SGordon Ross 	    Flags,			/* l */
446*94047d49SGordon Ross 	    SMB_LEASE_KEY_SZ,		/* # */
447*94047d49SGordon Ross 	    ls->ls_key,			/* c */
448*94047d49SGordon Ross 	    oldcache,		/* cur.st  l */
449*94047d49SGordon Ross 	    newcache);		/* new.st  l */
450*94047d49SGordon Ross 	    /* reserved (4.4.4.) */
451*94047d49SGordon Ross }
452*94047d49SGordon Ross 
453*94047d49SGordon Ross /*
454*94047d49SGordon Ross  * Client has an open handle and requests a lease.
455*94047d49SGordon Ross  * Convert SMB2 lease request info in to internal form,
456*94047d49SGordon Ross  * call common oplock code, convert result to SMB2.
457*94047d49SGordon Ross  *
458*94047d49SGordon Ross  * If necessary, "go async" here.
459*94047d49SGordon Ross  */
460*94047d49SGordon Ross void
461*94047d49SGordon Ross smb2_lease_acquire(smb_request_t *sr)
462*94047d49SGordon Ross {
463*94047d49SGordon Ross 	smb_arg_open_t *op = &sr->arg.open;
464*94047d49SGordon Ross 	smb_ofile_t *ofile = sr->fid_ofile;
465*94047d49SGordon Ross 	smb_lease_t *lease = ofile->f_lease;
466*94047d49SGordon Ross 	uint32_t status = NT_STATUS_OPLOCK_NOT_GRANTED;
467*94047d49SGordon Ross 	uint32_t have, want; /* lease flags */
468*94047d49SGordon Ross 	boolean_t NewGrant = B_FALSE;
469*94047d49SGordon Ross 
470*94047d49SGordon Ross 	/* Only disk trees get oplocks. */
471*94047d49SGordon Ross 	ASSERT((sr->tid_tree->t_res_type & STYPE_MASK) == STYPE_DISKTREE);
472*94047d49SGordon Ross 
473*94047d49SGordon Ross 	/*
474*94047d49SGordon Ross 	 * Only plain files (for now).
475*94047d49SGordon Ross 	 * Later, test SMB2_CAP_DIRECTORY_LEASING
476*94047d49SGordon Ross 	 */
477*94047d49SGordon Ross 	if (!smb_node_is_file(ofile->f_node)) {
478*94047d49SGordon Ross 		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
479*94047d49SGordon Ross 		return;
480*94047d49SGordon Ross 	}
481*94047d49SGordon Ross 
482*94047d49SGordon Ross 	if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_OPLOCKS)) {
483*94047d49SGordon Ross 		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
484*94047d49SGordon Ross 		return;
485*94047d49SGordon Ross 	}
486*94047d49SGordon Ross 
487*94047d49SGordon Ross 	/*
488*94047d49SGordon Ross 	 * SMB2: Convert to internal form.
489*94047d49SGordon Ross 	 * Caller should have setup the lease.
490*94047d49SGordon Ross 	 */
491*94047d49SGordon Ross 	ASSERT(op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE);
492*94047d49SGordon Ross 	ASSERT(lease != NULL);
493*94047d49SGordon Ross 	if (lease == NULL) {
494*94047d49SGordon Ross 		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
495*94047d49SGordon Ross 		return;
496*94047d49SGordon Ross 	}
497*94047d49SGordon Ross 	op->op_oplock_state = OPLOCK_LEVEL_GRANULAR |
498*94047d49SGordon Ross 	    (op->lease_state & CACHE_RWH);
499*94047d49SGordon Ross 
500*94047d49SGordon Ross 	/*
501*94047d49SGordon Ross 	 * Tree options may force shared oplocks,
502*94047d49SGordon Ross 	 * in which case we reduce the request.
503*94047d49SGordon Ross 	 */
504*94047d49SGordon Ross 	if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) {
505*94047d49SGordon Ross 		op->op_oplock_state &= ~WRITE_CACHING;
506*94047d49SGordon Ross 	}
507*94047d49SGordon Ross 
508*94047d49SGordon Ross 	/*
509*94047d49SGordon Ross 	 * Disallow downgrade
510*94047d49SGordon Ross 	 *
511*94047d49SGordon Ross 	 * Note that open with a lease is not allowed to turn off
512*94047d49SGordon Ross 	 * any cache rights.  If the client tries to "downgrade",
513*94047d49SGordon Ross 	 * any bits, just return the existing lease cache bits.
514*94047d49SGordon Ross 	 */
515*94047d49SGordon Ross 	have = lease->ls_state & CACHE_RWH;
516*94047d49SGordon Ross 	want = op->op_oplock_state & CACHE_RWH;
517*94047d49SGordon Ross 	if ((have & ~want) != 0) {
518*94047d49SGordon Ross 		op->op_oplock_state = have |
519*94047d49SGordon Ross 		    OPLOCK_LEVEL_GRANULAR;
520*94047d49SGordon Ross 		goto done;
521*94047d49SGordon Ross 	}
522*94047d49SGordon Ross 
523*94047d49SGordon Ross 	/*
524*94047d49SGordon Ross 	 * Handle oplock requests in three parts:
525*94047d49SGordon Ross 	 *	a: Requests with WRITE_CACHING
526*94047d49SGordon Ross 	 *	b: Requests with HANDLE_CACHING
527*94047d49SGordon Ross 	 *	c: Requests with READ_CACHING
528*94047d49SGordon Ross 	 * reducing the request before b and c.
529*94047d49SGordon Ross 	 *
530*94047d49SGordon Ross 	 * In each: first check if the lease grants the
531*94047d49SGordon Ross 	 * (possibly reduced) request, in which case we
532*94047d49SGordon Ross 	 * leave the lease unchanged and return what's
533*94047d49SGordon Ross 	 * granted by the lease.  Otherwise, try to get
534*94047d49SGordon Ross 	 * the oplock, and if the succeeds, wait for any
535*94047d49SGordon Ross 	 * breaks, update the lease, and return.
536*94047d49SGordon Ross 	 */
537*94047d49SGordon Ross 
538*94047d49SGordon Ross 	/*
539*94047d49SGordon Ross 	 * Try exclusive (request is RW or RWH)
540*94047d49SGordon Ross 	 */
541*94047d49SGordon Ross 	if ((op->op_oplock_state & WRITE_CACHING) != 0) {
542*94047d49SGordon Ross 		want = op->op_oplock_state & CACHE_RWH;
543*94047d49SGordon Ross 		if (have == want)
544*94047d49SGordon Ross 			goto done;
545*94047d49SGordon Ross 
546*94047d49SGordon Ross 		status = smb_oplock_request(sr, ofile,
547*94047d49SGordon Ross 		    &op->op_oplock_state);
548*94047d49SGordon Ross 		if (status == NT_STATUS_SUCCESS ||
549*94047d49SGordon Ross 		    status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
550*94047d49SGordon Ross 			NewGrant = B_TRUE;
551*94047d49SGordon Ross 			goto done;
552*94047d49SGordon Ross 		}
553*94047d49SGordon Ross 
554*94047d49SGordon Ross 		/*
555*94047d49SGordon Ross 		 * We did not get the exclusive oplock.
556*94047d49SGordon Ross 		 *
557*94047d49SGordon Ross 		 * There are odd rules about lease upgrade.
558*94047d49SGordon Ross 		 * If the existing lease grants R and the
559*94047d49SGordon Ross 		 * client fails to upgrade it to "RWH"
560*94047d49SGordon Ross 		 * (presumably due to handle conflicts)
561*94047d49SGordon Ross 		 * then just return the existing lease,
562*94047d49SGordon Ross 		 * even though upgrade to RH would work.
563*94047d49SGordon Ross 		 */
564*94047d49SGordon Ross 		if (have != 0) {
565*94047d49SGordon Ross 			op->op_oplock_state = have |
566*94047d49SGordon Ross 			    OPLOCK_LEVEL_GRANULAR;
567*94047d49SGordon Ross 			goto done;
568*94047d49SGordon Ross 		}
569*94047d49SGordon Ross 
570*94047d49SGordon Ross 		/*
571*94047d49SGordon Ross 		 * Keep trying without write.
572*94047d49SGordon Ross 		 * Need to re-init op_oplock_state
573*94047d49SGordon Ross 		 */
574*94047d49SGordon Ross 		op->op_oplock_state = OPLOCK_LEVEL_GRANULAR |
575*94047d49SGordon Ross 		    (op->lease_state & CACHE_RH);
576*94047d49SGordon Ross 	}
577*94047d49SGordon Ross 
578*94047d49SGordon Ross 	/*
579*94047d49SGordon Ross 	 * Try shared ("RH")
580*94047d49SGordon Ross 	 */
581*94047d49SGordon Ross 	if ((op->op_oplock_state & HANDLE_CACHING) != 0) {
582*94047d49SGordon Ross 		want = op->op_oplock_state & CACHE_RWH;
583*94047d49SGordon Ross 		if (have == want)
584*94047d49SGordon Ross 			goto done;
585*94047d49SGordon Ross 
586*94047d49SGordon Ross 		status = smb_oplock_request(sr, ofile,
587*94047d49SGordon Ross 		    &op->op_oplock_state);
588*94047d49SGordon Ross 		if (status == NT_STATUS_SUCCESS ||
589*94047d49SGordon Ross 		    status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
590*94047d49SGordon Ross 			NewGrant = B_TRUE;
591*94047d49SGordon Ross 			goto done;
592*94047d49SGordon Ross 		}
593*94047d49SGordon Ross 
594*94047d49SGordon Ross 		/*
595*94047d49SGordon Ross 		 * We did not get "RH", probably because
596*94047d49SGordon Ross 		 * ther were (old style) Level II oplocks.
597*94047d49SGordon Ross 		 * Continue, try for just read.
598*94047d49SGordon Ross 		 */
599*94047d49SGordon Ross 		op->op_oplock_state = OPLOCK_LEVEL_GRANULAR |
600*94047d49SGordon Ross 		    (op->lease_state & CACHE_R);
601*94047d49SGordon Ross 	}
602*94047d49SGordon Ross 
603*94047d49SGordon Ross 	/*
604*94047d49SGordon Ross 	 * Try shared ("R")
605*94047d49SGordon Ross 	 */
606*94047d49SGordon Ross 	if ((op->op_oplock_state & READ_CACHING) != 0) {
607*94047d49SGordon Ross 		want = op->op_oplock_state & CACHE_RWH;
608*94047d49SGordon Ross 		if (have == want)
609*94047d49SGordon Ross 			goto done;
610*94047d49SGordon Ross 
611*94047d49SGordon Ross 		status = smb_oplock_request(sr, ofile,
612*94047d49SGordon Ross 		    &op->op_oplock_state);
613*94047d49SGordon Ross 		if (status == NT_STATUS_SUCCESS ||
614*94047d49SGordon Ross 		    status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
615*94047d49SGordon Ross 			NewGrant = B_TRUE;
616*94047d49SGordon Ross 			goto done;
617*94047d49SGordon Ross 		}
618*94047d49SGordon Ross 
619*94047d49SGordon Ross 		/*
620*94047d49SGordon Ross 		 * We did not get "R".
621*94047d49SGordon Ross 		 * Fall into "none".
622*94047d49SGordon Ross 		 */
623*94047d49SGordon Ross 	}
624*94047d49SGordon Ross 
625*94047d49SGordon Ross 	/*
626*94047d49SGordon Ross 	 * None of the above were able to get an oplock.
627*94047d49SGordon Ross 	 * The lease has no caching rights, and we didn't
628*94047d49SGordon Ross 	 * add any in this request.  Return it as-is.
629*94047d49SGordon Ross 	 */
630*94047d49SGordon Ross 	op->op_oplock_state = OPLOCK_LEVEL_GRANULAR;
631*94047d49SGordon Ross 
632*94047d49SGordon Ross done:
633*94047d49SGordon Ross 	if (NewGrant) {
634*94047d49SGordon Ross 		/*
635*94047d49SGordon Ross 		 * After a new oplock grant, the status return
636*94047d49SGordon Ross 		 * may indicate we need to wait for breaks.
637*94047d49SGordon Ross 		 */
638*94047d49SGordon Ross 		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
639*94047d49SGordon Ross 			(void) smb2sr_go_async(sr);
640*94047d49SGordon Ross 			(void) smb_oplock_wait_break(ofile->f_node, 0);
641*94047d49SGordon Ross 			status = NT_STATUS_SUCCESS;
642*94047d49SGordon Ross 		}
643*94047d49SGordon Ross 		ASSERT(status == NT_STATUS_SUCCESS);
644*94047d49SGordon Ross 
645*94047d49SGordon Ross 		/*
646*94047d49SGordon Ross 		 * Keep track of what we got (in ofile->f_oplock.og_state)
647*94047d49SGordon Ross 		 * so we'll know what we had when sending a break later.
648*94047d49SGordon Ross 		 * Also update the lease with the new oplock state.
649*94047d49SGordon Ross 		 * Also track which ofile on the lease owns the oplock.
650*94047d49SGordon Ross 		 * The og_dialect here is the oplock dialect, not the
651*94047d49SGordon Ross 		 * SMB dialect.  Leasing, so SMB 2.1 (or later).
652*94047d49SGordon Ross 		 */
653*94047d49SGordon Ross 		ofile->f_oplock.og_dialect = SMB_VERS_2_1;
654*94047d49SGordon Ross 		ofile->f_oplock.og_state = op->op_oplock_state;
655*94047d49SGordon Ross 		mutex_enter(&lease->ls_mutex);
656*94047d49SGordon Ross 		lease->ls_state = op->op_oplock_state & CACHE_RWH;
657*94047d49SGordon Ross 		lease->ls_oplock_ofile = ofile;
658*94047d49SGordon Ross 		lease->ls_epoch++;
659*94047d49SGordon Ross 		mutex_exit(&lease->ls_mutex);
660*94047d49SGordon Ross 	}
661*94047d49SGordon Ross 
662*94047d49SGordon Ross 	/*
663*94047d49SGordon Ross 	 * Convert internal oplock state to SMB2
664*94047d49SGordon Ross 	 */
665*94047d49SGordon Ross 	op->op_oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
666*94047d49SGordon Ross 	op->lease_state = lease->ls_state & CACHE_RWH;
667*94047d49SGordon Ross 	op->lease_flags = (lease->ls_breaking != 0) ?
668*94047d49SGordon Ross 	    SMB2_LEASE_FLAG_BREAK_IN_PROGRESS : 0;
669*94047d49SGordon Ross 	op->lease_epoch = lease->ls_epoch;
670*94047d49SGordon Ross 	op->lease_version = lease->ls_version;
671*94047d49SGordon Ross }
672*94047d49SGordon Ross 
673*94047d49SGordon Ross /*
674*94047d49SGordon Ross  * This ofile has a lease and is about to close.
675*94047d49SGordon Ross  * Called by smb_ofile_close when there's a lease.
676*94047d49SGordon Ross  *
677*94047d49SGordon Ross  * With leases, just one ofile on a lease owns the oplock.
678*94047d49SGordon Ross  * If an ofile with a lease is closed and it's the one that
679*94047d49SGordon Ross  * owns the oplock, try to move the oplock to another ofile
680*94047d49SGordon Ross  * on the same lease.
681*94047d49SGordon Ross  */
682*94047d49SGordon Ross void
683*94047d49SGordon Ross smb2_lease_ofile_close(smb_ofile_t *ofile)
684*94047d49SGordon Ross {
685*94047d49SGordon Ross 	smb_node_t *node = ofile->f_node;
686*94047d49SGordon Ross 	smb_lease_t *lease = ofile->f_lease;
687*94047d49SGordon Ross 	smb_ofile_t *o;
688*94047d49SGordon Ross 
689*94047d49SGordon Ross 	/*
690*94047d49SGordon Ross 	 * If this ofile was not the oplock owner for this lease,
691*94047d49SGordon Ross 	 * we can leave things as they are.
692*94047d49SGordon Ross 	 */
693*94047d49SGordon Ross 	if (lease->ls_oplock_ofile != ofile)
694*94047d49SGordon Ross 		return;
695*94047d49SGordon Ross 
696*94047d49SGordon Ross 	/*
697*94047d49SGordon Ross 	 * Find another ofile to which we can move the oplock.
698*94047d49SGordon Ross 	 * The ofile must be open and allow a new ref.
699*94047d49SGordon Ross 	 */
700*94047d49SGordon Ross 	smb_llist_enter(&node->n_ofile_list, RW_READER);
701*94047d49SGordon Ross 	FOREACH_NODE_OFILE(node, o) {
702*94047d49SGordon Ross 		if (o == ofile)
703*94047d49SGordon Ross 			continue;
704*94047d49SGordon Ross 		if (o->f_lease != lease)
705*94047d49SGordon Ross 			continue;
706*94047d49SGordon Ross 		/* If we can get a hold, use this ofile. */
707*94047d49SGordon Ross 		if (smb_ofile_hold(o))
708*94047d49SGordon Ross 			break;
709*94047d49SGordon Ross 	}
710*94047d49SGordon Ross 	if (o == NULL) {
711*94047d49SGordon Ross 		/* Normal for last close on a lease. */
712*94047d49SGordon Ross 		smb_llist_exit(&node->n_ofile_list);
713*94047d49SGordon Ross 		return;
714*94047d49SGordon Ross 	}
715*94047d49SGordon Ross 	smb_oplock_move(node, ofile, o);
716*94047d49SGordon Ross 	lease->ls_oplock_ofile = o;
717*94047d49SGordon Ross 
718*94047d49SGordon Ross 	smb_llist_exit(&node->n_ofile_list);
719*94047d49SGordon Ross 	smb_ofile_release(o);
720*94047d49SGordon Ross }
721