From 72b35b0568511bf35ca88532dff910dc0f16847f Mon Sep 17 00:00:00 2001 From: Gordon Ross Date: Mon, 21 Feb 2022 12:10:09 -0500 Subject: 15188 smb oplock blocking access to folders Reviewed by: Albert Lee Reviewed by: Jerry Jelinek Reviewed by: Joyce McIntosh Reviewed by: Matt Barden Approved by: Dan McDonald --- usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h | 23 +- usr/src/cmd/smbsrv/testoplock/tol_main.c | 28 +- usr/src/uts/common/fs/smbsrv/smb2_dispatch.c | 4 +- usr/src/uts/common/fs/smbsrv/smb2_durable.c | 3 +- usr/src/uts/common/fs/smbsrv/smb2_lease.c | 590 +++++++++++++++++----- usr/src/uts/common/fs/smbsrv/smb2_oplock.c | 281 +++++++++-- usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c | 68 ++- usr/src/uts/common/fs/smbsrv/smb_locking_andx.c | 74 +-- usr/src/uts/common/fs/smbsrv/smb_ofile.c | 33 +- usr/src/uts/common/fs/smbsrv/smb_oplock.c | 225 ++++++++- usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c | 482 +++++++++--------- usr/src/uts/common/smbsrv/smb_kproto.h | 25 +- usr/src/uts/common/smbsrv/smb_ktypes.h | 4 + 13 files changed, 1280 insertions(+), 560 deletions(-) diff --git a/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h index f743dcd179..5ecc17210a 100644 --- a/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h +++ b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h @@ -22,6 +22,7 @@ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2020 Tintri by DDN, Inc. All rights reserved. + * Copyright 2022 RackTop Systems, Inc. */ /* @@ -70,6 +71,7 @@ int smb_account_connected(smb_user_t *user); * Common oplock functions */ uint32_t smb_oplock_request(smb_request_t *, smb_ofile_t *, uint32_t *); +uint32_t smb_oplock_request_LH(smb_request_t *, smb_ofile_t *, uint32_t *); uint32_t smb_oplock_ack_break(smb_request_t *, smb_ofile_t *, uint32_t *); uint32_t smb_oplock_break_PARENT(smb_node_t *, smb_ofile_t *); uint32_t smb_oplock_break_OPEN(smb_node_t *, smb_ofile_t *, @@ -83,22 +85,23 @@ uint32_t smb_oplock_break_WRITE(smb_node_t *, smb_ofile_t *); uint32_t smb_oplock_break_SETINFO(smb_node_t *, smb_ofile_t *ofile, uint32_t InfoClass); uint32_t smb_oplock_break_DELETE(smb_node_t *, smb_ofile_t *); +void smb_oplock_close(smb_ofile_t *); + +void smb_oplock_ind_break(smb_ofile_t *, uint32_t, boolean_t, uint32_t); +void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *, + uint32_t, boolean_t); +void smb_oplock_send_break(smb_request_t *); + +uint32_t smb_oplock_wait_ack(smb_request_t *); +uint32_t smb_oplock_wait_break(smb_request_t *, smb_node_t *, int); +uint32_t smb_oplock_wait_break_fem(smb_node_t *, int); void smb_oplock_move(smb_node_t *, smb_ofile_t *, smb_ofile_t *); /* * Protocol-specific oplock functions - * (and "server-level" functions) + * (not needed here) */ -void smb1_oplock_acquire(smb_request_t *, boolean_t); -void smb1_oplock_break_notification(smb_request_t *, uint32_t); -void smb2_oplock_break_notification(smb_request_t *, uint32_t); -void smb2_lease_break_notification(smb_request_t *, uint32_t, boolean_t); -void smb_oplock_ind_break(smb_ofile_t *, uint32_t, boolean_t, uint32_t); -void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *, - uint32_t, boolean_t); -void smb_oplock_send_brk(smb_request_t *); -uint32_t smb_oplock_wait_break(smb_node_t *, int); int smb_lock_range_access(smb_request_t *, smb_node_t *, uint64_t, uint64_t, boolean_t); diff --git a/usr/src/cmd/smbsrv/testoplock/tol_main.c b/usr/src/cmd/smbsrv/testoplock/tol_main.c index f78729a9d6..7691edbca3 100644 --- a/usr/src/cmd/smbsrv/testoplock/tol_main.c +++ b/usr/src/cmd/smbsrv/testoplock/tol_main.c @@ -234,6 +234,7 @@ do_req(int fid, char *arg2) static void do_ack(int fid, char *arg2) { + smb_node_t *node = &test_node; smb_ofile_t *ofile = &ofile_array[fid]; uint32_t oplock; uint32_t status; @@ -243,14 +244,20 @@ do_ack(int fid, char *arg2) if (arg2 != NULL) oplock = strtol(arg2, NULL, 16); + smb_llist_enter(&node->n_ofile_list, RW_READER); + mutex_enter(&node->n_oplock.ol_mutex); + ofile->f_oplock.og_breaking = 0; status = smb_oplock_ack_break(&test_sr, ofile, &oplock); + if (NT_SC_SEVERITY(status) == NT_STATUS_SEVERITY_SUCCESS) + ofile->f_oplock.og_state = oplock; + + mutex_exit(&node->n_oplock.ol_mutex); + smb_llist_exit(&node->n_ofile_list); + if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { printf(" ack: break fid=%d, break-in-progress\n", fid); - ofile->f_oplock.og_state = oplock; } - if (status == 0) - ofile->f_oplock.og_state = oplock; printf(" ack: break fid=%d, newstate=0x%x, status=0x%x (%s)\n", fid, oplock, status, xlate_nt_status(status)); @@ -550,7 +557,9 @@ smb_lock_range_access( } /* - * Test code replacement for: smb_oplock_send_brk() + * Test code replacement for combination of: + * smb_oplock_hdl_update() + * smb_oplock_send_brk() */ static void test_oplock_send_brk(smb_ofile_t *ofile, @@ -567,6 +576,8 @@ test_oplock_send_brk(smb_ofile_t *ofile, * In a real server, we would send a break to the client, * and keep track (at the SMB level) whether this oplock * was obtained via a lease or an old-style oplock. + * + * This part like: smb_oplock_hdl_update() */ if (AckReq) { uint32_t BreakTo; @@ -576,6 +587,7 @@ test_oplock_send_brk(smb_ofile_t *ofile, BreakTo = (NewLevel & CACHE_RWH) << BREAK_SHIFT; if (BreakTo == 0) BreakTo = BREAK_TO_NO_CACHING; + // ls_breaking = BreakTo; } else { if ((NewLevel & LEVEL_TWO_OPLOCK) != 0) BreakTo = BREAK_TO_TWO; @@ -583,12 +595,15 @@ test_oplock_send_brk(smb_ofile_t *ofile, BreakTo = BREAK_TO_NONE; } og->og_breaking = BreakTo; - last_ind_break_level = NewLevel; /* Set og_state in do_ack */ } else { og->og_state = NewLevel; + // If lease: ls_breaking = ... /* Clear og_breaking in do_ack */ } + + /* Next, smb_oplock_send_break() would send a break. */ + last_ind_break_level = NewLevel; } /* @@ -623,7 +638,6 @@ smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel, break; default: - /* Checked by caller. */ ASSERT(0); break; } @@ -638,7 +652,7 @@ smb_oplock_ind_break_in_ack(smb_request_t *sr, smb_ofile_t *ofile, } uint32_t -smb_oplock_wait_break(smb_node_t *node, int timeout) +smb_oplock_wait_break(smb_request_t *sr, smb_node_t *node, int timeout) { printf("*smb_oplock_wait_break (state=0x%x)\n", node->n_oplock.ol_state); diff --git a/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c b/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c index f01f019dec..658cf2de49 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c @@ -11,7 +11,7 @@ /* * Copyright 2019 Nexenta Systems, Inc. All rights reserved. - * Copyright 2020 RackTop Systems, Inc. + * Copyright 2022 RackTop Systems, Inc. */ @@ -1815,7 +1815,7 @@ smb2sr_run_postwork(smb_request_t *top_sr) switch (post_sr->smb2_cmd_code) { case SMB2_OPLOCK_BREAK: - smb_oplock_send_brk(post_sr); + smb_oplock_send_break(post_sr); break; default: ASSERT(0); diff --git a/usr/src/uts/common/fs/smbsrv/smb2_durable.c b/usr/src/uts/common/fs/smbsrv/smb2_durable.c index 190a7ca6cc..094489c658 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_durable.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_durable.c @@ -11,6 +11,7 @@ /* * Copyright 2017-2022 Tintri by DDN, Inc. All rights reserved. + * Copyright 2022 RackTop Systems, Inc. */ /* @@ -1426,7 +1427,7 @@ smb2_dh_reconnect(smb_request_t *sr) /* * The ofile is now in the caller's session & tree. * - * In case smb_ofile_hold or smb_oplock_send_brk() are + * In case smb_ofile_hold or smb_oplock_send_break() are * waiting for state RECONNECT to complete, wakeup. */ mutex_enter(&of->f_mutex); diff --git a/usr/src/uts/common/fs/smbsrv/smb2_lease.c b/usr/src/uts/common/fs/smbsrv/smb2_lease.c index 45f886e90a..81bf93d8fe 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_lease.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_lease.c @@ -60,10 +60,19 @@ smb2_lease_hold(smb_lease_t *ls) mutex_exit(&ls->ls_mutex); } +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--; @@ -81,16 +90,17 @@ smb2_lease_rele(smb_lease_t *ls) smb_llist_enter(bucket, RW_WRITER); mutex_enter(&ls->ls_mutex); - if (ls->ls_refcnt == 0) - smb_llist_remove(bucket, ls); - mutex_exit(&ls->ls_mutex); - if (ls->ls_refcnt == 0) { - mutex_destroy(&ls->ls_mutex); - kmem_cache_free(smb_lease_cache, ls); + smb_llist_remove(bucket, ls); + destroy = B_TRUE; } + mutex_exit(&ls->ls_mutex); smb_llist_exit(bucket); + + if (destroy) { + lease_destroy(ls); + } } /* @@ -150,6 +160,7 @@ smb2_lease_create(smb_request_t *sr, uint8_t *clnt) 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; @@ -197,8 +208,7 @@ smb2_lease_create(smb_request_t *sr, uint8_t *clnt) smb_llist_exit(bucket); if (newlease != NULL) { - mutex_destroy(&newlease->ls_mutex); - kmem_cache_free(smb_lease_cache, newlease); + lease_destroy(newlease); } if (lease != NULL) { @@ -213,9 +223,11 @@ smb2_lease_create(smb_request_t *sr, uint8_t *clnt) * Find the lease for a given: client_uuid, lease_key * Returns the lease with a new ref. */ -smb_lease_t * -smb2_lease_lookup(smb_server_t *sv, uint8_t *clnt_uuid, uint8_t *lease_key) +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; @@ -241,57 +253,36 @@ smb2_lease_lookup(smb_server_t *sv, uint8_t *clnt_uuid, uint8_t *lease_key) } /* - * Find an smb_ofile_t in the current tree that shares the - * specified lease and holds the oplock for the lease. - * If lease not found, NT_STATUS_OBJECT_NAME_NOT_FOUND. - * If no ofile (on the lease) holds the oplock, NT_STATUS_UNSUCCESSFUL. + * 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 -find_oplock_ofile(smb_request_t *sr, uint8_t *lease_key) +lease_find_oplock(smb_request_t *sr, smb_lease_t *lease) { - smb_tree_t *tree = sr->tid_tree; - smb_lease_t *lease; - smb_llist_t *of_list; + smb_node_t *node = lease->ls_node; smb_ofile_t *o; - uint32_t status = NT_STATUS_OBJECT_NAME_NOT_FOUND; - - SMB_TREE_VALID(tree); - of_list = &tree->t_ofile_list; - - smb_llist_enter(of_list, RW_READER); - for (o = smb_llist_head(of_list); o != NULL; - o = smb_llist_next(of_list, o)) { + uint32_t status = NT_STATUS_UNSUCCESSFUL; - ASSERT(o->f_magic == SMB_OFILE_MAGIC); - ASSERT(o->f_tree == tree); - - DTRACE_PROBE1(every_ofile, smb_ofile_t *, o); - if ((lease = o->f_lease) == NULL) - continue; // no lease - - if (bcmp(lease->ls_key, lease_key, UUID_LEN) != 0) - continue; // wrong lease + 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; /* - * Now we know the lease exists, so if we don't - * find the ofile that has the oplock, return: + * Found the ofile holding the oplock + * This hold released in smb_request_free */ - status = NT_STATUS_UNSUCCESSFUL; - - DTRACE_PROBE2(lease_ofile, smb_lease_t *, lease, - smb_ofile_t *, o); - if (lease->ls_oplock_ofile != o) - continue; // not oplock holder - - /* Found the ofile holding the oplock */ - if (smb_ofile_hold(o)) { + if (smb_ofile_hold_olbrk(o)) { sr->fid_ofile = o; status = NT_STATUS_SUCCESS; break; } } - smb_llist_exit(of_list); return (status); } @@ -300,15 +291,17 @@ find_oplock_ofile(smb_request_t *sr, uint8_t *lease_key) * 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; - uint8_t LeaseKey[UUID_LEN]; uint32_t LeaseState; - uint32_t LeaseBreakTo; + uint32_t BreakTo; uint32_t status; int rc = 0; @@ -324,56 +317,101 @@ smb2_lease_break_ack(smb_request_t *sr) &sr->smb_data, "6.#cl8.", /* reserved 6. */ UUID_LEN, /* # */ - LeaseKey, /* c */ - &LeaseState); /* l */ + olbrk->LeaseKey, /* c */ + &olbrk->NewLevel); /* l */ /* duration 8. */ if (rc != 0) return (SDRC_ERROR); + LeaseState = olbrk->NewLevel; - status = find_oplock_ofile(sr, LeaseKey); + /* + * 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) + + if (status != 0) { + /* Leased oplock not found. Must have closed. */ goto errout; + } - /* Success, so have sr->fid_ofile and lease */ + /* Success, so have sr->fid_ofile */ ofile = sr->fid_ofile; - lease = ofile->f_lease; - /* Either this ACK is unsolicited, or we timed out waiting. */ if (lease->ls_breaking == 0) { + /* + * This ACK is either unsolicited or too late, + * eg. we timed out the ACK and did it locally. + */ status = NT_STATUS_UNSUCCESSFUL; goto errout; } /* - * Process the lease break ack. - * * If the new LeaseState has any bits in excess of * the lease state we sent in the break, error... */ - LeaseBreakTo = (lease->ls_breaking >> BREAK_SHIFT) & + BreakTo = (lease->ls_breaking >> BREAK_SHIFT) & OPLOCK_LEVEL_CACHE_MASK; - if ((LeaseState & ~LeaseBreakTo) != 0) { + if ((LeaseState & ~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. + */ + ofile->f_oplock.og_breaking = 0; lease->ls_breaking = 0; + cv_broadcast(&lease->ls_ack_cv); LeaseState |= OPLOCK_LEVEL_GRANULAR; status = smb_oplock_ack_break(sr, ofile, &LeaseState); - if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { - (void) smb2sr_go_async(sr); - (void) smb_oplock_wait_break(sr, ofile->f_node, 0); - status = NT_STATUS_SUCCESS; - } - lease->ls_state = LeaseState & OPLOCK_LEVEL_CACHE_MASK; + ofile->f_oplock.og_state = LeaseState; + lease->ls_state = LeaseState & CACHE_RWH; + /* 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); @@ -389,7 +427,7 @@ errout: SSZ_LEASE_ACK, /* w */ /* reserved 6. */ UUID_LEN, /* # */ - LeaseKey, /* c */ + olbrk->LeaseKey, /* c */ LeaseState); /* l */ /* duration 8. */ @@ -404,25 +442,23 @@ errout: * * [MS-SMB2] 2.2.23.2 Lease Break Notification */ -void -smb2_lease_break_notification(smb_request_t *sr, uint32_t NewLevel, - boolean_t AckReq) +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; - uint32_t oldcache; - uint32_t newcache; - uint16_t Epoch; - uint16_t Flags; + uint16_t Flags = 0; /* - * Convert internal level to SMB2 + * Convert internal lease info to SMB2 */ - oldcache = ls->ls_state & OPLOCK_LEVEL_CACHE_MASK; - newcache = NewLevel & OPLOCK_LEVEL_CACHE_MASK; + if (AckReq) + Flags = SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED; if (ls->ls_version < 2) Epoch = 0; - else - Epoch = ls->ls_epoch; + OldLevel &= OPLOCK_LEVEL_CACHE_MASK; + NewLevel &= OPLOCK_LEVEL_CACHE_MASK; /* * SMB2 Header @@ -441,7 +477,6 @@ smb2_lease_break_notification(smb_request_t *sr, uint32_t NewLevel, * [MS-SMB2] says the current lease state preceeds the * new lease state, but that looks like an error... */ - Flags = AckReq ? SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED : 0; (void) smb_mbc_encodef( &sr->reply, "wwl#cll4.4.4.", SSZ_LEASE_BRK, /* w */ @@ -449,11 +484,265 @@ smb2_lease_break_notification(smb_request_t *sr, uint32_t NewLevel, Flags, /* l */ SMB_LEASE_KEY_SZ, /* # */ ls->ls_key, /* c */ - oldcache, /* cur.st l */ - newcache); /* new.st l */ + 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. + */ + status = smb_oplock_wait_ack(sr); + if (status == 0) + return; + + cmn_err(CE_NOTE, "clnt %s oplock break timeout", + sr->session->ip_addr_str); + DTRACE_PROBE1(ack_timeout, smb_request_t *, sr); + + /* + * 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 = 0; + lease->ls_breaking = 0; + cv_broadcast(&lease->ls_ack_cv); + + status = smb_oplock_ack_break(sr, ofile, &NewLevel); + + ofile->f_oplock.og_state = NewLevel; + lease->ls_state = NewLevel & CACHE_RWH; + /* 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, @@ -467,6 +756,7 @@ 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; @@ -509,6 +799,13 @@ smb2_lease_acquire(smb_request_t *sr) 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); + /* * Disallow downgrade * @@ -547,7 +844,7 @@ smb2_lease_acquire(smb_request_t *sr) if (have == want) goto done; - status = smb_oplock_request(sr, ofile, + status = smb_oplock_request_LH(sr, ofile, &op->op_oplock_state); if (status == NT_STATUS_SUCCESS || status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { @@ -587,7 +884,7 @@ smb2_lease_acquire(smb_request_t *sr) if (have == want) goto done; - status = smb_oplock_request(sr, ofile, + status = smb_oplock_request_LH(sr, ofile, &op->op_oplock_state); if (status == NT_STATUS_SUCCESS || status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { @@ -599,6 +896,7 @@ smb2_lease_acquire(smb_request_t *sr) * 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); @@ -612,7 +910,7 @@ smb2_lease_acquire(smb_request_t *sr) if (have == want) goto done; - status = smb_oplock_request(sr, ofile, + status = smb_oplock_request_LH(sr, ofile, &op->op_oplock_state); if (status == NT_STATUS_SUCCESS || status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { @@ -634,26 +932,30 @@ smb2_lease_acquire(smb_request_t *sr) 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) { - /* - * 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); - status = NT_STATUS_SUCCESS; - } - ASSERT(status == NT_STATUS_SUCCESS); + ofile->f_oplock.og_state = op->op_oplock_state; + ofile->f_oplock.og_breaking = 0; - /* - * Keep track of what we got (in lease->ls_state) - * so we'll know what we last told the client. - */ - mutex_enter(&lease->ls_mutex); - lease->ls_state = op->op_oplock_state & CACHE_RWH; + lease->ls_oplock_ofile = ofile; + lease->ls_state = op->op_oplock_state; + lease->ls_breaking = B_FALSE; lease->ls_epoch++; - mutex_exit(&lease->ls_mutex); + + if (ofile->dh_persist) { + smb2_dh_update_oplock(sr, ofile); + } } /* @@ -665,6 +967,21 @@ done: 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); + } } /* @@ -675,6 +992,12 @@ done: * 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) @@ -695,7 +1018,7 @@ smb2_lease_ofile_close(smb_ofile_t *ofile) /* * Find another ofile to which we can move the oplock. - * The ofile must be open and allow a new ref. + * First try for one that's open. Usually find one. */ FOREACH_NODE_OFILE(node, o) { if (o == ofile) @@ -704,17 +1027,58 @@ smb2_lease_ofile_close(smb_ofile_t *ofile) continue; if (o->f_oplock_closing) continue; - /* If we can get a hold, use this ofile. */ - if (smb_ofile_hold(o)) - break; + + 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); } - if (o == NULL) { - /* Normal for last close on a lease. */ - lease->ls_oplock_ofile = NULL; - return; + + /* + * 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); } - smb_oplock_move(node, ofile, o); - lease->ls_oplock_ofile = o; - smb_ofile_release(o); + /* + * Normal for last close on a lease. + * Wakeup ACK waiters too. + */ + lease->ls_state = 0; + lease->ls_breaking = 0; + cv_broadcast(&lease->ls_ack_cv); + + lease->ls_oplock_ofile = NULL; } diff --git a/usr/src/uts/common/fs/smbsrv/smb2_oplock.c b/usr/src/uts/common/fs/smbsrv/smb2_oplock.c index f8317b2e81..613b56dac9 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_oplock.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_oplock.c @@ -11,7 +11,7 @@ /* * Copyright 2020 Tintri by DDN, Inc. All rights reserved. - * Copyright 2019 RackTop Systems. + * Copyright 2022 RackTop Systems, Inc. */ /* @@ -19,6 +19,7 @@ */ #include +#include #define BATCH_OR_EXCL (OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE) @@ -30,11 +31,15 @@ * SMB2 Oplock Break Acknowledgement * [MS-SMB2] 3.3.5.22.1 Processing an Oplock Acknowledgment * Called via smb2_disp_table[] + * This is an "Ack" from the client. */ smb_sdrc_t smb2_oplock_break_ack(smb_request_t *sr) { + smb_arg_olbrk_t *olbrk = &sr->arg.olbrk; + smb_node_t *node; smb_ofile_t *ofile; + smb_oplock_grant_t *og; smb2fid_t smb2fid; uint32_t status; uint32_t NewLevel; @@ -72,16 +77,8 @@ smb2_oplock_break_ack(smb_request_t *sr) if (rc != 0) return (SDRC_ERROR); - /* Find the ofile */ - status = smb2sr_lookup_fid(sr, &smb2fid); - /* Success or NT_STATUS_FILE_CLOSED */ - - DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr); - if (status != 0) - goto errout; - /* - * Process an (old-style) oplock break ack. + * Convert SMB oplock level to internal form. */ switch (smbOplockLevel) { case SMB2_OPLOCK_LEVEL_NONE: /* 0x00 */ @@ -96,50 +93,91 @@ smb2_oplock_break_ack(smb_request_t *sr) case SMB2_OPLOCK_LEVEL_BATCH: /* 0x09 */ NewLevel = OPLOCK_LEVEL_BATCH; break; + + /* Note: _LEVEL_LEASE is not valid here. */ case SMB2_OPLOCK_LEVEL_LEASE: /* 0xFF */ - NewLevel = OPLOCK_LEVEL_NONE; - break; default: + /* + * Impossible NewLevel here, will cause + * NT_STATUS_INVALID_PARAMETER below. + */ + NewLevel = OPLOCK_LEVEL_GRANULAR; + break; + } + + /* for dtrace */ + olbrk->NewLevel = NewLevel; + + /* Find the ofile */ + status = smb2sr_lookup_fid(sr, &smb2fid); + /* Success or NT_STATUS_FILE_CLOSED */ + + DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr); + + if (status != 0) { + /* lookup fid failed */ + goto errout; + } + + if (NewLevel == OPLOCK_LEVEL_GRANULAR) { + /* Switch above got invalid smbOplockLevel */ status = NT_STATUS_INVALID_PARAMETER; goto errout; } + /* Success, so have sr->fid_ofile */ ofile = sr->fid_ofile; - if (ofile->f_oplock.og_breaking == 0) { + og = &ofile->f_oplock; + node = ofile->f_node; + + smb_llist_enter(&node->n_ofile_list, RW_READER); + mutex_enter(&node->n_oplock.ol_mutex); + + if (og->og_breaking == 0) { /* * This is an unsolicited Ack. (There is no * outstanding oplock break in progress now.) * There are WPTS tests that care which error * is returned. See [MS-SMB2] 3.3.5.22.1 */ - if (smbOplockLevel == SMB2_OPLOCK_LEVEL_LEASE) { - status = NT_STATUS_INVALID_PARAMETER; - goto errout; - } - if (NewLevel >= (ofile->f_oplock.og_state & - OPLOCK_LEVEL_TYPE_MASK)) { + if (NewLevel >= (og->og_state & OPLOCK_LEVEL_TYPE_MASK)) { status = NT_STATUS_INVALID_OPLOCK_PROTOCOL; - goto errout; + goto unlock_out; } status = NT_STATUS_INVALID_DEVICE_STATE; - goto errout; + goto unlock_out; } + + /* + * Process the oplock break ack. + * + * Clear breaking flags before we ack, + * because ack might set those. + */ ofile->f_oplock.og_breaking = 0; + cv_broadcast(&ofile->f_oplock.og_ack_cv); status = smb_oplock_ack_break(sr, ofile, &NewLevel); - if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { - status = smb2sr_go_async(sr); - if (status != 0) - goto errout; - (void) smb_oplock_wait_break(sr, ofile->f_node, 0); - status = 0; - } - if (status != 0) { - NewLevel = OPLOCK_LEVEL_NONE; - goto errout; - } ofile->f_oplock.og_state = NewLevel; + 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); + +errout: + sr->smb2_status = status; + DTRACE_SMB2_DONE(op__OplockBreak, smb_request_t *, sr); + if (status) { + smb2sr_put_error(sr, status); + return (SDRC_SUCCESS); + } + + /* + * Convert internal oplock state back to SMB form. + */ switch (NewLevel & OPLOCK_LEVEL_TYPE_MASK) { case OPLOCK_LEVEL_NONE: smbOplockLevel = SMB2_OPLOCK_LEVEL_NONE; @@ -159,14 +197,6 @@ smb2_oplock_break_ack(smb_request_t *sr) break; } -errout: - sr->smb2_status = status; - DTRACE_SMB2_DONE(op__OplockBreak, smb_request_t *, sr); - if (status) { - smb2sr_put_error(sr, status); - return (SDRC_SUCCESS); - } - /* * Encode an SMB2 Oplock Break Ack response * [MS-SMB2] 2.2.25.1 @@ -187,7 +217,7 @@ errout: * the SMB2 header and everything, in sr->reply. * The caller will send it and free the request. */ -void +static void smb2_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel) { smb_ofile_t *ofile = sr->fid_ofile; @@ -236,6 +266,137 @@ smb2_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel) smb2fid.temporal); /* q */ } +/* + * Send an oplock break over the wire, or if we can't, + * then process the oplock break locally. + * + * [MS-SMB2] 3.3.4.6 Object Store Indicates an Oplock Break + * + * Note: When "AckRequired" is set, and we're for any reason + * unable to communicate with the client so that they do an + * "oplock break ACK", then we absolutely MUST do a local ACK + * for this break indication (or close the ofile). + * + * The file-system level oplock code (smb_cmn_oplock.c) + * requires these ACK calls to clear "breaking" flags. + * + * This is called either from smb_oplock_async_break via a + * taskq job scheduled in smb_oplock_ind_break, or from the + * smb2sr_append_postwork() mechanism when we're doing a + * "break in ack", via smb_oplock_ind_break_in_ack. + * + * This runs much like other smb_request_t handlers, in the + * context of a worker task that calls with no locks held. + * + * Note that we have sr->fid_ofile here but all the other + * normal sr members may be NULL: uid_user, tid_tree. + * Also sr->session may or may not be the same session as + * the ofile came from (ofile->f_session) depending on + * whether this is a "live" open or an orphaned DH, + * where ofile->f_session will be NULL. + */ +void +smb2_oplock_send_break(smb_request_t *sr) +{ + smb_ofile_t *ofile = sr->fid_ofile; + smb_node_t *node = ofile->f_node; + uint32_t NewLevel = sr->arg.olbrk.NewLevel; + boolean_t AckReq = sr->arg.olbrk.AckRequired; + uint32_t status; + int rc; + + /* + * Build the break message in sr->reply. + * It's free'd in smb_request_free(). + * Always SMB2 oplock here (no lease) + */ + sr->reply.max_bytes = MLEN; + smb2_oplock_break_notification(sr, NewLevel); + + /* + * Try to send the break message to the client. + * If connected, this IF body will be true. + */ + if (sr->session == ofile->f_session) + rc = smb_session_send(sr->session, 0, &sr->reply); + else + rc = ENOTCONN; + + if (rc != 0) { + /* + * We were unable to send the oplock break request, + * presumably because the connection is gone. + * + * [MS-SMB2] 3.3.4.6 Object Store Indicates an Oplock Break + * If no connection is available, Open.IsResilient is FALSE, + * Open.IsDurable is FALSE, and Open.IsPersistent is FALSE, + * the server SHOULD close the Open as specified in... + */ + if (ofile->dh_persist == B_FALSE && + ofile->dh_vers != SMB2_RESILIENT && + (ofile->dh_vers == SMB2_NOT_DURABLE || + (NewLevel & OPLOCK_LEVEL_BATCH) == 0)) { + smb_ofile_close(ofile, 0); + return; + } + /* Keep this (durable) open. */ + 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. + */ + status = smb_oplock_wait_ack(sr); + if (status == 0) + return; + + cmn_err(CE_NOTE, "clnt %s oplock break timeout", + sr->session->ip_addr_str); + DTRACE_PROBE1(ack_timeout, smb_request_t *, sr); + + /* + * 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. + */ + NewLevel = OPLOCK_LEVEL_NONE; + } + + /* + * Do the ack locally. + */ + smb_llist_enter(&node->n_ofile_list, RW_READER); + mutex_enter(&node->n_oplock.ol_mutex); + + ofile->f_oplock.og_breaking = 0; + cv_broadcast(&ofile->f_oplock.og_ack_cv); + + status = smb_oplock_ack_break(sr, ofile, &NewLevel); + + ofile->f_oplock.og_state = NewLevel; + if (ofile->dh_persist) + smb2_dh_update_oplock(sr, ofile); + + 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 an oplock. * Convert SMB2 oplock request info in to internal form, @@ -289,6 +450,8 @@ smb2_oplock_acquire(smb_request_t *sr) /* * Tree options may force shared oplocks, * in which case we reduce the request. + * Can't get here with LEVEL_NONE, so + * this can only decrease the level. */ if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) { op->op_oplock_state = OPLOCK_LEVEL_TWO; @@ -315,41 +478,38 @@ smb2_oplock_acquire(smb_request_t *sr) } /* - * Either of the above may have returned the - * status code that says we should wait. - */ - if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { - (void) smb2sr_go_async(sr); - (void) smb_oplock_wait_break(sr, ofile->f_node, 0); - status = 0; - } - - /* - * Keep track of what we got (in ofile->f_oplock.og_state) + * Keep track of what we got (ofile->f_oplock.og_state etc) * so we'll know what we had when sending a break later. * The og_dialect here is the oplock dialect, not the * SMB dialect. No lease here, so SMB 2.0. */ - ofile->f_oplock.og_dialect = SMB_VERS_2_002; switch (status) { case NT_STATUS_SUCCESS: + case NT_STATUS_OPLOCK_BREAK_IN_PROGRESS: + ofile->f_oplock.og_dialect = SMB_VERS_2_002; ofile->f_oplock.og_state = op->op_oplock_state; + ofile->f_oplock.og_breaking = 0; + if (ofile->dh_persist) { + smb2_dh_update_oplock(sr, ofile); + } break; + case NT_STATUS_OPLOCK_NOT_GRANTED: - ofile->f_oplock.og_state = 0; op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE; return; + default: /* Caller did not check args sufficiently? */ cmn_err(CE_NOTE, "clnt %s oplock req. err 0x%x", sr->session->ip_addr_str, status); - ofile->f_oplock.og_state = 0; + DTRACE_PROBE2(other__error, smb_request_t *, sr, + uint32_t, status); op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE; return; } /* - * Have STATUS_SUCCESS + * Only success cases get here * Convert internal oplock state to SMB2 */ if (op->op_oplock_state & OPLOCK_LEVEL_GRANULAR) { @@ -364,6 +524,15 @@ smb2_oplock_acquire(smb_request_t *sr) } else { op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE; } + + /* + * An smb_oplock_reqest call may have returned the + * status code that says we should wait. + */ + if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { + (void) smb2sr_go_async(sr); + (void) smb_oplock_wait_break(sr, ofile->f_node, 0); + } } /* diff --git a/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c index bee55e2e60..441dc59a6e 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c +++ b/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c @@ -313,12 +313,32 @@ RecomputeOplockState(smb_node_t *node) uint32_t smb_oplock_request(smb_request_t *sr, smb_ofile_t *ofile, uint32_t *statep) +{ + smb_node_t *node = ofile->f_node; + uint32_t status; + + smb_llist_enter(&node->n_ofile_list, RW_READER); + mutex_enter(&node->n_oplock.ol_mutex); + + status = smb_oplock_request_LH(sr, ofile, statep); + + mutex_exit(&node->n_oplock.ol_mutex); + smb_llist_exit(&node->n_ofile_list); + + return (status); +} + +uint32_t +smb_oplock_request_LH(smb_request_t *sr, smb_ofile_t *ofile, uint32_t *statep) { smb_node_t *node = ofile->f_node; uint32_t type = *statep & OPLOCK_LEVEL_TYPE_MASK; uint32_t level = *statep & OPLOCK_LEVEL_CACHE_MASK; uint32_t status; + ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock)); + ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex)); + *statep = LEVEL_NONE; /* @@ -343,9 +363,6 @@ smb_oplock_request(smb_request_t *sr, smb_ofile_t *ofile, uint32_t *statep) return (NT_STATUS_OPLOCK_NOT_GRANTED); } - smb_llist_enter(&node->n_ofile_list, RW_READER); - mutex_enter(&node->n_oplock.ol_mutex); - /* * If Type is LEVEL_ONE or LEVEL_BATCH: * The operation MUST be failed with STATUS_OPLOCK_NOT_GRANTED @@ -495,25 +512,15 @@ smb_oplock_request(smb_request_t *sr, smb_ofile_t *ofile, uint32_t *statep) break; } - /* Give caller back the "Granular" bit. */ - if (status == NT_STATUS_SUCCESS) { + /* + * Give caller back the "Granular" bit, eg. when + * NT_STATUS_SUCCESS or NT_STATUS_OPLOCK_BREAK_IN_PROGRESS + */ + if (NT_SC_SEVERITY(status) == NT_STATUS_SEVERITY_SUCCESS) { *statep |= LEVEL_GRANULAR; - - /* - * The oplock lease may have moved to this ofile. Update. - * Minor violation of layering here (leases vs oplocks) - * but we want this update coverd by the oplock mutex. - */ -#ifndef TESTJIG - if (ofile->f_lease != NULL) - ofile->f_lease->ls_oplock_ofile = ofile; -#endif } out: - mutex_exit(&node->n_oplock.ol_mutex); - smb_llist_exit(&node->n_ofile_list); - return (status); } @@ -1481,8 +1488,8 @@ smb_oplock_ack_break( boolean_t FoundMatchingRHOplock = B_FALSE; int other_keys; - smb_llist_enter(&node->n_ofile_list, RW_READER); - mutex_enter(&node->n_oplock.ol_mutex); + ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock)); + ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex)); /* * If Open.Stream.Oplock is empty, the operation MUST be @@ -2069,6 +2076,7 @@ smb_oplock_ack_break( * the spec for this bit of code. Therefore, this will * return SUCCESS instead of OPLOCK_BREAK_IN_PROGRESS. */ + ASSERT(node->n_oplock.excl_open == ofile); node->n_oplock.ol_state = level | EXCLUSIVE; status = NT_STATUS_SUCCESS; break; /* case (READ_CACHING|WRITE_CACHING|...) */ @@ -2088,11 +2096,6 @@ out: type == LEVEL_GRANULAR && *rop != LEVEL_NONE) { *rop |= LEVEL_GRANULAR; - /* As above, leased oplock may have moved. */ -#ifndef TESTJIG - if (ofile->f_lease != NULL) - ofile->f_lease->ls_oplock_ofile = ofile; -#endif } /* @@ -2111,9 +2114,14 @@ out: * The spec. describes waiting for a break here, * but we let the caller do that (when needed) if * status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS + * + * After some research, smb_oplock_ack_break() + * never returns that status. Paranoid check. */ - mutex_exit(&node->n_oplock.ol_mutex); - smb_llist_exit(&node->n_ofile_list); + if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { + ASSERT(!"Unexpected OPLOCK_BREAK_IN_PROGRESS"); + status = NT_STATUS_SUCCESS; + } return (status); } @@ -2751,6 +2759,8 @@ smb_oplock_break_cmn(smb_node_t *node, * completes some earlier call to * 2.1.5.17.1.) */ + o = nol->excl_open; + ASSERT(o != NULL); smb_oplock_ind_break(o, LEVEL_TWO, B_TRUE, NT_STATUS_SUCCESS); @@ -2829,6 +2839,8 @@ smb_oplock_break_cmn(smb_node_t *node, * completes some earlier call to * 2.1.5.17.1.) */ + o = nol->excl_open; + ASSERT(o != NULL); smb_oplock_ind_break(o, LEVEL_NONE, B_TRUE, NT_STATUS_SUCCESS); @@ -2904,7 +2916,7 @@ smb_oplock_break_cmn(smb_node_t *node, * equals Open.TargetOplockKey, * go to the LeaveBreakToNone label. */ - if (o != NULL && + if ((o = nol->excl_open) != NULL && CompareOplockKeys(ofile, o, CmpFlags)) goto LeaveBreakToNone; diff --git a/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c b/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c index b58d3f9f0f..a09d5f80de 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c +++ b/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c @@ -22,6 +22,7 @@ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2020 Tintri by DDN, Inc. All rights reserved. + * Copyright 2022 RackTop Systems, Inc. */ /* @@ -250,7 +251,6 @@ smb_com_locking_andx(smb_request_t *sr) DWORD result; int rc; uint32_t ltype; - uint32_t status; smb_ofile_t *ofile; uint16_t tmp_pid; /* locking uses 16-bit pids */ uint32_t lrv_tot; @@ -288,18 +288,16 @@ smb_com_locking_andx(smb_request_t *sr) ltype = SMB_LOCK_TYPE_READWRITE; if (lock_type & LOCKING_ANDX_OPLOCK_RELEASE) { - uint32_t NewLevel; - if (oplock_level == 0) - NewLevel = OPLOCK_LEVEL_NONE; - else - NewLevel = OPLOCK_LEVEL_TWO; - status = smb_oplock_ack_break(sr, ofile, &NewLevel); - if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { - (void) smb_oplock_wait_break(sr, ofile->f_node, 0); - status = 0; - } + smb1_oplock_ack_break(sr, oplock_level); if (unlock_num == 0 && lock_num == 0) return (SDRC_NO_REPLY); + /* + * Don't allow combining other lock/unlock actions + * with an oplock ACK (normally don't get here). + */ + smbsr_error(sr, NT_STATUS_INVALID_PARAMETER, + ERRDOS, ERROR_INVALID_PARAMETER); + return (SDRC_ERROR); } /* @@ -413,57 +411,3 @@ out: return (SDRC_ERROR); return (SDRC_SUCCESS); } - -/* - * Compose an SMB1 Oplock Break Notification packet, including - * the SMB1 header and everything, in sr->reply. - * The caller will send it and free the request. - */ -void -smb1_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel) -{ - smb_ofile_t *ofile = sr->fid_ofile; - uint16_t fid; - uint8_t lock_type; - uint8_t oplock_level; - - /* - * Convert internal level to SMB1 - */ - switch (NewLevel) { - default: - ASSERT(0); - /* FALLTHROUGH */ - case OPLOCK_LEVEL_NONE: - oplock_level = 0; - break; - - case OPLOCK_LEVEL_TWO: - oplock_level = 1; - break; - } - - sr->smb_com = SMB_COM_LOCKING_ANDX; - sr->smb_tid = ofile->f_tree->t_tid; - sr->smb_pid = 0xFFFF; - sr->smb_uid = 0; - sr->smb_mid = 0xFFFF; - fid = ofile->f_fid; - lock_type = LOCKING_ANDX_OPLOCK_RELEASE; - - (void) smb_mbc_encodef( - &sr->reply, "Mb19.wwwwbb3.wbb10.", - /* "\xffSMB" M */ - sr->smb_com, /* b */ - /* status, flags, signature 19. */ - sr->smb_tid, /* w */ - sr->smb_pid, /* w */ - sr->smb_uid, /* w */ - sr->smb_mid, /* w */ - 8, /* word count b */ - 0xFF, /* AndX cmd b */ - /* AndX reserved, offset 3. */ - fid, - lock_type, - oplock_level); -} diff --git a/usr/src/uts/common/fs/smbsrv/smb_ofile.c b/usr/src/uts/common/fs/smbsrv/smb_ofile.c index 9279b71981..379006a5e8 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_ofile.c +++ b/usr/src/uts/common/fs/smbsrv/smb_ofile.c @@ -227,7 +227,7 @@ * Transition T7 * * This transition occurs in smb_session_durable_timers() and - * smb_oplock_send_brk(). The ofile will soon be closed. + * smb_oplock_send_break(). The ofile will soon be closed. * In the former case, f_timeout_offset nanoseconds have passed since * the ofile was orphaned. In the latter, an oplock break occured * on the ofile while it was orphaned. @@ -444,6 +444,10 @@ smb_ofile_open( * SMB_OFILE_STATE_OPEN protocol close, smb_ofile_drop * SMB_OFILE_STATE_EXPIRED called via smb2_dh_expire * SMB_OFILE_STATE_ORPHANED smb2_dh_shutdown + * + * Not that this enters the of->node->n_ofile_list rwlock as reader, + * (via smb_oplock_close) so this must not be called while holding + * that rwlock. */ void smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec) @@ -453,21 +457,7 @@ smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec) SMB_OFILE_VALID(of); if (of->f_ftype == SMB_FTYPE_DISK) { - smb_node_t *node = of->f_node; - - smb_llist_enter(&node->n_ofile_list, RW_READER); - mutex_enter(&node->n_oplock.ol_mutex); - - if (of->f_oplock_closing == B_FALSE) { - of->f_oplock_closing = B_TRUE; - - if (of->f_lease != NULL) - smb2_lease_ofile_close(of); - smb_oplock_break_CLOSE(node, of); - } - - mutex_exit(&node->n_oplock.ol_mutex); - smb_llist_exit(&node->n_ofile_list); + smb_oplock_close(of); } mutex_enter(&of->f_mutex); @@ -743,7 +733,7 @@ smb_ofile_enum(smb_ofile_t *of, smb_svcenum_t *svcenum) /* * Take a reference on an open file, in any of the states: - * RECONNECT, SAVE_DH, OPEN, ORPHANED. + * RECONNECT, SAVE_DH, OPEN, ORPHANED, EXPIRED. * Return TRUE if ref taken. Used for oplock breaks. * * Note: When the oplock break code calls this, it holds the @@ -774,13 +764,20 @@ again: goto again; case SMB_OFILE_STATE_OPEN: - case SMB_OFILE_STATE_ORPHANED: case SMB_OFILE_STATE_SAVE_DH: + case SMB_OFILE_STATE_ORPHANED: + case SMB_OFILE_STATE_EXPIRED: of->f_refcnt++; ret = B_TRUE; break; + /* + * This is called only when an ofile has an oplock, + * so if we come across states that should not have + * an oplock, let's debug how that happened. + */ default: + ASSERT(0); break; } mutex_exit(&of->f_mutex); diff --git a/usr/src/uts/common/fs/smbsrv/smb_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_oplock.c index 5215da8693..a3fd103fa1 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_oplock.c +++ b/usr/src/uts/common/fs/smbsrv/smb_oplock.c @@ -21,6 +21,7 @@ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2020 Tintri by DDN, Inc. All rights reserved. + * Copyright 2022 RackTop Systems, Inc. */ /* @@ -31,6 +32,199 @@ #define BATCH_OR_EXCL (OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE) +/* + * This is called by the SMB1 "Locking_andX" handler, + * for SMB1 oplock break acknowledgement. + * This is an "Ack" from the client. + */ +void +smb1_oplock_ack_break(smb_request_t *sr, uchar_t oplock_level) +{ + smb_ofile_t *ofile; + smb_node_t *node; + uint32_t NewLevel; + + ofile = sr->fid_ofile; + node = ofile->f_node; + + if (oplock_level == 0) + NewLevel = OPLOCK_LEVEL_NONE; + else + NewLevel = OPLOCK_LEVEL_TWO; + + smb_llist_enter(&node->n_ofile_list, RW_READER); + mutex_enter(&node->n_oplock.ol_mutex); + + ofile->f_oplock.og_breaking = 0; + cv_broadcast(&ofile->f_oplock.og_ack_cv); + + (void) smb_oplock_ack_break(sr, ofile, &NewLevel); + + ofile->f_oplock.og_state = NewLevel; + + mutex_exit(&node->n_oplock.ol_mutex); + smb_llist_exit(&node->n_ofile_list); +} + +/* + * Compose an SMB1 Oplock Break Notification packet, including + * the SMB1 header and everything, in sr->reply. + * The caller will send it and free the request. + */ +static void +smb1_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel) +{ + smb_ofile_t *ofile = sr->fid_ofile; + uint16_t fid; + uint8_t lock_type; + uint8_t oplock_level; + + /* + * Convert internal level to SMB1 + */ + switch (NewLevel) { + default: + ASSERT(0); + /* FALLTHROUGH */ + case OPLOCK_LEVEL_NONE: + oplock_level = 0; + break; + + case OPLOCK_LEVEL_TWO: + oplock_level = 1; + break; + } + + sr->smb_com = SMB_COM_LOCKING_ANDX; + sr->smb_tid = ofile->f_tree->t_tid; + sr->smb_pid = 0xFFFF; + sr->smb_uid = 0; + sr->smb_mid = 0xFFFF; + fid = ofile->f_fid; + lock_type = LOCKING_ANDX_OPLOCK_RELEASE; + + (void) smb_mbc_encodef( + &sr->reply, "Mb19.wwwwbb3.wbb10.", + /* "\xffSMB" M */ + sr->smb_com, /* b */ + /* status, flags, signature 19. */ + sr->smb_tid, /* w */ + sr->smb_pid, /* w */ + sr->smb_uid, /* w */ + sr->smb_mid, /* w */ + 8, /* word count b */ + 0xFF, /* AndX cmd b */ + /* AndX reserved, offset 3. */ + fid, + lock_type, + oplock_level); +} + +/* + * Send an oplock break over the wire, or if we can't, + * then process the oplock break locally. + * + * [MS-CIFS] 3.3.4.2 Object Store Indicates an OpLock Break + * + * This is mostly similar to smb2_oplock_send_break() + * See top comment there about the design. + * Called from smb_oplock_async_break. + * + * This handles only SMB1, which has no durable handles, + * and never has GRANULAR oplocks. + */ +void +smb1_oplock_send_break(smb_request_t *sr) +{ + smb_ofile_t *ofile = sr->fid_ofile; + smb_node_t *node = ofile->f_node; + uint32_t NewLevel = sr->arg.olbrk.NewLevel; + boolean_t AckReq = sr->arg.olbrk.AckRequired; + uint32_t status; + int rc; + + /* + * SMB1 clients should only get Level II oplocks if they + * set the capability indicating they know about them. + */ + if (NewLevel == OPLOCK_LEVEL_TWO && + ofile->f_oplock.og_dialect < NT_LM_0_12) + NewLevel = OPLOCK_LEVEL_NONE; + + /* + * Build the break message in sr->reply. + * It's free'd in smb_request_free(). + * Always SMB1 here. + */ + sr->reply.max_bytes = MLEN; + smb1_oplock_break_notification(sr, NewLevel); + + /* + * Try to send the break message to the client. + * If connected, this IF body will be true. + */ + if (sr->session == ofile->f_session) + rc = smb_session_send(sr->session, 0, &sr->reply); + else + rc = ENOTCONN; + + if (rc != 0) { + /* + * We were unable to send the oplock break request, + * presumably because the connection is gone. + * Just close the handle. + */ + smb_ofile_close(ofile, 0); + return; + } + + /* + * 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. + */ + status = smb_oplock_wait_ack(sr); + if (status == 0) + return; + + cmn_err(CE_NOTE, "clnt %s oplock break timeout", + sr->session->ip_addr_str); + DTRACE_PROBE1(ack_timeout, smb_request_t *, sr); + + /* + * Did not get an ACK, so do the ACK locally. + * Note: always break to none here, regardless + * of what the passed in cache level was. + */ + NewLevel = OPLOCK_LEVEL_NONE; + + smb_llist_enter(&node->n_ofile_list, RW_READER); + mutex_enter(&node->n_oplock.ol_mutex); + + ofile->f_oplock.og_breaking = 0; + cv_broadcast(&ofile->f_oplock.og_ack_cv); + + status = smb_oplock_ack_break(sr, ofile, &NewLevel); + + ofile->f_oplock.og_state = NewLevel; + + 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 an oplock. * Convert SMB1 oplock request info in to internal form, @@ -96,7 +290,7 @@ smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok) } /* - * If exclusive failed (or tree forced shared oplocks) + * If exclusive failed (or the tree forced shared oplocks) * and if the caller supports Level II, try shared. */ if (status == NT_STATUS_OPLOCK_NOT_GRANTED && level2ok) { @@ -106,16 +300,7 @@ smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok) } /* - * Either of the above may have returned the - * status code that says we should wait. - */ - if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { - (void) smb_oplock_wait_break(sr, ofile->f_node, 0); - status = 0; - } - - /* - * Keep track of what we got (in ofile->f_oplock.og_state) + * Keep track of what we got (ofile->f_oplock.og_state etc) * so we'll know what we had when sending a break later. * The og_dialect here is the oplock dialect, which may be * different than SMB dialect. Pre-NT clients did not @@ -123,27 +308,27 @@ smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok) * client that didn't set the CAP_LEVEL_II_OPLOCKS in * its capabilities, let og_dialect = LANMAN2_1. */ - ofile->f_oplock.og_dialect = (level2ok) ? - NT_LM_0_12 : LANMAN2_1; switch (status) { case NT_STATUS_SUCCESS: + case NT_STATUS_OPLOCK_BREAK_IN_PROGRESS: + ofile->f_oplock.og_dialect = (level2ok) ? + NT_LM_0_12 : LANMAN2_1; ofile->f_oplock.og_state = op->op_oplock_state; + ofile->f_oplock.og_breaking = 0; break; case NT_STATUS_OPLOCK_NOT_GRANTED: - ofile->f_oplock.og_state = 0; op->op_oplock_level = SMB_OPLOCK_NONE; return; default: /* Caller did not check args sufficiently? */ cmn_err(CE_NOTE, "clnt %s oplock req. err 0x%x", sr->session->ip_addr_str, status); - ofile->f_oplock.og_state = 0; op->op_oplock_level = SMB_OPLOCK_NONE; return; } /* - * Have STATUS_SUCCESS + * Only succes cases get here. * Convert internal oplock state to SMB1 */ if (op->op_oplock_state & OPLOCK_LEVEL_BATCH) { @@ -155,4 +340,12 @@ smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok) } else { op->op_oplock_level = SMB_OPLOCK_NONE; } + + /* + * An smb_oplock_reqest call may have returned the + * status code that says we should wait. + */ + if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { + (void) smb_oplock_wait_break(sr, ofile->f_node, 0); + } } diff --git a/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c index 76560f20a5..df917c3d0c 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c +++ b/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c @@ -73,7 +73,9 @@ int smb_oplock_timeout_ack = 30000; /* mSec. */ int smb_oplock_timeout_def = 45000; /* mSec. */ static void smb_oplock_async_break(void *); -static void smb_oplock_hdl_clear(smb_ofile_t *); +static void smb_oplock_hdl_update(smb_request_t *sr); +static void smb_oplock_hdl_moved(smb_ofile_t *); +static void smb_oplock_hdl_closed(smb_ofile_t *); static void smb_oplock_wait_break_cancel(smb_request_t *sr); @@ -181,8 +183,7 @@ smb_oplock_ind_break_in_ack(smb_request_t *ack_sr, smb_ofile_t *ofile, * We're going to schedule a request that will have a * reference to this ofile. Get the hold first. */ - if (ofile->f_oplock_closing || - !smb_ofile_hold_olbrk(ofile)) { + if (!smb_ofile_hold_olbrk(ofile)) { /* It's closing (or whatever). Nothing to do. */ return; } @@ -229,6 +230,8 @@ smb_oplock_ind_break_in_ack(smb_request_t *ack_sr, smb_ofile_t *ofile, sr->arg.olbrk.NewLevel = NewLevel; sr->arg.olbrk.AckRequired = AckRequired; + smb_oplock_hdl_update(sr); + if (use_postwork) { /* * Using smb2_cmd_code to indicate what to call. @@ -253,6 +256,8 @@ smb_oplock_ind_break_in_ack(smb_request_t *ack_sr, smb_ofile_t *ofile, * Schedule a request & taskq job to do oplock break work * as requested by the FS-level code (smb_cmn_oplock.c). * + * See also: smb_oplock_ind_break_in_ack + * * Note called with the node ofile list rwlock held and * the oplock mutex entered. */ @@ -281,11 +286,11 @@ smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel, break; case STATUS_NEW_HANDLE: - /* nothing to do (keep for observability) */ + smb_oplock_hdl_moved(ofile); return; case NT_STATUS_OPLOCK_HANDLE_CLOSED: - smb_oplock_hdl_clear(ofile); + smb_oplock_hdl_closed(ofile); return; default: @@ -297,8 +302,7 @@ smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel, * We're going to schedule a request that will have a * reference to this ofile. Get the hold first. */ - if (ofile->f_oplock_closing || - !smb_ofile_hold_olbrk(ofile)) { + if (!smb_ofile_hold_olbrk(ofile)) { /* It's closing (or whatever). Nothing to do. */ return; } @@ -350,6 +354,8 @@ smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel, sr->arg.olbrk.AckRequired = AckRequired; sr->smb2_status = CompletionStatus; + smb_oplock_hdl_update(sr); + (void) taskq_dispatch( sv->sv_worker_pool, smb_oplock_async_break, sr, TQ_SLEEP); @@ -362,16 +368,7 @@ smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel, * We have a hold on the ofile, which will be released in * smb_request_free (via sr->fid_ofile) * - * Note we have: sr->uid_user == NULL, sr->tid_tree == NULL. - * Nothing called here needs those. - * - * Note that NewLevel as provided by the FS up-call does NOT - * include the GRANULAR flag. The SMB level is expected to - * keep track of how each oplock was acquired (by lease or - * traditional oplock request) and put the GRANULAR flag - * back into the oplock state when calling down to the - * FS-level code. Also note that the lease break message - * carries only the cache flags, not the GRANULAR flag. + * Note we may have: sr->uid_user == NULL, sr->tid_tree == NULL. */ static void smb_oplock_async_break(void *arg) @@ -398,7 +395,7 @@ smb_oplock_async_break(void *arg) case STATUS_CANT_GRANT: case NT_STATUS_SUCCESS: - smb_oplock_send_brk(sr); + smb_oplock_send_break(sr); break; default: @@ -416,94 +413,115 @@ smb_oplock_async_break(void *arg) smb_request_free(sr); } -static void -smb_oplock_update(smb_request_t *sr, smb_ofile_t *ofile, uint32_t NewLevel) +/* + * Send an oplock (or lease) break to the client. + * If we can't, then do a local break. + * + * This is called either from smb_oplock_async_break via a + * taskq job scheduled in smb_oplock_ind_break, or from the + * smb2sr_append_postwork() mechanism when we're doing a + * "break in ack", via smb_oplock_ind_break_in_ack. + * + * We don't always have an sr->session here, so + * determine the oplock type (lease etc) from + * f_lease and f_oplock.og_dialect etc. + */ +void +smb_oplock_send_break(smb_request_t *sr) { + smb_ofile_t *ofile = sr->fid_ofile; + if (ofile->f_lease != NULL) - ofile->f_lease->ls_state = NewLevel & CACHE_RWH; + smb2_lease_send_break(sr); + else if (ofile->f_oplock.og_dialect >= SMB_VERS_2_BASE) + smb2_oplock_send_break(sr); else - ofile->f_oplock.og_state = NewLevel; + smb1_oplock_send_break(sr); +} - if (ofile->dh_persist) { - smb2_dh_update_oplock(sr, ofile); - } +/* + * Called by smb_oplock_ind_break for the case STATUS_NEW_HANDLE, + * which is an alias for NT_STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE. + * + * The FS-level oplock layer calls this to update the SMB-level state + * when the oplock for some lease is about to move to a different + * ofile on the lease. + * + * To avoid later confusion, clear og_state on this ofile now. + * Without this, smb_oplock_move() may issue debug complaints + * about moving oplock state onto a non-empty oplock. + */ +static const smb_ofile_t invalid_ofile; +static void +smb_oplock_hdl_moved(smb_ofile_t *ofile) +{ + smb_lease_t *ls = ofile->f_lease; + + ASSERT(ls != NULL); + if (ls != NULL && ls->ls_oplock_ofile == ofile) + ls->ls_oplock_ofile = (smb_ofile_t *)&invalid_ofile; + + ofile->f_oplock.og_state = 0; + ofile->f_oplock.og_breaking = 0; } -#ifdef DEBUG -int smb_oplock_debug_wait = 0; -#endif +/* + * See: NT_STATUS_OPLOCK_HANDLE_CLOSED above and + * smb_ofile_close, smb_oplock_break_CLOSE. + * + * The FS-level oplock layer calls this to update the + * SMB-level state when a handle loses its oplock. + */ +static void +smb_oplock_hdl_closed(smb_ofile_t *ofile) +{ + smb_lease_t *lease = ofile->f_lease; + + if (lease != NULL) { + if (lease->ls_oplock_ofile == ofile) { + /* + * smb2_lease_ofile_close should have + * moved the oplock to another ofile. + */ + ASSERT(0); + lease->ls_oplock_ofile = NULL; + } + } + ofile->f_oplock.og_state = 0; + ofile->f_oplock.og_breaking = 0; +} /* - * Send an oplock break over the wire, or if we can't, - * then process the oplock break locally. - * - * Note that we have sr->fid_ofile here but all the other - * normal sr members may be NULL: uid_user, tid_tree. - * Also sr->session may or may not be the same session as - * the ofile came from (ofile->f_session) depending on - * whether this is a "live" open or an orphaned DH, - * where ofile->f_session will be NULL. - * - * Given that we don't always have a session, we determine - * the oplock type (lease etc) from f_oplock.og_dialect. + * smb_oplock_hdl_update + * + * Called by smb_oplock_ind_break (and ...in_ack) just before we + * schedule smb_oplock_async_break / mb_oplock_send_break taskq job, + * so we can make any state changes that should happen immediately. + * + * Here, keep track of what we will send to the client. + * More importantly, if a break ack is expected, set + * the og_breaking flags to note that fact. */ -void -smb_oplock_send_brk(smb_request_t *sr) +static void +smb_oplock_hdl_update(smb_request_t *sr) { - smb_ofile_t *ofile; - smb_lease_t *lease; - uint32_t NewLevel; - boolean_t AckReq; - uint32_t status; - int rc; - - ofile = sr->fid_ofile; - NewLevel = sr->arg.olbrk.NewLevel; - AckReq = sr->arg.olbrk.AckRequired; - lease = ofile->f_lease; + smb_ofile_t *ofile = sr->fid_ofile; + smb_lease_t *lease = ofile->f_lease; + uint32_t NewLevel = sr->arg.olbrk.NewLevel; + boolean_t AckReq = sr->arg.olbrk.AckRequired; + +#ifdef DEBUG + smb_node_t *node = ofile->f_node; + ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock)); + ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex)); +#endif - /* - * Build the break message in sr->reply. - * It's free'd in smb_request_free(). - * Also updates the lease and NewLevel. - */ - sr->reply.max_bytes = MLEN; if (lease != NULL) { - /* - * The ofile has as lease. Must be SMB2+ - * Oplock state has changed, so update the epoch. - */ - mutex_enter(&lease->ls_mutex); + sr->arg.olbrk.OldLevel = lease->ls_state; lease->ls_epoch++; - mutex_exit(&lease->ls_mutex); - - /* Note, needs "old" state in ls_state */ - smb2_lease_break_notification(sr, - (NewLevel & CACHE_RWH), AckReq); NewLevel |= OPLOCK_LEVEL_GRANULAR; - } else if (ofile->f_oplock.og_dialect >= SMB_VERS_2_BASE) { - /* - * SMB2 using old-style oplock (no lease) - */ - smb2_oplock_break_notification(sr, NewLevel); - } else { - /* - * SMB1 clients should only get Level II oplocks if they - * set the capability indicating they know about them. - */ - if (NewLevel == OPLOCK_LEVEL_TWO && - ofile->f_oplock.og_dialect < NT_LM_0_12) - NewLevel = OPLOCK_LEVEL_NONE; - smb1_oplock_break_notification(sr, NewLevel); } - /* - * Keep track of what we last sent to the client, - * preserving the GRANULAR flag (if a lease). - * If we're expecting an ACK, set og_breaking - * (or maybe lease->ls_breaking) so we can - * filter unsolicited ACKs. - */ if (AckReq) { uint32_t BreakTo; @@ -517,174 +535,166 @@ smb_oplock_send_brk(smb_request_t *sr) BreakTo = BREAK_TO_TWO; else BreakTo = BREAK_TO_NONE; - ofile->f_oplock.og_breaking = BreakTo; } + ofile->f_oplock.og_breaking = BreakTo; /* Will update ls/og_state in ack. */ } else { - smb_oplock_update(sr, ofile, NewLevel); - } - - /* - * Try to send the break message to the client. - * When we get to multi-channel, this is supposed to - * try to send on every channel before giving up. - */ - if (sr->session == ofile->f_session) - rc = smb_session_send(sr->session, 0, &sr->reply); - else - rc = ENOTCONN; - - if (rc == 0) { - /* - * 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. - * - * If debugging, may want to break after a - * short wait to look into why we might be - * holding up progress. (i.e. locks?) + * Not expecting an Ack from the client. + * Update state immediately. */ -#ifdef DEBUG - if (smb_oplock_debug_wait > 0) { - status = smb_oplock_wait_break(sr, ofile->f_node, - smb_oplock_debug_wait); - if (status == 0) - return; - cmn_err(CE_NOTE, "clnt %s oplock break wait debug", - sr->session->ip_addr_str); - debug_enter("oplock_wait"); + ofile->f_oplock.og_state = NewLevel; + if (lease != NULL) { + lease->ls_state = NewLevel & CACHE_RWH; } -#endif - status = smb_oplock_wait_break(sr, ofile->f_node, - smb_oplock_timeout_ack); - if (status == 0) - return; + if (ofile->dh_persist) { + smb2_dh_update_oplock(sr, ofile); + } + } +} - cmn_err(CE_NOTE, "clnt %s oplock break timeout", - sr->session->ip_addr_str); - DTRACE_PROBE1(break_timeout, smb_ofile_t *, ofile); +/* + * Helper for smb_ofile_close + * + * 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. + */ +void +smb_oplock_close(smb_ofile_t *ofile) +{ + smb_node_t *node = ofile->f_node; - /* - * 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; - } else { - /* - * We were unable to send the oplock break request. - * Generally, that means we have no connection to this - * client right now, and this ofile will have state - * SMB_OFILE_STATE_ORPHANED. We either close the handle - * or break the oplock locally, in which case the client - * gets the updated oplock state when they reconnect. - * Decide whether to keep or close. - * - * Relevant [MS-SMB2] sections: - * - * 3.3.4.6 Object Store Indicates an Oplock Break - * If Open.Connection is NULL, Open.IsResilient is FALSE, - * Open.IsDurable is FALSE and Open.IsPersistent is FALSE, - * the server SHOULD close the Open as specified in... - * - * 3.3.4.7 Object Store Indicates a Lease Break - * If Open.Connection is NULL, the server MUST close the - * Open as specified in ... for the following cases: - * - Open.IsResilient is FALSE, Open.IsDurable is FALSE, - * and Open.IsPersistent is FALSE. - * - Lease.BreakToLeaseState does not contain - * ...HANDLE_CACHING and Open.IsDurable is TRUE. - * If Lease.LeaseOpens is empty, (... local ack to "none"). - */ + smb_llist_enter(&node->n_ofile_list, RW_READER); + mutex_enter(&node->n_oplock.ol_mutex); - /* - * See similar logic in smb_dh_should_save - */ - switch (ofile->dh_vers) { - case SMB2_RESILIENT: - break; /* keep DH */ - - case SMB2_DURABLE_V2: - if (ofile->dh_persist) - break; /* keep DH */ - /* FALLTHROUGH */ - case SMB2_DURABLE_V1: - /* IS durable (v1 or v2) */ - if ((NewLevel & (OPLOCK_LEVEL_BATCH | - OPLOCK_LEVEL_CACHE_HANDLE)) != 0) - break; /* keep DH */ - /* FALLTHROUGH */ - case SMB2_NOT_DURABLE: - default: - smb_ofile_close(ofile, 0); - return; - } - /* Keep this ofile (durable handle). */ + if (ofile->f_oplock_closing == B_FALSE) { + ofile->f_oplock_closing = B_TRUE; - if (!AckReq) { - /* Nothing more to do. */ - return; - } - } + if (ofile->f_lease != NULL) + smb2_lease_ofile_close(ofile); - /* - * We get here after either an oplock break ack timeout, - * or a send failure for a durable handle type that we - * preserve rather than just close. Do local ack. - */ - if (lease != NULL) - lease->ls_breaking = 0; - else - ofile->f_oplock.og_breaking = 0; + smb_oplock_break_CLOSE(node, ofile); - status = smb_oplock_ack_break(sr, ofile, &NewLevel); - if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { - /* Not expecting this status return. */ - cmn_err(CE_NOTE, "clnt local oplock ack wait?"); - (void) smb_oplock_wait_break(sr, ofile->f_node, - smb_oplock_timeout_ack); - status = 0; - } - if (status != 0) { - cmn_err(CE_NOTE, "clnt local oplock ack, " - "status=0x%x", status); + ofile->f_oplock.og_state = 0; + ofile->f_oplock.og_breaking = 0; + cv_broadcast(&ofile->f_oplock.og_ack_cv); } - /* Update ls/og_state as if we heard from the client. */ - smb_oplock_update(sr, ofile, NewLevel); + mutex_exit(&node->n_oplock.ol_mutex); + smb_llist_exit(&node->n_ofile_list); } /* - * See: NT_STATUS_OPLOCK_HANDLE_CLOSED above and - * smb_ofile_close, smb_oplock_break_CLOSE. - * - * The FS-level oplock layer calls this to update the - * SMB-level state when a handle loses its oplock. + * Called by smb_request_cancel() via sr->cancel_method + * Arg is the smb_node_t with the breaking oplock. */ static void -smb_oplock_hdl_clear(smb_ofile_t *ofile) +smb_oplock_wait_ack_cancel(smb_request_t *sr) { - smb_lease_t *lease = ofile->f_lease; + kcondvar_t *cvp = sr->cancel_arg2; + smb_ofile_t *ofile = sr->fid_ofile; + smb_node_t *node = ofile->f_node; + + mutex_enter(&node->n_oplock.ol_mutex); + cv_broadcast(cvp); + mutex_exit(&node->n_oplock.ol_mutex); +} + +/* + * Wait for an oplock break ACK to arrive. This is called after + * we've sent an oplock break or lease break to the client where + * an "Ack break" is expected back. If we get an Ack, that will + * wake us up via smb2_oplock_break_ack or smb2_lease_break_ack, + * which signal the CV on which we wait here. + */ +uint32_t +smb_oplock_wait_ack(smb_request_t *sr) +{ + smb_ofile_t *ofile = sr->fid_ofile; + smb_lease_t *lease = ofile->f_lease; + smb_node_t *node = ofile->f_node; + smb_oplock_t *ol = &node->n_oplock; + uint32_t *brp; + kcondvar_t *cvp; + clock_t time, rv; + uint32_t status = 0; + smb_req_state_t srstate; + + time = ddi_get_lbolt() + + MSEC_TO_TICK(smb_oplock_timeout_ack); if (lease != NULL) { - if (lease->ls_oplock_ofile == ofile) { - /* - * smb2_lease_ofile_close should have - * moved the oplock to another ofile. - */ - ASSERT(0); - lease->ls_oplock_ofile = NULL; + brp = &lease->ls_breaking; + cvp = &lease->ls_ack_cv; + } else { + brp = &ofile->f_oplock.og_breaking; + cvp = &ofile->f_oplock.og_ack_cv; + } + + /* + * Setup cancellation callback + */ + mutex_enter(&sr->sr_mutex); + if (sr->sr_state != SMB_REQ_STATE_ACTIVE) { + mutex_exit(&sr->sr_mutex); + return (NT_STATUS_CANCELLED); + } + sr->sr_state = SMB_REQ_STATE_WAITING_OLBRK; + sr->cancel_method = smb_oplock_wait_ack_cancel; + sr->cancel_arg2 = cvp; + mutex_exit(&sr->sr_mutex); + + /* + * Enter the wait loop + */ + mutex_enter(&ol->ol_mutex); + while (*brp != 0) { + rv = cv_timedwait(cvp, &ol->ol_mutex, time); + if (rv < 0) { + /* cv_timewait timeout */ + status = NT_STATUS_CANNOT_BREAK_OPLOCK; + break; + } + + /* + * Check if we were woken by smb_request_cancel, + * which sets state SMB_REQ_STATE_CANCEL_PENDING + * and signals the CV. The mutex enter/exit is + * just to ensure cache visibility of sr_state + * that was updated in smb_request_cancel. + */ + mutex_enter(&sr->sr_mutex); + srstate = sr->sr_state; + mutex_exit(&sr->sr_mutex); + if (srstate != SMB_REQ_STATE_WAITING_OLBRK) { + break; } } - ofile->f_oplock.og_state = 0; - ofile->f_oplock.og_breaking = 0; + mutex_exit(&ol->ol_mutex); + + /* + * Clear cancellation callback and see if it fired. + */ + mutex_enter(&sr->sr_mutex); + sr->cancel_method = NULL; + sr->cancel_arg2 = NULL; + switch (sr->sr_state) { + case SMB_REQ_STATE_WAITING_OLBRK: + sr->sr_state = SMB_REQ_STATE_ACTIVE; + /* status from above */ + break; + case SMB_REQ_STATE_CANCEL_PENDING: + sr->sr_state = SMB_REQ_STATE_CANCELLED; + status = NT_STATUS_CANCELLED; + break; + default: + status = NT_STATUS_INTERNAL_ERROR; + break; + } + mutex_exit(&sr->sr_mutex); + + return (status); } /* @@ -760,7 +770,9 @@ smb_oplock_wait_break(smb_request_t *sr, smb_node_t *node, int timeout) /* * Check if we were woken by smb_request_cancel, * which sets state SMB_REQ_STATE_CANCEL_PENDING - * and signals WaitingOpenCV. + * and signals the CV. The mutex enter/exit is + * just to ensure cache visibility of sr_state + * that was updated in smb_request_cancel. */ mutex_enter(&sr->sr_mutex); srstate = sr->sr_state; diff --git a/usr/src/uts/common/smbsrv/smb_kproto.h b/usr/src/uts/common/smbsrv/smb_kproto.h index 3ffc10f15e..c93ecb353e 100644 --- a/usr/src/uts/common/smbsrv/smb_kproto.h +++ b/usr/src/uts/common/smbsrv/smb_kproto.h @@ -267,6 +267,7 @@ int smb_net_id(uint32_t); * Common oplock functions */ uint32_t smb_oplock_request(smb_request_t *, smb_ofile_t *, uint32_t *); +uint32_t smb_oplock_request_LH(smb_request_t *, smb_ofile_t *, uint32_t *); uint32_t smb_oplock_ack_break(smb_request_t *, smb_ofile_t *, uint32_t *); uint32_t smb_oplock_break_PARENT(smb_node_t *, smb_ofile_t *); uint32_t smb_oplock_break_OPEN(smb_node_t *, smb_ofile_t *, @@ -280,6 +281,16 @@ uint32_t smb_oplock_break_WRITE(smb_node_t *, smb_ofile_t *); uint32_t smb_oplock_break_SETINFO(smb_node_t *, smb_ofile_t *ofile, uint32_t InfoClass); uint32_t smb_oplock_break_DELETE(smb_node_t *, smb_ofile_t *); +void smb_oplock_close(smb_ofile_t *); + +void smb_oplock_ind_break(smb_ofile_t *, uint32_t, boolean_t, uint32_t); +void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *, + uint32_t, boolean_t); +void smb_oplock_send_break(smb_request_t *); + +uint32_t smb_oplock_wait_ack(smb_request_t *); +uint32_t smb_oplock_wait_break(smb_request_t *, smb_node_t *, int); +uint32_t smb_oplock_wait_break_fem(smb_node_t *, int); void smb_oplock_move(smb_node_t *, smb_ofile_t *, smb_ofile_t *); @@ -287,16 +298,12 @@ void smb_oplock_move(smb_node_t *, smb_ofile_t *, smb_ofile_t *); * Protocol-specific oplock functions * (and "server-level" functions) */ +void smb1_oplock_ack_break(smb_request_t *, uchar_t); void smb1_oplock_acquire(smb_request_t *, boolean_t); -void smb1_oplock_break_notification(smb_request_t *, uint32_t); -void smb2_oplock_break_notification(smb_request_t *, uint32_t); -void smb2_lease_break_notification(smb_request_t *, uint32_t, boolean_t); -void smb_oplock_ind_break(smb_ofile_t *, uint32_t, boolean_t, uint32_t); -void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *, - uint32_t, boolean_t); -void smb_oplock_send_brk(smb_request_t *); -uint32_t smb_oplock_wait_break(smb_request_t *, smb_node_t *, int); -uint32_t smb_oplock_wait_break_fem(smb_node_t *, int); +void smb1_oplock_send_break(smb_request_t *); +void smb2_oplock_send_break(smb_request_t *); +void smb2_lease_ofile_close(smb_ofile_t *); +void smb2_lease_send_break(smb_request_t *); /* * range lock functions - node operations diff --git a/usr/src/uts/common/smbsrv/smb_ktypes.h b/usr/src/uts/common/smbsrv/smb_ktypes.h index f29a5b24c0..76def803ab 100644 --- a/usr/src/uts/common/smbsrv/smb_ktypes.h +++ b/usr/src/uts/common/smbsrv/smb_ktypes.h @@ -601,6 +601,7 @@ typedef struct smb_oplock_grant { uint32_t og_state; /* latest sent to client */ uint32_t og_breaking; /* BREAK_TO... flags */ uint16_t og_dialect; /* how to send breaks */ + kcondvar_t og_ack_cv; /* Wait for ACK */ /* File-system level state */ uint8_t onlist_II; uint8_t onlist_R; @@ -626,6 +627,7 @@ typedef struct smb_lease { uint32_t ls_breaking; /* BREAK_TO... flags */ uint16_t ls_epoch; uint16_t ls_version; + kcondvar_t ls_ack_cv; /* Wait for ACK */ uint8_t ls_key[SMB_LEASE_KEY_SZ]; uint8_t ls_clnt[SMB_LEASE_KEY_SZ]; } smb_lease_t; @@ -1657,7 +1659,9 @@ typedef struct smb_arg_lock { typedef struct smb_arg_olbrk { uint32_t NewLevel; + uint32_t OldLevel; boolean_t AckRequired; + uint8_t LeaseKey[SMB_LEASE_KEY_SZ]; } smb_arg_olbrk_t; /* -- cgit v1.2.3 From 7f6a299e282ed51917878b84744774a6634e5dc6 Mon Sep 17 00:00:00 2001 From: Gordon Ross Date: Wed, 23 Feb 2022 09:25:16 -0500 Subject: 15210 smbtorture lease and oplock failures Reviewed by: Joyce McIntosh Reviewed by: Andy Stormont Reviewed by: Matt Barden Approved by: Dan McDonald --- usr/src/cmd/smbsrv/testoplock/case01.ref | 9 +- usr/src/cmd/smbsrv/testoplock/case02.ref | 9 +- usr/src/cmd/smbsrv/testoplock/case03.ref | 6 +- usr/src/cmd/smbsrv/testoplock/case04.ref | 6 +- usr/src/cmd/smbsrv/testoplock/case05.ref | 33 +++--- usr/src/cmd/smbsrv/testoplock/case06.ref | 23 ++-- usr/src/cmd/smbsrv/testoplock/case07.ref | 6 +- usr/src/cmd/smbsrv/testoplock/case08.ref | 15 +-- usr/src/cmd/smbsrv/testoplock/case09.ref | 10 +- usr/src/cmd/smbsrv/testoplock/case10.ref | 10 +- usr/src/cmd/smbsrv/testoplock/case11.ref | 11 +- usr/src/cmd/smbsrv/testoplock/case12.ref | 34 +++--- usr/src/cmd/smbsrv/testoplock/case12.txt | 7 +- usr/src/cmd/smbsrv/testoplock/case13.ref | 24 +++-- usr/src/cmd/smbsrv/testoplock/case13.txt | 2 + usr/src/cmd/smbsrv/testoplock/case14.ref | 66 ++++++++++++ usr/src/cmd/smbsrv/testoplock/case14.txt | 51 +++++++++ usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h | 2 +- usr/src/cmd/smbsrv/testoplock/smbsrv/smb_ktypes.h | 10 +- usr/src/cmd/smbsrv/testoplock/tol_main.c | 123 ++++++++++++++-------- usr/src/uts/common/fs/smbsrv/smb2_lease.c | 68 +++++++----- usr/src/uts/common/fs/smbsrv/smb2_oplock.c | 13 +-- usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c | 86 +++++++++++---- usr/src/uts/common/fs/smbsrv/smb_oplock.c | 11 +- usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c | 117 +++++++++++++------- usr/src/uts/common/smbsrv/smb_kproto.h | 2 +- usr/src/uts/common/smbsrv/smb_ktypes.h | 12 ++- 27 files changed, 525 insertions(+), 241 deletions(-) create mode 100644 usr/src/cmd/smbsrv/testoplock/case14.ref create mode 100644 usr/src/cmd/smbsrv/testoplock/case14.txt diff --git a/usr/src/cmd/smbsrv/testoplock/case01.ref b/usr/src/cmd/smbsrv/testoplock/case01.ref index 6327c46b7e..f7bf8e0773 100644 --- a/usr/src/cmd/smbsrv/testoplock/case01.ref +++ b/usr/src/cmd/smbsrv/testoplock/case01.ref @@ -6,15 +6,16 @@ show ol_state=0x410 ( BATCH_OPLOCK EXCLUSIVE ) Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease= OgState=0x400 Brk=0x0 Excl=Y onlist: + fid=1 Lease= State=0x400 Excl=Y onlist: brk-open 2 *smb_oplock_ind_break fid=1 NewLevel=0x100, AckReq=1, ComplStatus=0x0 (SUCCESS) +*smb_oplock_send_break fid=1 NewLevel=0x100, OldLevel=0x400, AckReq=1) brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS) show ol_state=0x100410 ( BREAK_TO_TWO BATCH_OPLOCK EXCLUSIVE ) Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease= OgState=0x400 Brk=0x100000 Excl=Y onlist: + fid=1 Lease= State=0x400 BreakTo=0x100 Excl=Y onlist: ack 1 ack: break fid=1, newstate=0x100, status=0x0 (SUCCESS) open 2 @@ -25,5 +26,5 @@ show ol_state=0x100 ( LEVEL_TWO_OPLOCK ) Excl=n cnt_II=2 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II - fid=2 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II + fid=1 Lease= State=0x100 Excl=N onlist: II + fid=2 Lease= State=0x100 Excl=N onlist: II diff --git a/usr/src/cmd/smbsrv/testoplock/case02.ref b/usr/src/cmd/smbsrv/testoplock/case02.ref index 73488cbf27..ef251aafd5 100644 --- a/usr/src/cmd/smbsrv/testoplock/case02.ref +++ b/usr/src/cmd/smbsrv/testoplock/case02.ref @@ -6,15 +6,16 @@ show ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING ) Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease= OgState=0x807 Brk=0x0 Excl=Y onlist: + fid=1 Lease= State=0x807 Excl=Y onlist: brk-open 2 *smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=1, ComplStatus=0x0 (SUCCESS) +*smb_oplock_send_break fid=1 NewLevel=0x803, OldLevel=0x807, AckReq=1) brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS) show ol_state=0x30017 ( BREAK_TO_HANDLE_CACHING BREAK_TO_READ_CACHING EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING ) Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease= OgState=0x807 Brk=0x30000 Excl=Y onlist: + fid=1 Lease= State=0x807 BreakTo=0x803 Excl=Y onlist: ack 1 ack: break fid=1, newstate=0x803, status=0x0 (SUCCESS) open 2 @@ -25,5 +26,5 @@ show ol_state=0x3 ( HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=2 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease= OgState=0x803 Brk=0x0 Excl=N onlist: RH - fid=2 Lease= OgState=0x803 Brk=0x0 Excl=N onlist: RH + fid=1 Lease= State=0x803 Excl=N onlist: RH + fid=2 Lease= State=0x803 Excl=N onlist: RH diff --git a/usr/src/cmd/smbsrv/testoplock/case03.ref b/usr/src/cmd/smbsrv/testoplock/case03.ref index cdc7fe0089..0549760a2c 100644 --- a/usr/src/cmd/smbsrv/testoplock/case03.ref +++ b/usr/src/cmd/smbsrv/testoplock/case03.ref @@ -6,7 +6,7 @@ show ol_state=0x3 ( HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease=3 OgState=0x803 Brk=0x0 Excl=N onlist: RH + fid=1 Lease=3 State=0x803 Excl=N onlist: RH open 2 3 open 2 OK req 2 0x803 @@ -16,5 +16,5 @@ show ol_state=0x3 ( HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=3 OgState=0x0 Brk=0x0 Excl=N onlist: - fid=2 Lease=3 OgState=0x803 Brk=0x0 Excl=N onlist: RH + fid=1 Lease=3 State=0x0 Excl=N onlist: + fid=2 Lease=3 State=0x803 Excl=N onlist: RH diff --git a/usr/src/cmd/smbsrv/testoplock/case04.ref b/usr/src/cmd/smbsrv/testoplock/case04.ref index 32df3767a9..c7eed6cafb 100644 --- a/usr/src/cmd/smbsrv/testoplock/case04.ref +++ b/usr/src/cmd/smbsrv/testoplock/case04.ref @@ -6,7 +6,7 @@ show ol_state=0x1 ( READ_CACHING ) Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease=3 OgState=0x801 Brk=0x0 Excl=N onlist: R + fid=1 Lease=3 State=0x801 Excl=N onlist: R open 2 3 open 2 OK req 2 0x801 @@ -16,5 +16,5 @@ show ol_state=0x1 ( READ_CACHING ) Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=3 OgState=0x0 Brk=0x0 Excl=N onlist: - fid=2 Lease=3 OgState=0x801 Brk=0x0 Excl=N onlist: R + fid=1 Lease=3 State=0x0 Excl=N onlist: + fid=2 Lease=3 State=0x801 Excl=N onlist: R diff --git a/usr/src/cmd/smbsrv/testoplock/case05.ref b/usr/src/cmd/smbsrv/testoplock/case05.ref index 639bf0023b..cad3575fa8 100644 --- a/usr/src/cmd/smbsrv/testoplock/case05.ref +++ b/usr/src/cmd/smbsrv/testoplock/case05.ref @@ -6,7 +6,7 @@ show ol_state=0x3 ( HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0 ofile_cnt=1 - fid=2 Lease=4 OgState=0x803 Brk=0x0 Excl=N onlist: RH + fid=2 Lease=4 State=0x803 Excl=N onlist: RH open 3 4 open 3 OK req 3 0x803 @@ -16,29 +16,30 @@ show ol_state=0x3 ( HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0 ofile_cnt=2 - fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist: - fid=3 Lease=4 OgState=0x803 Brk=0x0 Excl=N onlist: RH + fid=2 Lease=4 State=0x0 Excl=N onlist: + fid=3 Lease=4 State=0x803 Excl=N onlist: RH open 1 open 1 OK brk-write 1 *smb_oplock_ind_break fid=3 NewLevel=0x0, AckReq=1, ComplStatus=0x0 (SUCCESS) +*smb_oplock_send_break fid=3 NewLevel=0x800, OldLevel=0x803, AckReq=1) brk-write 1 ret status=0x0 (SUCCESS) show ol_state=0x80003 ( BREAK_TO_NO_CACHING HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=1 ofile_cnt=3 - fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist: - fid=3 Lease=4 OgState=0x803 Brk=0x80000 Excl=N onlist: RHBQ(to none) - fid=1 Lease= OgState=0x0 Brk=0x0 Excl=N onlist: + fid=2 Lease=4 State=0x0 Excl=N onlist: + fid=3 Lease=4 State=0x803 BreakTo=0x800 Excl=N onlist: RHBQ(to none) + fid=1 Lease= State=0x0 Excl=N onlist: ack 3 ack: break fid=3, newstate=0x800, status=0x0 (SUCCESS) show ol_state=0x10000000 ( NO_OPLOCK ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=3 - fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist: - fid=3 Lease=4 OgState=0x800 Brk=0x0 Excl=N onlist: - fid=1 Lease= OgState=0x0 Brk=0x0 Excl=N onlist: + fid=2 Lease=4 State=0x0 Excl=N onlist: + fid=3 Lease=4 State=0x800 Excl=N onlist: + fid=1 Lease= State=0x0 Excl=N onlist: open 4 4 open 4 OK req 4 0x803 @@ -47,10 +48,10 @@ show ol_state=0x3 ( HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0 ofile_cnt=4 - fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist: - fid=3 Lease=4 OgState=0x800 Brk=0x0 Excl=N onlist: - fid=1 Lease= OgState=0x0 Brk=0x0 Excl=N onlist: - fid=4 Lease=4 OgState=0x803 Brk=0x0 Excl=N onlist: RH + fid=2 Lease=4 State=0x0 Excl=N onlist: + fid=3 Lease=4 State=0x800 Excl=N onlist: + fid=1 Lease= State=0x0 Excl=N onlist: + fid=4 Lease=4 State=0x803 Excl=N onlist: RH close 4 *smb_oplock_ind_break fid=4 NewLevel=0x0, AckReq=0, ComplStatus=0x216 (OPLOCK_HANDLE_CLOSED) close OK @@ -60,6 +61,6 @@ show ol_state=0x3 ( HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0 ofile_cnt=3 - fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist: - fid=3 Lease=4 OgState=0x803 Brk=0x0 Excl=N onlist: RH - fid=1 Lease= OgState=0x0 Brk=0x0 Excl=N onlist: + fid=2 Lease=4 State=0x0 Excl=N onlist: + fid=3 Lease=4 State=0x803 Excl=N onlist: RH + fid=1 Lease= State=0x0 Excl=N onlist: diff --git a/usr/src/cmd/smbsrv/testoplock/case06.ref b/usr/src/cmd/smbsrv/testoplock/case06.ref index daa69c3dd1..15a588219d 100644 --- a/usr/src/cmd/smbsrv/testoplock/case06.ref +++ b/usr/src/cmd/smbsrv/testoplock/case06.ref @@ -10,17 +10,18 @@ show ol_state=0x1 ( READ_CACHING ) Excl=n cnt_II=0 cnt_R=2 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist: R - fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist: R + fid=1 Lease=1 State=0x801 Excl=N onlist: R + fid=2 Lease=2 State=0x801 Excl=N onlist: R brk-write 1 *smb_oplock_ind_break fid=2 NewLevel=0x0, AckReq=0, ComplStatus=0x0 (SUCCESS) +*smb_oplock_send_break fid=2 NewLevel=0x800, OldLevel=0x801, AckReq=0) brk-write 1 ret status=0x0 (SUCCESS) show ol_state=0x1 ( READ_CACHING ) Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist: R - fid=2 Lease=2 OgState=0x800 Brk=0x0 Excl=N onlist: + fid=1 Lease=1 State=0x801 Excl=N onlist: R + fid=2 Lease=2 State=0x800 Excl=N onlist: open 3 2 open 3 OK req 3 0x801 @@ -34,23 +35,25 @@ show ol_state=0x1 ( READ_CACHING ) Excl=n cnt_II=0 cnt_R=2 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist: R - fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist: R + fid=1 Lease=1 State=0x801 Excl=N onlist: R + fid=2 Lease=2 State=0x801 Excl=N onlist: R brk-write 2 *smb_oplock_ind_break fid=1 NewLevel=0x0, AckReq=0, ComplStatus=0x0 (SUCCESS) +*smb_oplock_send_break fid=1 NewLevel=0x800, OldLevel=0x801, AckReq=0) brk-write 2 ret status=0x0 (SUCCESS) show ol_state=0x1 ( READ_CACHING ) Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x800 Brk=0x0 Excl=N onlist: - fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist: R + fid=1 Lease=1 State=0x800 Excl=N onlist: + fid=2 Lease=2 State=0x801 Excl=N onlist: R brk-write 1 *smb_oplock_ind_break fid=2 NewLevel=0x0, AckReq=0, ComplStatus=0x0 (SUCCESS) +*smb_oplock_send_break fid=2 NewLevel=0x800, OldLevel=0x801, AckReq=0) brk-write 1 ret status=0x0 (SUCCESS) show ol_state=0x10000000 ( NO_OPLOCK ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x800 Brk=0x0 Excl=N onlist: - fid=2 Lease=2 OgState=0x800 Brk=0x0 Excl=N onlist: + fid=1 Lease=1 State=0x800 Excl=N onlist: + fid=2 Lease=2 State=0x800 Excl=N onlist: diff --git a/usr/src/cmd/smbsrv/testoplock/case07.ref b/usr/src/cmd/smbsrv/testoplock/case07.ref index 11b1fa415d..0427292339 100644 --- a/usr/src/cmd/smbsrv/testoplock/case07.ref +++ b/usr/src/cmd/smbsrv/testoplock/case07.ref @@ -6,7 +6,7 @@ show ol_state=0x3 ( HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease=3 OgState=0x803 Brk=0x0 Excl=N onlist: RH + fid=1 Lease=3 State=0x803 Excl=N onlist: RH open 2 3 open 2 OK req 2 0x807 @@ -16,5 +16,5 @@ show ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING ) Excl=Y (FID=2) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=3 OgState=0x0 Brk=0x0 Excl=N onlist: - fid=2 Lease=3 OgState=0x807 Brk=0x0 Excl=Y onlist: + fid=1 Lease=3 State=0x0 Excl=N onlist: + fid=2 Lease=3 State=0x807 Excl=Y onlist: diff --git a/usr/src/cmd/smbsrv/testoplock/case08.ref b/usr/src/cmd/smbsrv/testoplock/case08.ref index 7f28032990..1bc366958d 100644 --- a/usr/src/cmd/smbsrv/testoplock/case08.ref +++ b/usr/src/cmd/smbsrv/testoplock/case08.ref @@ -6,31 +6,32 @@ show ol_state=0x15 ( EXCLUSIVE WRITE_CACHING READ_CACHING ) Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease=1 OgState=0x805 Brk=0x0 Excl=Y onlist: + fid=1 Lease=1 State=0x805 Excl=Y onlist: open 2 2 open 2 OK brk-open 2 *smb_oplock_ind_break fid=1 NewLevel=0x1, AckReq=1, ComplStatus=0x0 (SUCCESS) +*smb_oplock_send_break fid=1 NewLevel=0x801, OldLevel=0x805, AckReq=1) brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS) show ol_state=0x10015 ( BREAK_TO_READ_CACHING EXCLUSIVE WRITE_CACHING READ_CACHING ) Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x805 Brk=0x10000 Excl=Y onlist: - fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist: + fid=1 Lease=1 State=0x805 BreakTo=0x801 Excl=Y onlist: + fid=2 Lease=2 State=0x0 Excl=N onlist: ack 1 ack: break fid=1, newstate=0x801, status=0x0 (SUCCESS) show ol_state=0x1 ( READ_CACHING ) Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist: R - fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist: + fid=1 Lease=1 State=0x801 Excl=N onlist: R + fid=2 Lease=2 State=0x0 Excl=N onlist: req 2 0x801 req oplock fid=2 ret oplock=0x801 status=0x0 (SUCCESS) show ol_state=0x1 ( READ_CACHING ) Excl=n cnt_II=0 cnt_R=2 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist: R - fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist: R + fid=1 Lease=1 State=0x801 Excl=N onlist: R + fid=2 Lease=2 State=0x801 Excl=N onlist: R diff --git a/usr/src/cmd/smbsrv/testoplock/case09.ref b/usr/src/cmd/smbsrv/testoplock/case09.ref index 4003b133db..9eb3e85230 100644 --- a/usr/src/cmd/smbsrv/testoplock/case09.ref +++ b/usr/src/cmd/smbsrv/testoplock/case09.ref @@ -6,7 +6,7 @@ show ol_state=0x100 ( LEVEL_TWO_OPLOCK ) Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II + fid=1 Lease= State=0x100 Excl=N onlist: II open 2 2 open 2 OK brk-open 2 @@ -15,13 +15,13 @@ show ol_state=0x100 ( LEVEL_TWO_OPLOCK ) Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II - fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist: + fid=1 Lease= State=0x100 Excl=N onlist: II + fid=2 Lease=2 State=0x0 Excl=N onlist: req 2 0x803 req oplock fid=2 ret oplock=0x0 status=0xc00000e2 (OPLOCK_NOT_GRANTED) show ol_state=0x100 ( LEVEL_TWO_OPLOCK ) Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II - fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist: + fid=1 Lease= State=0x100 Excl=N onlist: II + fid=2 Lease=2 State=0x0 Excl=N onlist: diff --git a/usr/src/cmd/smbsrv/testoplock/case10.ref b/usr/src/cmd/smbsrv/testoplock/case10.ref index 9e0275d258..c3e4fdae96 100644 --- a/usr/src/cmd/smbsrv/testoplock/case10.ref +++ b/usr/src/cmd/smbsrv/testoplock/case10.ref @@ -6,7 +6,7 @@ show ol_state=0x100 ( LEVEL_TWO_OPLOCK ) Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II + fid=1 Lease= State=0x100 Excl=N onlist: II open 2 2 open 2 OK brk-open 2 @@ -15,8 +15,8 @@ show ol_state=0x100 ( LEVEL_TWO_OPLOCK ) Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II - fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist: + fid=1 Lease= State=0x100 Excl=N onlist: II + fid=2 Lease=2 State=0x0 Excl=N onlist: req 2 0x807 req oplock fid=2 ret oplock=0x0 status=0xc00000e2 (OPLOCK_NOT_GRANTED) req 2 0x803 @@ -27,5 +27,5 @@ show ol_state=0x101 ( LEVEL_TWO_OPLOCK READ_CACHING ) Excl=n cnt_II=1 cnt_R=1 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II - fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist: R + fid=1 Lease= State=0x100 Excl=N onlist: II + fid=2 Lease=2 State=0x801 Excl=N onlist: R diff --git a/usr/src/cmd/smbsrv/testoplock/case11.ref b/usr/src/cmd/smbsrv/testoplock/case11.ref index 05bae87e04..d1a88b4518 100644 --- a/usr/src/cmd/smbsrv/testoplock/case11.ref +++ b/usr/src/cmd/smbsrv/testoplock/case11.ref @@ -6,11 +6,12 @@ show ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING ) Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease=1 OgState=0x807 Brk=0x0 Excl=Y onlist: + fid=1 Lease=1 State=0x807 Excl=Y onlist: open 2 open 2 OK brk-open 2 *smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=1, ComplStatus=0x0 (SUCCESS) +*smb_oplock_send_break fid=1 NewLevel=0x803, OldLevel=0x807, AckReq=1) brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS) ack 1 ack: break fid=1, newstate=0x803, status=0x0 (SUCCESS) @@ -18,13 +19,13 @@ show ol_state=0x3 ( HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x803 Brk=0x0 Excl=N onlist: RH - fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist: + fid=1 Lease=1 State=0x803 Excl=N onlist: RH + fid=2 Lease= State=0x0 Excl=N onlist: req 2 0x100 req oplock fid=2 ret oplock=0x0 status=0xc00000e2 (OPLOCK_NOT_GRANTED) show ol_state=0x3 ( HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x803 Brk=0x0 Excl=N onlist: RH - fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist: + fid=1 Lease=1 State=0x803 Excl=N onlist: RH + fid=2 Lease= State=0x0 Excl=N onlist: diff --git a/usr/src/cmd/smbsrv/testoplock/case12.ref b/usr/src/cmd/smbsrv/testoplock/case12.ref index bc36b7b6de..ebbe5d4c18 100644 --- a/usr/src/cmd/smbsrv/testoplock/case12.ref +++ b/usr/src/cmd/smbsrv/testoplock/case12.ref @@ -6,11 +6,12 @@ show ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING ) Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease=1 OgState=0x807 Brk=0x0 Excl=Y onlist: + fid=1 Lease=1 State=0x807 Excl=Y onlist: open 2 open 2 OK brk-open 2 *smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=1, ComplStatus=0x0 (SUCCESS) +*smb_oplock_send_break fid=1 NewLevel=0x803, OldLevel=0x807, AckReq=1) brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS) waiters 2 1 waiters 0 -> 1 @@ -18,8 +19,8 @@ show ol_state=0x30017 ( BREAK_TO_HANDLE_CACHING BREAK_TO_READ_CACHING EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING ) Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x807 Brk=0x30000 Excl=Y onlist: - fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist: + fid=1 Lease=1 State=0x807 BreakTo=0x803 Excl=Y onlist: + fid=2 Lease= State=0x0 Excl=N onlist: open 3 open 3 OK brk-open 3 4 @@ -30,25 +31,28 @@ show ol_state=0x80017 ( BREAK_TO_NO_CACHING EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING ) Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=3 - fid=1 Lease=1 OgState=0x807 Brk=0x30000 Excl=Y onlist: - fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist: - fid=3 Lease= OgState=0x0 Brk=0x0 Excl=N onlist: + fid=1 Lease=1 State=0x807 BreakTo=0x803 Excl=Y onlist: + fid=2 Lease= State=0x0 Excl=N onlist: + fid=3 Lease= State=0x0 Excl=N onlist: ack 1 0x803 -*smb_oplock_ind_break fid=1 NewLevel=0x0, AckReq=1, ComplStatus=0x8000002e (CANNOT_GRANT_REQUESTED_OPLOCK) ack: break fid=1, newstate=0x803, status=0x0 (SUCCESS) +*smb_oplock_ind_break_in_ack fid=1 NewLevel=0x1, AckReq=1 +*smb_oplock_send_break fid=1 NewLevel=0x801, OldLevel=0x803, AckReq=1) show ol_state=0x80003 ( BREAK_TO_NO_CACHING HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=1 ofile_cnt=3 - fid=1 Lease=1 OgState=0x803 Brk=0x80000 Excl=N onlist: RHBQ(to none) - fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist: - fid=3 Lease= OgState=0x0 Brk=0x0 Excl=N onlist: -ack 1 0x800 - ack: break fid=1, newstate=0x800, status=0x0 (SUCCESS) + fid=1 Lease=1 State=0x803 BreakTo=0x801 Excl=N onlist: RHBQ(to none) + fid=2 Lease= State=0x0 Excl=N onlist: + fid=3 Lease= State=0x0 Excl=N onlist: +ack 1 0x801 + ack: break fid=1, newstate=0x801, status=0x0 (SUCCESS) +*smb_oplock_ind_break_in_ack fid=1 NewLevel=0x0, AckReq=0 +*smb_oplock_send_break fid=1 NewLevel=0x800, OldLevel=0x801, AckReq=0) show ol_state=0x10000000 ( NO_OPLOCK ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=3 - fid=1 Lease=1 OgState=0x800 Brk=0x0 Excl=N onlist: - fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist: - fid=3 Lease= OgState=0x0 Brk=0x0 Excl=N onlist: + fid=1 Lease=1 State=0x800 Excl=N onlist: + fid=2 Lease= State=0x0 Excl=N onlist: + fid=3 Lease= State=0x0 Excl=N onlist: diff --git a/usr/src/cmd/smbsrv/testoplock/case12.txt b/usr/src/cmd/smbsrv/testoplock/case12.txt index 23b4f63092..1cbaed4d72 100644 --- a/usr/src/cmd/smbsrv/testoplock/case12.txt +++ b/usr/src/cmd/smbsrv/testoplock/case12.txt @@ -20,9 +20,10 @@ show # and brk-open shoud block (break in progress) # # ack the first lease break above (RWH to RH) -# should get a new break ind. (RH to none) +# should get a new break ind. (RH to R) ar=1 ack 1 0x803 show -# got break ind? -ack 1 0x800 +# ack the second lease break (RH to R) +# should get a new break ind. (R to none) ar=0 +ack 1 0x801 show diff --git a/usr/src/cmd/smbsrv/testoplock/case13.ref b/usr/src/cmd/smbsrv/testoplock/case13.ref index a35a5992ce..9dc00b5ce6 100644 --- a/usr/src/cmd/smbsrv/testoplock/case13.ref +++ b/usr/src/cmd/smbsrv/testoplock/case13.ref @@ -6,7 +6,7 @@ show ol_state=0x1 ( READ_CACHING ) Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist: R + fid=1 Lease=1 State=0x801 Excl=N onlist: R open 2 1 open 2 OK @@ -19,23 +19,31 @@ show ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING ) Excl=Y (FID=2) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x0 Brk=0x0 Excl=N onlist: - fid=2 Lease=1 OgState=0x807 Brk=0x0 Excl=Y onlist: + fid=1 Lease=1 State=0x0 Excl=N onlist: + fid=2 Lease=1 State=0x807 Excl=Y onlist: move 2 1 move 2 1 +show + ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING ) + Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 + ofile_cnt=2 + fid=1 Lease=1 State=0x807 Excl=Y onlist: + fid=2 Lease=1 State=0x0 Excl=N onlist: + close 2 close OK show ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING ) Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 ofile_cnt=1 - fid=1 Lease=1 OgState=0x807 Brk=0x0 Excl=Y onlist: + fid=1 Lease=1 State=0x807 Excl=Y onlist: open 3 2 open 3 OK brk-open 3 *smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=1, ComplStatus=0x0 (SUCCESS) +*smb_oplock_send_break fid=1 NewLevel=0x803, OldLevel=0x807, AckReq=1) brk-open 3 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS) ack 1 ack: break fid=1, newstate=0x803, status=0x0 (SUCCESS) @@ -43,8 +51,8 @@ show ol_state=0x3 ( HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x803 Brk=0x0 Excl=N onlist: RH - fid=3 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist: + fid=1 Lease=1 State=0x803 Excl=N onlist: RH + fid=3 Lease=2 State=0x0 Excl=N onlist: req 3 0x801 req oplock fid=3 ret oplock=0x801 status=0x0 (SUCCESS) @@ -52,5 +60,5 @@ show ol_state=0x23 ( MIXED_R_AND_RH HANDLE_CACHING READ_CACHING ) Excl=n cnt_II=0 cnt_R=1 cnt_RH=1 cnt_RHBQ=0 ofile_cnt=2 - fid=1 Lease=1 OgState=0x803 Brk=0x0 Excl=N onlist: RH - fid=3 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist: R + fid=1 Lease=1 State=0x803 Excl=N onlist: RH + fid=3 Lease=2 State=0x801 Excl=N onlist: R diff --git a/usr/src/cmd/smbsrv/testoplock/case13.txt b/usr/src/cmd/smbsrv/testoplock/case13.txt index c8d012690d..f7af15d5c9 100644 --- a/usr/src/cmd/smbsrv/testoplock/case13.txt +++ b/usr/src/cmd/smbsrv/testoplock/case13.txt @@ -12,6 +12,8 @@ req 2 0x807 show move 2 1 +show + close 2 show diff --git a/usr/src/cmd/smbsrv/testoplock/case14.ref b/usr/src/cmd/smbsrv/testoplock/case14.ref new file mode 100644 index 0000000000..b019488ae9 --- /dev/null +++ b/usr/src/cmd/smbsrv/testoplock/case14.ref @@ -0,0 +1,66 @@ + +open 1 1 + open 1 OK +req 1 0x807 + req oplock fid=1 ret oplock=0x807 status=0x0 (SUCCESS) +show + ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING ) + Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 + ofile_cnt=1 + fid=1 Lease=1 State=0x807 Excl=Y onlist: + +brk-setinfo 1 0xa + brk-setinfo 1 0xa ret status=0x0 (SUCCESS) +show + ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING ) + Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 + ofile_cnt=1 + fid=1 Lease=1 State=0x807 Excl=Y onlist: + + +open 2 2 + open 2 OK +brk-open 2 +*smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=1, ComplStatus=0x0 (SUCCESS) +*smb_oplock_send_break fid=1 NewLevel=0x803, OldLevel=0x807, AckReq=1) + brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS) +show + ol_state=0x30017 ( BREAK_TO_HANDLE_CACHING BREAK_TO_READ_CACHING EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING ) + Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0 + ofile_cnt=2 + fid=1 Lease=1 State=0x807 BreakTo=0x803 Excl=Y onlist: + fid=2 Lease=2 State=0x0 Excl=N onlist: + +ack 1 0x803 + ack: break fid=1, newstate=0x803, status=0x0 (SUCCESS) +show + ol_state=0x3 ( HANDLE_CACHING READ_CACHING ) + Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0 + ofile_cnt=2 + fid=1 Lease=1 State=0x803 Excl=N onlist: RH + fid=2 Lease=2 State=0x0 Excl=N onlist: + +req 2 0x807 + req oplock fid=2 ret oplock=0x0 status=0xc00000e2 (OPLOCK_NOT_GRANTED) +req 2 0x803 + req oplock fid=2 ret oplock=0x803 status=0x0 (SUCCESS) + +brk-setinfo 1 0xa +*smb_oplock_ind_break fid=2 NewLevel=0x1, AckReq=1, ComplStatus=0x0 (SUCCESS) +*smb_oplock_send_break fid=2 NewLevel=0x801, OldLevel=0x803, AckReq=1) + brk-setinfo 1 0xa ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS) +show + ol_state=0x3 ( HANDLE_CACHING READ_CACHING ) + Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=1 + ofile_cnt=2 + fid=1 Lease=1 State=0x803 Excl=N onlist: RH + fid=2 Lease=2 State=0x803 BreakTo=0x801 Excl=N onlist: RHBQ(to read) + +ack 2 0x801 + ack: break fid=2, newstate=0x801, status=0x0 (SUCCESS) +show + ol_state=0x23 ( MIXED_R_AND_RH HANDLE_CACHING READ_CACHING ) + Excl=n cnt_II=0 cnt_R=1 cnt_RH=1 cnt_RHBQ=0 + ofile_cnt=2 + fid=1 Lease=1 State=0x803 Excl=N onlist: RH + fid=2 Lease=2 State=0x801 Excl=N onlist: R diff --git a/usr/src/cmd/smbsrv/testoplock/case14.txt b/usr/src/cmd/smbsrv/testoplock/case14.txt new file mode 100644 index 0000000000..685d4d3637 --- /dev/null +++ b/usr/src/cmd/smbsrv/testoplock/case14.txt @@ -0,0 +1,51 @@ +# Input for testoplock, case 13 +# simulate smbtorture smb2.lease.v2_rename + +# Create (open_if, lease=e0dd(1), RWH, epoch=0x4711 ) +# resp: fid=1, RWH, epoch=0x4712 +# Expect no breaks, one handle, state=RWH +open 1 1 +req 1 0x807 +show + +# SetInfo fid=1, newname=...dst.dat +# resp OK (no breaks) +brk-setinfo 1 0xa +show + +# Create, open_if, lease=e0dd(1), RWH, epoch=0x4712 +# resp fid=5 RWH, flags=0 (not breaking) +# Close fid=5 / resp +# This is handled without calling the common oplock layer, +# by logic at the top of smb2_lease_acquire + +# Create, open_if, lease=feed(2), RWH, epoch=0x0044 +# (resp pending, will break RWH to RH) +open 2 2 +brk-open 2 +show + +# Lease Break Notify, lease=e0dd(1), RWH to RH, epoch=0x4713 +# Lease Break Ack, lease=e0dd, RH +# resp (same) +ack 1 0x803 +show + +# Create-resp, fid=9 lease=feed(2), state=RH, epoch=0x0045 +# Now that open 2 breaking is done, it can request. +# Will fail RWH, succeed RH +req 2 0x807 +req 2 0x803 + +# SetInfo fid=1, newname=...src.dat +# (resp blocked -- win10 does not go pending? we do) +# Should get rid of handle caching (RH to R) +brk-setinfo 1 0xa +show + +# Lease Break Notify, lease=feed(2), RH to R, epoch=0x0046 +# Lease Break Ack, lease=feed(2), state=R +# Break Ack Resp (same) +ack 2 0x801 +show +# SetInfo resp, fid=1 (note: races with Break Ack resp.) diff --git a/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h index 5ecc17210a..2a7c31235a 100644 --- a/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h +++ b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h @@ -92,7 +92,7 @@ void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *, uint32_t, boolean_t); void smb_oplock_send_break(smb_request_t *); -uint32_t smb_oplock_wait_ack(smb_request_t *); +uint32_t smb_oplock_wait_ack(smb_request_t *, uint32_t); uint32_t smb_oplock_wait_break(smb_request_t *, smb_node_t *, int); uint32_t smb_oplock_wait_break_fem(smb_node_t *, int); diff --git a/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_ktypes.h b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_ktypes.h index 416c7b2ba4..95eab0f824 100644 --- a/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_ktypes.h +++ b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_ktypes.h @@ -21,7 +21,7 @@ /* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2020 Nexenta by DDN, Inc. All rights reserved. - * Copyright 2021 RackTop Systems, Inc. + * Copyright 2022 RackTop Systems, Inc. */ /* @@ -96,9 +96,11 @@ typedef struct smb_oplock { */ typedef struct smb_oplock_grant { /* smb protocol-level state */ - uint32_t og_state; /* latest sent to client */ - uint32_t og_breaking; /* BREAK_TO... flags */ + uint32_t og_state; /* what client has now */ + uint32_t og_breakto; /* level breaking to */ + boolean_t og_breaking; uint16_t og_dialect; /* how to send breaks */ + kcondvar_t og_ack_cv; /* Wait for ACK */ /* File-system level state */ uint8_t onlist_II; uint8_t onlist_R; @@ -109,6 +111,8 @@ typedef struct smb_oplock_grant { #define SMB_LEASE_KEY_SZ 16 +struct smb_lease; + #define SMB_NODE_MAGIC 0x4E4F4445 /* 'NODE' */ #define SMB_NODE_VALID(p) ASSERT((p)->n_magic == SMB_NODE_MAGIC) diff --git a/usr/src/cmd/smbsrv/testoplock/tol_main.c b/usr/src/cmd/smbsrv/testoplock/tol_main.c index 7691edbca3..8cc81a4343 100644 --- a/usr/src/cmd/smbsrv/testoplock/tol_main.c +++ b/usr/src/cmd/smbsrv/testoplock/tol_main.c @@ -36,6 +36,8 @@ #include #include +extern const char *xlate_nt_status(uint32_t); + #define OPLOCK_CACHE_RWH (READ_CACHING | HANDLE_CACHING | WRITE_CACHING) #define OPLOCK_TYPE (LEVEL_TWO_OPLOCK | LEVEL_ONE_OPLOCK |\ BATCH_OPLOCK | OPLOCK_LEVEL_GRANULAR) @@ -48,7 +50,7 @@ smb_request_t test_sr; uint32_t last_ind_break_level; char cmdbuf[100]; -extern const char *xlate_nt_status(uint32_t); +static void run_ind_break_in_ack(smb_ofile_t *); #define BIT_DEF(name) { name, #name } @@ -134,16 +136,20 @@ do_show(void) printf(" ofile_cnt=%d\n", node->n_ofile_list.ll_count); FOREACH_NODE_OFILE(node, f) { smb_oplock_grant_t *og = &f->f_oplock; - printf(" fid=%d Lease=%s OgState=0x%x Brk=0x%x", + printf(" fid=%d Lease=%s State=0x%x", f->f_fid, f->TargetOplockKey, /* lease */ - f->f_oplock.og_state, - f->f_oplock.og_breaking); - printf(" Excl=%s onlist: %s %s %s", - (ol->excl_open == f) ? "Y" : "N", - og->onlist_II ? "II" : "", - og->onlist_R ? "R" : "", - og->onlist_RH ? "RH" : ""); + og->og_state); + if (og->og_breaking) + printf(" BreakTo=0x%x", og->og_breakto); + printf(" Excl=%s onlist:", + (ol->excl_open == f) ? "Y" : "N"); + if (og->onlist_II) + printf(" II"); + if (og->onlist_R) + printf(" R"); + if (og->onlist_RH) + printf(" RH"); if (og->onlist_RHBQ) { printf(" RHBQ(to %s)", og->BreakingToRead ? @@ -224,8 +230,13 @@ do_req(int fid, char *arg2) * Request an oplock */ status = smb_oplock_request(&test_sr, ofile, &oplock); - if (status == 0) + if (status == 0 || + status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { ofile->f_oplock.og_state = oplock; + /* When no break pending, breakto=state */ + ofile->f_oplock.og_breakto = oplock; + ofile->f_oplock.og_breaking = B_FALSE; + } printf(" req oplock fid=%d ret oplock=0x%x status=0x%x (%s)\n", fid, oplock, status, xlate_nt_status(status)); } @@ -249,18 +260,22 @@ do_ack(int fid, char *arg2) ofile->f_oplock.og_breaking = 0; status = smb_oplock_ack_break(&test_sr, ofile, &oplock); - if (NT_SC_SEVERITY(status) == NT_STATUS_SEVERITY_SUCCESS) + if (status == 0) ofile->f_oplock.og_state = oplock; mutex_exit(&node->n_oplock.ol_mutex); smb_llist_exit(&node->n_ofile_list); if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { + /* should not get this status */ printf(" ack: break fid=%d, break-in-progress\n", fid); + ASSERT(0); } printf(" ack: break fid=%d, newstate=0x%x, status=0x%x (%s)\n", fid, oplock, status, xlate_nt_status(status)); + + run_ind_break_in_ack(ofile); } static void @@ -335,8 +350,8 @@ do_brk_setinfo(int fid, char *arg2) status = smb_oplock_break_SETINFO( &test_node, ofile, infoclass); - printf(" brk-setinfo %d ret status=0x%x (%s)\n", - fid, status, xlate_nt_status(status)); + printf(" brk-setinfo %d 0x%x ret status=0x%x (%s)\n", + fid, infoclass, status, xlate_nt_status(status)); } @@ -559,47 +574,35 @@ smb_lock_range_access( /* * Test code replacement for combination of: * smb_oplock_hdl_update() - * smb_oplock_send_brk() + * smb_oplock_send_break() + * + * In a real server, we would send a break to the client, + * and keep track (at the SMB level) whether this oplock + * was obtained via a lease or an old-style oplock. */ static void -test_oplock_send_brk(smb_ofile_t *ofile, +test_oplock_send_break(smb_ofile_t *ofile, uint32_t NewLevel, boolean_t AckReq) { smb_oplock_grant_t *og = &ofile->f_oplock; + uint32_t OldLevel; /* Skip building a message. */ if ((og->og_state & OPLOCK_LEVEL_GRANULAR) != 0) NewLevel |= OPLOCK_LEVEL_GRANULAR; - /* - * In a real server, we would send a break to the client, - * and keep track (at the SMB level) whether this oplock - * was obtained via a lease or an old-style oplock. - * - * This part like: smb_oplock_hdl_update() - */ - if (AckReq) { - uint32_t BreakTo; - - if ((og->og_state & OPLOCK_LEVEL_GRANULAR) != 0) { - - BreakTo = (NewLevel & CACHE_RWH) << BREAK_SHIFT; - if (BreakTo == 0) - BreakTo = BREAK_TO_NO_CACHING; - // ls_breaking = BreakTo; - } else { - if ((NewLevel & LEVEL_TWO_OPLOCK) != 0) - BreakTo = BREAK_TO_TWO; - else - BreakTo = BREAK_TO_NONE; - } - og->og_breaking = BreakTo; - /* Set og_state in do_ack */ - } else { + OldLevel = og->og_state; + og->og_breakto = NewLevel; + og->og_breaking = B_TRUE; + + printf("*smb_oplock_send_break fid=%d " + "NewLevel=0x%x, OldLevel=0x%x, AckReq=%d)\n", + ofile->f_fid, NewLevel, OldLevel, AckReq); + + if (!AckReq) { og->og_state = NewLevel; - // If lease: ls_breaking = ... - /* Clear og_breaking in do_ack */ + og->og_breaking = B_FALSE; } /* Next, smb_oplock_send_break() would send a break. */ @@ -629,12 +632,14 @@ smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel, case NT_STATUS_SUCCESS: case NT_STATUS_CANNOT_GRANT_REQUESTED_OPLOCK: - test_oplock_send_brk(ofile, NewLevel, AckReq); + test_oplock_send_break(ofile, NewLevel, AckReq); break; case NT_STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE: case NT_STATUS_OPLOCK_HANDLE_CLOSED: og->og_state = OPLOCK_LEVEL_NONE; + og->og_breakto = OPLOCK_LEVEL_NONE; + og->og_breaking = B_FALSE; break; default: @@ -643,12 +648,42 @@ smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel, } } +/* Arrange for break_in_ack to run after ack completes. */ +static uint32_t break_in_ack_NewLevel; +static boolean_t break_in_ack_AckReq; +static boolean_t break_in_ack_called; + void smb_oplock_ind_break_in_ack(smb_request_t *sr, smb_ofile_t *ofile, uint32_t NewLevel, boolean_t AckRequired) { ASSERT(sr == &test_sr); - smb_oplock_ind_break(ofile, NewLevel, AckRequired, STATUS_CANT_GRANT); + + /* Process these after ack */ + ASSERT(!break_in_ack_called); + break_in_ack_called = B_TRUE; + break_in_ack_NewLevel = NewLevel; + break_in_ack_AckReq = AckRequired; +} + +static void +run_ind_break_in_ack(smb_ofile_t *ofile) +{ + uint32_t NewLevel; + boolean_t AckReq; + + /* Process these after ack */ + if (!break_in_ack_called) + return; + break_in_ack_called = B_FALSE; + NewLevel = break_in_ack_NewLevel; + AckReq = break_in_ack_AckReq; + + printf("*smb_oplock_ind_break_in_ack fid=%d NewLevel=0x%x," + " AckReq=%d\n", + ofile->f_fid, NewLevel, AckReq); + + test_oplock_send_break(ofile, NewLevel, AckReq); } uint32_t diff --git a/usr/src/uts/common/fs/smbsrv/smb2_lease.c b/usr/src/uts/common/fs/smbsrv/smb2_lease.c index 81bf93d8fe..c440fa16c8 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_lease.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_lease.c @@ -301,7 +301,6 @@ smb2_lease_break_ack(smb_request_t *sr) smb_node_t *node; smb_ofile_t *ofile; uint32_t LeaseState; - uint32_t BreakTo; uint32_t status; int rc = 0; @@ -363,7 +362,7 @@ smb2_lease_break_ack(smb_request_t *sr) /* Success, so have sr->fid_ofile */ ofile = sr->fid_ofile; - if (lease->ls_breaking == 0) { + if (lease->ls_breaking == B_FALSE) { /* * This ACK is either unsolicited or too late, * eg. we timed out the ACK and did it locally. @@ -376,9 +375,7 @@ smb2_lease_break_ack(smb_request_t *sr) * If the new LeaseState has any bits in excess of * the lease state we sent in the break, error... */ - BreakTo = (lease->ls_breaking >> BREAK_SHIFT) & - OPLOCK_LEVEL_CACHE_MASK; - if ((LeaseState & ~BreakTo) != 0) { + if ((LeaseState & ~(lease->ls_breakto)) != 0) { status = NT_STATUS_REQUEST_NOT_ACCEPTED; goto errout; } @@ -388,16 +385,18 @@ smb2_lease_break_ack(smb_request_t *sr) * * Clear breaking flags before we ack, * because ack might set those. + * Signal both CVs, out of paranoia. */ - ofile->f_oplock.og_breaking = 0; - lease->ls_breaking = 0; + 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 & CACHE_RWH; + lease->ls_state = LeaseState; /* ls_epoch does not change here */ if (ofile->dh_persist) @@ -676,7 +675,7 @@ smb2_lease_send_break(smb_request_t *sr) * We're expecting an ACK. Wait in this thread * so we can log clients that don't respond. */ - status = smb_oplock_wait_ack(sr); + status = smb_oplock_wait_ack(sr, NewLevel); if (status == 0) return; @@ -718,14 +717,14 @@ smb2_lease_send_break(smb_request_t *sr) /* * Now continue like the non-lease code */ - ofile->f_oplock.og_breaking = 0; - lease->ls_breaking = 0; + 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 & CACHE_RWH; + lease->ls_state = NewLevel; /* ls_epoch does not change here */ if (ofile->dh_persist) @@ -748,7 +747,7 @@ unlock_out: * Convert SMB2 lease request info in to internal form, * call common oplock code, convert result to SMB2. * - * If necessary, "go async" here. + * If necessary, "go async" here (at the end). */ void smb2_lease_acquire(smb_request_t *sr) @@ -807,15 +806,18 @@ smb2_lease_acquire(smb_request_t *sr) mutex_enter(&node->n_oplock.ol_mutex); /* - * Disallow downgrade + * MS-SMB2 3.3.5.9.8 and 3.3.5.9.11 Lease (V2) create contexts * - * Note that open with a lease is not allowed to turn off - * any cache rights. If the client tries to "downgrade", - * any bits, just return the existing lease cache bits. + * 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) { + if ((have & ~want) != 0 || lease->ls_breaking) { op->op_oplock_state = have | OPLOCK_LEVEL_GRANULAR; goto done; @@ -840,9 +842,7 @@ smb2_lease_acquire(smb_request_t *sr) * Try exclusive (request is RW or RWH) */ if ((op->op_oplock_state & WRITE_CACHING) != 0) { - want = op->op_oplock_state & CACHE_RWH; - if (have == want) - goto done; + /* Alread checked (want & ~have) */ status = smb_oplock_request_LH(sr, ofile, &op->op_oplock_state); @@ -881,7 +881,7 @@ smb2_lease_acquire(smb_request_t *sr) */ if ((op->op_oplock_state & HANDLE_CACHING) != 0) { want = op->op_oplock_state & CACHE_RWH; - if (have == want) + if ((want & ~have) == 0) goto done; status = smb_oplock_request_LH(sr, ofile, @@ -907,7 +907,7 @@ smb2_lease_acquire(smb_request_t *sr) */ if ((op->op_oplock_state & READ_CACHING) != 0) { want = op->op_oplock_state & CACHE_RWH; - if (have == want) + if ((want & ~have) == 0) goto done; status = smb_oplock_request_LH(sr, ofile, @@ -945,11 +945,13 @@ done: * this has to be using granular oplocks. */ if (NewGrant) { - ofile->f_oplock.og_state = op->op_oplock_state; - ofile->f_oplock.og_breaking = 0; + 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 = op->op_oplock_state; + lease->ls_state = ofile->f_oplock.og_state; + lease->ls_breakto = ofile->f_oplock.og_breakto; lease->ls_breaking = B_FALSE; lease->ls_epoch++; @@ -988,6 +990,11 @@ done: * 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 @@ -1009,6 +1016,12 @@ smb2_lease_ofile_close(smb_ofile_t *ofile) 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. @@ -1077,7 +1090,8 @@ smb2_lease_ofile_close(smb_ofile_t *ofile) * Wakeup ACK waiters too. */ lease->ls_state = 0; - lease->ls_breaking = 0; + lease->ls_breakto = 0; + lease->ls_breaking = B_FALSE; cv_broadcast(&lease->ls_ack_cv); lease->ls_oplock_ofile = NULL; diff --git a/usr/src/uts/common/fs/smbsrv/smb2_oplock.c b/usr/src/uts/common/fs/smbsrv/smb2_oplock.c index 613b56dac9..0d807870bc 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_oplock.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_oplock.c @@ -133,7 +133,7 @@ smb2_oplock_break_ack(smb_request_t *sr) smb_llist_enter(&node->n_ofile_list, RW_READER); mutex_enter(&node->n_oplock.ol_mutex); - if (og->og_breaking == 0) { + if (og->og_breaking == B_FALSE) { /* * This is an unsolicited Ack. (There is no * outstanding oplock break in progress now.) @@ -154,7 +154,7 @@ smb2_oplock_break_ack(smb_request_t *sr) * Clear breaking flags before we ack, * because ack might set those. */ - ofile->f_oplock.og_breaking = 0; + ofile->f_oplock.og_breaking = B_FALSE; cv_broadcast(&ofile->f_oplock.og_ack_cv); status = smb_oplock_ack_break(sr, ofile, &NewLevel); @@ -355,7 +355,7 @@ smb2_oplock_send_break(smb_request_t *sr) * We're expecting an ACK. Wait in this thread * so we can log clients that don't respond. */ - status = smb_oplock_wait_ack(sr); + status = smb_oplock_wait_ack(sr, NewLevel); if (status == 0) return; @@ -377,7 +377,7 @@ smb2_oplock_send_break(smb_request_t *sr) smb_llist_enter(&node->n_ofile_list, RW_READER); mutex_enter(&node->n_oplock.ol_mutex); - ofile->f_oplock.og_breaking = 0; + ofile->f_oplock.og_breaking = B_FALSE; cv_broadcast(&ofile->f_oplock.og_ack_cv); status = smb_oplock_ack_break(sr, ofile, &NewLevel); @@ -487,8 +487,9 @@ smb2_oplock_acquire(smb_request_t *sr) case NT_STATUS_SUCCESS: case NT_STATUS_OPLOCK_BREAK_IN_PROGRESS: ofile->f_oplock.og_dialect = SMB_VERS_2_002; - ofile->f_oplock.og_state = op->op_oplock_state; - ofile->f_oplock.og_breaking = 0; + 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; if (ofile->dh_persist) { smb2_dh_update_oplock(sr, ofile); } diff --git a/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c index 441dc59a6e..840ab49367 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c +++ b/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c @@ -563,6 +563,12 @@ smb_oplock_req_excl( 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 + /* * Don't allow grants on closing ofiles. */ @@ -1063,6 +1069,12 @@ smb_oplock_req_shared( 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 + /* * Don't allow grants on closing ofiles. */ @@ -1601,21 +1613,10 @@ smb_oplock_ack_break( * OplockCompletionStatus = STATUS_SUCCESS. * (Because BreakingOplockOpen is equal to the * passed-in Open, the operation ends at this point.) - * - * It should be OK to return the reduced oplock - * (*rop = LEVEL_NONE) here and avoid the need - * to send another oplock break. This is safe - * because we already have an Ack of the break - * to Level_II, and the additional break to none - * would use AckRequired = FALSE. - * - * If we followed the spec here, we'd have: - * smb_oplock_ind_break(ofile, - * LEVEL_NONE, B_FALSE, - * NT_STATUS_SUCCESS); - * (Or smb_oplock_ind_break_in_ack...) */ - *rop = LEVEL_NONE; /* Reduced from L2 */ + smb_oplock_ind_break_in_ack( + sr, ofile, + LEVEL_NONE, B_FALSE); } status = NT_STATUS_SUCCESS; goto out; @@ -1691,9 +1692,19 @@ smb_oplock_ack_break( BreakToLevel = READ_CACHING; break; case BREAK_TO_NO_CACHING: - default: BreakToLevel = LEVEL_NONE; break; + default: + ASSERT(0); + /* FALLTHROUGH */ + case 0: + /* + * This can happen when we have multiple RH opens, + * and one of them breaks (RH to R). Happens in + * the smbtorture smb2.lease.v2rename test. + */ + BreakToLevel = CACHE_R; + break; } /* Switch Open.Stream.Oplock.State */ @@ -1703,6 +1714,21 @@ smb_oplock_ack_break( case (READ_CACHING|HANDLE_CACHING): case (READ_CACHING|HANDLE_CACHING|BREAK_TO_READ_CACHING): case (READ_CACHING|HANDLE_CACHING|BREAK_TO_NO_CACHING): + /* + * XXX: Missing from [MS-FSA] + * + * If we previously sent a break to none and the + * client Ack level is R instead of none, we + * need to send another break. We can then + * proceed as if we got level = none. + */ + if (level == CACHE_R && BreakToLevel == LEVEL_NONE) { + smb_oplock_ind_break_in_ack( + sr, ofile, + LEVEL_NONE, B_FALSE); + level = LEVEL_NONE; + } + /* * For each RHOpContext ThisContext in * Open.Stream.Oplock.RHBreakQueue: @@ -2001,7 +2027,11 @@ smb_oplock_ack_break( */ /* - * Breaking R to none? This is like: + * Breaking R to none. + * + * We sent break exclusive (RWH or RW) to none and + * the client Ack reduces to R instead of to none. + * Need to send another break. This is like: * "If BreakCacheLevel contains READ_CACHING..." * from smb_oplock_break_cmn. */ @@ -2015,16 +2045,24 @@ smb_oplock_ack_break( } /* - * Breaking RH to R or RH to none? This is like: + * Breaking RH to R or RH to none. + * + * We sent break from (RWH or RW) to (R or none), + * and the client Ack reduces to RH instead of none. + * Need to send another break. This is like: * "If BreakCacheLevel equals HANDLE_CACHING..." * from smb_oplock_break_cmn. + * + * Note: Windows always does break to CACHE_R here, + * letting another Ack and ind_break round trip + * take us the rest of the way from R to none. */ if (level == CACHE_RH && (BreakToLevel == CACHE_R || BreakToLevel == LEVEL_NONE)) { smb_oplock_ind_break_in_ack( sr, ofile, - BreakToLevel, B_TRUE); + CACHE_R, B_TRUE); ofile->f_oplock.BreakingToRead = (BreakToLevel & READ_CACHING) ? 1: 0; @@ -2331,6 +2369,12 @@ smb_oplock_break_CLOSE(smb_node_t *node, smb_ofile_t *ofile) 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 Oplock.IIOplocks is not empty: * For each Open ThisOpen in Oplock.IIOplocks: @@ -2701,6 +2745,12 @@ smb_oplock_break_cmn(smb_node_t *node, smb_llist_enter(&node->n_ofile_list, RW_READER); mutex_enter(&node->n_oplock.ol_mutex); +#ifdef DEBUG + FOREACH_NODE_OFILE(node, o) { + DTRACE_PROBE1(each_ofile, smb_ofile_t *, o); + } +#endif + if (node->n_oplock.ol_state == 0 || node->n_oplock.ol_state == NO_OPLOCK) goto out; diff --git a/usr/src/uts/common/fs/smbsrv/smb_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_oplock.c index a3fd103fa1..58447a98f3 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_oplock.c +++ b/usr/src/uts/common/fs/smbsrv/smb_oplock.c @@ -55,7 +55,7 @@ smb1_oplock_ack_break(smb_request_t *sr, uchar_t oplock_level) smb_llist_enter(&node->n_ofile_list, RW_READER); mutex_enter(&node->n_oplock.ol_mutex); - ofile->f_oplock.og_breaking = 0; + ofile->f_oplock.og_breaking = B_FALSE; cv_broadcast(&ofile->f_oplock.og_ack_cv); (void) smb_oplock_ack_break(sr, ofile, &NewLevel); @@ -189,7 +189,7 @@ smb1_oplock_send_break(smb_request_t *sr) * We're expecting an ACK. Wait in this thread * so we can log clients that don't respond. */ - status = smb_oplock_wait_ack(sr); + status = smb_oplock_wait_ack(sr, NewLevel); if (status == 0) return; @@ -207,7 +207,7 @@ smb1_oplock_send_break(smb_request_t *sr) smb_llist_enter(&node->n_ofile_list, RW_READER); mutex_enter(&node->n_oplock.ol_mutex); - ofile->f_oplock.og_breaking = 0; + ofile->f_oplock.og_breaking = B_FALSE; cv_broadcast(&ofile->f_oplock.og_ack_cv); status = smb_oplock_ack_break(sr, ofile, &NewLevel); @@ -313,8 +313,9 @@ smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok) case NT_STATUS_OPLOCK_BREAK_IN_PROGRESS: ofile->f_oplock.og_dialect = (level2ok) ? NT_LM_0_12 : LANMAN2_1; - ofile->f_oplock.og_state = op->op_oplock_state; - ofile->f_oplock.og_breaking = 0; + 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; break; case NT_STATUS_OPLOCK_NOT_GRANTED: op->op_oplock_level = SMB_OPLOCK_NONE; diff --git a/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c index df917c3d0c..fbb2b51c24 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c +++ b/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c @@ -227,9 +227,18 @@ smb_oplock_ind_break_in_ack(smb_request_t *ack_sr, smb_ofile_t *ofile, sr->uid_user = ofile->f_user; smb_user_hold_internal(sr->uid_user); } + if (ofile->f_lease != NULL) + NewLevel |= OPLOCK_LEVEL_GRANULAR; + sr->arg.olbrk.NewLevel = NewLevel; sr->arg.olbrk.AckRequired = AckRequired; + /* + * Could do this in _hdl_update but this way it's + * visible in the dtrace fbt entry probe. + */ + sr->arg.olbrk.OldLevel = ofile->f_oplock.og_breakto; + smb_oplock_hdl_update(sr); if (use_postwork) { @@ -350,14 +359,23 @@ smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel, sr->uid_user = ofile->f_user; smb_user_hold_internal(sr->uid_user); } + if (ofile->f_lease != NULL) + NewLevel |= OPLOCK_LEVEL_GRANULAR; + sr->arg.olbrk.NewLevel = NewLevel; sr->arg.olbrk.AckRequired = AckRequired; sr->smb2_status = CompletionStatus; + /* + * Could do this in _hdl_update but this way it's + * visible in the dtrace fbt entry probe. + */ + sr->arg.olbrk.OldLevel = ofile->f_oplock.og_breakto; + smb_oplock_hdl_update(sr); - (void) taskq_dispatch( - sv->sv_worker_pool, + /* Will call smb_oplock_send_break */ + (void) taskq_dispatch(sv->sv_worker_pool, smb_oplock_async_break, sr, TQ_SLEEP); } @@ -462,7 +480,8 @@ smb_oplock_hdl_moved(smb_ofile_t *ofile) ls->ls_oplock_ofile = (smb_ofile_t *)&invalid_ofile; ofile->f_oplock.og_state = 0; - ofile->f_oplock.og_breaking = 0; + ofile->f_oplock.og_breakto = 0; + ofile->f_oplock.og_breaking = B_FALSE; } /* @@ -488,7 +507,8 @@ smb_oplock_hdl_closed(smb_ofile_t *ofile) } } ofile->f_oplock.og_state = 0; - ofile->f_oplock.og_breaking = 0; + ofile->f_oplock.og_breakto = 0; + ofile->f_oplock.og_breaking = B_FALSE; } /* @@ -499,8 +519,16 @@ smb_oplock_hdl_closed(smb_ofile_t *ofile) * so we can make any state changes that should happen immediately. * * Here, keep track of what we will send to the client. - * More importantly, if a break ack is expected, set - * the og_breaking flags to note that fact. + * Saves old state in arg.olbck.OldLevel + * + * Note that because we may be in the midst of processing an + * smb_oplock_ack_break call here, the _breaking flag will be + * temporarily false, and is set true again if this ack causes + * another break. This makes it tricky to know when to update + * the epoch, which is not supposed to increment when there's + * already an unacknowledged break out to the client. + * We can recognize that by comparing ls_state vs ls_breakto. + * If no unacknowledged break, ls_state == ls_breakto. */ static void smb_oplock_hdl_update(smb_request_t *sr) @@ -516,36 +544,28 @@ smb_oplock_hdl_update(smb_request_t *sr) ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex)); #endif + /* Caller sets arg.olbrk.OldLevel */ + ofile->f_oplock.og_breakto = NewLevel; + ofile->f_oplock.og_breaking = B_TRUE; if (lease != NULL) { - sr->arg.olbrk.OldLevel = lease->ls_state; - lease->ls_epoch++; - NewLevel |= OPLOCK_LEVEL_GRANULAR; - } + // If no unacknowledged break, update epoch. + if (lease->ls_breakto == lease->ls_state) + lease->ls_epoch++; - if (AckReq) { - uint32_t BreakTo; + lease->ls_breakto = NewLevel; + lease->ls_breaking = B_TRUE; + } - if (lease != NULL) { - BreakTo = (NewLevel & CACHE_RWH) << BREAK_SHIFT; - if (BreakTo == 0) - BreakTo = BREAK_TO_NO_CACHING; - lease->ls_breaking = BreakTo; - } else { - if ((NewLevel & LEVEL_TWO_OPLOCK) != 0) - BreakTo = BREAK_TO_TWO; - else - BreakTo = BREAK_TO_NONE; - } - ofile->f_oplock.og_breaking = BreakTo; - /* Will update ls/og_state in ack. */ - } else { + if (!AckReq) { /* * Not expecting an Ack from the client. * Update state immediately. */ ofile->f_oplock.og_state = NewLevel; + ofile->f_oplock.og_breaking = B_FALSE; if (lease != NULL) { - lease->ls_state = NewLevel & CACHE_RWH; + lease->ls_state = NewLevel; + lease->ls_breaking = B_FALSE; } if (ofile->dh_persist) { smb2_dh_update_oplock(sr, ofile); @@ -577,7 +597,8 @@ smb_oplock_close(smb_ofile_t *ofile) smb_oplock_break_CLOSE(node, ofile); ofile->f_oplock.og_state = 0; - ofile->f_oplock.og_breaking = 0; + ofile->f_oplock.og_breakto = 0; + ofile->f_oplock.og_breaking = B_FALSE; cv_broadcast(&ofile->f_oplock.og_ack_cv); } @@ -605,33 +626,48 @@ smb_oplock_wait_ack_cancel(smb_request_t *sr) * Wait for an oplock break ACK to arrive. This is called after * we've sent an oplock break or lease break to the client where * an "Ack break" is expected back. If we get an Ack, that will - * wake us up via smb2_oplock_break_ack or smb2_lease_break_ack, - * which signal the CV on which we wait here. + * wake us up via smb2_oplock_break_ack or smb2_lease_break_ack. + * + * Wait until state reduced to NewLevel (or less). + * Note that in multi-break cases, we might wait here for just + * one ack when another has become pending, in which case the + * og_breakto might be a subset of NewLevel. Wait until the + * state field is no longer a superset of NewLevel. */ uint32_t -smb_oplock_wait_ack(smb_request_t *sr) +smb_oplock_wait_ack(smb_request_t *sr, uint32_t NewLevel) { smb_ofile_t *ofile = sr->fid_ofile; smb_lease_t *lease = ofile->f_lease; smb_node_t *node = ofile->f_node; smb_oplock_t *ol = &node->n_oplock; - uint32_t *brp; - kcondvar_t *cvp; + uint32_t *state_p; + kcondvar_t *cv_p; clock_t time, rv; uint32_t status = 0; smb_req_state_t srstate; + uint32_t wait_mask; time = ddi_get_lbolt() + MSEC_TO_TICK(smb_oplock_timeout_ack); + /* + * Wait on either lease state or oplock state + */ if (lease != NULL) { - brp = &lease->ls_breaking; - cvp = &lease->ls_ack_cv; + state_p = &lease->ls_state; + cv_p = &lease->ls_ack_cv; } else { - brp = &ofile->f_oplock.og_breaking; - cvp = &ofile->f_oplock.og_ack_cv; + state_p = &ofile->f_oplock.og_state; + cv_p = &ofile->f_oplock.og_ack_cv; } + /* + * These are all the bits that we wait to be cleared. + */ + wait_mask = ~NewLevel & (CACHE_RWH | + LEVEL_TWO | LEVEL_ONE | LEVEL_BATCH); + /* * Setup cancellation callback */ @@ -642,15 +678,16 @@ smb_oplock_wait_ack(smb_request_t *sr) } sr->sr_state = SMB_REQ_STATE_WAITING_OLBRK; sr->cancel_method = smb_oplock_wait_ack_cancel; - sr->cancel_arg2 = cvp; + sr->cancel_arg2 = cv_p; mutex_exit(&sr->sr_mutex); /* * Enter the wait loop */ mutex_enter(&ol->ol_mutex); - while (*brp != 0) { - rv = cv_timedwait(cvp, &ol->ol_mutex, time); + + while ((*state_p & wait_mask) != 0) { + rv = cv_timedwait(cv_p, &ol->ol_mutex, time); if (rv < 0) { /* cv_timewait timeout */ status = NT_STATUS_CANNOT_BREAK_OPLOCK; diff --git a/usr/src/uts/common/smbsrv/smb_kproto.h b/usr/src/uts/common/smbsrv/smb_kproto.h index c93ecb353e..4a231051ff 100644 --- a/usr/src/uts/common/smbsrv/smb_kproto.h +++ b/usr/src/uts/common/smbsrv/smb_kproto.h @@ -288,7 +288,7 @@ void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *, uint32_t, boolean_t); void smb_oplock_send_break(smb_request_t *); -uint32_t smb_oplock_wait_ack(smb_request_t *); +uint32_t smb_oplock_wait_ack(smb_request_t *, uint32_t); uint32_t smb_oplock_wait_break(smb_request_t *, smb_node_t *, int); uint32_t smb_oplock_wait_break_fem(smb_node_t *, int); diff --git a/usr/src/uts/common/smbsrv/smb_ktypes.h b/usr/src/uts/common/smbsrv/smb_ktypes.h index 76def803ab..f871fe76b2 100644 --- a/usr/src/uts/common/smbsrv/smb_ktypes.h +++ b/usr/src/uts/common/smbsrv/smb_ktypes.h @@ -598,8 +598,9 @@ typedef struct smb_oplock { */ typedef struct smb_oplock_grant { /* smb protocol-level state */ - uint32_t og_state; /* latest sent to client */ - uint32_t og_breaking; /* BREAK_TO... flags */ + uint32_t og_state; /* what client has now */ + uint32_t og_breakto; /* level breaking to */ + boolean_t og_breaking; uint16_t og_dialect; /* how to send breaks */ kcondvar_t og_ack_cv; /* Wait for ACK */ /* File-system level state */ @@ -614,7 +615,7 @@ typedef struct smb_oplock_grant { typedef struct smb_lease { list_node_t ls_lnd; /* sv_lease_ht */ - kmutex_t ls_mutex; + kmutex_t ls_mutex; /* for ls_refcnt */ smb_llist_t *ls_bucket; struct smb_node *ls_node; /* @@ -623,10 +624,11 @@ typedef struct smb_lease { */ void *ls_oplock_ofile; uint32_t ls_refcnt; - uint32_t ls_state; - uint32_t ls_breaking; /* BREAK_TO... flags */ + uint32_t ls_state; /* what client has now */ + uint32_t ls_breakto; /* level breaking to */ uint16_t ls_epoch; uint16_t ls_version; + boolean_t ls_breaking; kcondvar_t ls_ack_cv; /* Wait for ACK */ uint8_t ls_key[SMB_LEASE_KEY_SZ]; uint8_t ls_clnt[SMB_LEASE_KEY_SZ]; -- cgit v1.2.3