/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. * Copyright 2021-2023 RackTop Systems, Inc. */ /* * Dispatch function for SMB2_OPLOCK_BREAK */ #include #include /* StructSize for the two "break" message formats. */ #define SSZ_OPLOCK 24 #define SSZ_LEASE_ACK 36 #define SSZ_LEASE_BRK 44 #define NODE_FLAGS_DELETING (NODE_FLAGS_DELETE_ON_CLOSE |\ NODE_FLAGS_DELETE_COMMITTED) static const char lease_zero[UUID_LEN] = { 0 }; static kmem_cache_t *smb_lease_cache = NULL; void smb2_lease_init() { if (smb_lease_cache != NULL) return; smb_lease_cache = kmem_cache_create("smb_lease_cache", sizeof (smb_lease_t), 8, NULL, NULL, NULL, NULL, NULL, 0); } void smb2_lease_fini() { if (smb_lease_cache != NULL) { kmem_cache_destroy(smb_lease_cache); smb_lease_cache = NULL; } } /* * Take a ref on the lease if it's not being destroyed. * Returns false when the lease is being destroyed. * Otherwise, returns true. */ static boolean_t smb2_lease_hold(smb_lease_t *ls) { boolean_t ret; mutex_enter(&ls->ls_mutex); ret = !ls->ls_destroying; if (ret) ls->ls_refcnt++; mutex_exit(&ls->ls_mutex); return (ret); } static void lease_destroy(smb_lease_t *ls) { smb_node_release(ls->ls_node); mutex_destroy(&ls->ls_mutex); kmem_cache_free(smb_lease_cache, ls); } void smb2_lease_rele(smb_lease_t *ls) { smb_llist_t *bucket; boolean_t destroy = B_FALSE; mutex_enter(&ls->ls_mutex); ls->ls_refcnt--; if (ls->ls_refcnt != 0 || ls->ls_destroying) { mutex_exit(&ls->ls_mutex); return; } ls->ls_destroying = B_TRUE; mutex_exit(&ls->ls_mutex); /* * Get the list lock, then re-check the refcnt * and if it's still zero, unlink & destroy. */ bucket = ls->ls_bucket; smb_llist_enter(bucket, RW_WRITER); mutex_enter(&ls->ls_mutex); if (ls->ls_refcnt == 0) { smb_llist_remove(bucket, ls); destroy = B_TRUE; } else { ls->ls_destroying = B_FALSE; } mutex_exit(&ls->ls_mutex); smb_llist_exit(bucket); if (destroy) { lease_destroy(ls); } } /* * Compute a hash from a uuid * Based on mod_hash_bystr() */ static uint_t smb_hash_uuid(const uint8_t *uuid) { char *k = (char *)uuid; uint_t hash = 0; uint_t g; int i; ASSERT(k); for (i = 0; i < UUID_LEN; i++) { hash = (hash << 4) + k[i]; if ((g = (hash & 0xf0000000)) != 0) { hash ^= (g >> 24); hash ^= g; } } return (hash); } /* * Add or update a lease table entry for a new ofile. * (in the per-session lease table) * See [MS-SMB2] 3.3.5.9.8 * Handling the SMB2_CREATE_REQUEST_LEASE Create Context */ uint32_t smb2_lease_create(smb_request_t *sr, uint8_t *clnt) { smb_arg_open_t *op = &sr->arg.open; uint8_t *key = op->lease_key; smb_ofile_t *of = sr->fid_ofile; smb_hash_t *ht = sr->sr_server->sv_lease_ht; smb_llist_t *bucket; smb_lease_t *lease; smb_lease_t *newlease; size_t hashkey; uint32_t status = NT_STATUS_INVALID_PARAMETER; if (bcmp(key, lease_zero, UUID_LEN) == 0) return (status); /* * Find or create, and add a ref for the new ofile. */ hashkey = smb_hash_uuid(key); hashkey &= (ht->num_buckets - 1); bucket = &ht->buckets[hashkey].b_list; newlease = kmem_cache_alloc(smb_lease_cache, KM_SLEEP); bzero(newlease, sizeof (smb_lease_t)); mutex_init(&newlease->ls_mutex, NULL, MUTEX_DEFAULT, NULL); newlease->ls_bucket = bucket; newlease->ls_node = of->f_node; smb_node_ref(newlease->ls_node); newlease->ls_refcnt = 1; newlease->ls_epoch = op->lease_epoch; newlease->ls_version = op->lease_version; bcopy(key, newlease->ls_key, UUID_LEN); bcopy(clnt, newlease->ls_clnt, UUID_LEN); smb_llist_enter(bucket, RW_WRITER); for (lease = smb_llist_head(bucket); lease != NULL; lease = smb_llist_next(bucket, lease)) { /* * Looking for this lease ID, on a node * that's not being deleted. */ if (bcmp(lease->ls_key, key, UUID_LEN) == 0 && bcmp(lease->ls_clnt, clnt, UUID_LEN) == 0 && (lease->ls_node->flags & NODE_FLAGS_DELETING) == 0 && smb2_lease_hold(lease)) break; } if (lease == NULL) { lease = newlease; smb_llist_insert_head(bucket, lease); newlease = NULL; /* don't free */ } smb_llist_exit(bucket); /* * If we found an existing lease, make sure it refers to the same node. */ if (lease->ls_node != of->f_node) { /* Same lease ID, different node! */ #ifdef DEBUG cmn_err(CE_NOTE, "new lease on node %p (%s) " "conflicts with existing node %p (%s)", (void *) of->f_node, of->f_node->od_name, (void *) lease->ls_node, lease->ls_node->od_name); #endif DTRACE_PROBE2(dup_lease, smb_request_t *, sr, smb_lease_t *, lease); smb2_lease_rele(lease); lease = NULL; /* error */ } if (newlease != NULL) { lease_destroy(newlease); } if (lease != NULL) { of->f_lease = lease; status = NT_STATUS_SUCCESS; } return (status); } /* * Find the lease for a given: client_uuid, lease_key * Returns the lease with a new ref. */ static smb_lease_t * lease_lookup(smb_request_t *sr, uint8_t *lease_key) { smb_server_t *sv = sr->sr_server; uint8_t *clnt_uuid = sr->session->clnt_uuid; smb_hash_t *ht = sv->sv_lease_ht; smb_llist_t *bucket; smb_lease_t *lease; size_t hashkey; hashkey = smb_hash_uuid(lease_key); hashkey &= (ht->num_buckets - 1); bucket = &ht->buckets[hashkey].b_list; smb_llist_enter(bucket, RW_READER); lease = smb_llist_head(bucket); while (lease != NULL) { if (bcmp(lease->ls_key, lease_key, UUID_LEN) == 0 && bcmp(lease->ls_clnt, clnt_uuid, UUID_LEN) == 0 && smb2_lease_hold(lease)) break; lease = smb_llist_next(bucket, lease); } smb_llist_exit(bucket); return (lease); } /* * Find the oplock smb_ofile_t for the specified lease. * If no such ofile, NT_STATUS_UNSUCCESSFUL. * On success, ofile (held) in sr->fid_ofile. */ static uint32_t lease_find_oplock(smb_request_t *sr, smb_lease_t *lease) { smb_node_t *node = lease->ls_node; smb_ofile_t *o; uint32_t status = NT_STATUS_UNSUCCESSFUL; ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock)); ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex)); ASSERT(sr->fid_ofile == NULL); FOREACH_NODE_OFILE(node, o) { if (o->f_lease != lease) continue; if (o != lease->ls_oplock_ofile) continue; /* * Found the ofile holding the oplock * This hold released in smb_request_free */ if (smb_ofile_hold_olbrk(o)) { sr->fid_ofile = o; status = NT_STATUS_SUCCESS; break; } } return (status); } /* * This is called by smb2_oplock_break_ack when the struct size * indicates this is a lease break (SZ_LEASE). See: * [MS-SMB2] 3.3.5.22.2 Processing a Lease Acknowledgment * This is an "Ack" from the client. */ smb_sdrc_t smb2_lease_break_ack(smb_request_t *sr) { smb_arg_olbrk_t *olbrk = &sr->arg.olbrk; smb_lease_t *lease; smb_node_t *node; smb_ofile_t *ofile; uint32_t LeaseState; uint32_t status; int rc = 0; if (sr->session->dialect < SMB_VERS_2_1) return (SDRC_ERROR); /* * Decode an SMB2 Lease Acknowldgement * [MS-SMB2] 2.2.24.2 * Note: Struct size decoded by caller. */ rc = smb_mbc_decodef( &sr->smb_data, "6.#cl8.", /* reserved 6. */ UUID_LEN, /* # */ olbrk->LeaseKey, /* c */ &olbrk->NewLevel); /* l */ /* duration 8. */ if (rc != 0) return (SDRC_ERROR); LeaseState = olbrk->NewLevel; /* * Find the lease via the given key. */ lease = lease_lookup(sr, olbrk->LeaseKey); if (lease == NULL) { /* * It's unusual to skip the dtrace start/done * probes like this, but trying to run them * with no lease->node would be complex and * would not show anything particularly useful. * Do the start probe after we find the ofile. */ status = NT_STATUS_OBJECT_NAME_NOT_FOUND; smb2sr_put_error(sr, status); return (SDRC_SUCCESS); } // Note: lease ref; smb_lease_rele() below. node = lease->ls_node; /* * Find the leased oplock. Hold locks so it can't move * until we're done with ACK-break processing. */ smb_llist_enter(&node->n_ofile_list, RW_READER); mutex_enter(&node->n_oplock.ol_mutex); status = lease_find_oplock(sr, lease); /* Normally have sr->fid_ofile now. */ DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr); if (status != 0) { /* Leased oplock not found. Must have closed. */ goto errout; } /* Success, so have sr->fid_ofile */ ofile = sr->fid_ofile; if (lease->ls_breaking == B_FALSE) { /* * This ACK is either unsolicited or too late, * eg. we timed out the ACK and did it locally. */ status = NT_STATUS_UNSUCCESSFUL; goto errout; } /* * If the new LeaseState has any bits in excess of * the lease state we sent in the break, error... */ if ((LeaseState & ~(lease->ls_breakto)) != 0) { status = NT_STATUS_REQUEST_NOT_ACCEPTED; goto errout; } /* * Process the lease break ack. * * Clear breaking flags before we ack, * because ack might set those. * Signal both CVs, out of paranoia. */ ofile->f_oplock.og_breaking = B_FALSE; cv_broadcast(&ofile->f_oplock.og_ack_cv); lease->ls_breaking = B_FALSE; cv_broadcast(&lease->ls_ack_cv); LeaseState |= OPLOCK_LEVEL_GRANULAR; status = smb_oplock_ack_break(sr, ofile, &LeaseState); ofile->f_oplock.og_state = LeaseState; lease->ls_state = LeaseState; /* ls_epoch does not change here */ if (ofile->dh_persist) smb2_dh_update_oplock(sr, ofile); errout: sr->smb2_status = status; DTRACE_SMB2_DONE(op__OplockBreak, smb_request_t *, sr); mutex_exit(&node->n_oplock.ol_mutex); smb_llist_exit(&node->n_ofile_list); smb2_lease_rele(lease); if (status) { smb2sr_put_error(sr, status); return (SDRC_SUCCESS); } /* * Encode an SMB2 Lease Ack. response * [MS-SMB2] 2.2.25.2 */ LeaseState &= OPLOCK_LEVEL_CACHE_MASK; (void) smb_mbc_encodef( &sr->reply, "w6.#cl8.", SSZ_LEASE_ACK, /* w */ /* reserved 6. */ UUID_LEN, /* # */ olbrk->LeaseKey, /* c */ LeaseState); /* l */ /* duration 8. */ return (SDRC_SUCCESS); } /* * Compose an SMB2 Lease Break Notification packet, including * the SMB2 header and everything, in sr->reply. * The caller will send it and free the request. * * [MS-SMB2] 2.2.23.2 Lease Break Notification */ static void smb2_lease_break_notification(smb_request_t *sr, uint32_t OldLevel, uint32_t NewLevel, uint16_t Epoch, boolean_t AckReq) { smb_lease_t *ls = sr->fid_ofile->f_lease; uint16_t Flags = 0; /* * Convert internal lease info to SMB2 */ if (AckReq) Flags = SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED; if (ls->ls_version < 2) Epoch = 0; OldLevel &= OPLOCK_LEVEL_CACHE_MASK; NewLevel &= OPLOCK_LEVEL_CACHE_MASK; /* * SMB2 Header */ sr->smb2_cmd_code = SMB2_OPLOCK_BREAK; sr->smb2_hdr_flags = SMB2_FLAGS_SERVER_TO_REDIR; sr->smb_tid = 0; sr->smb_pid = 0; sr->smb2_ssnid = 0; sr->smb2_messageid = UINT64_MAX; (void) smb2_encode_header(sr, B_FALSE); /* * SMB2 Oplock Break, variable part * * [MS-SMB2] says the current lease state preceeds the * new lease state, but that looks like an error... */ (void) smb_mbc_encodef( &sr->reply, "wwl#cll4.4.4.", SSZ_LEASE_BRK, /* w */ Epoch, /* w */ Flags, /* l */ SMB_LEASE_KEY_SZ, /* # */ ls->ls_key, /* c */ OldLevel, /* cur.st l */ NewLevel); /* new.st l */ /* reserved (4.4.4.) */ } /* * Do our best to send a lease break message to the client. * When we get to multi-channel, this is supposed to try * every channel before giving up. For now, try every * connected session with an ofile sharing this lease. * * If this ofile has a valid session, try that first. * Otherwise look on the node list for other ofiles with * the same lease and a connected session. */ static int lease_send_any_cn(smb_request_t *sr) { smb_ofile_t *o; smb_ofile_t *ofile = sr->fid_ofile; smb_lease_t *lease = ofile->f_lease; smb_node_t *node = ofile->f_node; int rc = ENOTCONN; /* * If the passed oplock ofile has a session, * this IF expression will be true. */ if (sr->session == ofile->f_session) { rc = smb_session_send(sr->session, 0, &sr->reply); if (rc == 0) return (rc); } smb_llist_enter(&node->n_ofile_list, RW_READER); FOREACH_NODE_OFILE(node, o) { if (o->f_lease != lease) continue; if (smb_ofile_hold(o)) { /* Has a session. */ rc = smb_session_send(o->f_session, 0, &sr->reply); smb_llist_post(&node->n_ofile_list, o, smb_ofile_release_LL); } if (rc == 0) break; } smb_llist_exit(&node->n_ofile_list); return (rc); } /* * See smb_llist_post on node->n_ofile_list below. * Can't call smb_ofile_close with that list entered. */ static void lease_ofile_close_rele(void *arg) { smb_ofile_t *of = (smb_ofile_t *)arg; smb_ofile_close(of, 0); smb_ofile_release(of); } /* * [MS-SMB2] 3.3.4.7 Object Store Indicates a Lease Break * If no connection, for each Open in Lease.LeaseOpens, * the server MUST close the Open as specified in sec... * for the following cases: * - Open.IsDurable, Open.IsResilient, and * Open.IsPersistent are all FALSE. * - Open.IsDurable is TRUE and Lease.BreakToLeaseState * does not contain SMB2_LEASE_HANDLE_CACHING and */ static void lease_close_notconn(smb_request_t *sr, uint32_t NewLevel) { smb_ofile_t *o; smb_ofile_t *ofile = sr->fid_ofile; smb_lease_t *lease = ofile->f_lease; smb_node_t *node = ofile->f_node; smb_llist_enter(&node->n_ofile_list, RW_READER); FOREACH_NODE_OFILE(node, o) { if (o->f_lease != lease) continue; if (o->f_oplock_closing) continue; if (o->dh_persist) continue; if (o->dh_vers == SMB2_RESILIENT) continue; if (o->dh_vers == SMB2_NOT_DURABLE || (NewLevel & OPLOCK_LEVEL_CACHE_HANDLE) == 0) { if (smb_ofile_hold_olbrk(o)) { smb_llist_post(&node->n_ofile_list, o, lease_ofile_close_rele); } } } smb_llist_exit(&node->n_ofile_list); } /* * Send a lease break over the wire, or if we can't, * then process the lease break locally. * * [MS-SMB2] 3.3.4.7 Object Store Indicates a Lease Break * * This is mostly similar to smb2_oplock_send_break() * See top comment there about the design. * * Differences beween a lease break and oplock break: * * Leases are an SMB-level mechanism whereby multiple open * SMB file handles can share an oplock. All SMB handles * on the lease enjoy the same caching rights. Down at the * file-system level, just one oplock holds the cache rights * for a lease, but (this is the tricky part) that oplock can * MOVE among the SMB file handles sharing the lease. Such * oplock moves can happen when a handle is closed (if that * handle is the one with the oplock) or when a new open on * the lease causes an upgrade of the caching rights. * * We have to deal here with lease movement because this call * happens asynchronously after the smb_oplock_ind_break call, * meaning that the oplock for the lease may have moved by the * time this runs. In addition, the ofile holding the oplock * might not be the best one to use to send a lease break. * If the oplock is held by a handle that's "orphaned" and * there are other handles on the lease with active sessions, * we want to send the lease break on an active session. * * Also note: NewLevel (as provided by smb_oplock_ind_break etc.) * does NOT include the GRANULAR flag. This level is expected to * keep track of how each oplock was acquired (by lease or not) * and put the GRANULAR flag back in when appropriate. */ void smb2_lease_send_break(smb_request_t *sr) { smb_ofile_t *old_ofile; smb_ofile_t *ofile = sr->fid_ofile; smb_node_t *node = ofile->f_node; smb_lease_t *lease = ofile->f_lease; smb_arg_olbrk_t *olbrk = &sr->arg.olbrk; boolean_t AckReq = olbrk->AckRequired; uint32_t OldLevel = olbrk->OldLevel; uint32_t NewLevel = olbrk->NewLevel; uint32_t status; int rc; NewLevel |= OPLOCK_LEVEL_GRANULAR; /* * Build the break message in sr->reply. * It's free'd in smb_request_free(). * Always an SMB2 lease here. */ sr->reply.max_bytes = MLEN; smb2_lease_break_notification(sr, OldLevel, NewLevel, lease->ls_epoch, AckReq); /* * Try to send the break message to the client, * on any connection with this lease. */ rc = lease_send_any_cn(sr); if (rc != 0) { /* * We were unable to send the oplock break request, * presumably because the connection is gone. * Close uninteresting handles. */ lease_close_notconn(sr, NewLevel); /* Note: some handles may remain on the lease. */ if (!AckReq) return; /* Do local Ack below. */ } else { /* * OK, we were able to send the break message. * If no ack. required, we're done. */ if (!AckReq) return; /* * We're expecting an ACK. Wait in this thread * so we can log clients that don't respond. * Note: this can also fail for other reasons * such as client disconnect or server shutdown. */ status = smb_oplock_wait_ack(sr, NewLevel); if (status == 0) return; DTRACE_PROBE2(wait__ack__failed, smb_request_t *, sr, uint32_t, status); /* * Will do local ack below. Note, after timeout, * do a break to none or "no caching" regardless * of what the passed in cache level was. * That means: clear all except GRANULAR. */ NewLevel = OPLOCK_LEVEL_GRANULAR; } /* * Do the ack locally. * * Find the ofile with the leased oplock * (may have moved before we took locks) */ smb_llist_enter(&node->n_ofile_list, RW_READER); mutex_enter(&node->n_oplock.ol_mutex); old_ofile = ofile; sr->fid_ofile = NULL; status = lease_find_oplock(sr, lease); if (status != 0) { /* put back old_ofile */ sr->fid_ofile = old_ofile; goto unlock_out; } smb_llist_post(&node->n_ofile_list, old_ofile, smb_ofile_release_LL); ofile = sr->fid_ofile; /* * Now continue like the non-lease code */ ofile->f_oplock.og_breaking = B_FALSE; lease->ls_breaking = B_FALSE; cv_broadcast(&lease->ls_ack_cv); status = smb_oplock_ack_break(sr, ofile, &NewLevel); ofile->f_oplock.og_state = NewLevel; lease->ls_state = NewLevel; /* ls_epoch does not change here */ if (ofile->dh_persist) smb2_dh_update_oplock(sr, ofile); unlock_out: mutex_exit(&node->n_oplock.ol_mutex); smb_llist_exit(&node->n_ofile_list); #ifdef DEBUG if (status != 0) { cmn_err(CE_NOTE, "clnt %s local oplock ack, status=0x%x", sr->session->ip_addr_str, status); } #endif } /* * Client has an open handle and requests a lease. * Convert SMB2 lease request info in to internal form, * call common oplock code, convert result to SMB2. * * If necessary, "go async" here (at the end). */ void smb2_lease_acquire(smb_request_t *sr) { smb_arg_open_t *op = &sr->arg.open; smb_ofile_t *ofile = sr->fid_ofile; smb_lease_t *lease = ofile->f_lease; smb_node_t *node = ofile->f_node; uint32_t status = NT_STATUS_OPLOCK_NOT_GRANTED; uint32_t have, want; /* lease flags */ boolean_t NewGrant = B_FALSE; /* Only disk trees get oplocks. */ ASSERT((sr->tid_tree->t_res_type & STYPE_MASK) == STYPE_DISKTREE); /* * Only plain files (for now). * Later, test SMB2_CAP_DIRECTORY_LEASING */ if (!smb_node_is_file(ofile->f_node)) { op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE; return; } if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_OPLOCKS)) { op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE; return; } /* * SMB2: Convert to internal form. * Caller should have setup the lease. */ ASSERT(op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE); ASSERT(lease != NULL); if (lease == NULL) { op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE; return; } op->op_oplock_state = OPLOCK_LEVEL_GRANULAR | (op->lease_state & CACHE_RWH); /* * Tree options may force shared oplocks, * in which case we reduce the request. */ if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) { op->op_oplock_state &= ~WRITE_CACHING; } /* * Using the "Locks Held" (LH) variant of smb_oplock_request * below so things won't change underfoot. */ smb_llist_enter(&node->n_ofile_list, RW_READER); mutex_enter(&node->n_oplock.ol_mutex); /* * MS-SMB2 3.3.5.9.8 and 3.3.5.9.11 Lease (V2) create contexts * * If the caching state requested in LeaseState of the (create ctx) * is not a superset of Lease.LeaseState or if Lease.Breaking is TRUE, * the server MUST NOT promote Lease.LeaseState. If the lease state * requested is a superset of Lease.LeaseState and Lease.Breaking is * FALSE, the server MUST request promotion of the lease state from * the underlying object store to the new caching state. */ have = lease->ls_state & CACHE_RWH; want = op->op_oplock_state & CACHE_RWH; if ((have & ~want) != 0 || lease->ls_breaking) { op->op_oplock_state = have | OPLOCK_LEVEL_GRANULAR; goto done; } /* * Handle oplock requests in three parts: * a: Requests with WRITE_CACHING * b: Requests with HANDLE_CACHING * c: Requests with READ_CACHING * reducing the request before b and c. * * In each: first check if the lease grants the * (possibly reduced) request, in which case we * leave the lease unchanged and return what's * granted by the lease. Otherwise, try to get * the oplock, and if the succeeds, wait for any * breaks, update the lease, and return. */ /* * Try exclusive (request is RW or RWH) */ if ((op->op_oplock_state & WRITE_CACHING) != 0) { /* Alread checked (want & ~have) */ status = smb_oplock_request_LH(sr, ofile, &op->op_oplock_state); if (status == NT_STATUS_SUCCESS || status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { NewGrant = B_TRUE; goto done; } /* * We did not get the exclusive oplock. * * There are odd rules about lease upgrade. * If the existing lease grants R and the * client fails to upgrade it to "RWH" * (presumably due to handle conflicts) * then just return the existing lease, * even though upgrade to RH would work. */ if (have != 0) { op->op_oplock_state = have | OPLOCK_LEVEL_GRANULAR; goto done; } /* * Keep trying without write. * Need to re-init op_oplock_state */ op->op_oplock_state = OPLOCK_LEVEL_GRANULAR | (op->lease_state & CACHE_RH); } /* * Try shared ("RH") */ if ((op->op_oplock_state & HANDLE_CACHING) != 0) { want = op->op_oplock_state & CACHE_RWH; if ((want & ~have) == 0) goto done; status = smb_oplock_request_LH(sr, ofile, &op->op_oplock_state); if (status == NT_STATUS_SUCCESS || status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { NewGrant = B_TRUE; goto done; } /* * We did not get "RH", probably because * ther were (old style) Level II oplocks. * Continue, try for just read. * Again, re-init op_oplock_state */ op->op_oplock_state = OPLOCK_LEVEL_GRANULAR | (op->lease_state & CACHE_R); } /* * Try shared ("R") */ if ((op->op_oplock_state & READ_CACHING) != 0) { want = op->op_oplock_state & CACHE_RWH; if ((want & ~have) == 0) goto done; status = smb_oplock_request_LH(sr, ofile, &op->op_oplock_state); if (status == NT_STATUS_SUCCESS || status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { NewGrant = B_TRUE; goto done; } /* * We did not get "R". * Fall into "none". */ } /* * None of the above were able to get an oplock. * The lease has no caching rights, and we didn't * add any in this request. Return it as-is. */ op->op_oplock_state = OPLOCK_LEVEL_GRANULAR; done: /* * Only success cases get here */ /* * Keep track of what we got (ofile->f_oplock.og_state etc) * so we'll know what we had when sending a break later. * Also keep a copy of some things in the lease. * * Not using og_dialect here, as ofile->f_lease tells us * this has to be using granular oplocks. */ if (NewGrant) { ofile->f_oplock.og_state = op->op_oplock_state; ofile->f_oplock.og_breakto = op->op_oplock_state; ofile->f_oplock.og_breaking = B_FALSE; lease->ls_oplock_ofile = ofile; lease->ls_state = ofile->f_oplock.og_state; lease->ls_breakto = ofile->f_oplock.og_breakto; lease->ls_breaking = B_FALSE; lease->ls_epoch++; if (ofile->dh_persist) { smb2_dh_update_oplock(sr, ofile); } } /* * Convert internal oplock state to SMB2 */ op->op_oplock_level = SMB2_OPLOCK_LEVEL_LEASE; op->lease_state = lease->ls_state & CACHE_RWH; op->lease_flags = (lease->ls_breaking != 0) ? SMB2_LEASE_FLAG_BREAK_IN_PROGRESS : 0; op->lease_epoch = lease->ls_epoch; op->lease_version = lease->ls_version; /* * End of lock-held region */ mutex_exit(&node->n_oplock.ol_mutex); smb_llist_exit(&node->n_ofile_list); /* * After a new oplock grant, the status return * may indicate we need to wait for breaks. */ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { (void) smb2sr_go_async(sr); (void) smb_oplock_wait_break(sr, ofile->f_node, 0); } } /* * This ofile has a lease and is about to close. * Called by smb_ofile_close when there's a lease. * * Note that a client may close an ofile in response to an * oplock break or lease break intead of doing an Ack break, * so this must wake anything that might be waiting on an ack * when the last close of a lease happens. * * With leases, just one ofile on a lease owns the oplock. * If an ofile with a lease is closed and it's the one that * owns the oplock, try to move the oplock to another ofile * on the same lease. * * Would prefer that we could just use smb_ofile_hold_olbrk * to select a suitable destination for the move, but this * is called while holding the owning tree ofile list etc * which can cause deadlock as described in illumos 13850 * when smb_ofile_hold_olbrk has to wait. XXX todo */ void smb2_lease_ofile_close(smb_ofile_t *ofile) { smb_node_t *node = ofile->f_node; smb_lease_t *lease = ofile->f_lease; smb_ofile_t *o; ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock)); ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex)); #ifdef DEBUG FOREACH_NODE_OFILE(node, o) { DTRACE_PROBE1(each_ofile, smb_ofile_t *, o); } #endif /* * If this ofile was not the oplock owner for this lease, * we can leave things as they are. */ if (lease->ls_oplock_ofile != ofile) return; /* * Find another ofile to which we can move the oplock. * First try for one that's open. Usually find one. */ FOREACH_NODE_OFILE(node, o) { if (o == ofile) continue; if (o->f_lease != lease) continue; if (o->f_oplock_closing) continue; mutex_enter(&o->f_mutex); if (o->f_state == SMB_OFILE_STATE_OPEN) { smb_oplock_move(node, ofile, o); lease->ls_oplock_ofile = o; mutex_exit(&o->f_mutex); return; } mutex_exit(&o->f_mutex); } /* * Now try for one that's orphaned etc. */ FOREACH_NODE_OFILE(node, o) { if (o == ofile) continue; if (o->f_lease != lease) continue; if (o->f_oplock_closing) continue; /* * Allow most states as seen in smb_ofile_hold_olbrk * without waiting for "_reconnect" or "_saving". * Skip "_expired" because that's about to close. * This is OK because just swapping the oplock state * between two ofiles does not interfere with the * dh_save or reconnect code paths. */ mutex_enter(&o->f_mutex); switch (o->f_state) { case SMB_OFILE_STATE_OPEN: case SMB_OFILE_STATE_SAVE_DH: case SMB_OFILE_STATE_SAVING: case SMB_OFILE_STATE_ORPHANED: case SMB_OFILE_STATE_RECONNECT: smb_oplock_move(node, ofile, o); lease->ls_oplock_ofile = o; mutex_exit(&o->f_mutex); return; } mutex_exit(&o->f_mutex); } /* * Normal for last close on a lease. * Wakeup ACK waiters too. */ lease->ls_state = 0; lease->ls_breakto = 0; lease->ls_breaking = B_FALSE; cv_broadcast(&lease->ls_ack_cv); lease->ls_oplock_ofile = NULL; }