/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2020 Nexenta by DDN, Inc. All rights reserved. * Copyright 2022 RackTop Systems, Inc. */ /* * (SMB1/SMB2) common (FS-level) Oplock support. * * This is the file-system (FS) level oplock code. This level * knows about the rules by which various kinds of oplocks may * coexist and how they interact. Note that this code should * have NO knowledge of specific SMB protocol details. Those * details are handled in smb_srv_oplock.c and related. * * This file is intentionally written to very closely follow the * [MS-FSA] specification sections about oplocks. Almost every * section of code is preceeded by a block of text from that * specification describing the logic. Where the implementation * differs from what the spec. describes, there are notes like: * Implementation specific: ... */ #include #include /* * Several short-hand defines and enums used in this file. */ #define NODE_FLAGS_DELETING (NODE_FLAGS_DELETE_ON_CLOSE |\ NODE_FLAGS_DELETE_COMMITTED) static uint32_t smb_oplock_req_excl( smb_ofile_t *ofile, /* in: the "Open" */ uint32_t *rop); /* in: "RequestedOplock", out:NewOplockLevel */ static uint32_t smb_oplock_req_shared( smb_ofile_t *ofile, /* the "Open" */ uint32_t *rop, /* in: "RequestedOplock", out:NewOplockLevel */ boolean_t GrantingInAck); static uint32_t smb_oplock_break_cmn(smb_node_t *node, smb_ofile_t *ofile, uint32_t BreakCacheLevel); /* * [MS-FSA] 2.1.4.12.2 Algorithm to Compare Oplock Keys * * The inputs for this algorithm are: * * OperationOpen: The Open used in the request that can * cause an oplock to break. * OplockOpen: The Open originally used to request the oplock, * as specified in section 2.1.5.17. * Flags: If unspecified it is considered to contain 0. * Valid nonzero values are: * PARENT_OBJECT * * This algorithm returns TRUE if the appropriate oplock key field of * OperationOpen equals OplockOpen.TargetOplockKey, and FALSE otherwise. * * Note: Unlike many comparison functions, ARG ORDER MATTERS. */ static boolean_t CompareOplockKeys(smb_ofile_t *OperOpen, smb_ofile_t *OplockOpen, int flags) { static const uint8_t key0[SMB_LEASE_KEY_SZ] = { 0 }; /* * When we're called via FEM, (smb_oplock_break_...) * the OperOpen arg is NULL because I/O outside of SMB * doesn't have an "ofile". That's "not a match". */ if (OperOpen == NULL) return (B_FALSE); ASSERT(OplockOpen != NULL); /* * If OperationOpen equals OplockOpen: * Return TRUE. */ if (OperOpen == OplockOpen) return (B_TRUE); /* * If both OperationOpen.TargetOplockKey and * OperationOpen.ParentOplockKey are empty * or both OplockOpen.TargetOplockKey and * OplockOpen.ParentOplockKey are empty: * Return FALSE. */ if (bcmp(OperOpen->TargetOplockKey, key0, sizeof (key0)) == 0 && bcmp(OperOpen->ParentOplockKey, key0, sizeof (key0)) == 0) return (B_FALSE); if (bcmp(OplockOpen->TargetOplockKey, key0, sizeof (key0)) == 0 && bcmp(OplockOpen->ParentOplockKey, key0, sizeof (key0)) == 0) return (B_FALSE); /* * If OplockOpen.TargetOplockKey is empty or... */ if (bcmp(OplockOpen->TargetOplockKey, key0, sizeof (key0)) == 0) return (B_FALSE); /* * If Flags contains PARENT_OBJECT: */ if ((flags & PARENT_OBJECT) != 0) { /* * If OperationOpen.ParentOplockKey is empty: * Return FALSE. */ if (bcmp(OperOpen->ParentOplockKey, key0, sizeof (key0)) == 0) return (B_FALSE); /* * If OperationOpen.ParentOplockKey equals * OplockOpen.TargetOplockKey: * return TRUE, else FALSE */ if (bcmp(OperOpen->ParentOplockKey, OplockOpen->TargetOplockKey, SMB_LEASE_KEY_SZ) == 0) { return (B_TRUE); } } else { /* * ... from above: * (Flags does not contain PARENT_OBJECT and * OperationOpen.TargetOplockKey is empty): * Return FALSE. */ if (bcmp(OperOpen->TargetOplockKey, key0, sizeof (key0)) == 0) return (B_FALSE); /* * If OperationOpen.TargetOplockKey equals * OplockOpen.TargetOplockKey: * Return TRUE, else FALSE */ if (bcmp(OperOpen->TargetOplockKey, OplockOpen->TargetOplockKey, SMB_LEASE_KEY_SZ) == 0) { return (B_TRUE); } } return (B_FALSE); } /* * 2.1.4.13 Algorithm to Recompute the State of a Shared Oplock * * The inputs for this algorithm are: * ThisOplock: The Oplock on whose state is being recomputed. */ static void RecomputeOplockState(smb_node_t *node) { smb_oplock_t *ol = &node->n_oplock; ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock)); ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex)); /* * If ThisOplock.IIOplocks, ThisOplock.ROplocks, ThisOplock.RHOplocks, * and ThisOplock.RHBreakQueue are all empty: * Set ThisOplock.State to NO_OPLOCK. */ if (ol->cnt_II == 0 && ol->cnt_R == 0 && ol->cnt_RH == 0 && ol->cnt_RHBQ == 0) { ol->ol_state = NO_OPLOCK; return; } /* * Else If ThisOplock.ROplocks is not empty and either * ThisOplock.RHOplocks or ThisOplock.RHBreakQueue are not empty: * Set ThisOplock.State to * (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH). */ else if (ol->cnt_R != 0 && (ol->cnt_RH != 0 || ol->cnt_RHBQ != 0)) { ol->ol_state = (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH); } /* * Else If ThisOplock.ROplocks is empty and * ThisOplock.RHOplocks is not empty: * Set ThisOplock.State to (READ_CACHING|HANDLE_CACHING). */ else if (ol->cnt_R == 0 && ol->cnt_RH != 0) { ol->ol_state = (READ_CACHING|HANDLE_CACHING); } /* * Else If ThisOplock.ROplocks is not empty and * ThisOplock.IIOplocks is not empty: * Set ThisOplock.State to (READ_CACHING|LEVEL_TWO_OPLOCK). */ else if (ol->cnt_R != 0 && ol->cnt_II != 0) { ol->ol_state = (READ_CACHING|LEVEL_TWO_OPLOCK); } /* * Else If ThisOplock.ROplocks is not empty and * ThisOplock.IIOplocks is empty: * Set ThisOplock.State to READ_CACHING. */ else if (ol->cnt_R != 0 && ol->cnt_II == 0) { ol->ol_state = READ_CACHING; } /* * Else If ThisOplock.ROplocks is empty and * ThisOplock.IIOplocks is not empty: * Set ThisOplock.State to LEVEL_TWO_OPLOCK. */ else if (ol->cnt_R == 0 && ol->cnt_II != 0) { ol->ol_state = LEVEL_TWO_OPLOCK; } else { smb_ofile_t *o; int cntBrkToRead; /* * ThisOplock.RHBreakQueue MUST be non-empty by this point. */ ASSERT(ol->cnt_RHBQ != 0); /* * How many on RHBQ have BreakingToRead set? */ cntBrkToRead = 0; FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_RHBQ == 0) continue; if (o->f_oplock.BreakingToRead) cntBrkToRead++; } /* * If RHOpContext.BreakingToRead is TRUE for * every RHOpContext on ThisOplock.RHBreakQueue: */ if (cntBrkToRead == ol->cnt_RHBQ) { /* * Set ThisOplock.State to * (READ_CACHING|HANDLE_CACHING|BREAK_TO_READ_CACHING). */ ol->ol_state = (READ_CACHING|HANDLE_CACHING| BREAK_TO_READ_CACHING); } /* * Else If RHOpContext.BreakingToRead is FALSE for * every RHOpContext on ThisOplock.RHBreakQueue: */ else if (cntBrkToRead == 0) { /* * Set ThisOplock.State to * (READ_CACHING|HANDLE_CACHING|BREAK_TO_NO_CACHING). */ ol->ol_state = (READ_CACHING|HANDLE_CACHING| BREAK_TO_NO_CACHING); } else { /* * Set ThisOplock.State to * (READ_CACHING|HANDLE_CACHING). */ ol->ol_state = (READ_CACHING|HANDLE_CACHING); } } } /* * [MS-FSA] 2.1.5.17 Server Requests an Oplock * * The server (caller) provides: * Open - The Open on which the oplock is being requested. (ofile) * Type - The type of oplock being requested. Valid values are as follows: * LEVEL_TWO (Corresponds to SMB2_OPLOCK_LEVEL_II) * LEVEL_ONE (Corresponds to SMB2_OPLOCK_LEVEL_EXCLUSIVE) * LEVEL_BATCH (Corresponds to SMB2_OPLOCK_LEVEL_BATCH) * LEVEL_GRANULAR (Corresponds to SMB2_OPLOCK_LEVEL_LEASE) * RequestedOplockLevel - A combination of zero or more of the * following flags (ignored if Type != LEVEL_GRANULAR) * READ_CACHING * HANDLE_CACHING * WRITE_CACHING * * (Type + RequestedOplockLevel come in *statep) * * Returns: * *statep = NewOplockLevel (possibly less than requested) * containing: LEVEL_NONE, LEVEL_TWO + cache_flags * NTSTATUS */ 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; /* * If Open.Stream.StreamType is DirectoryStream: * The operation MUST be failed with STATUS_INVALID_PARAMETER * under either of the following conditions: * * Type is not LEVEL_GRANULAR. * * Type is LEVEL_GRANULAR but RequestedOplockLevel is * neither READ_CACHING nor (READ_CACHING|HANDLE_CACHING). */ if (!smb_node_is_file(node)) { /* ofile is a directory. */ if (type != LEVEL_GRANULAR) return (NT_STATUS_INVALID_PARAMETER); if (level != READ_CACHING && level != (READ_CACHING|HANDLE_CACHING)) return (NT_STATUS_INVALID_PARAMETER); /* * We're not supporting directory leases yet. * Todo. */ return (NT_STATUS_OPLOCK_NOT_GRANTED); } /* * If Type is LEVEL_ONE or LEVEL_BATCH: * The operation MUST be failed with STATUS_OPLOCK_NOT_GRANTED * under either of the following conditions: * Open.File.OpenList contains more than one Open * whose Stream is the same as Open.Stream. * Open.Mode contains either FILE_SYNCHRONOUS_IO_ALERT or * FILE_SYNCHRONOUS_IO_NONALERT. * Request an exclusive oplock according to the algorithm in * section 2.1.5.17.1, setting the algorithm's params as follows: * Pass in the current Open. * RequestedOplock = Type. * The operation MUST at this point return any status code * returned by the exclusive oplock request algorithm. */ if (type == LEVEL_ONE || type == LEVEL_BATCH) { if (node->n_open_count > 1) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* XXX: Should be a flag on the ofile. */ if (node->flags & NODE_FLAGS_WRITE_THROUGH) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } *statep = type; status = smb_oplock_req_excl(ofile, statep); goto out; } /* * Else If Type is LEVEL_TWO: * The operation MUST be failed with STATUS_OPLOCK_NOT_GRANTED under * either of the following conditions: * Open.Stream.ByteRangeLockList is not empty. * Open.Mode contains either FILE_SYNCHRONOUS_IO_ALERT or * FILE_SYNCHRONOUS_IO_NONALERT. * Request a shared oplock according to the algorithm in * section 2.1.5.17.2, setting the algorithm's parameters as follows: * Pass in the current Open. * RequestedOplock = Type. * GrantingInAck = FALSE. * The operation MUST at this point return any status code * returned by the shared oplock request algorithm. */ if (type == LEVEL_TWO) { if (smb_lock_range_access(sr, node, 0, ~0, B_FALSE) != 0) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* XXX: Should be a flag on the ofile. */ if (node->flags & NODE_FLAGS_WRITE_THROUGH) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } *statep = type; status = smb_oplock_req_shared(ofile, statep, B_FALSE); goto out; } /* * Else If Type is LEVEL_GRANULAR: * Sub-cases on RequestedOplockLevel (our "level") * * This is the last Type, so error on !granular and then * deal with the cache levels using one less indent. */ if (type != LEVEL_GRANULAR) { status = NT_STATUS_INVALID_PARAMETER; goto out; } switch (level) { /* * If RequestedOplockLevel is READ_CACHING or * (READ_CACHING|HANDLE_CACHING): * The operation MUST be failed with STATUS_OPLOCK_NOT_GRANTED * under either of the following conditions: * Open.Stream.ByteRangeLockList is not empty. * Open.Mode contains either FILE_SYNCHRONOUS_IO_ALERT or * FILE_SYNCHRONOUS_IO_NONALERT. * Request a shared oplock according to the algorithm in * section 2.1.5.17.2, setting the parameters as follows: * Pass in the current Open. * RequestedOplock = RequestedOplockLevel. * GrantingInAck = FALSE. * * The operation MUST at this point return any status code * returned by the shared oplock request algorithm. */ case READ_CACHING: case (READ_CACHING|HANDLE_CACHING): if (smb_lock_range_access(sr, node, 0, ~0, B_FALSE) != 0) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* XXX: Should be a flag on the ofile. */ if (node->flags & NODE_FLAGS_WRITE_THROUGH) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } *statep = level; status = smb_oplock_req_shared(ofile, statep, B_FALSE); break; /* * Else If RequestedOplockLevel is * (READ_CACHING|WRITE_CACHING) or * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING): * If Open.Mode contains either FILE_SYNCHRONOUS_IO_ALERT or * FILE_SYNCHRONOUS_IO_NONALERT, the operation MUST be failed * with STATUS_OPLOCK_NOT_GRANTED. * Request an exclusive oplock according to the algorithm in * section 2.1.5.17.1, setting the parameters as follows: * Pass in the current Open. * RequestedOplock = RequestedOplockLevel. * The operation MUST at this point return any status code * returned by the exclusive oplock request algorithm. */ case (READ_CACHING | WRITE_CACHING): case (READ_CACHING | WRITE_CACHING | HANDLE_CACHING): /* XXX: Should be a flag on the ofile. */ if (node->flags & NODE_FLAGS_WRITE_THROUGH) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } *statep = level; status = smb_oplock_req_excl(ofile, statep); break; /* * Else if RequestedOplockLevel is 0 (that is, no flags): * The operation MUST return STATUS_SUCCESS at this point. */ case 0: *statep = 0; status = NT_STATUS_SUCCESS; break; /* * Else * The operation MUST be failed with STATUS_INVALID_PARAMETER. */ default: status = NT_STATUS_INVALID_PARAMETER; break; } /* * 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; } out: return (status); } /* * 2.1.5.17.1 Algorithm to Request an Exclusive Oplock * * The inputs for requesting an exclusive oplock are: * Open: The Open on which the oplock is being requested. * RequestedOplock: The oplock type being requested. One of: * LEVEL_ONE, LEVEL_BATCH, CACHE_RW, CACHE_RWH * * On completion, the object store MUST return: * Status: An NTSTATUS code that specifies the result. * NewOplockLevel: The type of oplock that the requested oplock has been * broken (reduced) to. If a failure status is returned in Status, * the value of this field is undefined. Valid values are as follows: * LEVEL_NONE (that is, no oplock) * LEVEL_TWO * A combination of one or more of the following flags: * READ_CACHING * HANDLE_CACHING * WRITE_CACHING * AcknowledgeRequired: A Boolean value: TRUE if the server MUST * acknowledge the oplock break; FALSE if not, as specified in * section 2.1.5.18. If a failure status is returned in Status, * the value of this field is undefined. * * Note: Stores NewOplockLevel in *rop */ static uint32_t smb_oplock_req_excl( smb_ofile_t *ofile, /* in: the "Open" */ uint32_t *rop) /* in: "RequestedOplock", out:NewOplockLevel */ { smb_node_t *node = ofile->f_node; smb_ofile_t *o; boolean_t GrantExcl = B_FALSE; uint32_t status = NT_STATUS_OPLOCK_NOT_GRANTED; 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. */ if (ofile->f_oplock_closing) return (status); /* * If Open.Stream.Oplock is empty: * Build a new Oplock object with fields initialized as follows: * Oplock.State set to NO_OPLOCK. * All other fields set to 0/empty. * Store the new Oplock object in Open.Stream.Oplock. * EndIf * * Implementation specific: * Open.Stream.Oplock maps to: node->n_oplock */ if (node->n_oplock.ol_state == 0) { node->n_oplock.ol_state = NO_OPLOCK; } /* * If Open.Stream.Oplock.State contains * LEVEL_TWO_OPLOCK or NO_OPLOCK: ... * * Per ms, this is the "If" matching the unbalalanced * "Else If" below (for which we requested clarification). */ if ((node->n_oplock.ol_state & (LEVEL_TWO | NO_OPLOCK)) != 0) { /* * If Open.Stream.Oplock.State contains LEVEL_TWO_OPLOCK and * RequestedOplock contains one or more of READ_CACHING, * HANDLE_CACHING, or WRITE_CACHING, the operation MUST be * failed with Status set to STATUS_OPLOCK_NOT_GRANTED. */ if ((node->n_oplock.ol_state & LEVEL_TWO) != 0 && (*rop & CACHE_RWH) != 0) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* * [ from dochelp@ms ] * * By this point if there is a level II oplock present, * the caller can only be requesting an old-style oplock * because we rejected enhanced oplock requests above. * If the caller is requesting an old-style oplock our * caller already verfied that there is only one handle * open to this stream, and we've already verified that * this request is for a legacy oplock, meaning that there * can be at most one level II oplock (and no R oplocks), * and the level II oplock belongs to this handle. Clear * the level II oplock and grant the exclusive oplock. */ /* * If Open.Stream.Oplock.State is equal to LEVEL_TWO_OPLOCK: * Remove the first Open ThisOpen from * Open.Stream.Oplock.IIOplocks (there is supposed to be * exactly one present), and notify the server of an * oplock break according to the algorithm in section * 2.1.5.17.3, setting the algorithm's parameters as follows: * BreakingOplockOpen = ThisOpen. * NewOplockLevel = LEVEL_NONE. * AcknowledgeRequired = FALSE. * OplockCompletionStatus = STATUS_SUCCESS. * (The operation does not end at this point; this call * to 2.1.5.17.3 completes some earlier call to 2.1.5.17.2.) * * Implementation specific: * * As explained above, the passed in ofile should be the * only open file on this node. Out of caution, we'll * walk the ofile list as usual here, making sure there * are no LevelII oplocks remaining, as those may not * coexist with the exclusive oplock were're creating * in this call. Also, if the passed in ofile has a * LevelII oplock, don't do an "ind break" up call on * this ofile, as that would just cause an immediate * "break to none" of the oplock we'll grant here. * If there were other ofiles with LevelII oplocks, * it would be appropriate to "ind break" those. */ if ((node->n_oplock.ol_state & LEVEL_TWO) != 0) { FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_II == 0) continue; o->f_oplock.onlist_II = B_FALSE; node->n_oplock.cnt_II--; ASSERT(node->n_oplock.cnt_II >= 0); if (o == ofile) continue; DTRACE_PROBE1(unexpected, smb_ofile_t *, o); smb_oplock_ind_break(o, LEVEL_NONE, B_FALSE, NT_STATUS_SUCCESS); } } /* * Note the spec. had an extra "EndIf" here. * Confirmed by dochelp@ms */ /* * If Open.File.OpenList contains more than one Open whose * Stream is the same as Open.Stream, and NO_OPLOCK is present * in Open.Stream.Oplock.State, the operation MUST be failed * with Status set to STATUS_OPLOCK_NOT_GRANTED. * * Implementation specific: * Allow other opens if they have the same lease ours, * so we can upgrade RH to RWH (for example). Therefore * only count opens with a different TargetOplockKey. * Also ignore "attribute-only" opens. */ if ((node->n_oplock.ol_state & NO_OPLOCK) != 0) { FOREACH_NODE_OFILE(node, o) { if (!smb_ofile_is_open(o)) continue; if ((o->f_granted_access & FILE_DATA_ALL) == 0) continue; if (!CompareOplockKeys(ofile, o, 0)) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } } } /* * If Open.Stream.IsDeleted is TRUE and RequestedOplock * contains HANDLE_CACHING, the operation MUST be failed * with Status set to STATUS_OPLOCK_NOT_GRANTED. */ if (((node->flags & NODE_FLAGS_DELETING) != 0) && (*rop & HANDLE_CACHING) != 0) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* Set GrantExclusiveOplock to TRUE. */ GrantExcl = B_TRUE; } /* * "Else" If (Open.Stream.Oplock.State contains one or more of * READ_CACHING, WRITE_CACHING, or HANDLE_CACHING) and * (Open.Stream.Oplock.State contains none of (BREAK_ANY)) and * (Open.Stream.Oplock.RHBreakQueue is empty): */ else if ((node->n_oplock.ol_state & CACHE_RWH) != 0 && (node->n_oplock.ol_state & BREAK_ANY) == 0 && node->n_oplock.cnt_RHBQ == 0) { /* * This is a granular oplock and it is not breaking. */ /* * If RequestedOplock contains none of READ_CACHING, * WRITE_CACHING, or HANDLE_CACHING, the operation * MUST be failed with Status set to * STATUS_OPLOCK_NOT_GRANTED. */ if ((*rop & CACHE_RWH) == 0) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* * If Open.Stream.IsDeleted (already checked above) */ /* * Switch (Open.Stream.Oplock.State): */ switch (node->n_oplock.ol_state) { case CACHE_R: /* * If RequestedOplock is neither * (READ_CACHING|WRITE_CACHING) nor * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING), * the operation MUST be failed with Status set * to STATUS_OPLOCK_NOT_GRANTED. */ if (*rop != CACHE_RW && *rop != CACHE_RWH) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* * For each Open ThisOpen in * Open.Stream.Oplock.ROplocks: * If ThisOpen.TargetOplockKey != * Open.TargetOplockKey, the operation * MUST be failed with Status set to * STATUS_OPLOCK_NOT_GRANTED. * EndFor */ FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_R == 0) continue; if (!CompareOplockKeys(ofile, o, 0)) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } } /* * For each Open o in Open.Stream.Oplock.ROplocks: * Remove o from Open.Stream.Oplock.ROplocks. * Notify the server of an oplock break * according to the algorithm in section * 2.1.5.17.3, setting the algorithm's * parameters as follows: * BreakingOplockOpen = o. * NewOplockLevel = RequestedOplock. * AcknowledgeRequired = FALSE. * OplockCompletionStatus = * STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE. * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.2.) * EndFor * * Note: Upgrade to excl. on same lease. * Won't send a break for this. */ FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_R == 0) continue; o->f_oplock.onlist_R = B_FALSE; node->n_oplock.cnt_R--; ASSERT(node->n_oplock.cnt_R >= 0); smb_oplock_ind_break(o, *rop, B_FALSE, STATUS_NEW_HANDLE); } /* * Set GrantExclusiveOplock to TRUE. * EndCase // _R */ GrantExcl = B_TRUE; break; case CACHE_RH: /* * If RequestedOplock is not * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING) * or Open.Stream.Oplock.RHBreakQueue is not empty, * the operation MUST be failed with Status set to * STATUS_OPLOCK_NOT_GRANTED. * Note: Have RHBreakQueue==0 from above. */ if (*rop != CACHE_RWH) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* * For each Open ThisOpen in * Open.Stream.Oplock.RHOplocks: * If ThisOpen.TargetOplockKey != * Open.TargetOplockKey, the operation * MUST be failed with Status set to * STATUS_OPLOCK_NOT_GRANTED. * EndFor */ FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_RH == 0) continue; if (!CompareOplockKeys(ofile, o, 0)) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } } /* * For each Open o in Open.Stream.Oplock.RHOplocks: * Remove o from Open.Stream.Oplock.RHOplocks. * Notify the server of an oplock break * according to the algorithm in section * 2.1.5.17.3, setting the algorithm's * parameters as follows: * BreakingOplockOpen = o. * NewOplockLevel = RequestedOplock. * AcknowledgeRequired = FALSE. * OplockCompletionStatus = * STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE. * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.2.) * EndFor * * Note: Upgrade to excl. on same lease. * Won't send a break for this. */ FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_RH == 0) continue; o->f_oplock.onlist_RH = B_FALSE; node->n_oplock.cnt_RH--; ASSERT(node->n_oplock.cnt_RH >= 0); smb_oplock_ind_break(o, *rop, B_FALSE, STATUS_NEW_HANDLE); } /* * Set GrantExclusiveOplock to TRUE. * EndCase // _RH */ GrantExcl = B_TRUE; break; case (CACHE_RWH | EXCLUSIVE): /* * If RequestedOplock is not * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING), * the operation MUST be failed with Status set to * STATUS_OPLOCK_NOT_GRANTED. */ if (*rop != CACHE_RWH) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* Deliberate FALL-THROUGH to next Case statement. */ /* FALLTHROUGH */ case (CACHE_RW | EXCLUSIVE): /* * If RequestedOplock is neither * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING) nor * (READ_CACHING|WRITE_CACHING), the operation MUST be * failed with Status set to STATUS_OPLOCK_NOT_GRANTED. */ if (*rop != CACHE_RWH && *rop != CACHE_RW) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } o = node->n_oplock.excl_open; if (o == NULL) { ASSERT(0); GrantExcl = B_TRUE; break; } /* * If Open.TargetOplockKey != * Open.Stream.Oplock.ExclusiveOpen.TargetOplockKey, * the operation MUST be failed with Status set to * STATUS_OPLOCK_NOT_GRANTED. */ if (!CompareOplockKeys(ofile, o, 0)) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* * Notify the server of an oplock break according to * the algorithm in section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = * Open.Stream.Oplock.ExclusiveOpen. * NewOplockLevel = RequestedOplock. * AcknowledgeRequired = FALSE. * OplockCompletionStatus = * STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE. * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.1.) * * Set Open.Stream.Oplock.ExclusiveOpen to NULL. * Set GrantExclusiveOplock to TRUE. * * Note: We will keep this exclusive oplock, * but move it to a new handle on this lease. * Won't send a break for this. */ smb_oplock_ind_break(o, *rop, B_FALSE, STATUS_NEW_HANDLE); node->n_oplock.excl_open = o = NULL; GrantExcl = B_TRUE; break; default: /* * The operation MUST be failed with Status set to * STATUS_OPLOCK_NOT_GRANTED. */ status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* switch n_oplock.ol_state */ } /* EndIf CACHE_RWH & !BREAK_ANY... */ else { /* * The operation MUST be failed with... */ status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* * If GrantExclusiveOplock is TRUE: * * Set Open.Stream.Oplock.ExclusiveOpen = Open. * Set Open.Stream.Oplock.State = * (RequestedOplock|EXCLUSIVE). */ if (GrantExcl) { node->n_oplock.excl_open = ofile; node->n_oplock.ol_state = *rop | EXCLUSIVE; /* * This operation MUST be made cancelable... * This operation waits until the oplock is * broken or canceled, as specified in * section 2.1.5.17.3. Note: This function * does not cause breaks that require a wait, * so never returns ..._BREAK_IN_PROGRESS. * * When the operation specified in section * 2.1.5.17.3 is called, its following input * parameters are transferred to this routine * and then returned by it: * * Status is set to OplockCompletionStatus * NewOplockLevel, AcknowledgeRequired... * from the operation specified in * section 2.1.5.17.3. */ /* Keep *rop = ... from caller. */ status = NT_STATUS_SUCCESS; /* * First oplock grant installs FEM hooks. */ if (node->n_oplock.ol_fem == B_FALSE) { if (smb_fem_oplock_install(node) != 0) { cmn_err(CE_NOTE, "smb_fem_oplock_install failed"); } else { node->n_oplock.ol_fem = B_TRUE; } } } out: if (status == NT_STATUS_OPLOCK_NOT_GRANTED) *rop = LEVEL_NONE; return (status); } /* * 2.1.5.17.2 Algorithm to Request a Shared Oplock * * The inputs for requesting a shared oplock are: * Open: The Open on which the oplock is being requested. * RequestedOplock: The oplock type being requested. * GrantingInAck: A Boolean value, TRUE if this oplock is being * requested as part of an oplock break acknowledgement, * FALSE if not. * * On completion, the object store MUST return: * Status: An NTSTATUS code that specifies the result. * NewOplockLevel: The type of oplock that the requested oplock has been * broken (reduced) to. If a failure status is returned in Status, * the value of this field is undefined. Valid values are as follows: * LEVEL_NONE (that is, no oplock) * LEVEL_TWO * A combination of one or more of the following flags: * READ_CACHING * HANDLE_CACHING * WRITE_CACHING * AcknowledgeRequired: A Boolean value: TRUE if the server MUST * acknowledge the oplock break; FALSE if not, as specified in * section 2.1.5.18. If a failure status is returned in Status, * the value of this field is undefined. * * Note: Stores NewOplockLevel in *rop */ static uint32_t smb_oplock_req_shared( smb_ofile_t *ofile, /* in: the "Open" */ uint32_t *rop, /* in: "RequestedOplock", out:NewOplockLevel */ boolean_t GrantingInAck) { smb_node_t *node = ofile->f_node; smb_ofile_t *o; boolean_t OplockGranted = B_FALSE; uint32_t status = NT_STATUS_OPLOCK_NOT_GRANTED; 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. */ if (ofile->f_oplock_closing) return (status); /* * If Open.Stream.Oplock is empty: * Build a new Oplock object with fields initialized as follows: * Oplock.State set to NO_OPLOCK. * All other fields set to 0/empty. * Store the new Oplock object in Open.Stream.Oplock. * EndIf * * Implementation specific: * Open.Stream.Oplock maps to: node->n_oplock */ if (node->n_oplock.ol_state == 0) { node->n_oplock.ol_state = NO_OPLOCK; } /* * If (GrantingInAck is FALSE) and (Open.Stream.Oplock.State * contains one or more of BREAK_TO_TWO, BREAK_TO_NONE, * BREAK_TO_TWO_TO_NONE, BREAK_TO_READ_CACHING, * BREAK_TO_WRITE_CACHING, BREAK_TO_HANDLE_CACHING, * BREAK_TO_NO_CACHING, or EXCLUSIVE), then: * The operation MUST be failed with Status set to * STATUS_OPLOCK_NOT_GRANTED. * EndIf */ if (GrantingInAck == B_FALSE && (node->n_oplock.ol_state & (BREAK_ANY | EXCLUSIVE)) != 0) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* Switch (RequestedOplock): */ switch (*rop) { case LEVEL_TWO: /* * The operation MUST be failed with Status set to * STATUS_OPLOCK_NOT_GRANTED if Open.Stream.Oplock.State * is anything other than the following: * NO_OPLOCK * LEVEL_TWO_OPLOCK * READ_CACHING * (LEVEL_TWO_OPLOCK|READ_CACHING) */ switch (node->n_oplock.ol_state) { default: status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; case NO_OPLOCK: case LEVEL_TWO: case READ_CACHING: case (LEVEL_TWO | READ_CACHING): break; } /* Deliberate FALL-THROUGH to next Case statement. */ /* FALLTHROUGH */ case READ_CACHING: /* * The operation MUST be failed with Status set to * STATUS_OPLOCK_NOT_GRANTED if GrantingInAck is FALSE * and Open.Stream.Oplock.State is anything other than... */ switch (node->n_oplock.ol_state) { default: if (GrantingInAck == B_FALSE) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } break; case NO_OPLOCK: case LEVEL_TWO: case READ_CACHING: case (LEVEL_TWO | READ_CACHING): case (READ_CACHING | HANDLE_CACHING): case (READ_CACHING | HANDLE_CACHING | MIXED_R_AND_RH): case (READ_CACHING | HANDLE_CACHING | BREAK_TO_READ_CACHING): case (READ_CACHING | HANDLE_CACHING | BREAK_TO_NO_CACHING): break; } if (GrantingInAck == B_FALSE) { /* * If there is an Open on * Open.Stream.Oplock.RHOplocks * whose TargetOplockKey is equal to * Open.TargetOplockKey, the operation * MUST be failed with Status set to * STATUS_OPLOCK_NOT_GRANTED. * * If there is an Open on * Open.Stream.Oplock.RHBreakQueue * whose TargetOplockKey is equal to * Open.TargetOplockKey, the operation * MUST be failed with Status set to * STATUS_OPLOCK_NOT_GRANTED. * * Implement both in one list walk. */ FOREACH_NODE_OFILE(node, o) { if ((o->f_oplock.onlist_RH || o->f_oplock.onlist_RHBQ) && CompareOplockKeys(ofile, o, 0)) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } } /* * If there is an Open ThisOpen on * Open.Stream.Oplock.ROplocks whose * TargetOplockKey is equal to Open.TargetOplockKey * (there is supposed to be at most one present): * * Remove ThisOpen from Open...ROplocks. * * Notify the server of an oplock break * according to the algorithm in section * 2.1.5.17.3, setting the algorithm's * parameters as follows: * * BreakingOplockOpen = ThisOpen * * NewOplockLevel = READ_CACHING * * AcknowledgeRequired = FALSE * * OplockCompletionStatus = * STATUS_..._NEW_HANDLE * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.2.) * EndIf * * If this SMB2 lease already has an "R" handle, * we'll update that lease locally to point to * this new handle. */ FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_R == 0) continue; if (CompareOplockKeys(ofile, o, 0)) { o->f_oplock.onlist_R = B_FALSE; node->n_oplock.cnt_R--; ASSERT(node->n_oplock.cnt_R >= 0); smb_oplock_ind_break(o, CACHE_R, B_FALSE, STATUS_NEW_HANDLE); } } } /* EndIf !GrantingInAck */ /* * If RequestedOplock equals LEVEL_TWO: * Add Open to Open.Stream.Oplock.IIOplocks. * Else // RequestedOplock equals READ_CACHING: * Add Open to Open.Stream.Oplock.ROplocks. * EndIf */ if (*rop == LEVEL_TWO) { ofile->f_oplock.onlist_II = B_TRUE; node->n_oplock.cnt_II++; } else { /* (*rop == READ_CACHING) */ if (ofile->f_oplock.onlist_R == B_FALSE) { ofile->f_oplock.onlist_R = B_TRUE; node->n_oplock.cnt_R++; } } /* * Recompute Open.Stream.Oplock.State according to the * algorithm in section 2.1.4.13, passing Open.Stream.Oplock * as the ThisOplock parameter. * Set OplockGranted to TRUE. */ RecomputeOplockState(node); OplockGranted = B_TRUE; break; case (READ_CACHING|HANDLE_CACHING): /* * The operation MUST be failed with Status set to * STATUS_OPLOCK_NOT_GRANTED if GrantingInAck is FALSE * and Open.Stream.Oplock.State is anything other than... */ switch (node->n_oplock.ol_state) { default: if (GrantingInAck == B_FALSE) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } break; case NO_OPLOCK: case READ_CACHING: case (READ_CACHING | HANDLE_CACHING): case (READ_CACHING | HANDLE_CACHING | MIXED_R_AND_RH): case (READ_CACHING | HANDLE_CACHING | BREAK_TO_READ_CACHING): case (READ_CACHING | HANDLE_CACHING | BREAK_TO_NO_CACHING): break; } /* * If Open.Stream.IsDeleted is TRUE, the operation MUST be * failed with Status set to STATUS_OPLOCK_NOT_GRANTED. */ if ((node->flags & NODE_FLAGS_DELETING) != 0) { status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } if (GrantingInAck == B_FALSE) { /* * If there is an Open ThisOpen on * Open.Stream.Oplock.ROplocks whose * TargetOplockKey is equal to Open.TargetOplockKey * (there is supposed to be at most one present): * * Remove ThisOpen from Open...ROplocks. * * Notify the server of an oplock break * according to the algorithm in section * 2.1.5.17.3, setting the algorithm's * parameters as follows: * * BreakingOplockOpen = ThisOpen * * NewOplockLevel = CACHE_RH * * AcknowledgeRequired = FALSE * * OplockCompletionStatus = * STATUS_..._NEW_HANDLE * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.2.) * EndIf * * If this SMB2 lease already has an "R" handle, * we'll update that lease locally to point to * this new handle (upgrade to "RH"). */ FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_R == 0) continue; if (CompareOplockKeys(ofile, o, 0)) { o->f_oplock.onlist_R = B_FALSE; node->n_oplock.cnt_R--; ASSERT(node->n_oplock.cnt_R >= 0); smb_oplock_ind_break(o, CACHE_RH, B_FALSE, STATUS_NEW_HANDLE); } } /* * If there is an Open ThisOpen on * Open.Stream.Oplock.RHOplocks whose * TargetOplockKey is equal to Open.TargetOplockKey * (there is supposed to be at most one present): * XXX: Note, the spec. was missing a step: * XXX: Remove the open from RHOplocks * XXX: Confirm with MS dochelp * * Notify the server of an oplock break * according to the algorithm in section * 2.1.5.17.3, setting the algorithm's * parameters as follows: * * BreakingOplockOpen = ThisOpen * * NewOplockLevel = * (READ_CACHING|HANDLE_CACHING) * * AcknowledgeRequired = FALSE * * OplockCompletionStatus = * STATUS_..._NEW_HANDLE * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.2.) * EndIf * * If this SMB2 lease already has an "RH" handle, * we'll update that lease locally to point to * this new handle. */ FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_RH == 0) continue; if (CompareOplockKeys(ofile, o, 0)) { o->f_oplock.onlist_RH = B_FALSE; node->n_oplock.cnt_RH--; ASSERT(node->n_oplock.cnt_RH >= 0); smb_oplock_ind_break(o, CACHE_RH, B_FALSE, STATUS_NEW_HANDLE); } } } /* EndIf !GrantingInAck */ /* * Add Open to Open.Stream.Oplock.RHOplocks. */ if (ofile->f_oplock.onlist_RH == B_FALSE) { ofile->f_oplock.onlist_RH = B_TRUE; node->n_oplock.cnt_RH++; } /* * Recompute Open.Stream.Oplock.State according to the * algorithm in section 2.1.4.13, passing Open.Stream.Oplock * as the ThisOplock parameter. * Set OplockGranted to TRUE. */ RecomputeOplockState(node); OplockGranted = B_TRUE; break; default: /* No other value of RequestedOplock is possible. */ ASSERT(0); status = NT_STATUS_OPLOCK_NOT_GRANTED; goto out; } /* EndSwitch (RequestedOplock) */ /* * If OplockGranted is TRUE: * This operation MUST be made cancelable by inserting it into * CancelableOperations.CancelableOperationList. * The operation waits until the oplock is broken or canceled, * as specified in section 2.1.5.17.3. * When the operation specified in section 2.1.5.17.3 is called, * its following input parameters are transferred to this routine * and returned by it: * Status is set to OplockCompletionStatus from the * operation specified in section 2.1.5.17.3. * NewOplockLevel is set to NewOplockLevel from the * operation specified in section 2.1.5.17.3. * AcknowledgeRequired is set to AcknowledgeRequired from * the operation specified in section 2.1.5.17.3. * EndIf */ if (OplockGranted) { /* Note: *rop already set. */ if ((node->n_oplock.ol_state & BREAK_ANY) != 0) { status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS; /* Caller does smb_oplock_wait_break() */ } else { status = NT_STATUS_SUCCESS; } /* * First oplock grant installs FEM hooks. */ if (node->n_oplock.ol_fem == B_FALSE) { if (smb_fem_oplock_install(node) != 0) { cmn_err(CE_NOTE, "smb_fem_oplock_install failed"); } else { node->n_oplock.ol_fem = B_TRUE; } } } out: if (status == NT_STATUS_OPLOCK_NOT_GRANTED) *rop = LEVEL_NONE; return (status); } /* * 2.1.5.17.3 Indicating an Oplock Break to the Server * See smb_srv_oplock.c */ /* * 2.1.5.18 Server Acknowledges an Oplock Break * * The server provides: * Open - The Open associated with the oplock that has broken. * Type - As part of the acknowledgement, the server indicates a * new oplock it would like in place of the one that has broken. * Valid values are as follows: * LEVEL_NONE * LEVEL_TWO * LEVEL_GRANULAR - If this oplock type is specified, * the server additionally provides: * RequestedOplockLevel - A combination of zero or more of * the following flags: * READ_CACHING * HANDLE_CACHING * WRITE_CACHING * * If the server requests a new oplock and it is granted, the request * does not complete until the oplock is broken; the operation waits for * this to happen. Processing of an oplock break is described in * section 2.1.5.17.3. Whether the new oplock is granted or not, the * object store MUST return: * * Status - An NTSTATUS code indicating the result of the operation. * * If the server requests a new oplock and it is granted, then when the * oplock breaks and the request finally completes, the object store MUST * additionally return: * NewOplockLevel: The type of oplock the requested oplock has * been broken to. Valid values are as follows: * LEVEL_NONE (that is, no oplock) * LEVEL_TWO * A combination of one or more of the following flags: * READ_CACHING * HANDLE_CACHING * WRITE_CACHING * AcknowledgeRequired: A Boolean value; TRUE if the server MUST * acknowledge the oplock break, FALSE if not, as specified in * section 2.1.5.17.2. * * Note: Stores NewOplockLevel in *rop */ uint32_t smb_oplock_ack_break( smb_request_t *sr, smb_ofile_t *ofile, uint32_t *rop) { smb_node_t *node = ofile->f_node; uint32_t type = *rop & OPLOCK_LEVEL_TYPE_MASK; uint32_t level = *rop & OPLOCK_LEVEL_CACHE_MASK; uint32_t status = NT_STATUS_SUCCESS; uint32_t BreakToLevel; boolean_t NewOplockGranted = B_FALSE; boolean_t ReturnBreakToNone = B_FALSE; boolean_t FoundMatchingRHOplock = B_FALSE; int other_keys; 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 * failed with Status set to STATUS_INVALID_OPLOCK_PROTOCOL. */ if (node->n_oplock.ol_state == 0) { status = NT_STATUS_INVALID_OPLOCK_PROTOCOL; goto out; } if (type == LEVEL_NONE || type == LEVEL_TWO) { /* * If Open.Stream.Oplock.ExclusiveOpen is not equal to Open, * the operation MUST be failed with Status set to * STATUS_INVALID_OPLOCK_PROTOCOL. */ if (node->n_oplock.excl_open != ofile) { status = NT_STATUS_INVALID_OPLOCK_PROTOCOL; goto out; } /* * If Type is LEVEL_TWO and Open.Stream.Oplock.State * contains BREAK_TO_TWO: * Set Open.Stream.Oplock.State to LEVEL_TWO_OPLOCK. * Set NewOplockGranted to TRUE. */ if (type == LEVEL_TWO && (node->n_oplock.ol_state & BREAK_TO_TWO) != 0) { node->n_oplock.ol_state = LEVEL_TWO; NewOplockGranted = B_TRUE; } /* * Else If Open.Stream.Oplock.State contains * BREAK_TO_TWO or BREAK_TO_NONE: * Set Open.Stream.Oplock.State to NO_OPLOCK. */ else if ((node->n_oplock.ol_state & (BREAK_TO_TWO | BREAK_TO_NONE)) != 0) { node->n_oplock.ol_state = NO_OPLOCK; } /* * Else If Open.Stream.Oplock.State contains * BREAK_TO_TWO_TO_NONE: * Set Open.Stream.Oplock.State to NO_OPLOCK. * Set ReturnBreakToNone to TRUE. */ else if ((node->n_oplock.ol_state & BREAK_TO_TWO_TO_NONE) != 0) { node->n_oplock.ol_state = NO_OPLOCK; ReturnBreakToNone = B_TRUE; } /* * Else * The operation MUST be failed with Status set to * STATUS_INVALID_OPLOCK_PROTOCOL. */ else { status = NT_STATUS_INVALID_OPLOCK_PROTOCOL; goto out; } /* * For each Open WaitingOpen on Open.Stream.Oplock.WaitList: * Indicate that the operation associated with * WaitingOpen can continue according to the * algorithm in section 2.1.4.12.1, setting * OpenToRelease = WaitingOpen. * Remove WaitingOpen from Open.Stream.Oplock.WaitList. * EndFor */ if (node->n_oplock.waiters) cv_broadcast(&node->n_oplock.WaitingOpenCV); /* * Set Open.Stream.Oplock.ExclusiveOpen to NULL. */ node->n_oplock.excl_open = NULL; if (NewOplockGranted) { /* * The operation waits until the newly-granted * Level 2 oplock is broken, as specified in * section 2.1.5.17.3. * * Here we have just Ack'ed a break-to-II * so now get the level II oplock. We also * checked for break-to-none above, so this * will not need to wait for oplock breaks. */ status = smb_oplock_req_shared(ofile, rop, B_TRUE); } else if (ReturnBreakToNone) { /* * In this case the server was expecting the oplock * to break to Level 2, but because the oplock is * actually breaking to None (that is, no oplock), * the object store MUST indicate an oplock break * to the server according to the algorithm in * section 2.1.5.17.3, setting the algorithm's * parameters as follows: * BreakingOplockOpen = Open. * NewOplockLevel = LEVEL_NONE. * AcknowledgeRequired = FALSE. * OplockCompletionStatus = STATUS_SUCCESS. * (Because BreakingOplockOpen is equal to the * passed-in Open, the operation ends at this point.) */ smb_oplock_ind_break_in_ack( sr, ofile, LEVEL_NONE, B_FALSE); } status = NT_STATUS_SUCCESS; goto out; } /* LEVEL_NONE or LEVEL_TWO */ if (type != LEVEL_GRANULAR) { status = NT_STATUS_INVALID_OPLOCK_PROTOCOL; goto out; } /* LEVEL_GRANULAR */ /* * Let BREAK_LEVEL_MASK = (BREAK_TO_READ_CACHING | * BREAK_TO_WRITE_CACHING | BREAK_TO_HANDLE_CACHING | * BREAK_TO_NO_CACHING), * R_AND_RH_GRANTED = (READ_CACHING | HANDLE_CACHING | * MIXED_R_AND_RH), * RH_GRANTED = (READ_CACHING | HANDLE_CACHING) * * (See BREAK_LEVEL_MASK in smb_oplock.h) */ #define RH_GRANTED (READ_CACHING|HANDLE_CACHING) #define R_AND_RH_GRANTED (RH_GRANTED|MIXED_R_AND_RH) /* * If there are no BREAK_LEVEL_MASK flags set, this is invalid, * unless the state is R_AND_RH_GRANTED or RH_GRANTED, in which * case we'll need to see if the RHBreakQueue is empty. */ /* * If (Open.Stream.Oplock.State does not contain any flag in * BREAK_LEVEL_MASK and * (Open.Stream.Oplock.State != R_AND_RH_GRANTED) and * (Open.Stream.Oplock.State != RH_GRANTED)) or * (((Open.Stream.Oplock.State == R_AND_RH_GRANTED) or * (Open.Stream.Oplock.State == RH_GRANTED)) and * Open.Stream.Oplock.RHBreakQueue is empty): * The request MUST be failed with Status set to * STATUS_INVALID_OPLOCK_PROTOCOL. * EndIf */ if ((node->n_oplock.ol_state & BREAK_LEVEL_MASK) == 0) { if ((node->n_oplock.ol_state != R_AND_RH_GRANTED) && (node->n_oplock.ol_state != RH_GRANTED)) { status = NT_STATUS_INVALID_OPLOCK_PROTOCOL; goto out; } /* State is R_AND_RH_GRANTED or RH_GRANTED */ if (node->n_oplock.cnt_RHBQ == 0) { status = NT_STATUS_INVALID_OPLOCK_PROTOCOL; goto out; } } /* * Compute the "Break To" cache level from the * BREAK_TO_... flags */ switch (node->n_oplock.ol_state & BREAK_LEVEL_MASK) { case (BREAK_TO_READ_CACHING | BREAK_TO_WRITE_CACHING | BREAK_TO_HANDLE_CACHING): BreakToLevel = CACHE_RWH; break; case (BREAK_TO_READ_CACHING | BREAK_TO_WRITE_CACHING): BreakToLevel = CACHE_RW; break; case (BREAK_TO_READ_CACHING | BREAK_TO_HANDLE_CACHING): BreakToLevel = CACHE_RH; break; case BREAK_TO_READ_CACHING: BreakToLevel = READ_CACHING; break; case BREAK_TO_NO_CACHING: 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 */ switch (node->n_oplock.ol_state) { case (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH): 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: * If ThisContext.Open equals Open: * (see below) * * Implementation skips the list walk, because * we can get the ofile directly. */ if (ofile->f_oplock.onlist_RHBQ) { smb_ofile_t *o; /* * Set FoundMatchingRHOplock to TRUE. * If ThisContext.BreakingToRead is FALSE: * If RequestedOplockLevel is not 0 and * Open.Stream.Oplock.WaitList is not empty: * The object store MUST indicate an * oplock break to the server according to * the algorithm in section 2.1.5.17.3, * setting the algorithm's params as follows: * BreakingOplockOpen = Open. * NewOplockLevel = LEVEL_NONE. * AcknowledgeRequired = TRUE. * OplockCompletionStatus = * STATUS_CANNOT_GRANT_... * (Because BreakingOplockOpen is equal to the * passed Open, the operation ends at this point.) * EndIf */ FoundMatchingRHOplock = B_TRUE; if (ofile->f_oplock.BreakingToRead == B_FALSE) { if (level != 0 && node->n_oplock.waiters) { /* The ofile stays on RHBQ */ smb_oplock_ind_break_in_ack( sr, ofile, LEVEL_NONE, B_TRUE); status = NT_STATUS_SUCCESS; goto out; } } /* * Else // ThisContext.BreakingToRead is TRUE. * If Open.Stream.Oplock.WaitList is not empty and * (RequestedOplockLevel is CACHE_RW or CACHE_RWH: * The object store MUST indicate an oplock * break to the server according to the * algorithm in section 2.1.5.17.3, setting * the algorithm's parameters as follows: * * BreakingOplockOpen = Open * * NewOplockLevel = READ_CACHING * * AcknowledgeRequired = TRUE * * OplockCompletionStatus = * STATUS_CANNOT_GRANT... * (Because BreakingOplockOpen is equal to the * passed-in Open, the operation ends at this * point.) * EndIf * EndIf */ else { /* BreakingToRead is TRUE */ if (node->n_oplock.waiters && (level == CACHE_RW || level == CACHE_RWH)) { /* The ofile stays on RHBQ */ smb_oplock_ind_break_in_ack( sr, ofile, CACHE_R, B_TRUE); status = NT_STATUS_SUCCESS; goto out; } } /* * Remove ThisContext from Open...RHBreakQueue. */ ofile->f_oplock.onlist_RHBQ = B_FALSE; node->n_oplock.cnt_RHBQ--; ASSERT(node->n_oplock.cnt_RHBQ >= 0); /* * The operation waiting for the Read-Handle * oplock to break can continue if there are * no more Read-Handle oplocks outstanding, or * if all the remaining Read-Handle oplocks * have the same oplock key as the waiting * operation. * * For each Open WaitingOpen on Open...WaitList: * * * If (Open...RHBreakQueue is empty) or * (all RHOpContext.Open.TargetOplockKey values * on Open.Stream.Oplock.RHBreakQueue are * equal to WaitingOpen.TargetOplockKey): * * Indicate that the operation assoc. * with WaitingOpen can continue * according to the algorithm in * section 2.1.4.12.1, setting * OpenToRelease = WaitingOpen. * * Remove WaitingOpen from * Open.Stream.Oplock.WaitList. * * EndIf * EndFor */ other_keys = 0; FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_RHBQ == 0) continue; if (!CompareOplockKeys(ofile, o, 0)) other_keys++; } if (other_keys == 0) cv_broadcast(&node->n_oplock.WaitingOpenCV); /* * If RequestedOplockLevel is 0 (that is, no flags): * * Recompute Open.Stream.Oplock.State * according to the algorithm in section * 2.1.4.13, passing Open.Stream.Oplock as * the ThisOplock parameter. * * The algorithm MUST return Status set to * STATUS_SUCCESS at this point. */ if (level == 0) { RecomputeOplockState(node); status = NT_STATUS_SUCCESS; goto out; } /* * Else If RequestedOplockLevel does not contain * WRITE_CACHING: * * The object store MUST request a shared oplock * according to the algorithm in section * 2.1.5.17.2, setting the algorithm's * parameters as follows: * * Open = current Open. * * RequestedOplock = * RequestedOplockLevel. * * GrantingInAck = TRUE. * * The operation MUST at this point return any * status code returned by the shared oplock * request algorithm. */ else if ((level & WRITE_CACHING) == 0) { *rop = level; status = smb_oplock_req_shared( ofile, rop, B_TRUE); goto out; } /* * Set Open.Stream.Oplock.ExclusiveOpen to * ThisContext.Open. * Set Open.Stream.Oplock.State to * (RequestedOplockLevel|EXCLUSIVE). * This operation MUST be made cancelable by * inserting it into CancelableOperations... * This operation waits until the oplock is * broken or canceled, as specified in * section 2.1.5.17.3. * * Implementation note: * * Once we assing ol_state below, there * will be no BREAK_TO_... flags set, * so no need to wait for oplock breaks. */ node->n_oplock.excl_open = ofile; node->n_oplock.ol_state = level | EXCLUSIVE; status = NT_STATUS_SUCCESS; } /* onlist_RHBQ */ if (FoundMatchingRHOplock == B_FALSE) { /* The operation MUST be failed with Status... */ status = NT_STATUS_INVALID_OPLOCK_PROTOCOL; goto out; } break; /* case (READ_CACHING|HANDLE_CACHING...) */ case (READ_CACHING|WRITE_CACHING|EXCLUSIVE|BREAK_TO_READ_CACHING): case (READ_CACHING|WRITE_CACHING|EXCLUSIVE|BREAK_TO_NO_CACHING): case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE| BREAK_TO_READ_CACHING|BREAK_TO_WRITE_CACHING): case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE| BREAK_TO_READ_CACHING|BREAK_TO_HANDLE_CACHING): case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE| BREAK_TO_READ_CACHING): case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE| BREAK_TO_NO_CACHING): /* * If Open.Stream.Oplock.ExclusiveOpen != Open: * * The operation MUST be failed with Status set to * STATUS_INVALID_OPLOCK_PROTOCOL. * EndIf */ if (node->n_oplock.excl_open != ofile) { status = NT_STATUS_INVALID_OPLOCK_PROTOCOL; goto out; } /* * If Open.Stream.Oplock.WaitList is not empty and * Open.Stream.Oplock.State does not contain HANDLE_CACHING * and RequestedOplockLevel is CACHE_RWH: * The object store MUST indicate an oplock break to * the server according to the algorithm in section * 2.1.5.17.3, setting the algorithm's params as follows: * * BreakingOplockOpen = Open. * * NewOplockLevel = BreakToLevel (see above) * * AcknowledgeRequired = TRUE. * * OplockCompletionStatus = * STATUS_CANNOT_GRANT_REQUESTED_OPLOCK. * (Because BreakingOplockOpen is equal to the passed-in * Open, the operation ends at this point.) */ if (node->n_oplock.waiters && (node->n_oplock.ol_state & HANDLE_CACHING) == 0 && level == CACHE_RWH) { smb_oplock_ind_break_in_ack( sr, ofile, BreakToLevel, B_TRUE); status = NT_STATUS_SUCCESS; goto out; } /* * Else If Open.Stream.IsDeleted is TRUE and * RequestedOplockLevel contains HANDLE_CACHING: */ else if (((node->flags & NODE_FLAGS_DELETING) != 0) && (level & HANDLE_CACHING) != 0) { /* * The object store MUST indicate an oplock break to * the server according to the algorithm in section * 2.1.5.17.3, setting the algorithm's params as * follows: * * BreakingOplockOpen = Open. * * NewOplockLevel = RequestedOplockLevel * without HANDLE_CACHING (for example if * RequestedOplockLevel is * (READ_CACHING|HANDLE_CACHING), then * NewOplockLevel would be just READ_CACHING). * * AcknowledgeRequired = TRUE. * * OplockCompletionStatus = * STATUS_CANNOT_GRANT_REQUESTED_OPLOCK. * (Because BreakingOplockOpen is equal to the * passed-in Open, the operation ends at this point.) */ level &= ~HANDLE_CACHING; smb_oplock_ind_break_in_ack( sr, ofile, level, B_TRUE); status = NT_STATUS_SUCCESS; goto out; } /* * For each Open WaitingOpen on Open.Stream.Oplock.WaitList: * * Indicate that the operation associated with * WaitingOpen can continue according to the algorithm * in section 2.1.4.12.1, setting OpenToRelease * = WaitingOpen. * * Remove WaitingOpen from Open.Stream.Oplock.WaitList. * EndFor */ cv_broadcast(&node->n_oplock.WaitingOpenCV); /* * If RequestedOplockLevel does not contain WRITE_CACHING: * * Set Open.Stream.Oplock.ExclusiveOpen to NULL. * EndIf */ if ((level & WRITE_CACHING) == 0) { node->n_oplock.excl_open = NULL; } /* * If RequestedOplockLevel is 0 (that is, no flags): * * Set Open.Stream.Oplock.State to NO_OPLOCK. * * The operation returns Status set to STATUS_SUCCESS * at this point. */ if (level == 0) { node->n_oplock.ol_state = NO_OPLOCK; status = NT_STATUS_SUCCESS; goto out; } /* * Deal with possibly still pending breaks. * Two cases: R to none, RH to R or none. * * XXX: These two missing from [MS-FSA] */ /* * 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. */ if (level == CACHE_R && BreakToLevel == LEVEL_NONE) { smb_oplock_ind_break_in_ack( sr, ofile, LEVEL_NONE, B_FALSE); node->n_oplock.ol_state = NO_OPLOCK; status = NT_STATUS_SUCCESS; goto out; } /* * 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, CACHE_R, B_TRUE); ofile->f_oplock.BreakingToRead = (BreakToLevel & READ_CACHING) ? 1: 0; ASSERT(!(ofile->f_oplock.onlist_RHBQ)); ofile->f_oplock.onlist_RHBQ = B_TRUE; node->n_oplock.cnt_RHBQ++; RecomputeOplockState(node); status = NT_STATUS_SUCCESS; goto out; } /* * Else If RequestedOplockLevel does not contain WRITE_CACHING: * * The object store MUST request a shared oplock * according to the algorithm in section 2.1.5.17.2, * setting the algorithm's parameters as follows: * * Pass in the current Open. * * RequestedOplock = RequestedOplockLevel. * * GrantingInAck = TRUE. * * The operation MUST at this point return any status * returned by the shared oplock request algorithm. */ if ((level & WRITE_CACHING) == 0) { *rop = level; status = smb_oplock_req_shared(ofile, rop, B_TRUE); goto out; } /* * Note that because this oplock is being set up as part of * an acknowledgement of an exclusive oplock break, * Open.Stream.Oplock.ExclusiveOpen was set * at the time of the original oplock request; * it contains Open. * * Set Open.Stream.Oplock.State to * (RequestedOplockLevel|EXCLUSIVE). * * This operation MUST be made cancelable... * * This operation waits until the oplock is broken or * canceled, as specified in section 2.1.5.17.3. * * Implementation notes: * * This can only be a break from RWH to RW. * The assignment of ol_state below means there will be * no BREAK_TO_... bits set, and therefore no need for * "waits until the oplock is broken" as described in * 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|...) */ default: /* The operation MUST be failed with Status */ status = NT_STATUS_INVALID_OPLOCK_PROTOCOL; break; } /* Switch (oplock.state) */ out: if (status == NT_STATUS_INVALID_OPLOCK_PROTOCOL) *rop = LEVEL_NONE; if (status == NT_STATUS_SUCCESS && type == LEVEL_GRANULAR && *rop != LEVEL_NONE) { *rop |= LEVEL_GRANULAR; } /* * If this node no longer has any oplock grants, let's * go ahead and remove the FEM hooks now. We could leave * that until close, but this lets access outside of SMB * be free of FEM oplock work after a "break to none". */ if (node->n_oplock.ol_state == NO_OPLOCK && node->n_oplock.ol_fem == B_TRUE) { smb_fem_oplock_uninstall(node); node->n_oplock.ol_fem = B_FALSE; } /* * 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. */ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { ASSERT(!"Unexpected OPLOCK_BREAK_IN_PROGRESS"); status = NT_STATUS_SUCCESS; } return (status); } /* * 2.1.4.12 Algorithm to Check for an Oplock Break * * The inputs for this algorithm are: * * Open: The Open being used in the request calling this algorithm. * * Oplock: The Oplock being checked. * * Operation: A code describing the operation being processed. * * OpParams: Parameters associated with the Operation code that are * passed in from the calling request. For example, if Operation is * OPEN, as specified in section 2.1.5.1, then OpParams will have the * members DesiredAccess and CreateDisposition. Each of these is a * parameter to the open request as specified in section 2.1.5.1. * This parameter could be empty, depending on the Operation code. * * Flags: An optional parameter. If unspecified it is considered to * contain 0. Valid nonzero values are: * PARENT_OBJECT * * The algorithm uses the following local variables: * * Boolean values (initialized to FALSE): * BreakToTwo, BreakToNone, NeedToWait * * BreakCacheLevel – MAY contain 0 or a combination of one or more of * READ_CACHING, WRITE_CACHING, or HANDLE_CACHING, as specified in * section 2.1.1.10. Initialized to 0. * Note that there are only four legal nonzero combinations of flags * for BreakCacheLevel: * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING) * (READ_CACHING|WRITE_CACHING) * WRITE_CACHING * HANDLE_CACHING * * Algorithm: (all) * If Oplock is not empty and Oplock.State is not NO_OPLOCK: * If Flags contains PARENT_OBJECT: * If Operation is OPEN, CLOSE, FLUSH_DATA, * FS_CONTROL(set_encryption) or * SET_INFORMATION(Basic, Allocation, EoF, * Rename, Link, Shortname, VDL): * Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING). * EndIf * Else // Normal operation (not PARENT_OBJECT) * Switch (Operation): * Case OPEN, CLOSE, ... * EndSwitch * EndIf // not parent * // Common section for all above * If BreakToTwo is TRUE: * ... * Else If BreakToNone * ... * EndIf * ... * EndIf * * This implementation uses separate functions for each of: * if (flags & PARENT)... else * switch (Operation)... */ /* * If Flags contains PARENT_OBJECT: * ... * Note that this function is unusual in that the node arg is * the PARENT directory node, and ofile is NOT on the ofile list * of that directory but one of the nodes under it. * * Note that until we implement directory leases, this is a no-op. */ uint32_t smb_oplock_break_PARENT(smb_node_t *node, smb_ofile_t *ofile) { uint32_t BreakCacheLevel; /* * If Operation is OPEN, CLOSE, FLUSH_DATA, * FS_CONTROL(set_encryption) or * SET_INFORMATION(Basic, Allocation, EoF, * Rename, Link, Shortname, VDL): * Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING). * EndIf */ BreakCacheLevel = PARENT_OBJECT | (READ_CACHING|WRITE_CACHING); return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel)); } /* * Helper for the cases where section 2.1.5.1 says: * * If Open.Stream.Oplock is not empty and Open.Stream.Oplock.State * contains BATCH_OPLOCK, the object store MUST check for an oplock * break according to the algorithm in section 2.1.4.12, * with input values as follows: * Open equal to this operation's Open * Oplock equal to Open.Stream.Oplock * Operation equal to "OPEN" * OpParams containing two members: * (DesiredAccess, CreateDisposition) * * So basically, just call smb_oplock_break_OPEN(), but * only if there's a batch oplock. */ uint32_t smb_oplock_break_BATCH(smb_node_t *node, smb_ofile_t *ofile, uint32_t DesiredAccess, uint32_t CreateDisposition) { if ((node->n_oplock.ol_state & BATCH_OPLOCK) == 0) return (0); return (smb_oplock_break_OPEN(node, ofile, DesiredAccess, CreateDisposition)); } /* * Case OPEN, as specified in section 2.1.5.1: * * Note: smb_ofile_open constructs a partially complete smb_ofile_t * for this call, which can be considerd a "proposed open". This * open may or may not turn into a usable open depending on what * happens in the remainder of the ofile_open code path. */ uint32_t smb_oplock_break_OPEN(smb_node_t *node, smb_ofile_t *ofile, uint32_t DesiredAccess, uint32_t CreateDisposition) { uint32_t BreakCacheLevel = 0; /* BreakToTwo, BreakToNone, NeedToWait */ /* * If OpParams.DesiredAccess contains no flags other than * FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, or SYNCHRONIZE, * the algorithm returns at this point. * EndIf */ if ((DesiredAccess & ~(FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE | READ_CONTROL)) == 0) return (0); /* * If OpParams.CreateDisposition is FILE_SUPERSEDE, * FILE_OVERWRITE, or FILE_OVERWRITE_IF: * Set BreakToNone to TRUE, set BreakCacheLevel to * (READ_CACHING|WRITE_CACHING). * Else * Set BreakToTwo to TRUE, * set BreakCacheLevel to WRITE_CACHING. * EndIf */ if (CreateDisposition == FILE_SUPERSEDE || CreateDisposition == FILE_OVERWRITE || CreateDisposition == FILE_OVERWRITE_IF) { BreakCacheLevel = BREAK_TO_NONE | (READ_CACHING|WRITE_CACHING); } else { /* * CreateDispositons: OPEN, OPEN_IF */ BreakCacheLevel = BREAK_TO_TWO | WRITE_CACHING; } return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel)); } /* * Case OPEN_BREAK_H, as specified in section 2.1.5.1: * Set BreakCacheLevel to HANDLE_CACHING. * EndCase */ uint32_t smb_oplock_break_HANDLE(smb_node_t *node, smb_ofile_t *ofile) { uint32_t BreakCacheLevel = HANDLE_CACHING; return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel)); } /* * Case CLOSE, as specified in section 2.1.5.4: * * The MS-FSA spec. describes sending oplock break indications * (smb_oplock_ind_break ... NT_STATUS_OPLOCK_HANDLE_CLOSED) * for several cases where the ofile we're closing has some * oplock grants. We modify these slightly and use them to * clear out the SMB-level oplock state. We could probably * just skip most of these, as the caller knows this handle is * closing and could just discard the SMB-level oplock state. * For now, keeping this close to what the spec says. */ void smb_oplock_break_CLOSE(smb_node_t *node, smb_ofile_t *ofile) { smb_ofile_t *o; ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock)); ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex)); #ifdef DEBUG FOREACH_NODE_OFILE(node, o) { DTRACE_PROBE1(each_ofile, smb_ofile_t *, o); } #endif /* * If Oplock.IIOplocks is not empty: * For each Open ThisOpen in Oplock.IIOplocks: * If ThisOpen == Open: * Remove ThisOpen from Oplock.IIOplocks. * Notify the server of an oplock break according to * the algorithm in section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = ThisOpen. * NewOplockLevel = LEVEL_NONE. * AcknowledgeRequired = FALSE. * OplockCompletionStatus = STATUS_SUCCESS. * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.2.) * EndIf * EndFor * Recompute Oplock.State according to the algorithm in * section 2.1.4.13, passing Oplock as the ThisOplock parameter. * EndIf */ if (node->n_oplock.cnt_II > 0) { o = ofile; /* No need for list walk */ if (o->f_oplock.onlist_II) { o->f_oplock.onlist_II = B_FALSE; node->n_oplock.cnt_II--; ASSERT(node->n_oplock.cnt_II >= 0); /* * The spec. says to do: * smb_oplock_ind_break(o, * LEVEL_NONE, B_FALSE, * NT_STATUS_SUCCESS); * * We'll use STATUS_OPLOCK_HANDLE_CLOSED * like all the other ind_break calls in * this function, so the SMB-level will * just clear out its oplock state. */ smb_oplock_ind_break(o, LEVEL_NONE, B_FALSE, NT_STATUS_OPLOCK_HANDLE_CLOSED); } RecomputeOplockState(node); } /* * If Oplock.ROplocks is not empty: * For each Open ThisOpen in Oplock.ROplocks: * If ThisOpen == Open: * Remove ThisOpen from Oplock.ROplocks. * Notify the server of an oplock break according to * the algorithm in section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = ThisOpen. * NewOplockLevel = LEVEL_NONE. * AcknowledgeRequired = FALSE. * OplockCompletionStatus = * STATUS_OPLOCK_HANDLE_CLOSED. * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.2.) * EndIf * EndFor * Recompute Oplock.State according to the algorithm in * section 2.1.4.13, passing Oplock as the ThisOplock parameter. * EndIf */ if (node->n_oplock.cnt_R > 0) { o = ofile; /* No need for list walk */ if (o->f_oplock.onlist_R) { o->f_oplock.onlist_R = B_FALSE; node->n_oplock.cnt_R--; ASSERT(node->n_oplock.cnt_R >= 0); smb_oplock_ind_break(o, LEVEL_NONE, B_FALSE, NT_STATUS_OPLOCK_HANDLE_CLOSED); } RecomputeOplockState(node); } /* * If Oplock.RHOplocks is not empty: * For each Open ThisOpen in Oplock.RHOplocks: * If ThisOpen == Open: * Remove ThisOpen from Oplock.RHOplocks. * Notify the server of an oplock break according to * the algorithm in section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = ThisOpen. * NewOplockLevel = LEVEL_NONE. * AcknowledgeRequired = FALSE. * OplockCompletionStatus = * STATUS_OPLOCK_HANDLE_CLOSED. * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.2.) * EndIf * EndFor * Recompute Oplock.State according to the algorithm in * section 2.1.4.13, passing Oplock as the ThisOplock parameter. * EndIf */ if (node->n_oplock.cnt_RH > 0) { o = ofile; /* No need for list walk */ if (o->f_oplock.onlist_RH) { o->f_oplock.onlist_RH = B_FALSE; node->n_oplock.cnt_RH--; ASSERT(node->n_oplock.cnt_RH >= 0); smb_oplock_ind_break(o, LEVEL_NONE, B_FALSE, NT_STATUS_OPLOCK_HANDLE_CLOSED); } RecomputeOplockState(node); } /* * If Oplock.RHBreakQueue is not empty: * For each RHOpContext ThisContext in Oplock.RHBreakQueue: * If ThisContext.Open == Open: * Remove ThisContext from Oplock.RHBreakQueue. * EndIf * EndFor * Recompute Oplock.State according to the algorithm in * section 2.1.4.13, passing Oplock as the ThisOplock parameter. * For each Open WaitingOpen on Oplock.WaitList: * If Oplock.RHBreakQueue is empty: * (or) If the value of every * RHOpContext.Open.TargetOplockKey * on Oplock.RHBreakQueue is equal to * WaitingOpen .TargetOplockKey: * Indicate that the op. assoc. with * WaitingOpen can continue according to * the algorithm in section 2.1.4.12.1, * setting OpenToRelease = WaitingOpen. * Remove WaitingOpen from Oplock.WaitList. * EndIf * EndFor * EndIf */ if (node->n_oplock.cnt_RHBQ > 0) { o = ofile; /* No need for list walk */ if (o->f_oplock.onlist_RHBQ) { o->f_oplock.onlist_RHBQ = B_FALSE; node->n_oplock.cnt_RHBQ--; ASSERT(node->n_oplock.cnt_RHBQ >= 0); } RecomputeOplockState(node); /* * We don't keep a WaitingOpen list, so just * wake them all and let them look at the * updated Oplock.RHBreakQueue */ cv_broadcast(&node->n_oplock.WaitingOpenCV); } /* * If Open equals Open.Oplock.ExclusiveOpen * If Oplock.State contains none of (BREAK_ANY): * Notify the server of an oplock break according to * the algorithm in section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = Oplock.ExclusiveOpen. * NewOplockLevel = LEVEL_NONE. * AcknowledgeRequired = FALSE. * OplockCompletionStatus equal to: * STATUS_OPLOCK_HANDLE_CLOSED if * Oplock.State contains any of * READ_CACHING, WRITE_CACHING, or * HANDLE_CACHING. * STATUS_SUCCESS otherwise. * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.1.) * EndIf * Set Oplock.ExclusiveOpen to NULL. * Set Oplock.State to NO_OPLOCK. * For each Open WaitingOpen on Oplock.WaitList: * Indicate that the operation associated with WaitingOpen * can continue according to the algorithm in section * 2.1.4.12.1, setting OpenToRelease = WaitingOpen. * Remove WaitingOpen from Oplock.WaitList. * EndFor * EndIf * * Modify this slightly from what the spec. says and only * up-call the break with status STATUS_OPLOCK_HANDLE_CLOSED. * The STATUS_SUCCESS case would do nothing at the SMB level, * so we'll just skip that part. */ if (ofile == node->n_oplock.excl_open) { uint32_t level = node->n_oplock.ol_state & CACHE_RWH; if (level != 0 && (node->n_oplock.ol_state & BREAK_ANY) == 0) { smb_oplock_ind_break(ofile, LEVEL_NONE, B_FALSE, NT_STATUS_OPLOCK_HANDLE_CLOSED); } node->n_oplock.excl_open = NULL; node->n_oplock.ol_state = NO_OPLOCK; cv_broadcast(&node->n_oplock.WaitingOpenCV); } /* * The CLOSE sub-case of 2.1.5.4 (separate function here) * happens to always leave BreakCacheLevel=0 (see 2.1.5.4) * so there's never a need to call smb_oplock_break_cmn() * in this function. If that changed and we were to have * BreakCacheLevel != 0 here, then we'd need to call: * smb_oplock_break_cmn(node, ofile, BreakCacheLevel); */ if ((node->n_oplock.ol_state & BREAK_ANY) == 0) cv_broadcast(&node->n_oplock.WaitingOpenCV); /* * If no longer any oplock, remove FEM hooks. */ if (node->n_oplock.ol_state == NO_OPLOCK && node->n_oplock.ol_fem == B_TRUE) { smb_fem_oplock_uninstall(node); node->n_oplock.ol_fem = B_FALSE; } } /* * Case READ, as specified in section 2.1.5.2: * Set BreakToTwo to TRUE * Set BreakCacheLevel to WRITE_CACHING. * EndCase */ uint32_t smb_oplock_break_READ(smb_node_t *node, smb_ofile_t *ofile) { uint32_t BreakCacheLevel = BREAK_TO_TWO | WRITE_CACHING; return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel)); } /* * Case FLUSH_DATA, as specified in section 2.1.5.6: * Set BreakToTwo to TRUE * Set BreakCacheLevel to WRITE_CACHING. * EndCase * Callers just use smb_oplock_break_READ() -- same thing. */ /* * Case LOCK_CONTROL, as specified in section 2.1.5.7: * Note: Spec does fall-through to WRITE here. * * Case WRITE, as specified in section 2.1.5.3: * Set BreakToNone to TRUE * Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING). * EndCase */ uint32_t smb_oplock_break_WRITE(smb_node_t *node, smb_ofile_t *ofile) { uint32_t BreakCacheLevel = BREAK_TO_NONE | (READ_CACHING|WRITE_CACHING); return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel)); } /* * Case SET_INFORMATION, as specified in section 2.1.5.14: * Switch (OpParams.FileInformationClass): * Case FileEndOfFileInformation: * Case FileAllocationInformation: * Set BreakToNone to TRUE * Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING). * EndCase * Case FileRenameInformation: * Case FileLinkInformation: * Case FileShortNameInformation: * Set BreakCacheLevel to HANDLE_CACHING. * If Oplock.State contains BATCH_OPLOCK, * set BreakToNone to TRUE. * EndCase * Case FileDispositionInformation: * If OpParams.DeleteFile is TRUE, * Set BreakCacheLevel to HANDLE_CACHING. * EndCase * EndSwitch */ uint32_t smb_oplock_break_SETINFO(smb_node_t *node, smb_ofile_t *ofile, uint32_t InfoClass) { uint32_t BreakCacheLevel = 0; switch (InfoClass) { case FileEndOfFileInformation: case FileAllocationInformation: BreakCacheLevel = BREAK_TO_NONE | (READ_CACHING|WRITE_CACHING); break; case FileRenameInformation: case FileLinkInformation: case FileShortNameInformation: BreakCacheLevel = HANDLE_CACHING; if (node->n_oplock.ol_state & BATCH_OPLOCK) { BreakCacheLevel |= BREAK_TO_NONE; } break; case FileDispositionInformation: /* Only called if (OpParams.DeleteFile is TRUE) */ BreakCacheLevel = HANDLE_CACHING; break; } return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel)); } /* * This one is not from the spec. It appears that Windows will * open a handle for an SMB1 delete call (at least internally). * We don't open a handle for delete, but do want to break as if * we had done, so this breaks like a combination of: * break_BATCH(... DELETE, FILE_OPEN_IF) * break_HANDLE(...) */ uint32_t smb_oplock_break_DELETE(smb_node_t *node, smb_ofile_t *ofile) { uint32_t BreakCacheLevel = HANDLE_CACHING; if ((node->n_oplock.ol_state & BATCH_OPLOCK) != 0) BreakCacheLevel |= BREAK_TO_TWO; return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel)); } /* * Case FS_CONTROL, as specified in section 2.1.5.9: * If OpParams.ControlCode is FSCTL_SET_ZERO_DATA: * Set BreakToNone to TRUE. * Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING). * EndIf * EndCase * Callers just use smb_oplock_break_WRITE() -- same thing. */ /* * Common section for all cases above * Note: When called via FEM: ofile == NULL */ static uint32_t smb_oplock_break_cmn(smb_node_t *node, smb_ofile_t *ofile, uint32_t BreakCacheLevel) { smb_oplock_t *nol = &node->n_oplock; uint32_t CmpFlags, status; boolean_t BreakToTwo, BreakToNone, NeedToWait; smb_ofile_t *o = NULL; CmpFlags = (BreakCacheLevel & PARENT_OBJECT); BreakToTwo = (BreakCacheLevel & BREAK_TO_TWO) != 0; BreakToNone = (BreakCacheLevel & BREAK_TO_NONE) != 0; BreakCacheLevel &= (READ_CACHING | WRITE_CACHING | HANDLE_CACHING); NeedToWait = B_FALSE; status = NT_STATUS_SUCCESS; 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; if (BreakToTwo) { /* * If (Oplock.State != LEVEL_TWO_OPLOCK) and * ((Oplock.ExclusiveOpen is empty) or * (Oplock.ExclusiveOpen.TargetOplockKey != * Open.TargetOplockKey)): */ if ((nol->ol_state != LEVEL_TWO_OPLOCK) && (((o = nol->excl_open) == NULL) || !CompareOplockKeys(ofile, o, CmpFlags))) { /* * If (Oplock.State contains EXCLUSIVE) and * (Oplock.State contains none of READ_CACHING, * WRITE_CACHING, or HANDLE_CACHING): */ if ((nol->ol_state & EXCLUSIVE) != 0 && (nol->ol_state & CACHE_RWH) == 0) { /* * If Oplock.State contains none of: * BREAK_TO_NONE, * BREAK_TO_TWO, * BREAK_TO_TWO_TO_NONE, * BREAK_TO_READ_CACHING, * BREAK_TO_WRITE_CACHING, * BREAK_TO_HANDLE_CACHING, * BREAK_TO_NO_CACHING: */ if ((nol->ol_state & BREAK_ANY) == 0) { /* * Oplock.State MUST contain either * LEVEL_ONE_OPLOCK or BATCH_OPLOCK. * Set BREAK_TO_TWO in Oplock.State. */ ASSERT((nol->ol_state & (LEVEL_ONE | LEVEL_BATCH)) != 0); nol->ol_state |= BREAK_TO_TWO; /* * Notify the server of an oplock break * according to the algorithm in section * 2.1.5.17.3, setting the algorithm's * parameters as follows: * BreakingOplockOpen = * Oplock.ExclusiveOpen. * NewOplockLevel = LEVEL_TWO. * AcknowledgeRequired = TRUE. * Compl_Status = STATUS_SUCCESS. * (The operation does not end at this * point; this call to 2.1.5.17.3 * 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); } /* * The operation that called this algorithm * MUST be made cancelable by ... * The operation that called this algorithm * waits until the oplock break is * acknowledged, as specified in section * 2.1.5.18, or the operation is canceled. */ status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS; /* Caller does smb_oplock_wait_break() */ } } } else if (BreakToNone) { /* * If (Oplock.State == LEVEL_TWO_OPLOCK) or * (Oplock.ExclusiveOpen is empty) or * (Oplock.ExclusiveOpen.TargetOplockKey != * Open.TargetOplockKey): */ if (nol->ol_state == LEVEL_TWO_OPLOCK || (((o = nol->excl_open) == NULL) || !CompareOplockKeys(ofile, o, CmpFlags))) { /* * If (Oplock.State != NO_OPLOCK) and * (Oplock.State contains neither * WRITE_CACHING nor HANDLE_CACHING): */ if (nol->ol_state != NO_OPLOCK && (nol->ol_state & (WRITE_CACHING | HANDLE_CACHING)) == 0) { /* * If Oplock.State contains none of: * LEVEL_TWO_OPLOCK, * BREAK_TO_NONE, * BREAK_TO_TWO, * BREAK_TO_TWO_TO_NONE, * BREAK_TO_READ_CACHING, * BREAK_TO_WRITE_CACHING, * BREAK_TO_HANDLE_CACHING, or * BREAK_TO_NO_CACHING: */ if ((nol->ol_state & (LEVEL_TWO_OPLOCK | BREAK_ANY)) == 0) { /* * There could be a READ_CACHING-only * oplock here. Those are broken later. * * If Oplock.State contains READ_CACHING * go to the LeaveBreakToNone label. * Set BREAK_TO_NONE in Oplock.State. */ if ((nol->ol_state & READ_CACHING) != 0) goto LeaveBreakToNone; nol->ol_state |= BREAK_TO_NONE; /* * Notify the server of an oplock break * according to the algorithm in section * 2.1.5.17.3, setting the algorithm's * parameters as follows: * BreakingOplockOpen = * Oplock.ExclusiveOpen. * NewOplockLevel = LEVEL_NONE. * AcknowledgeRequired = TRUE. * Commpl_Status = STATUS_SUCCESS. * (The operation does not end at this * point; this call to 2.1.5.17.3 * 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); } /* * Else If Oplock.State equals LEVEL_TWO_OPLOCK * or (LEVEL_TWO_OPLOCK|READ_CACHING): */ else if (nol->ol_state == LEVEL_TWO || nol->ol_state == (LEVEL_TWO|READ_CACHING)) { /* * For each Open O in Oplock.IIOplocks: * Remove O from Oplock.IIOplocks. * Notify the server of an oplock * break according to the algorithm * in section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = ThisOpen. * NewOplockLevel = LEVEL_NONE. * AcknowledgeRequired = FALSE. * Compl_Status = STATUS_SUCCESS. * (The operation does not end at * this point; this call to * 2.1.5.17.3 completes some * earlier call to 2.1.5.17.2.) * EndFor */ FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_II == 0) continue; o->f_oplock.onlist_II = B_FALSE; nol->cnt_II--; ASSERT(nol->cnt_II >= 0); smb_oplock_ind_break(o, LEVEL_NONE, B_FALSE, NT_STATUS_SUCCESS); } /* * If Oplock.State equals * (LEVEL_TWO_OPLOCK|READ_CACHING): * Set Oplock.State = READ_CACHING. * Else * Set Oplock.State = NO_OPLOCK. * EndIf * Go to the LeaveBreakToNone label. */ if (nol->ol_state == (LEVEL_TWO_OPLOCK | READ_CACHING)) { nol->ol_state = READ_CACHING; } else { nol->ol_state = NO_OPLOCK; } goto LeaveBreakToNone; } /* * Else If Oplock.State contains BREAK_TO_TWO: * Clear BREAK_TO_TWO from Oplock.State. * Set BREAK_TO_TWO_TO_NONE in Oplock.State * EndIf */ else if (nol->ol_state & BREAK_TO_TWO) { nol->ol_state &= ~BREAK_TO_TWO; nol->ol_state |= BREAK_TO_TWO_TO_NONE; } /* * If Oplock.ExclusiveOpen is not empty, * and Oplock.Excl_Open.TargetOplockKey * equals Open.TargetOplockKey, * go to the LeaveBreakToNone label. */ if ((o = nol->excl_open) != NULL && CompareOplockKeys(ofile, o, CmpFlags)) goto LeaveBreakToNone; /* * The operation that called this algorithm * MUST be made cancelable by ... * The operation that called this algorithm * waits until the opl. break is acknowledged, * as specified in section 2.1.5.18, or the * operation is canceled. */ status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS; /* Caller does smb_oplock_wait_break() */ } } } LeaveBreakToNone: /* * if (BreakCacheLevel != 0) and (pp 37) * If Oplock.State contains any flags that are in BreakCacheLevel: * (Body of that "If" was here to just above the out label.) */ if ((nol->ol_state & BreakCacheLevel) == 0) goto out; /* * If Oplock.ExclusiveOpen is not empty, call the * algorithm in section 2.1.4.12.2, passing * Open as the OperationOpen parameter, * Oplock.ExclusiveOpen as the OplockOpen parameter, * and Flags as the Flagsparameter. * If the algorithm returns TRUE: * The algorithm returns at this point. */ if ((o = nol->excl_open) != NULL && CompareOplockKeys(ofile, o, CmpFlags) == B_TRUE) { status = NT_STATUS_SUCCESS; goto out; } /* * Switch (Oplock.State): */ switch (nol->ol_state) { case (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH): case READ_CACHING: case (LEVEL_TWO_OPLOCK|READ_CACHING): /* * If BreakCacheLevel contains READ_CACHING: */ if ((BreakCacheLevel & READ_CACHING) != 0) { /* * For each Open ThisOpen in Oplock.ROplocks: * Call the algorithm in section 2.1.4.12.2, pass: * Open as the OperationOpen parameter, * ThisOpen as the OplockOpen parameter, * and Flags as the Flagsparameter. * If the algorithm returns FALSE: * Remove ThisOpen from Oplock.ROplocks. * Notify the server of an oplock break * according to the algorithm in * section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = ThisOpen. * NewOplockLevel = LEVEL_NONE. * AcknowledgeRequired = FALSE. * Compl_Status = STATUS_SUCCESS. * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.2.) * EndIf * EndFor */ FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_R == 0) continue; if (!CompareOplockKeys(ofile, o, CmpFlags)) { o->f_oplock.onlist_R = B_FALSE; nol->cnt_R--; ASSERT(nol->cnt_R >= 0); smb_oplock_ind_break(o, LEVEL_NONE, B_FALSE, NT_STATUS_SUCCESS); } } } /* * If Oplock.State equals * (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH): * // Do nothing; FALL THROUGH to next Case statement. * Else * Recompute Oplock.State according to the * algorithm in section 2.1.4.13, passing * Oplock as the ThisOplock parameter. * EndIf */ if (nol->ol_state == (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH)) goto case_cache_rh; RecomputeOplockState(node); break; /* EndCase XXX Note: spec. swapped this with prev. Endif. */ case_cache_rh: case (READ_CACHING|HANDLE_CACHING): /* * If BreakCacheLevel equals HANDLE_CACHING: */ if (BreakCacheLevel == HANDLE_CACHING) { /* * For each Open ThisOpen in Oplock.RHOplocks: * If ThisOpen.OplockKey != Open.OplockKey: */ FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_RH == 0) continue; if (!CompareOplockKeys(ofile, o, CmpFlags)) { /* * Remove ThisOpen from * Oplock.RHOplocks. */ o->f_oplock.onlist_RH = B_FALSE; nol->cnt_RH--; ASSERT(nol->cnt_RH >= 0); /* * Notify the server of an oplock break * according to the algorithm in * section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = ThisOpen. * NewOplockLevel = READ_CACHING. * AcknowledgeRequired = TRUE. * Compl_Status = STATUS_SUCCESS. * (The operation does not end at this * point; this call to 2.1.5.17.3 * completes some earlier call to * 2.1.5.17.2.) */ smb_oplock_ind_break(o, READ_CACHING, B_TRUE, NT_STATUS_SUCCESS); /* * Initialize a new RHOpContext object, * setting its fields as follows: * RHOpCtx.Open = ThisOpen. * RHOpCtx.BreakingToRead = TRUE. * Add the new RHOpContext object to * Oplock.RHBreakQueue. * Set NeedToWait to TRUE. */ o->f_oplock.BreakingToRead = B_TRUE; ASSERT(!(o->f_oplock.onlist_RHBQ)); o->f_oplock.onlist_RHBQ = B_TRUE; nol->cnt_RHBQ++; NeedToWait = B_TRUE; } } } /* * Else If BreakCacheLevel contains both * READ_CACHING and WRITE_CACHING: */ else if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) == (READ_CACHING | WRITE_CACHING)) { /* * For each RHOpContext ThisContext in * Oplock.RHBreakQueue: * Call the algorithm in section 2.1.4.12.2, * passing Open as the OperationOpen parameter, * ThisContext.Open as the OplockOpen parameter, * and Flags as the Flags parameter. * If the algorithm returns FALSE: * Set ThisContext.BreakingToRead to FALSE. * If BreakCacheLevel & HANDLE_CACHING: * Set NeedToWait to TRUE. * EndIf * EndIf * EndFor */ FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_RHBQ == 0) continue; if (!CompareOplockKeys(ofile, o, CmpFlags)) { o->f_oplock.BreakingToRead = B_FALSE; if (BreakCacheLevel & HANDLE_CACHING) NeedToWait = B_TRUE; } } /* * For each Open ThisOpen in Oplock.RHOplocks: * Call the algorithm in section 2.1.4.12.2, * passing Open as the OperationOpen parameter, * ThisOpen as the OplockOpen parameter, and * Flags as the Flagsparameter. * If the algorithm returns FALSE: * Remove ThisOpen from Oplock.RHOplocks. * Notify the server of an oplock break * according to the algorithm in * section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = ThisOpen. * NewOplockLevel = LEVEL_NONE. * AcknowledgeRequired = TRUE. * Compl_Status = STATUS_SUCCESS. * (The operation does not end at this * point; this call to 2.1.5.17.3 * completes some earlier call to * 2.1.5.17.2.) * Initialize a new RHOpContext object, * setting its fields as follows: * RHOpCtx.Open = ThisOpen. * RHOpCtx.BreakingToRead = FALSE * Add the new RHOpContext object to * Oplock.RHBreakQueue. * If BreakCacheLevel contains * HANDLE_CACHING: * Set NeedToWait to TRUE. * EndIf * EndIf * EndFor */ FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_RH == 0) continue; if (!CompareOplockKeys(ofile, o, CmpFlags)) { o->f_oplock.onlist_RH = B_FALSE; nol->cnt_RH--; ASSERT(nol->cnt_RH >= 0); smb_oplock_ind_break(o, LEVEL_NONE, B_TRUE, NT_STATUS_SUCCESS); o->f_oplock.BreakingToRead = B_FALSE; ASSERT(!(o->f_oplock.onlist_RHBQ)); o->f_oplock.onlist_RHBQ = B_TRUE; nol->cnt_RHBQ++; if (BreakCacheLevel & HANDLE_CACHING) NeedToWait = B_TRUE; } } } // If the oplock is explicitly losing HANDLE_CACHING, RHBreakQueue is // not empty, and the algorithm has not yet decided to wait, this operation // might have to wait if there is an oplock on RHBreakQueue with a // non-matching key. This is done because even if this operation didn't // cause a break of a currently-granted Read-Handle caching oplock, it // might have done so had a currently-breaking oplock still been granted. /* * If (NeedToWait is FALSE) and * (Oplock.RHBreakQueue is empty) and (XXX: Not empty) * (BreakCacheLevel contains HANDLE_CACHING): * For each RHOpContext ThisContex in Oplock.RHBreakQueue: * If ThisContext.Open.OplockKey != Open.OplockKey: * Set NeedToWait to TRUE. * Break out of the For loop. * EndIf * EndFor * EndIf * Recompute Oplock.State according to the algorithm in * section 2.1.4.13, passing Oplock as ThisOplock. */ if (NeedToWait == B_FALSE && (BreakCacheLevel & HANDLE_CACHING) != 0) { FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_RHBQ == 0) continue; if (!CompareOplockKeys(ofile, o, CmpFlags)) { NeedToWait = B_TRUE; break; } } } RecomputeOplockState(node); break; case (READ_CACHING|HANDLE_CACHING|BREAK_TO_READ_CACHING): /* * If BreakCacheLevel contains READ_CACHING: */ if ((BreakCacheLevel & READ_CACHING) != 0) { /* * For each RHOpContext ThisContext in * Oplock.RHBreakQueue: * Call the algorithm in section 2.1.4.12.2, * passing Open = OperationOpen parameter, * ThisContext.Open = OplockOpen parameter, * and Flags as the Flags parameter. * If the algorithm returns FALSE: * Set ThisCtx.BreakingToRead = FALSE. * EndIf * Recompute Oplock.State according to the * algorithm in section 2.1.4.13, passing * Oplock as the ThisOplock parameter. * EndFor */ FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_RHBQ == 0) continue; if (!CompareOplockKeys(ofile, o, CmpFlags)) { o->f_oplock.BreakingToRead = B_FALSE; } } RecomputeOplockState(node); } /* FALLTHROUGH */ case (READ_CACHING|HANDLE_CACHING|BREAK_TO_NO_CACHING): /* * If BreakCacheLevel contains HANDLE_CACHING: * For each RHOpContext ThisContext in Oplock.RHBreakQueue: * If ThisContext.Open.OplockKey != Open.OplockKey: * Set NeedToWait to TRUE. * Break out of the For loop. * EndIf * EndFor * EndIf */ if ((BreakCacheLevel & HANDLE_CACHING) != 0) { FOREACH_NODE_OFILE(node, o) { if (o->f_oplock.onlist_RHBQ == 0) continue; if (!CompareOplockKeys(ofile, o, CmpFlags)) { NeedToWait = B_TRUE; break; } } } break; case (READ_CACHING|WRITE_CACHING|EXCLUSIVE): /* * If BreakCacheLevel contains both * READ_CACHING and WRITE_CACHING: * Notify the server of an oplock break according to * the algorithm in section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = Oplock.ExclusiveOpen. * NewOplockLevel = LEVEL_NONE. * AcknowledgeRequired = TRUE. * OplockCompletionStatus = STATUS_SUCCESS. * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.1.) * Set Oplock.State to (READ_CACHING|WRITE_CACHING| \ * EXCLUSIVE|BREAK_TO_NO_CACHING). * Set NeedToWait to TRUE. */ if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) == (READ_CACHING | WRITE_CACHING)) { o = nol->excl_open; ASSERT(o != NULL); smb_oplock_ind_break(o, LEVEL_NONE, B_TRUE, NT_STATUS_SUCCESS); nol->ol_state = (READ_CACHING|WRITE_CACHING| EXCLUSIVE|BREAK_TO_NO_CACHING); NeedToWait = B_TRUE; } /* * Else If BreakCacheLevel contains WRITE_CACHING: * Notify the server of an oplock break according to * the algorithm in section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = Oplock.ExclusiveOpen. * NewOplockLevel = READ_CACHING. * AcknowledgeRequired = TRUE. * OplockCompletionStatus = STATUS_SUCCESS. * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.1.) * Set Oplock.State to (READ_CACHING|WRITE_CACHING| * EXCLUSIVE|BREAK_TO_READ_CACHING). * Set NeedToWait to TRUE. * EndIf */ else if ((BreakCacheLevel & WRITE_CACHING) != 0) { o = nol->excl_open; ASSERT(o != NULL); smb_oplock_ind_break(o, READ_CACHING, B_TRUE, NT_STATUS_SUCCESS); nol->ol_state = (READ_CACHING|WRITE_CACHING| EXCLUSIVE|BREAK_TO_READ_CACHING); NeedToWait = B_TRUE; } break; case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE): /* * If BreakCacheLevel equals WRITE_CACHING: * Notify the server of an oplock break according to * the algorithm in section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = Oplock.ExclusiveOpen. * NewOplockLevel = (READ_CACHING|HANDLE_CACHING). * AcknowledgeRequired = TRUE. * OplockCompletionStatus = STATUS_SUCCESS. * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.1.) * Set Oplock.State to (READ_CACHING|WRITE_CACHING| * HANDLE_CACHING|EXCLUSIVE| * BREAK_TO_READ_CACHING| * BREAK_TO_HANDLE_CACHING). * Set NeedToWait to TRUE. */ if (BreakCacheLevel == WRITE_CACHING) { o = nol->excl_open; ASSERT(o != NULL); smb_oplock_ind_break(o, CACHE_RH, B_TRUE, NT_STATUS_SUCCESS); nol->ol_state = (READ_CACHING|WRITE_CACHING|HANDLE_CACHING| EXCLUSIVE|BREAK_TO_READ_CACHING| BREAK_TO_HANDLE_CACHING); NeedToWait = B_TRUE; } /* * Else If BreakCacheLevel equals HANDLE_CACHING: * Notify the server of an oplock break according to * the algorithm in section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = Oplock.ExclusiveOpen. * NewOplockLevel = (READ_CACHING|WRITE_CACHING). * AcknowledgeRequired = TRUE. * OplockCompletionStatus = STATUS_SUCCESS. * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.1.) * Set Oplock.State to (READ_CACHING|WRITE_CACHING| * HANDLE_CACHING|EXCLUSIVE| * BREAK_TO_READ_CACHING| * BREAK_TO_WRITE_CACHING). * Set NeedToWait to TRUE. */ else if (BreakCacheLevel == HANDLE_CACHING) { o = nol->excl_open; ASSERT(o != NULL); smb_oplock_ind_break(o, CACHE_RW, B_TRUE, NT_STATUS_SUCCESS); nol->ol_state = (READ_CACHING|WRITE_CACHING|HANDLE_CACHING| EXCLUSIVE|BREAK_TO_READ_CACHING| BREAK_TO_WRITE_CACHING); NeedToWait = B_TRUE; } /* * Else If BreakCacheLevel contains both * READ_CACHING and WRITE_CACHING: * Notify the server of an oplock break according to * the algorithm in section 2.1.5.17.3, setting the * algorithm's parameters as follows: * BreakingOplockOpen = Oplock.ExclusiveOpen. * NewOplockLevel = LEVEL_NONE. * AcknowledgeRequired = TRUE. * OplockCompletionStatus = STATUS_SUCCESS. * (The operation does not end at this point; * this call to 2.1.5.17.3 completes some * earlier call to 2.1.5.17.1.) * Set Oplock.State to (READ_CACHING|WRITE_CACHING| * HANDLE_CACHING|EXCLUSIVE| * BREAK_TO_NO_CACHING). * Set NeedToWait to TRUE. * EndIf */ else if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) == (READ_CACHING | WRITE_CACHING)) { o = nol->excl_open; ASSERT(o != NULL); smb_oplock_ind_break(o, LEVEL_NONE, B_TRUE, NT_STATUS_SUCCESS); nol->ol_state = (READ_CACHING|WRITE_CACHING|HANDLE_CACHING| EXCLUSIVE|BREAK_TO_NO_CACHING); NeedToWait = B_TRUE; } break; case (READ_CACHING|WRITE_CACHING|EXCLUSIVE|BREAK_TO_READ_CACHING): /* * If BreakCacheLevel contains READ_CACHING: * Set Oplock.State to (READ_CACHING|WRITE_CACHING| * EXCLUSIVE|BREAK_TO_NO_CACHING). * EndIf * If BreakCacheLevel contains either * READ_CACHING or WRITE_CACHING: * Set NeedToWait to TRUE. * EndIf */ if ((BreakCacheLevel & READ_CACHING) != 0) { nol->ol_state = (READ_CACHING|WRITE_CACHING| EXCLUSIVE|BREAK_TO_NO_CACHING); } if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) != 0) { NeedToWait = B_TRUE; } break; case (READ_CACHING|WRITE_CACHING|EXCLUSIVE|BREAK_TO_NO_CACHING): /* * If BreakCacheLevel contains either * READ_CACHING or WRITE_CACHING: * Set NeedToWait to TRUE. * EndIf */ if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) != 0) { NeedToWait = B_TRUE; } break; case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE| BREAK_TO_READ_CACHING|BREAK_TO_WRITE_CACHING): /* * If BreakCacheLevel == WRITE_CACHING: * Set Oplock.State to (READ_CACHING|WRITE_CACHING| * HANDLE_CACHING|EXCLUSIVE|BREAK_TO_READ_CACHING). * Else If BreakCacheLevel contains both * READ_CACHING and WRITE_CACHING: * Set Oplock.State to (READ_CACHING|WRITE_CACHING| * HANDLE_CACHING|EXCLUSIVE|BREAK_TO_NO_CACHING). * EndIf * Set NeedToWait to TRUE. */ if (BreakCacheLevel == WRITE_CACHING) { nol->ol_state = (READ_CACHING|WRITE_CACHING| HANDLE_CACHING|EXCLUSIVE|BREAK_TO_READ_CACHING); } else if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) == (READ_CACHING | WRITE_CACHING)) { nol->ol_state = (READ_CACHING|WRITE_CACHING| HANDLE_CACHING|EXCLUSIVE|BREAK_TO_NO_CACHING); } NeedToWait = B_TRUE; break; case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE| BREAK_TO_READ_CACHING|BREAK_TO_HANDLE_CACHING): /* * If BreakCacheLevel == HANDLE_CACHING: * Set Oplock.State to (READ_CACHING|WRITE_CACHING| * HANDLE_CACHING|EXCLUSIVE| * BREAK_TO_READ_CACHING). * Else If BreakCacheLevel contains READ_CACHING: * Set Oplock.State to (READ_CACHING|WRITE_CACHING| * HANDLE_CACHING|EXCLUSIVE| * BREAK_TO_NO_CACHING). * EndIf * Set NeedToWait to TRUE. */ if (BreakCacheLevel == HANDLE_CACHING) { nol->ol_state = (READ_CACHING|WRITE_CACHING| HANDLE_CACHING|EXCLUSIVE| BREAK_TO_READ_CACHING); } else if ((BreakCacheLevel & READ_CACHING) != 0) { nol->ol_state = (READ_CACHING|WRITE_CACHING| HANDLE_CACHING|EXCLUSIVE| BREAK_TO_NO_CACHING); } NeedToWait = B_TRUE; break; case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE| BREAK_TO_READ_CACHING): /* * If BreakCacheLevel contains READ_CACHING, * Set Oplock.State to (READ_CACHING|WRITE_CACHING| * HANDLE_CACHING|EXCLUSIVE| * BREAK_TO_NO_CACHING). * EndIf * Set NeedToWait to TRUE. */ if ((BreakCacheLevel & READ_CACHING) != 0) { nol->ol_state = (READ_CACHING|WRITE_CACHING| HANDLE_CACHING|EXCLUSIVE| BREAK_TO_NO_CACHING); } NeedToWait = B_TRUE; break; case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE| BREAK_TO_NO_CACHING): NeedToWait = B_TRUE; break; } /* Switch */ if (NeedToWait) { /* * The operation that called this algorithm MUST be * made cancelable by inserting it into * CancelableOperations.CancelableOperationList. * The operation that called this algorithm waits until * the oplock break is acknowledged, as specified in * section 2.1.5.18, or the operation is canceled. */ status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS; /* Caller does smb_oplock_wait_break() */ } out: mutex_exit(&node->n_oplock.ol_mutex); smb_llist_exit(&node->n_ofile_list); return (status); } /* * smb_oplock_move() * * Helper function for smb2_lease_ofile_close, where we're closing the * ofile that has the oplock for a given lease, and need to move that * oplock to another handle with the same lease. * * This is not described in [MS-FSA], so presumably Windows does this * by keeping oplock objects separate from the open files (no action * needed in the FSA layer). We keep the oplock state as part of the * ofile, so we need to relocate the oplock state in this case. * * Note that in here, we're moving state for both the FSA level and * the SMB level (which is unusual) but this is the easiest way to * make sure we move the state without any other effects. */ void smb_oplock_move(smb_node_t *node, smb_ofile_t *fr_ofile, smb_ofile_t *to_ofile) { /* * These are the two common states for an ofile with * a lease that's not the one holding the oplock. * Log if it's not either of these. */ static const smb_oplock_grant_t og0 = { 0 }; static const smb_oplock_grant_t og8 = { .og_state = OPLOCK_LEVEL_GRANULAR, 0 }; smb_oplock_grant_t og_tmp; ASSERT(fr_ofile->f_node == node); ASSERT(to_ofile->f_node == node); ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex)); /* * The ofile to which we're moving the oplock * should NOT have any oplock state. However, * as long as we just swap state between the * two oplocks, we won't invalidate any of * the node's "onlist" counts etc. */ if (bcmp(&to_ofile->f_oplock, &og0, sizeof (og0)) != 0 && bcmp(&to_ofile->f_oplock, &og8, sizeof (og8)) != 0) { #ifdef DEBUG cmn_err(CE_NOTE, "smb_oplock_move: not empty?"); #endif DTRACE_PROBE2(dst__not__empty, smb_node_t *, node, smb_ofile_t *, to_ofile); } og_tmp = to_ofile->f_oplock; to_ofile->f_oplock = fr_ofile->f_oplock; fr_ofile->f_oplock = og_tmp; if (node->n_oplock.excl_open == fr_ofile) node->n_oplock.excl_open = to_ofile; }