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