1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2016 Nexenta Systems, Inc. All rights reserved. 14 */ 15 16 17 #include <smbsrv/smb2_kproto.h> 18 #include <smbsrv/smb_kstat.h> 19 #include <smbsrv/smb2.h> 20 21 /* 22 * Saved state for a command that "goes async". When a compound request 23 * contains a command that may block indefinitely, the compound reply is 24 * composed with an "interim response" for that command, and information 25 * needed to actually dispatch that command is saved on a list of "async" 26 * commands for this compound request. After the compound reply is sent, 27 * the list of async commands is processed, and those may block as long 28 * as they need to without affecting the initial compound request. 29 * 30 * Now interestingly, this "async" mechanism is not used with the full 31 * range of asynchrony that one might imagine. The design of async 32 * request processing can be drastically simplified if we can assume 33 * that there's no need to run more than one async command at a time. 34 * With that simplifying assumption, we can continue using the current 35 * "one worker thread per request message" model, which has very simple 36 * locking rules etc. The same worker thread that handles the initial 37 * compound request can handle the list of async requests. 38 * 39 * As it turns out, SMB2 clients do not try to use more than one "async" 40 * command in a compound. If they were to do so, the [MS-SMB2] spec. 41 * allows us to decline additional async requests with an error. 42 * 43 * smb_async_req_t is the struct used to save an "async" request on 44 * the list of requests that had an interim reply in the initial 45 * compound reply. This includes everything needed to restart 46 * processing at the async command. 47 */ 48 49 typedef struct smb2_async_req { 50 51 smb_sdrc_t (*ar_func)(smb_request_t *); 52 53 int ar_cmd_hdr; /* smb2_cmd_hdr offset */ 54 int ar_cmd_len; /* length from hdr */ 55 56 /* 57 * SMB2 header fields. 58 */ 59 uint16_t ar_cmd_code; 60 uint16_t ar_uid; 61 uint16_t ar_tid; 62 uint32_t ar_pid; 63 uint32_t ar_hdr_flags; 64 uint64_t ar_messageid; 65 } smb2_async_req_t; 66 67 void smb2sr_do_async(smb_request_t *); 68 smb_sdrc_t smb2_invalid_cmd(smb_request_t *); 69 static void smb2_tq_work(void *); 70 71 static const smb_disp_entry_t 72 smb2_disp_table[SMB2__NCMDS] = { 73 74 /* text-name, pre, func, post, cmd-code, dialect, flags */ 75 76 { "smb2_negotiate", NULL, 77 smb2_negotiate, NULL, 0, 0, 78 SDDF_SUPPRESS_TID | SDDF_SUPPRESS_UID }, 79 80 { "smb2_session_setup", NULL, 81 smb2_session_setup, NULL, 0, 0, 82 SDDF_SUPPRESS_TID | SDDF_SUPPRESS_UID }, 83 84 { "smb2_logoff", NULL, 85 smb2_logoff, NULL, 0, 0, 86 SDDF_SUPPRESS_TID }, 87 88 { "smb2_tree_connect", NULL, 89 smb2_tree_connect, NULL, 0, 0, 90 SDDF_SUPPRESS_TID }, 91 92 { "smb2_tree_disconn", NULL, 93 smb2_tree_disconn, NULL, 0, 0 }, 94 95 { "smb2_create", NULL, 96 smb2_create, NULL, 0, 0 }, 97 98 { "smb2_close", NULL, 99 smb2_close, NULL, 0, 0 }, 100 101 { "smb2_flush", NULL, 102 smb2_flush, NULL, 0, 0 }, 103 104 { "smb2_read", NULL, 105 smb2_read, NULL, 0, 0 }, 106 107 { "smb2_write", NULL, 108 smb2_write, NULL, 0, 0 }, 109 110 { "smb2_lock", NULL, 111 smb2_lock, NULL, 0, 0 }, 112 113 { "smb2_ioctl", NULL, 114 smb2_ioctl, NULL, 0, 0 }, 115 116 { "smb2_cancel", NULL, 117 smb2_cancel, NULL, 0, 0, 118 SDDF_SUPPRESS_UID | SDDF_SUPPRESS_TID }, 119 120 { "smb2_echo", NULL, 121 smb2_echo, NULL, 0, 0, 122 SDDF_SUPPRESS_UID | SDDF_SUPPRESS_TID }, 123 124 { "smb2_query_dir", NULL, 125 smb2_query_dir, NULL, 0, 0 }, 126 127 { "smb2_change_notify", NULL, 128 smb2_change_notify, NULL, 0, 0 }, 129 130 { "smb2_query_info", NULL, 131 smb2_query_info, NULL, 0, 0 }, 132 133 { "smb2_set_info", NULL, 134 smb2_set_info, NULL, 0, 0 }, 135 136 { "smb2_oplock_break_ack", NULL, 137 smb2_oplock_break_ack, NULL, 0, 0 }, 138 139 { "smb2_invalid_cmd", NULL, 140 smb2_invalid_cmd, NULL, 0, 0, 141 SDDF_SUPPRESS_UID | SDDF_SUPPRESS_TID }, 142 }; 143 144 smb_sdrc_t 145 smb2_invalid_cmd(smb_request_t *sr) 146 { 147 #ifdef DEBUG 148 cmn_err(CE_NOTE, "clnt %s bad SMB2 cmd code", 149 sr->session->ip_addr_str); 150 #endif 151 sr->smb2_status = NT_STATUS_INVALID_PARAMETER; 152 return (SDRC_DROP_VC); 153 } 154 155 /* 156 * This is the SMB2 handler for new smb requests, called from 157 * smb_session_reader after SMB negotiate is done. For most SMB2 158 * requests, we just enqueue them for the smb_session_worker to 159 * execute via the task queue, so they can block for resources 160 * without stopping the reader thread. A few protocol messages 161 * are special cases and are handled directly here in the reader 162 * thread so they don't wait for taskq scheduling. 163 * 164 * This function must either enqueue the new request for 165 * execution via the task queue, or execute it directly 166 * and then free it. If this returns non-zero, the caller 167 * will drop the session. 168 */ 169 int 170 smb2sr_newrq(smb_request_t *sr) 171 { 172 struct mbuf_chain *mbc = &sr->command; 173 uint32_t magic; 174 int rc, skip; 175 176 if (smb_mbc_peek(mbc, 0, "l", &magic) != 0) 177 goto drop; 178 179 if (magic != SMB2_PROTOCOL_MAGIC) 180 goto drop; 181 182 /* 183 * Walk the SMB2 commands in this compound message and 184 * keep track of the range of message IDs it uses. 185 */ 186 for (;;) { 187 if (smb2_decode_header(sr) != 0) 188 goto drop; 189 190 /* 191 * Cancel requests are special: They refer to 192 * an earlier message ID (or an async. ID), 193 * never a new ID, and are never compounded. 194 * This is intentionally not "goto drop" 195 * because rc may be zero (success). 196 */ 197 if (sr->smb2_cmd_code == SMB2_CANCEL) { 198 rc = smb2_newrq_cancel(sr); 199 smb_request_free(sr); 200 return (rc); 201 } 202 203 /* 204 * Keep track of the total credits in this compound 205 * and the first (real) message ID (not: 0, -1) 206 * While we're looking, verify that all (real) IDs 207 * are (first <= ID < (first + msg_credits)) 208 */ 209 if (sr->smb2_credit_charge == 0) 210 sr->smb2_credit_charge = 1; 211 sr->smb2_total_credits += sr->smb2_credit_charge; 212 213 if (sr->smb2_messageid != 0 && 214 sr->smb2_messageid != UINT64_MAX) { 215 216 if (sr->smb2_first_msgid == 0) 217 sr->smb2_first_msgid = sr->smb2_messageid; 218 219 if (sr->smb2_messageid < sr->smb2_first_msgid || 220 sr->smb2_messageid >= (sr->smb2_first_msgid + 221 sr->smb2_total_credits)) { 222 long long id = (long long) sr->smb2_messageid; 223 cmn_err(CE_WARN, "clnt %s msg ID 0x%llx " 224 "out of sequence in compound", 225 sr->session->ip_addr_str, id); 226 } 227 } 228 229 /* Normal loop exit on next == zero */ 230 if (sr->smb2_next_command == 0) 231 break; 232 233 /* Abundance of caution... */ 234 if (sr->smb2_next_command < SMB2_HDR_SIZE) 235 goto drop; 236 237 /* Advance to the next header. */ 238 skip = sr->smb2_next_command - SMB2_HDR_SIZE; 239 if (MBC_ROOM_FOR(mbc, skip) == 0) 240 goto drop; 241 mbc->chain_offset += skip; 242 } 243 /* Rewind back to the top. */ 244 mbc->chain_offset = 0; 245 246 /* 247 * Submit the request to the task queue, which calls 248 * smb2_tq_work when the workload permits. 249 */ 250 sr->sr_time_submitted = gethrtime(); 251 sr->sr_state = SMB_REQ_STATE_SUBMITTED; 252 smb_srqueue_waitq_enter(sr->session->s_srqueue); 253 (void) taskq_dispatch(sr->sr_server->sv_worker_pool, 254 smb2_tq_work, sr, TQ_SLEEP); 255 return (0); 256 257 drop: 258 smb_request_free(sr); 259 return (-1); 260 } 261 262 static void 263 smb2_tq_work(void *arg) 264 { 265 smb_request_t *sr; 266 smb_srqueue_t *srq; 267 268 sr = (smb_request_t *)arg; 269 SMB_REQ_VALID(sr); 270 271 srq = sr->session->s_srqueue; 272 smb_srqueue_waitq_to_runq(srq); 273 sr->sr_worker = curthread; 274 sr->sr_time_active = gethrtime(); 275 276 /* 277 * Always dispatch to the work function, because cancelled 278 * requests need an error reply (NT_STATUS_CANCELLED). 279 */ 280 mutex_enter(&sr->sr_mutex); 281 if (sr->sr_state == SMB_REQ_STATE_SUBMITTED) 282 sr->sr_state = SMB_REQ_STATE_ACTIVE; 283 mutex_exit(&sr->sr_mutex); 284 285 smb2sr_work(sr); 286 287 smb_srqueue_runq_exit(srq); 288 } 289 290 /* 291 * smb2sr_work 292 * 293 * This function processes each SMB command in the current request 294 * (which may be a compound request) building a reply containing 295 * SMB reply messages, one-to-one with the SMB commands. Some SMB 296 * commands (change notify, blocking locks) may require both an 297 * "interim response" and a later "async response" at completion. 298 * In such cases, we'll encode the interim response in the reply 299 * compound we're building, and put the (now async) command on a 300 * list of commands that need further processing. After we've 301 * finished processing the commands in this compound and building 302 * the compound reply, we'll send the compound reply, and finally 303 * process the list of async commands. 304 * 305 * As we work our way through the compound request and reply, 306 * we need to keep track of the bounds of the current request 307 * and reply. For the request, this uses an MBC_SHADOW_CHAIN 308 * that begins at smb2_cmd_hdr. The reply is appended to the 309 * sr->reply chain starting at smb2_reply_hdr. 310 * 311 * This function must always free the smb request, or arrange 312 * for it to be completed and free'd later (if SDRC_SR_KEPT). 313 */ 314 void 315 smb2sr_work(struct smb_request *sr) 316 { 317 const smb_disp_entry_t *sdd; 318 smb_disp_stats_t *sds; 319 smb_session_t *session; 320 uint32_t msg_len; 321 uint16_t cmd_idx; 322 int rc = 0; 323 boolean_t disconnect = B_FALSE; 324 boolean_t related; 325 326 session = sr->session; 327 328 ASSERT(sr->tid_tree == 0); 329 ASSERT(sr->uid_user == 0); 330 ASSERT(sr->fid_ofile == 0); 331 sr->smb_fid = (uint16_t)-1; 332 sr->smb2_status = 0; 333 334 /* temporary until we identify a user */ 335 sr->user_cr = zone_kcred(); 336 337 cmd_start: 338 /* 339 * Note that we don't check sr_state here and abort the 340 * compound if cancelled (etc.) because some SMB2 command 341 * handlers need to do work even when cancelled. 342 * 343 * We treat some status codes as if "sticky", meaning 344 * once they're set after some command handler returns, 345 * all remaining commands get this status without even 346 * calling the command-specific handler. 347 */ 348 if (sr->smb2_status != NT_STATUS_CANCELLED && 349 sr->smb2_status != NT_STATUS_INSUFFICIENT_RESOURCES) 350 sr->smb2_status = 0; 351 352 /* 353 * Decode the request header 354 * 355 * Most problems with decoding will result in the error 356 * STATUS_INVALID_PARAMETER. If the decoding problem 357 * prevents continuing, we'll close the connection. 358 * [MS-SMB2] 3.3.5.2.6 Handling Incorrectly Formatted... 359 */ 360 sr->smb2_cmd_hdr = sr->command.chain_offset; 361 if ((rc = smb2_decode_header(sr)) != 0) { 362 cmn_err(CE_WARN, "clnt %s bad SMB2 header", 363 session->ip_addr_str); 364 disconnect = B_TRUE; 365 goto cleanup; 366 } 367 368 /* 369 * The SMB2_FLAGS_SERVER_TO_REDIR should only appear 370 * in messages from the server back to the client. 371 */ 372 if ((sr->smb2_hdr_flags & SMB2_FLAGS_SERVER_TO_REDIR) != 0) { 373 cmn_err(CE_WARN, "clnt %s bad SMB2 flags", 374 session->ip_addr_str); 375 disconnect = B_TRUE; 376 goto cleanup; 377 } 378 related = (sr->smb2_hdr_flags & SMB2_FLAGS_RELATED_OPERATIONS); 379 380 /* 381 * In case we bail out with an error before we get to the 382 * section that computes the credit grant, initialize the 383 * response header fields so that credits won't change. 384 * Note: SMB 2.02 clients may send credit charge zero. 385 */ 386 if (sr->smb2_credit_charge == 0) 387 sr->smb2_credit_charge = 1; 388 sr->smb2_credit_response = sr->smb2_credit_charge; 389 390 /* 391 * Reserve space for the reply header, and save the offset. 392 * The reply header will be overwritten later. If we have 393 * already exhausted the output space, then this client is 394 * trying something funny. Log it and kill 'em. 395 */ 396 sr->smb2_reply_hdr = sr->reply.chain_offset; 397 if ((rc = smb2_encode_header(sr, B_FALSE)) != 0) { 398 cmn_err(CE_WARN, "clnt %s excessive reply", 399 session->ip_addr_str); 400 disconnect = B_TRUE; 401 goto cleanup; 402 } 403 404 /* 405 * Figure out the length of data following the SMB2 header. 406 * It ends at either the next SMB2 header if there is one 407 * (smb2_next_command != 0) or at the end of the message. 408 */ 409 if (sr->smb2_next_command != 0) { 410 /* [MS-SMB2] says this is 8-byte aligned */ 411 msg_len = sr->smb2_next_command; 412 if ((msg_len & 7) != 0 || (msg_len < SMB2_HDR_SIZE) || 413 ((sr->smb2_cmd_hdr + msg_len) > sr->command.max_bytes)) { 414 cmn_err(CE_WARN, "clnt %s bad SMB2 next cmd", 415 session->ip_addr_str); 416 disconnect = B_TRUE; 417 goto cleanup; 418 } 419 } else { 420 msg_len = sr->command.max_bytes - sr->smb2_cmd_hdr; 421 } 422 423 /* 424 * Setup a shadow chain for this SMB2 command, starting 425 * with the header and ending at either the next command 426 * or the end of the message. The signing check below 427 * needs the entire SMB2 command. After that's done, we 428 * advance chain_offset to the end of the header where 429 * the command specific handlers continue decoding. 430 */ 431 (void) MBC_SHADOW_CHAIN(&sr->smb_data, &sr->command, 432 sr->smb2_cmd_hdr, msg_len); 433 434 /* 435 * We will consume the data for this request from smb_data. 436 * That effectively consumes msg_len bytes from sr->command 437 * but doesn't update its chain_offset, so we need to update 438 * that here to make later received bytes accounting work. 439 */ 440 sr->command.chain_offset = sr->smb2_cmd_hdr + msg_len; 441 ASSERT(sr->command.chain_offset <= sr->command.max_bytes); 442 443 /* 444 * Validate the commmand code, get dispatch table entries. 445 * [MS-SMB2] 3.3.5.2.6 Handling Incorrectly Formatted... 446 * 447 * The last slot in the dispatch table is used to handle 448 * invalid commands. Same for statistics. 449 */ 450 if (sr->smb2_cmd_code < SMB2_INVALID_CMD) 451 cmd_idx = sr->smb2_cmd_code; 452 else 453 cmd_idx = SMB2_INVALID_CMD; 454 sdd = &smb2_disp_table[cmd_idx]; 455 sds = &session->s_server->sv_disp_stats2[cmd_idx]; 456 457 /* 458 * If this command is NOT "related" to the previous, 459 * clear out the UID, TID, FID state that might be 460 * left over from the previous command. 461 * 462 * If the command IS related, any new IDs are ignored, 463 * and we simply continue with the previous user, tree, 464 * and open file. 465 */ 466 if (!related) { 467 /* 468 * Drop user, tree, file; carefully ordered to 469 * avoid dangling references: file, tree, user 470 */ 471 if (sr->fid_ofile != NULL) { 472 smb_ofile_request_complete(sr->fid_ofile); 473 smb_ofile_release(sr->fid_ofile); 474 sr->fid_ofile = NULL; 475 } 476 if (sr->tid_tree != NULL) { 477 smb_tree_release(sr->tid_tree); 478 sr->tid_tree = NULL; 479 } 480 if (sr->uid_user != NULL) { 481 smb_user_release(sr->uid_user); 482 sr->uid_user = NULL; 483 sr->user_cr = zone_kcred(); 484 } 485 } 486 487 /* 488 * Make sure we have a user and tree as needed 489 * according to the flags for the this command. 490 * Note that we may have inherited these. 491 */ 492 if ((sdd->sdt_flags & SDDF_SUPPRESS_UID) == 0) { 493 /* 494 * This command requires a user session. 495 */ 496 if (related) { 497 /* 498 * Previous command should have given us a user. 499 * [MS-SMB2] 3.3.5.2 Handling Related Requests 500 */ 501 if (sr->uid_user == NULL) { 502 smb2sr_put_error(sr, 503 NT_STATUS_INVALID_PARAMETER); 504 goto cmd_done; 505 } 506 sr->smb_uid = sr->uid_user->u_uid; 507 } else { 508 /* 509 * Lookup the UID 510 * [MS-SMB2] 3.3.5.2 Verifying the Session 511 */ 512 ASSERT(sr->uid_user == NULL); 513 sr->uid_user = smb_session_lookup_uid(session, 514 sr->smb_uid); 515 if (sr->uid_user == NULL) { 516 smb2sr_put_error(sr, 517 NT_STATUS_USER_SESSION_DELETED); 518 goto cmd_done; 519 } 520 sr->user_cr = smb_user_getcred(sr->uid_user); 521 } 522 ASSERT(sr->uid_user != NULL); 523 } 524 525 if ((sdd->sdt_flags & SDDF_SUPPRESS_TID) == 0) { 526 /* 527 * This command requires a tree connection. 528 */ 529 if (related) { 530 /* 531 * Previous command should have given us a tree. 532 * [MS-SMB2] 3.3.5.2 Handling Related Requests 533 */ 534 if (sr->tid_tree == NULL) { 535 smb2sr_put_error(sr, 536 NT_STATUS_INVALID_PARAMETER); 537 goto cmd_done; 538 } 539 sr->smb_tid = sr->tid_tree->t_tid; 540 } else { 541 /* 542 * Lookup the TID 543 * [MS-SMB2] 3.3.5.2 Verifying the Tree Connect 544 */ 545 ASSERT(sr->tid_tree == NULL); 546 sr->tid_tree = smb_session_lookup_tree(session, 547 sr->smb_tid); 548 if (sr->tid_tree == NULL) { 549 smb2sr_put_error(sr, 550 NT_STATUS_NETWORK_NAME_DELETED); 551 goto cmd_done; 552 } 553 } 554 ASSERT(sr->tid_tree != NULL); 555 } 556 557 /* 558 * SMB2 signature verification, two parts: 559 * (a) Require SMB2_FLAGS_SIGNED (for most request types) 560 * (b) If SMB2_FLAGS_SIGNED is set, check the signature. 561 * [MS-SMB2] 3.3.5.2.4 Verifying the Signature 562 */ 563 564 /* 565 * No user session means no signature check. That's OK, 566 * i.e. for commands marked SDDF_SUPPRESS_UID above. 567 * Note, this also means we won't sign the reply. 568 */ 569 if (sr->uid_user == NULL) 570 sr->smb2_hdr_flags &= ~SMB2_FLAGS_SIGNED; 571 572 /* 573 * The SDDF_SUPPRESS_UID dispatch is set for requests that 574 * don't need a UID (user). These also don't require a 575 * signature check here. 576 */ 577 if ((sdd->sdt_flags & SDDF_SUPPRESS_UID) == 0 && 578 sr->uid_user != NULL && 579 (sr->uid_user->u_sign_flags & SMB_SIGNING_CHECK) != 0) { 580 /* 581 * This request type should be signed, and 582 * we're configured to require signatures. 583 */ 584 if ((sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED) == 0) { 585 smb2sr_put_error(sr, NT_STATUS_ACCESS_DENIED); 586 goto cmd_done; 587 } 588 rc = smb2_sign_check_request(sr); 589 if (rc != 0) { 590 DTRACE_PROBE1(smb2__sign__check, smb_request_t, sr); 591 smb2sr_put_error(sr, NT_STATUS_ACCESS_DENIED); 592 goto cmd_done; 593 } 594 } 595 596 /* 597 * Now that the signing check is done with smb_data, 598 * advance past the SMB2 header we decoded earlier. 599 * This leaves sr->smb_data correctly positioned 600 * for command-specific decoding in the dispatch 601 * function called next. 602 */ 603 sr->smb_data.chain_offset = sr->smb2_cmd_hdr + SMB2_HDR_SIZE; 604 605 /* 606 * SMB2 credits determine how many simultaneous commands the 607 * client may issue, and bounds the range of message IDs those 608 * commands may use. With multi-credit support, commands may 609 * use ranges of message IDs, where the credits used by each 610 * command are proportional to their data transfer size. 611 * 612 * Every command may request an increase or decrease of 613 * the currently granted credits, based on the difference 614 * between the credit request and the credit charge. 615 * [MS-SMB2] 3.3.1.2 Algorithm for the Granting of Credits 616 * 617 * Most commands have credit_request=1, credit_charge=1, 618 * which keeps the credit grant unchanged. 619 * 620 * All we're really doing here (for now) is reducing the 621 * credit_response if the client requests a credit increase 622 * that would take their credit over the maximum, and 623 * limiting the decrease so they don't run out of credits. 624 * 625 * Later, this could do something dynamic based on load. 626 * 627 * One other non-obvious bit about credits: We keep the 628 * session s_max_credits low until the 1st authentication, 629 * at which point we'll set the normal maximum_credits. 630 * Some clients ask for more credits with session setup, 631 * and we need to handle that requested increase _after_ 632 * the command-specific handler returns so it won't be 633 * restricted to the lower (pre-auth) limit. 634 */ 635 sr->smb2_credit_response = sr->smb2_credit_request; 636 if (sr->smb2_credit_request < sr->smb2_credit_charge) { 637 uint16_t cur, d; 638 639 mutex_enter(&session->s_credits_mutex); 640 cur = session->s_cur_credits; 641 642 /* Handle credit decrease. */ 643 d = sr->smb2_credit_charge - sr->smb2_credit_request; 644 cur -= d; 645 if (cur & 0x8000) { 646 /* 647 * underflow (bad credit charge or request) 648 * leave credits unchanged (response=charge) 649 */ 650 cur = session->s_cur_credits; 651 sr->smb2_credit_response = sr->smb2_credit_charge; 652 DTRACE_PROBE1(smb2__credit__neg, smb_request_t, sr); 653 } 654 655 /* 656 * The server MUST ensure that the number of credits 657 * held by the client is never reduced to zero. 658 * [MS-SMB2] 3.3.1.2 659 */ 660 if (cur == 0) { 661 cur = 1; 662 sr->smb2_credit_response += 1; 663 DTRACE_PROBE1(smb2__credit__min, smb_request_t, sr); 664 } 665 666 DTRACE_PROBE3(smb2__credit__decrease, 667 smb_request_t, sr, int, (int)cur, 668 int, (int)session->s_cur_credits); 669 670 session->s_cur_credits = cur; 671 mutex_exit(&session->s_credits_mutex); 672 } 673 674 /* 675 * The real work: call the SMB2 command handler 676 * (except for "sticky" smb2_status - see above) 677 */ 678 sr->sr_time_start = gethrtime(); 679 rc = SDRC_SUCCESS; 680 if (sr->smb2_status == 0) { 681 /* NB: not using pre_op */ 682 rc = (*sdd->sdt_function)(sr); 683 /* NB: not using post_op */ 684 } else { 685 smb2sr_put_error(sr, sr->smb2_status); 686 } 687 688 MBC_FLUSH(&sr->raw_data); 689 690 /* 691 * Second half of SMB2 credit handling (increases) 692 */ 693 if (sr->smb2_credit_request > sr->smb2_credit_charge) { 694 uint16_t cur, d; 695 696 mutex_enter(&session->s_credits_mutex); 697 cur = session->s_cur_credits; 698 699 /* Handle credit increase. */ 700 d = sr->smb2_credit_request - sr->smb2_credit_charge; 701 cur += d; 702 703 /* 704 * If new credits would be above max, 705 * reduce the credit grant. 706 */ 707 if (cur > session->s_max_credits) { 708 d = cur - session->s_max_credits; 709 cur = session->s_max_credits; 710 sr->smb2_credit_response -= d; 711 DTRACE_PROBE1(smb2__credit__max, smb_request_t, sr); 712 } 713 714 DTRACE_PROBE3(smb2__credit__increase, 715 smb_request_t, sr, int, (int)cur, 716 int, (int)session->s_cur_credits); 717 718 session->s_cur_credits = cur; 719 mutex_exit(&session->s_credits_mutex); 720 } 721 722 cmd_done: 723 /* 724 * Pad the reply to align(8) if necessary. 725 */ 726 if (sr->reply.chain_offset & 7) { 727 int padsz = 8 - (sr->reply.chain_offset & 7); 728 (void) smb_mbc_encodef(&sr->reply, "#.", padsz); 729 } 730 ASSERT((sr->reply.chain_offset & 7) == 0); 731 732 /* 733 * Record some statistics: latency, rx bytes, tx bytes. 734 */ 735 smb_server_inc_req(sr->sr_server); 736 smb_latency_add_sample(&sds->sdt_lat, 737 gethrtime() - sr->sr_time_start); 738 atomic_add_64(&sds->sdt_rxb, 739 (int64_t)(sr->command.chain_offset - sr->smb2_cmd_hdr)); 740 atomic_add_64(&sds->sdt_txb, 741 (int64_t)(sr->reply.chain_offset - sr->smb2_reply_hdr)); 742 743 switch (rc) { 744 case SDRC_SUCCESS: 745 break; 746 default: 747 /* 748 * SMB2 does not use the other dispatch return codes. 749 * If we see something else, log an event so we'll 750 * know something is returning bogus status codes. 751 * If you see these in the log, use dtrace to find 752 * the code returning something else. 753 */ 754 #ifdef DEBUG 755 cmn_err(CE_NOTE, "handler for %u returned 0x%x", 756 sr->smb2_cmd_code, rc); 757 #endif 758 /* FALLTHROUGH */ 759 case SDRC_ERROR: 760 if (sr->smb2_status == 0) 761 sr->smb2_status = NT_STATUS_INTERNAL_ERROR; 762 break; 763 case SDRC_DROP_VC: 764 disconnect = B_TRUE; 765 goto cleanup; 766 767 case SDRC_NO_REPLY: 768 /* will free sr */ 769 goto cleanup; 770 } 771 772 /* 773 * If there's a next command, figure out where it starts, 774 * and fill in the next command offset for the reply. 775 * Note: We sanity checked smb2_next_command above 776 * (the offset to the next command). Similarly set 777 * smb2_next_reply as the offset to the next reply. 778 */ 779 if (sr->smb2_next_command != 0) { 780 sr->command.chain_offset = 781 sr->smb2_cmd_hdr + sr->smb2_next_command; 782 sr->smb2_next_reply = 783 sr->reply.chain_offset - sr->smb2_reply_hdr; 784 } else { 785 sr->smb2_next_reply = 0; 786 } 787 788 /* 789 * Overwrite the SMB2 header for the response of 790 * this command (possibly part of a compound). 791 * encode_header adds: SMB2_FLAGS_SERVER_TO_REDIR 792 */ 793 (void) smb2_encode_header(sr, B_TRUE); 794 795 if (sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED) 796 smb2_sign_reply(sr); 797 798 if (sr->smb2_next_command != 0) 799 goto cmd_start; 800 801 /* 802 * We've done all the commands in this compound. 803 * Send it out. 804 */ 805 smb2_send_reply(sr); 806 807 /* 808 * If any of the requests "went async", process those now. 809 * The async. function "keeps" this sr, changing its state 810 * to completed and calling smb_request_free(). 811 */ 812 if (sr->sr_async_req != NULL) { 813 smb2sr_do_async(sr); 814 return; 815 } 816 817 cleanup: 818 if (disconnect) { 819 smb_rwx_rwenter(&session->s_lock, RW_WRITER); 820 switch (session->s_state) { 821 case SMB_SESSION_STATE_DISCONNECTED: 822 case SMB_SESSION_STATE_TERMINATED: 823 break; 824 default: 825 smb_soshutdown(session->sock); 826 session->s_state = SMB_SESSION_STATE_DISCONNECTED; 827 break; 828 } 829 smb_rwx_rwexit(&session->s_lock); 830 } 831 832 mutex_enter(&sr->sr_mutex); 833 sr->sr_state = SMB_REQ_STATE_COMPLETED; 834 mutex_exit(&sr->sr_mutex); 835 836 smb_request_free(sr); 837 } 838 839 /* 840 * Dispatch an async request using saved information. 841 * See smb2sr_save_async and [MS-SMB2] 3.3.4.2 842 * 843 * This is sort of a "lite" version of smb2sr_work. Initialize the 844 * command and reply areas as they were when the command-speicific 845 * handler started (in case it needs to decode anything again). 846 * Call the async function, which builds the command-specific part 847 * of the response. Finally, send the response and free the sr. 848 */ 849 void 850 smb2sr_do_async(smb_request_t *sr) 851 { 852 const smb_disp_entry_t *sdd; 853 smb2_async_req_t *ar; 854 smb_sdrc_t (*ar_func)(smb_request_t *); 855 int sdrc; 856 857 /* 858 * Restore what smb2_decode_header found. 859 * (In lieu of decoding it again.) 860 */ 861 ar = sr->sr_async_req; 862 sr->smb2_cmd_hdr = ar->ar_cmd_hdr; 863 sr->smb2_cmd_code = ar->ar_cmd_code; 864 sr->smb2_hdr_flags = ar->ar_hdr_flags; 865 sr->smb2_async_id = (uintptr_t)ar; 866 sr->smb2_messageid = ar->ar_messageid; 867 sr->smb_pid = ar->ar_pid; 868 sr->smb_tid = ar->ar_tid; 869 sr->smb_uid = ar->ar_uid; 870 sr->smb2_status = 0; 871 872 /* 873 * Async requests don't grant credits, because any credits 874 * should have gone out with the interim reply. 875 * An async reply goes alone (no next reply). 876 */ 877 sr->smb2_credit_response = 0; 878 sr->smb2_next_reply = 0; 879 880 /* 881 * Setup input mbuf_chain 882 */ 883 ASSERT(ar->ar_cmd_len >= SMB2_HDR_SIZE); 884 (void) MBC_SHADOW_CHAIN(&sr->smb_data, &sr->command, 885 sr->smb2_cmd_hdr + SMB2_HDR_SIZE, 886 ar->ar_cmd_len - SMB2_HDR_SIZE); 887 888 /* 889 * Done with sr_async_req 890 */ 891 ar_func = ar->ar_func; 892 kmem_free(ar, sizeof (*ar)); 893 sr->sr_async_req = ar = NULL; 894 895 /* 896 * Setup output mbuf_chain 897 */ 898 MBC_FLUSH(&sr->reply); 899 sr->smb2_reply_hdr = sr->reply.chain_offset; 900 (void) smb2_encode_header(sr, B_FALSE); 901 902 VERIFY3U(sr->smb2_cmd_code, <, SMB2_INVALID_CMD); 903 sdd = &smb2_disp_table[sr->smb2_cmd_code]; 904 905 /* 906 * Keep the UID, TID, ofile we have. 907 */ 908 if ((sdd->sdt_flags & SDDF_SUPPRESS_UID) == 0 && 909 sr->uid_user == NULL) { 910 smb2sr_put_error(sr, NT_STATUS_USER_SESSION_DELETED); 911 goto cmd_done; 912 } 913 if ((sdd->sdt_flags & SDDF_SUPPRESS_TID) == 0 && 914 sr->tid_tree == NULL) { 915 smb2sr_put_error(sr, NT_STATUS_NETWORK_NAME_DELETED); 916 goto cmd_done; 917 } 918 919 /* 920 * Signature already verified 921 * Credits handled... 922 * 923 * Just call the async handler function. 924 */ 925 sdrc = ar_func(sr); 926 switch (sdrc) { 927 case SDRC_SUCCESS: 928 break; 929 case SDRC_ERROR: 930 if (sr->smb2_status == 0) 931 sr->smb2_status = NT_STATUS_INTERNAL_ERROR; 932 break; 933 case SDRC_SR_KEPT: 934 /* This SR will be completed later. */ 935 return; 936 } 937 938 cmd_done: 939 smb2sr_finish_async(sr); 940 } 941 942 void 943 smb2sr_finish_async(smb_request_t *sr) 944 { 945 smb_disp_stats_t *sds; 946 947 /* 948 * Pad the reply to align(8) if necessary. 949 */ 950 if (sr->reply.chain_offset & 7) { 951 int padsz = 8 - (sr->reply.chain_offset & 7); 952 (void) smb_mbc_encodef(&sr->reply, "#.", padsz); 953 } 954 ASSERT((sr->reply.chain_offset & 7) == 0); 955 956 /* 957 * Record some statistics: (just tx bytes here) 958 */ 959 sds = &sr->session->s_server->sv_disp_stats2[sr->smb2_cmd_code]; 960 atomic_add_64(&sds->sdt_txb, (int64_t)(sr->reply.chain_offset)); 961 962 /* 963 * Put (overwrite) the final SMB2 header. 964 * The call adds: SMB2_FLAGS_SERVER_TO_REDIR 965 */ 966 (void) smb2_encode_header(sr, B_TRUE); 967 968 if (sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED) 969 smb2_sign_reply(sr); 970 971 smb2_send_reply(sr); 972 973 /* 974 * Done. Unlink and free. 975 */ 976 977 mutex_enter(&sr->sr_mutex); 978 sr->sr_state = SMB_REQ_STATE_COMPLETED; 979 mutex_exit(&sr->sr_mutex); 980 981 smb_request_free(sr); 982 } 983 984 /* 985 * In preparation for sending an "interim response", save 986 * all the state we'll need to run an async command later, 987 * and assign an "async id" for this (now async) command. 988 * See [MS-SMB2] 3.3.4.2 989 * 990 * If more than one request in a compound request tries to 991 * "go async", we can "say no". See [MS-SMB2] 3.3.4.2 992 * If an operation would require asynchronous processing 993 * but resources are constrained, the server MAY choose to 994 * fail that operation with STATUS_INSUFFICIENT_RESOURCES. 995 * 996 * For simplicity, we further restrict the cases where we're 997 * willing to "go async", and only allow the last command in a 998 * compound to "go async". It happens that this is the only 999 * case where we're actually asked to go async anyway. This 1000 * simplification also means there can be at most one command 1001 * in a compound that "goes async" (the last one). 1002 * 1003 * If we agree to "go async", this should return STATUS_PENDING. 1004 * Otherwise return STATUS_INSUFFICIENT_RESOURCES for this and 1005 * all requests following this request. (See the comments re. 1006 * "sticky" smb2_status values in smb2sr_work). 1007 * 1008 * Note: the Async ID we assign here is arbitrary, and need only 1009 * be unique among pending async responses on this connection, so 1010 * this just uses an object address as the Async ID. 1011 * 1012 * Also, the assigned worker is the ONLY thread using this 1013 * async request object (sr_async_req) so no locking. 1014 */ 1015 uint32_t 1016 smb2sr_go_async(smb_request_t *sr, 1017 smb_sdrc_t (*async_func)(smb_request_t *)) 1018 { 1019 smb2_async_req_t *ar; 1020 1021 if (sr->smb2_next_command != 0) 1022 return (NT_STATUS_INSUFFICIENT_RESOURCES); 1023 1024 ASSERT(sr->sr_async_req == NULL); 1025 ar = kmem_zalloc(sizeof (*ar), KM_SLEEP); 1026 1027 /* 1028 * Place an interim response in the compound reply. 1029 * 1030 * Turn on the "async" flag for both the (synchronous) 1031 * interim response and the (later) async response, 1032 * by storing that in flags before coping into ar. 1033 */ 1034 sr->smb2_hdr_flags |= SMB2_FLAGS_ASYNC_COMMAND; 1035 sr->smb2_async_id = (uintptr_t)ar; 1036 1037 ar->ar_func = async_func; 1038 ar->ar_cmd_hdr = sr->smb2_cmd_hdr; 1039 ar->ar_cmd_len = sr->smb_data.max_bytes - sr->smb2_cmd_hdr; 1040 1041 ar->ar_cmd_code = sr->smb2_cmd_code; 1042 ar->ar_hdr_flags = sr->smb2_hdr_flags; 1043 ar->ar_messageid = sr->smb2_messageid; 1044 ar->ar_pid = sr->smb_pid; 1045 ar->ar_tid = sr->smb_tid; 1046 ar->ar_uid = sr->smb_uid; 1047 1048 sr->sr_async_req = ar; 1049 1050 /* Interim responses are NOT signed. */ 1051 sr->smb2_hdr_flags &= ~SMB2_FLAGS_SIGNED; 1052 1053 return (NT_STATUS_PENDING); 1054 } 1055 1056 int 1057 smb2_decode_header(smb_request_t *sr) 1058 { 1059 uint64_t ssnid; 1060 uint32_t pid, tid; 1061 uint16_t hdr_len; 1062 int rc; 1063 1064 rc = smb_mbc_decodef( 1065 &sr->command, "Nwww..wwllqllq16c", 1066 &hdr_len, /* w */ 1067 &sr->smb2_credit_charge, /* w */ 1068 &sr->smb2_chan_seq, /* w */ 1069 /* reserved .. */ 1070 &sr->smb2_cmd_code, /* w */ 1071 &sr->smb2_credit_request, /* w */ 1072 &sr->smb2_hdr_flags, /* l */ 1073 &sr->smb2_next_command, /* l */ 1074 &sr->smb2_messageid, /* q */ 1075 &pid, /* l */ 1076 &tid, /* l */ 1077 &ssnid, /* q */ 1078 sr->smb2_sig); /* 16c */ 1079 if (rc) 1080 return (rc); 1081 1082 if (hdr_len != SMB2_HDR_SIZE) 1083 return (-1); 1084 1085 sr->smb_uid = (uint16_t)ssnid; /* XXX wide UIDs */ 1086 1087 if (sr->smb2_hdr_flags & SMB2_FLAGS_ASYNC_COMMAND) { 1088 sr->smb2_async_id = pid | 1089 ((uint64_t)tid) << 32; 1090 } else { 1091 sr->smb_pid = pid; 1092 sr->smb_tid = (uint16_t)tid; /* XXX wide TIDs */ 1093 } 1094 1095 return (rc); 1096 } 1097 1098 int 1099 smb2_encode_header(smb_request_t *sr, boolean_t overwrite) 1100 { 1101 uint64_t ssnid = sr->smb_uid; 1102 uint64_t pid_tid_aid; /* pid+tid, or async id */ 1103 uint32_t reply_hdr_flags; 1104 int rc; 1105 1106 if (sr->smb2_hdr_flags & SMB2_FLAGS_ASYNC_COMMAND) { 1107 pid_tid_aid = sr->smb2_async_id; 1108 } else { 1109 pid_tid_aid = sr->smb_pid | 1110 ((uint64_t)sr->smb_tid) << 32; 1111 } 1112 reply_hdr_flags = sr->smb2_hdr_flags | SMB2_FLAGS_SERVER_TO_REDIR; 1113 1114 if (overwrite) { 1115 rc = smb_mbc_poke(&sr->reply, 1116 sr->smb2_reply_hdr, 1117 "Nwwlwwllqqq16c", 1118 SMB2_HDR_SIZE, /* w */ 1119 sr->smb2_credit_charge, /* w */ 1120 sr->smb2_status, /* l */ 1121 sr->smb2_cmd_code, /* w */ 1122 sr->smb2_credit_response, /* w */ 1123 reply_hdr_flags, /* l */ 1124 sr->smb2_next_reply, /* l */ 1125 sr->smb2_messageid, /* q */ 1126 pid_tid_aid, /* q */ 1127 ssnid, /* q */ 1128 sr->smb2_sig); /* 16c */ 1129 } else { 1130 rc = smb_mbc_encodef(&sr->reply, 1131 "Nwwlwwllqqq16c", 1132 SMB2_HDR_SIZE, /* w */ 1133 sr->smb2_credit_charge, /* w */ 1134 sr->smb2_status, /* l */ 1135 sr->smb2_cmd_code, /* w */ 1136 sr->smb2_credit_response, /* w */ 1137 reply_hdr_flags, /* l */ 1138 sr->smb2_next_reply, /* l */ 1139 sr->smb2_messageid, /* q */ 1140 pid_tid_aid, /* q */ 1141 ssnid, /* q */ 1142 sr->smb2_sig); /* 16c */ 1143 } 1144 1145 return (rc); 1146 } 1147 1148 void 1149 smb2_send_reply(smb_request_t *sr) 1150 { 1151 1152 if (smb_session_send(sr->session, 0, &sr->reply) == 0) 1153 sr->reply.chain = 0; 1154 } 1155 1156 /* 1157 * This wrapper function exists to help catch calls to smbsr_status() 1158 * (which is SMB1-specific) in common code. See smbsr_status(). 1159 * If the log message below is seen, put a dtrace probe on this 1160 * function with a stack() action to see who is calling the SMB1 1161 * "put error" from common code, and fix it. 1162 */ 1163 void 1164 smbsr_status_smb2(smb_request_t *sr, DWORD status) 1165 { 1166 const char *name; 1167 1168 if (sr->smb2_cmd_code < SMB2__NCMDS) 1169 name = smb2_disp_table[sr->smb2_cmd_code].sdt_name; 1170 else 1171 name = "<unknown>"; 1172 #ifdef DEBUG 1173 cmn_err(CE_NOTE, "smbsr_status called for %s", name); 1174 #endif 1175 1176 smb2sr_put_error_data(sr, status, NULL); 1177 } 1178 1179 void 1180 smb2sr_put_errno(struct smb_request *sr, int errnum) 1181 { 1182 uint32_t status = smb_errno2status(errnum); 1183 smb2sr_put_error_data(sr, status, NULL); 1184 } 1185 1186 void 1187 smb2sr_put_error(smb_request_t *sr, uint32_t status) 1188 { 1189 smb2sr_put_error_data(sr, status, NULL); 1190 } 1191 1192 /* 1193 * Build an SMB2 error response. [MS-SMB2] 2.2.2 1194 */ 1195 void 1196 smb2sr_put_error_data(smb_request_t *sr, uint32_t status, mbuf_chain_t *mbc) 1197 { 1198 DWORD len; 1199 1200 /* 1201 * The common dispatch code writes this when it 1202 * updates the SMB2 header before sending. 1203 */ 1204 sr->smb2_status = status; 1205 1206 /* Rewind to the end of the SMB header. */ 1207 sr->reply.chain_offset = sr->smb2_reply_hdr + SMB2_HDR_SIZE; 1208 1209 /* 1210 * NB: Must provide at least one byte of error data, 1211 * per [MS-SMB2] 2.2.2 1212 */ 1213 if (mbc != NULL && (len = MBC_LENGTH(mbc)) != 0) { 1214 (void) smb_mbc_encodef( 1215 &sr->reply, 1216 "wwlC", 1217 9, /* StructSize */ /* w */ 1218 0, /* reserved */ /* w */ 1219 len, /* l */ 1220 mbc); /* C */ 1221 } else { 1222 (void) smb_mbc_encodef( 1223 &sr->reply, 1224 "wwl.", 1225 9, /* StructSize */ /* w */ 1226 0, /* reserved */ /* w */ 1227 0); /* l. */ 1228 } 1229 } 1230 1231 /* 1232 * smb2sr_lookup_fid 1233 * 1234 * Setup sr->fid_ofile, either inherited from a related command, 1235 * or obtained via FID lookup. Similar inheritance logic as in 1236 * smb2sr_work. 1237 */ 1238 uint32_t 1239 smb2sr_lookup_fid(smb_request_t *sr, smb2fid_t *fid) 1240 { 1241 boolean_t related = sr->smb2_hdr_flags & 1242 SMB2_FLAGS_RELATED_OPERATIONS; 1243 1244 if (related) { 1245 if (sr->fid_ofile == NULL) 1246 return (NT_STATUS_INVALID_PARAMETER); 1247 sr->smb_fid = sr->fid_ofile->f_fid; 1248 return (0); 1249 } 1250 1251 /* 1252 * If we could be sure this is called only once per cmd, 1253 * we could simply ASSERT(sr->fid_ofile == NULL) here. 1254 * However, there are cases where it can be called again 1255 * handling the same command, so let's tolerate that. 1256 */ 1257 if (sr->fid_ofile == NULL) { 1258 sr->smb_fid = (uint16_t)fid->temporal; 1259 sr->fid_ofile = smb_ofile_lookup_by_fid(sr, sr->smb_fid); 1260 } 1261 if (sr->fid_ofile == NULL) 1262 return (NT_STATUS_FILE_CLOSED); 1263 1264 return (0); 1265 } 1266 1267 /* 1268 * smb2_dispatch_stats_init 1269 * 1270 * Initializes dispatch statistics for SMB2. 1271 * See also smb_dispatch_stats_init(), which fills in 1272 * the lower part of the statistics array, from zero 1273 * through SMB_COM_NUM; 1274 */ 1275 void 1276 smb2_dispatch_stats_init(smb_server_t *sv) 1277 { 1278 smb_disp_stats_t *sds = sv->sv_disp_stats2; 1279 smb_kstat_req_t *ksr; 1280 int i; 1281 1282 ksr = ((smbsrv_kstats_t *)sv->sv_ksp->ks_data)->ks_reqs2; 1283 1284 for (i = 0; i < SMB2__NCMDS; i++, ksr++) { 1285 smb_latency_init(&sds[i].sdt_lat); 1286 (void) strlcpy(ksr->kr_name, smb2_disp_table[i].sdt_name, 1287 sizeof (ksr->kr_name)); 1288 } 1289 } 1290 1291 /* 1292 * smb2_dispatch_stats_fini 1293 * 1294 * Frees and destroyes the resources used for statistics. 1295 */ 1296 void 1297 smb2_dispatch_stats_fini(smb_server_t *sv) 1298 { 1299 smb_disp_stats_t *sds = sv->sv_disp_stats2; 1300 int i; 1301 1302 for (i = 0; i < SMB2__NCMDS; i++) 1303 smb_latency_destroy(&sds[i].sdt_lat); 1304 } 1305 1306 void 1307 smb2_dispatch_stats_update(smb_server_t *sv, 1308 smb_kstat_req_t *ksr, int first, int nreq) 1309 { 1310 smb_disp_stats_t *sds = sv->sv_disp_stats2; 1311 int i; 1312 int last; 1313 1314 last = first + nreq - 1; 1315 1316 if ((first < SMB2__NCMDS) && (last < SMB2__NCMDS)) { 1317 for (i = first; i <= last; i++, ksr++) { 1318 ksr->kr_rxb = sds[i].sdt_rxb; 1319 ksr->kr_txb = sds[i].sdt_txb; 1320 mutex_enter(&sds[i].sdt_lat.ly_mutex); 1321 ksr->kr_nreq = sds[i].sdt_lat.ly_a_nreq; 1322 ksr->kr_sum = sds[i].sdt_lat.ly_a_sum; 1323 ksr->kr_a_mean = sds[i].sdt_lat.ly_a_mean; 1324 ksr->kr_a_stddev = 1325 sds[i].sdt_lat.ly_a_stddev; 1326 ksr->kr_d_mean = sds[i].sdt_lat.ly_d_mean; 1327 ksr->kr_d_stddev = 1328 sds[i].sdt_lat.ly_d_stddev; 1329 sds[i].sdt_lat.ly_d_mean = 0; 1330 sds[i].sdt_lat.ly_d_nreq = 0; 1331 sds[i].sdt_lat.ly_d_stddev = 0; 1332 sds[i].sdt_lat.ly_d_sum = 0; 1333 mutex_exit(&sds[i].sdt_lat.ly_mutex); 1334 } 1335 } 1336 } 1337