/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2020 Tintri by DDN, Inc. All rights reserved. * Copyright 2022 RackTop Systems, Inc. */ /* * smb1 oplock support */ #include #define BATCH_OR_EXCL (OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE) /* * This is called by the SMB1 "Locking_andX" handler, * for SMB1 oplock break acknowledgement. * This is an "Ack" from the client. */ void smb1_oplock_ack_break(smb_request_t *sr, uchar_t oplock_level) { smb_ofile_t *ofile; smb_node_t *node; uint32_t NewLevel; ofile = sr->fid_ofile; node = ofile->f_node; if (oplock_level == 0) NewLevel = OPLOCK_LEVEL_NONE; else NewLevel = OPLOCK_LEVEL_TWO; smb_llist_enter(&node->n_ofile_list, RW_READER); mutex_enter(&node->n_oplock.ol_mutex); ofile->f_oplock.og_breaking = B_FALSE; cv_broadcast(&ofile->f_oplock.og_ack_cv); (void) smb_oplock_ack_break(sr, ofile, &NewLevel); ofile->f_oplock.og_state = NewLevel; mutex_exit(&node->n_oplock.ol_mutex); smb_llist_exit(&node->n_ofile_list); } /* * Compose an SMB1 Oplock Break Notification packet, including * the SMB1 header and everything, in sr->reply. * The caller will send it and free the request. */ static void smb1_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel) { smb_ofile_t *ofile = sr->fid_ofile; uint16_t fid; uint8_t lock_type; uint8_t oplock_level; /* * Convert internal level to SMB1 */ switch (NewLevel) { default: ASSERT(0); /* FALLTHROUGH */ case OPLOCK_LEVEL_NONE: oplock_level = 0; break; case OPLOCK_LEVEL_TWO: oplock_level = 1; break; } sr->smb_com = SMB_COM_LOCKING_ANDX; sr->smb_tid = ofile->f_tree->t_tid; sr->smb_pid = 0xFFFF; sr->smb_uid = 0; sr->smb_mid = 0xFFFF; fid = ofile->f_fid; lock_type = LOCKING_ANDX_OPLOCK_RELEASE; (void) smb_mbc_encodef( &sr->reply, "Mb19.wwwwbb3.wbb10.", /* "\xffSMB" M */ sr->smb_com, /* b */ /* status, flags, signature 19. */ sr->smb_tid, /* w */ sr->smb_pid, /* w */ sr->smb_uid, /* w */ sr->smb_mid, /* w */ 8, /* word count b */ 0xFF, /* AndX cmd b */ /* AndX reserved, offset 3. */ fid, lock_type, oplock_level); } /* * Send an oplock break over the wire, or if we can't, * then process the oplock break locally. * * [MS-CIFS] 3.3.4.2 Object Store Indicates an OpLock Break * * This is mostly similar to smb2_oplock_send_break() * See top comment there about the design. * Called from smb_oplock_async_break. * * This handles only SMB1, which has no durable handles, * and never has GRANULAR oplocks. */ void smb1_oplock_send_break(smb_request_t *sr) { smb_ofile_t *ofile = sr->fid_ofile; smb_node_t *node = ofile->f_node; uint32_t NewLevel = sr->arg.olbrk.NewLevel; boolean_t AckReq = sr->arg.olbrk.AckRequired; uint32_t status; int rc; /* * SMB1 clients should only get Level II oplocks if they * set the capability indicating they know about them. */ if (NewLevel == OPLOCK_LEVEL_TWO && ofile->f_oplock.og_dialect < NT_LM_0_12) NewLevel = OPLOCK_LEVEL_NONE; /* * Build the break message in sr->reply. * It's free'd in smb_request_free(). * Always SMB1 here. */ sr->reply.max_bytes = MLEN; smb1_oplock_break_notification(sr, NewLevel); /* * Try to send the break message to the client. * If connected, this IF body will be true. */ if (sr->session == ofile->f_session) rc = smb_session_send(sr->session, 0, &sr->reply); else rc = ENOTCONN; if (rc != 0) { /* * We were unable to send the oplock break request, * presumably because the connection is gone. * Just close the handle. */ smb_ofile_close(ofile, 0); return; } /* * OK, we were able to send the break message. * If no ack. required, we're done. */ if (!AckReq) return; /* * We're expecting an ACK. Wait in this thread * so we can log clients that don't respond. * Note: this can also fail for other reasons * such as client disconnect or server shutdown. */ status = smb_oplock_wait_ack(sr, NewLevel); if (status == 0) return; DTRACE_PROBE2(wait__ack__failed, smb_request_t *, sr, uint32_t, status); /* * Did not get an ACK, so do the ACK locally. * Note: always break to none here, regardless * of what the passed in cache level was. */ NewLevel = OPLOCK_LEVEL_NONE; smb_llist_enter(&node->n_ofile_list, RW_READER); mutex_enter(&node->n_oplock.ol_mutex); ofile->f_oplock.og_breaking = B_FALSE; cv_broadcast(&ofile->f_oplock.og_ack_cv); status = smb_oplock_ack_break(sr, ofile, &NewLevel); ofile->f_oplock.og_state = NewLevel; mutex_exit(&node->n_oplock.ol_mutex); smb_llist_exit(&node->n_ofile_list); #ifdef DEBUG if (status != 0) { cmn_err(CE_NOTE, "clnt %s local oplock ack, status=0x%x", sr->session->ip_addr_str, status); } #endif } /* * Client has an open handle and requests an oplock. * Convert SMB1 oplock request info in to internal form, * call common oplock code, convert result to SMB1. */ void smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok) { smb_arg_open_t *op = &sr->arg.open; smb_ofile_t *ofile = sr->fid_ofile; uint32_t status; /* Only disk trees get oplocks. */ if ((sr->tid_tree->t_res_type & STYPE_MASK) != STYPE_DISKTREE) { op->op_oplock_level = SMB_OPLOCK_NONE; return; } if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_OPLOCKS)) { op->op_oplock_level = SMB_OPLOCK_NONE; return; } if (!smb_session_levelII_oplocks(sr->session)) level2ok = B_FALSE; /* Common code checks file type. */ /* * SMB1: Convert to internal form. */ switch (op->op_oplock_level) { case SMB_OPLOCK_BATCH: op->op_oplock_state = OPLOCK_LEVEL_BATCH; break; case SMB_OPLOCK_EXCLUSIVE: op->op_oplock_state = OPLOCK_LEVEL_ONE; break; case SMB_OPLOCK_LEVEL_II: op->op_oplock_state = OPLOCK_LEVEL_TWO; break; case SMB_OPLOCK_NONE: default: op->op_oplock_level = SMB_OPLOCK_NONE; return; } /* * Tree options may force shared oplocks */ if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) { op->op_oplock_state = OPLOCK_LEVEL_TWO; } /* * Try exclusive first, if requested */ if ((op->op_oplock_state & BATCH_OR_EXCL) != 0) { status = smb_oplock_request(sr, ofile, &op->op_oplock_state); } else { status = NT_STATUS_OPLOCK_NOT_GRANTED; } /* * If exclusive failed (or the tree forced shared oplocks) * and if the caller supports Level II, try shared. */ if (status == NT_STATUS_OPLOCK_NOT_GRANTED && level2ok) { op->op_oplock_state = OPLOCK_LEVEL_TWO; status = smb_oplock_request(sr, ofile, &op->op_oplock_state); } /* * Keep track of what we got (ofile->f_oplock.og_state etc) * so we'll know what we had when sending a break later. * The og_dialect here is the oplock dialect, which may be * different than SMB dialect. Pre-NT clients did not * support "Level II" oplocks. If we're talking to a * client that didn't set the CAP_LEVEL_II_OPLOCKS in * its capabilities, let og_dialect = LANMAN2_1. */ switch (status) { case NT_STATUS_SUCCESS: case NT_STATUS_OPLOCK_BREAK_IN_PROGRESS: ofile->f_oplock.og_dialect = (level2ok) ? NT_LM_0_12 : LANMAN2_1; ofile->f_oplock.og_state = op->op_oplock_state; ofile->f_oplock.og_breakto = op->op_oplock_state; ofile->f_oplock.og_breaking = B_FALSE; break; case NT_STATUS_OPLOCK_NOT_GRANTED: op->op_oplock_level = SMB_OPLOCK_NONE; return; default: /* Caller did not check args sufficiently? */ cmn_err(CE_NOTE, "clnt %s oplock req. err 0x%x", sr->session->ip_addr_str, status); op->op_oplock_level = SMB_OPLOCK_NONE; return; } /* * Only succes cases get here. * Convert internal oplock state to SMB1 */ if (op->op_oplock_state & OPLOCK_LEVEL_BATCH) { op->op_oplock_level = SMB_OPLOCK_BATCH; } else if (op->op_oplock_state & OPLOCK_LEVEL_ONE) { op->op_oplock_level = SMB_OPLOCK_EXCLUSIVE; } else if (op->op_oplock_state & OPLOCK_LEVEL_TWO) { op->op_oplock_level = SMB_OPLOCK_LEVEL_II; } else { op->op_oplock_level = SMB_OPLOCK_NONE; } /* * An smb_oplock_reqest call may have returned the * status code that says we should wait. */ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { (void) smb_oplock_wait_break(sr, ofile->f_node, 0); } }