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 2020 Tintri by DDN, Inc. All rights reserved. 14 * Copyright 2019 Joyent, Inc. 15 * Copyright 2022 RackTop Systems, Inc. 16 */ 17 18 /* 19 * Test & debug program for oplocks 20 * 21 * This implements a simple command reader which accepts 22 * commands to simulate oplock events, and prints the 23 * state changes and actions that would happen after 24 * each event. 25 */ 26 27 #include <sys/types.h> 28 #include <sys/debug.h> 29 #include <sys/stddef.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <strings.h> 34 #include <unistd.h> 35 36 #include <smbsrv/smb_kproto.h> 37 #include <smbsrv/smb_oplock.h> 38 39 #define OPLOCK_CACHE_RWH (READ_CACHING | HANDLE_CACHING | WRITE_CACHING) 40 #define OPLOCK_TYPE (LEVEL_TWO_OPLOCK | LEVEL_ONE_OPLOCK |\ 41 BATCH_OPLOCK | OPLOCK_LEVEL_GRANULAR) 42 43 #define MAXFID 10 44 45 smb_node_t root_node, test_node; 46 smb_ofile_t ofile_array[MAXFID]; 47 smb_request_t test_sr; 48 uint32_t last_ind_break_level; 49 char cmdbuf[100]; 50 51 extern const char *xlate_nt_status(uint32_t); 52 53 #define BIT_DEF(name) { name, #name } 54 55 struct bit_defs { 56 uint32_t mask; 57 const char *name; 58 } state_bits[] = { 59 BIT_DEF(NO_OPLOCK), 60 BIT_DEF(BREAK_TO_NO_CACHING), 61 BIT_DEF(BREAK_TO_WRITE_CACHING), 62 BIT_DEF(BREAK_TO_HANDLE_CACHING), 63 BIT_DEF(BREAK_TO_READ_CACHING), 64 BIT_DEF(BREAK_TO_TWO_TO_NONE), 65 BIT_DEF(BREAK_TO_NONE), 66 BIT_DEF(BREAK_TO_TWO), 67 BIT_DEF(BATCH_OPLOCK), 68 BIT_DEF(LEVEL_ONE_OPLOCK), 69 BIT_DEF(LEVEL_TWO_OPLOCK), 70 BIT_DEF(MIXED_R_AND_RH), 71 BIT_DEF(EXCLUSIVE), 72 BIT_DEF(WRITE_CACHING), 73 BIT_DEF(HANDLE_CACHING), 74 BIT_DEF(READ_CACHING), 75 { 0, NULL } 76 }; 77 78 /* 79 * Helper to print flags fields 80 */ 81 static void 82 print_bits32(char *label, struct bit_defs *bit, uint32_t state) 83 { 84 printf("%s0x%x (", label, state); 85 while (bit->mask != 0) { 86 if ((state & bit->mask) != 0) 87 printf(" %s", bit->name); 88 bit++; 89 } 90 printf(" )\n"); 91 } 92 93 /* 94 * Command language: 95 * 96 */ 97 const char helpstr[] = "Commands:\n" 98 "help\t\tList commands\n" 99 "show\t\tShow OpLock state etc.\n" 100 "open FID\n" 101 "close FID\n" 102 "req FID [OplockLevel]\n" 103 "ack FID [OplockLevel]\n" 104 "brk-parent FID\n" 105 "brk-open [OverWrite]\n" 106 "brk-handle FID\n" 107 "brk-read FID\n" 108 "brk-write FID\n" 109 "brk-setinfo FID [InfoClass]\n" 110 "move FID1 FID2\n" 111 "waiters FID [count]\n"; 112 113 /* 114 * Command handlers 115 */ 116 117 static void 118 do_show(void) 119 { 120 smb_node_t *node = &test_node; 121 smb_oplock_t *ol = &node->n_oplock; 122 uint32_t state = ol->ol_state; 123 smb_ofile_t *f; 124 125 print_bits32(" ol_state=", state_bits, state); 126 127 if (ol->excl_open != NULL) 128 printf(" Excl=Y (FID=%d)", ol->excl_open->f_fid); 129 else 130 printf(" Excl=n"); 131 printf(" cnt_II=%d cnt_R=%d cnt_RH=%d cnt_RHBQ=%d\n", 132 ol->cnt_II, ol->cnt_R, ol->cnt_RH, ol->cnt_RHBQ); 133 134 printf(" ofile_cnt=%d\n", node->n_ofile_list.ll_count); 135 FOREACH_NODE_OFILE(node, f) { 136 smb_oplock_grant_t *og = &f->f_oplock; 137 printf(" fid=%d Lease=%s OgState=0x%x Brk=0x%x", 138 f->f_fid, 139 f->TargetOplockKey, /* lease */ 140 f->f_oplock.og_state, 141 f->f_oplock.og_breaking); 142 printf(" Excl=%s onlist: %s %s %s", 143 (ol->excl_open == f) ? "Y" : "N", 144 og->onlist_II ? "II" : "", 145 og->onlist_R ? "R" : "", 146 og->onlist_RH ? "RH" : ""); 147 if (og->onlist_RHBQ) { 148 printf(" RHBQ(to %s)", 149 og->BreakingToRead ? 150 "read" : "none"); 151 } 152 printf("\n"); 153 } 154 } 155 156 static void 157 do_open(int fid, char *arg2) 158 { 159 smb_node_t *node = &test_node; 160 smb_ofile_t *ofile = &ofile_array[fid]; 161 162 /* 163 * Simulate an open (minimal init) 164 */ 165 if (ofile->f_refcnt) { 166 printf("open fid %d already opened\n"); 167 return; 168 } 169 170 if (arg2 != NULL) { 171 (void) strlcpy((char *)ofile->TargetOplockKey, arg2, 172 SMB_LEASE_KEY_SZ); 173 } 174 175 ofile->f_refcnt++; 176 node->n_open_count++; 177 smb_llist_insert_tail(&node->n_ofile_list, ofile); 178 printf(" open %d OK\n", fid); 179 } 180 181 static void 182 do_close(int fid) 183 { 184 smb_node_t *node = &test_node; 185 smb_ofile_t *ofile = &ofile_array[fid]; 186 187 /* 188 * Simulate an close 189 */ 190 if (ofile->f_refcnt <= 0) { 191 printf(" close fid %d already closed\n"); 192 return; 193 } 194 195 smb_llist_enter(&node->n_ofile_list, RW_READER); 196 mutex_enter(&node->n_oplock.ol_mutex); 197 198 smb_oplock_break_CLOSE(ofile->f_node, ofile); 199 200 smb_llist_remove(&node->n_ofile_list, ofile); 201 node->n_open_count--; 202 203 mutex_exit(&node->n_oplock.ol_mutex); 204 smb_llist_exit(&node->n_ofile_list); 205 206 ofile->f_refcnt--; 207 208 bzero(ofile->TargetOplockKey, SMB_LEASE_KEY_SZ); 209 210 printf(" close OK\n"); 211 } 212 213 static void 214 do_req(int fid, char *arg2) 215 { 216 smb_ofile_t *ofile = &ofile_array[fid]; 217 uint32_t oplock = BATCH_OPLOCK; 218 uint32_t status; 219 220 if (arg2 != NULL) 221 oplock = strtol(arg2, NULL, 16); 222 223 /* 224 * Request an oplock 225 */ 226 status = smb_oplock_request(&test_sr, ofile, &oplock); 227 if (status == 0) 228 ofile->f_oplock.og_state = oplock; 229 printf(" req oplock fid=%d ret oplock=0x%x status=0x%x (%s)\n", 230 fid, oplock, status, xlate_nt_status(status)); 231 } 232 233 234 static void 235 do_ack(int fid, char *arg2) 236 { 237 smb_node_t *node = &test_node; 238 smb_ofile_t *ofile = &ofile_array[fid]; 239 uint32_t oplock; 240 uint32_t status; 241 242 /* Default to level in last smb_oplock_ind_break() */ 243 oplock = last_ind_break_level; 244 if (arg2 != NULL) 245 oplock = strtol(arg2, NULL, 16); 246 247 smb_llist_enter(&node->n_ofile_list, RW_READER); 248 mutex_enter(&node->n_oplock.ol_mutex); 249 250 ofile->f_oplock.og_breaking = 0; 251 status = smb_oplock_ack_break(&test_sr, ofile, &oplock); 252 if (NT_SC_SEVERITY(status) == NT_STATUS_SEVERITY_SUCCESS) 253 ofile->f_oplock.og_state = oplock; 254 255 mutex_exit(&node->n_oplock.ol_mutex); 256 smb_llist_exit(&node->n_ofile_list); 257 258 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { 259 printf(" ack: break fid=%d, break-in-progress\n", fid); 260 } 261 262 printf(" ack: break fid=%d, newstate=0x%x, status=0x%x (%s)\n", 263 fid, oplock, status, xlate_nt_status(status)); 264 } 265 266 static void 267 do_brk_parent(int fid) 268 { 269 smb_ofile_t *ofile = &ofile_array[fid]; 270 uint32_t status; 271 272 status = smb_oplock_break_PARENT(&test_node, ofile); 273 printf(" brk-parent %d ret status=0x%x (%s)\n", 274 fid, status, xlate_nt_status(status)); 275 } 276 277 static void 278 do_brk_open(int fid, char *arg2) 279 { 280 smb_ofile_t *ofile = &ofile_array[fid]; 281 uint32_t status; 282 int disp = FILE_OPEN; 283 284 if (arg2 != NULL) 285 disp = strtol(arg2, NULL, 16); 286 287 status = smb_oplock_break_OPEN(&test_node, ofile, 7, disp); 288 printf(" brk-open %d ret status=0x%x (%s)\n", 289 fid, status, xlate_nt_status(status)); 290 } 291 292 static void 293 do_brk_handle(int fid) 294 { 295 smb_ofile_t *ofile = &ofile_array[fid]; 296 uint32_t status; 297 298 status = smb_oplock_break_HANDLE(&test_node, ofile); 299 printf(" brk-handle %d ret status=0x%x (%s)\n", 300 fid, status, xlate_nt_status(status)); 301 302 } 303 304 static void 305 do_brk_read(int fid) 306 { 307 smb_ofile_t *ofile = &ofile_array[fid]; 308 uint32_t status; 309 310 status = smb_oplock_break_READ(ofile->f_node, ofile); 311 printf(" brk-read %d ret status=0x%x (%s)\n", 312 fid, status, xlate_nt_status(status)); 313 } 314 315 static void 316 do_brk_write(int fid) 317 { 318 smb_ofile_t *ofile = &ofile_array[fid]; 319 uint32_t status; 320 321 status = smb_oplock_break_WRITE(ofile->f_node, ofile); 322 printf(" brk-write %d ret status=0x%x (%s)\n", 323 fid, status, xlate_nt_status(status)); 324 } 325 326 static void 327 do_brk_setinfo(int fid, char *arg2) 328 { 329 smb_ofile_t *ofile = &ofile_array[fid]; 330 uint32_t status; 331 int infoclass = FileEndOfFileInformation; /* 20 */ 332 333 if (arg2 != NULL) 334 infoclass = strtol(arg2, NULL, 16); 335 336 status = smb_oplock_break_SETINFO( 337 &test_node, ofile, infoclass); 338 printf(" brk-setinfo %d ret status=0x%x (%s)\n", 339 fid, status, xlate_nt_status(status)); 340 341 } 342 343 /* 344 * Move oplock to another FD, as specified, 345 * or any other available open 346 */ 347 static void 348 do_move(int fid, char *arg2) 349 { 350 smb_node_t *node = &test_node; 351 smb_ofile_t *ofile = &ofile_array[fid]; 352 smb_ofile_t *of2; 353 int fid2; 354 355 if (arg2 == NULL) { 356 fprintf(stderr, "move: FID2 required\n"); 357 return; 358 } 359 fid2 = atoi(arg2); 360 if (fid2 <= 0 || fid2 >= MAXFID) { 361 fprintf(stderr, "move: bad FID2 %d\n", fid2); 362 return; 363 } 364 of2 = &ofile_array[fid2]; 365 366 mutex_enter(&node->n_oplock.ol_mutex); 367 368 smb_oplock_move(&test_node, ofile, of2); 369 370 mutex_exit(&node->n_oplock.ol_mutex); 371 372 printf(" move %d %d\n", fid, fid2); 373 } 374 375 /* 376 * Set/clear oplock.waiters, which affects ack-break 377 */ 378 static void 379 do_waiters(int fid, char *arg2) 380 { 381 smb_node_t *node = &test_node; 382 smb_oplock_t *ol = &node->n_oplock; 383 int old, new = 0; 384 385 if (arg2 != NULL) 386 new = atoi(arg2); 387 388 old = ol->waiters; 389 ol->waiters = new; 390 391 printf(" waiters %d -> %d\n", old, new); 392 } 393 394 int 395 main(int argc, char *argv[]) 396 { 397 smb_node_t *node = &test_node; 398 char *cmd; 399 char *arg1; 400 char *arg2; 401 char *savep; 402 char *sep = " \t\n"; 403 char *prompt = NULL; 404 int fid; 405 406 if (isatty(0)) 407 prompt = "> "; 408 409 mutex_init(&node->n_mutex, NULL, MUTEX_DEFAULT, NULL); 410 411 smb_llist_constructor(&node->n_ofile_list, sizeof (smb_ofile_t), 412 offsetof(smb_ofile_t, f_node_lnd)); 413 414 for (fid = 0; fid < MAXFID; fid++) { 415 smb_ofile_t *f = &ofile_array[fid]; 416 417 f->f_magic = SMB_OFILE_MAGIC; 418 mutex_init(&f->f_mutex, NULL, MUTEX_DEFAULT, NULL); 419 f->f_fid = fid; 420 f->f_ftype = SMB_FTYPE_DISK; 421 f->f_node = &test_node; 422 } 423 424 for (;;) { 425 if (prompt) { 426 (void) fputs(prompt, stdout); 427 fflush(stdout); 428 } 429 430 cmd = fgets(cmdbuf, sizeof (cmdbuf), stdin); 431 if (cmd == NULL) 432 break; 433 if (cmd[0] == '#') 434 continue; 435 436 if (prompt == NULL) { 437 /* Put commands in the output too. */ 438 (void) fputs(cmdbuf, stdout); 439 } 440 cmd = strtok_r(cmd, sep, &savep); 441 if (cmd == NULL) 442 continue; 443 444 /* 445 * Commands with no args 446 */ 447 if (0 == strcmp(cmd, "help")) { 448 (void) fputs(helpstr, stdout); 449 continue; 450 } 451 452 if (0 == strcmp(cmd, "show")) { 453 do_show(); 454 continue; 455 } 456 457 /* 458 * Commands with one arg (the FID) 459 */ 460 arg1 = strtok_r(NULL, sep, &savep); 461 if (arg1 == NULL) { 462 fprintf(stderr, "%s missing arg1\n", cmd); 463 continue; 464 } 465 fid = atoi(arg1); 466 if (fid <= 0 || fid >= MAXFID) { 467 fprintf(stderr, "%s bad FID %d\n", cmd, fid); 468 continue; 469 } 470 471 if (0 == strcmp(cmd, "close")) { 472 do_close(fid); 473 continue; 474 } 475 if (0 == strcmp(cmd, "brk-parent")) { 476 do_brk_parent(fid); 477 continue; 478 } 479 if (0 == strcmp(cmd, "brk-handle")) { 480 do_brk_handle(fid); 481 continue; 482 } 483 if (0 == strcmp(cmd, "brk-read")) { 484 do_brk_read(fid); 485 continue; 486 } 487 if (0 == strcmp(cmd, "brk-write")) { 488 do_brk_write(fid); 489 continue; 490 } 491 492 /* 493 * Commands with an (optional) arg2. 494 */ 495 arg2 = strtok_r(NULL, sep, &savep); 496 497 if (0 == strcmp(cmd, "open")) { 498 do_open(fid, arg2); 499 continue; 500 } 501 if (0 == strcmp(cmd, "req")) { 502 do_req(fid, arg2); 503 continue; 504 } 505 if (0 == strcmp(cmd, "ack")) { 506 do_ack(fid, arg2); 507 continue; 508 } 509 if (0 == strcmp(cmd, "brk-open")) { 510 do_brk_open(fid, arg2); 511 continue; 512 } 513 if (0 == strcmp(cmd, "brk-setinfo")) { 514 do_brk_setinfo(fid, arg2); 515 continue; 516 } 517 if (0 == strcmp(cmd, "move")) { 518 do_move(fid, arg2); 519 continue; 520 } 521 if (0 == strcmp(cmd, "waiters")) { 522 do_waiters(fid, arg2); 523 continue; 524 } 525 526 fprintf(stderr, "%s unknown command. Try help\n", cmd); 527 } 528 return (0); 529 } 530 531 /* 532 * A few functions called by the oplock code 533 * Stubbed out, and/or just print a message. 534 */ 535 536 boolean_t 537 smb_node_is_file(smb_node_t *node) 538 { 539 return (B_TRUE); 540 } 541 542 boolean_t 543 smb_ofile_is_open(smb_ofile_t *ofile) 544 { 545 return (ofile->f_refcnt != 0); 546 } 547 548 int 549 smb_lock_range_access( 550 smb_request_t *sr, 551 smb_node_t *node, 552 uint64_t start, 553 uint64_t length, 554 boolean_t will_write) 555 { 556 return (0); 557 } 558 559 /* 560 * Test code replacement for combination of: 561 * smb_oplock_hdl_update() 562 * smb_oplock_send_brk() 563 */ 564 static void 565 test_oplock_send_brk(smb_ofile_t *ofile, 566 uint32_t NewLevel, boolean_t AckReq) 567 { 568 smb_oplock_grant_t *og = &ofile->f_oplock; 569 570 /* Skip building a message. */ 571 572 if ((og->og_state & OPLOCK_LEVEL_GRANULAR) != 0) 573 NewLevel |= OPLOCK_LEVEL_GRANULAR; 574 575 /* 576 * In a real server, we would send a break to the client, 577 * and keep track (at the SMB level) whether this oplock 578 * was obtained via a lease or an old-style oplock. 579 * 580 * This part like: smb_oplock_hdl_update() 581 */ 582 if (AckReq) { 583 uint32_t BreakTo; 584 585 if ((og->og_state & OPLOCK_LEVEL_GRANULAR) != 0) { 586 587 BreakTo = (NewLevel & CACHE_RWH) << BREAK_SHIFT; 588 if (BreakTo == 0) 589 BreakTo = BREAK_TO_NO_CACHING; 590 // ls_breaking = BreakTo; 591 } else { 592 if ((NewLevel & LEVEL_TWO_OPLOCK) != 0) 593 BreakTo = BREAK_TO_TWO; 594 else 595 BreakTo = BREAK_TO_NONE; 596 } 597 og->og_breaking = BreakTo; 598 /* Set og_state in do_ack */ 599 } else { 600 og->og_state = NewLevel; 601 // If lease: ls_breaking = ... 602 /* Clear og_breaking in do_ack */ 603 } 604 605 /* Next, smb_oplock_send_break() would send a break. */ 606 last_ind_break_level = NewLevel; 607 } 608 609 /* 610 * Simplified version of what's in smb_srv_oplock.c 611 */ 612 void 613 smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel, 614 boolean_t AckReq, uint32_t status) 615 { 616 smb_oplock_grant_t *og = &ofile->f_oplock; 617 618 printf("*smb_oplock_ind_break fid=%d NewLevel=0x%x," 619 " AckReq=%d, ComplStatus=0x%x (%s)\n", 620 ofile->f_fid, NewLevel, AckReq, 621 status, xlate_nt_status(status)); 622 623 /* 624 * Note that the CompletionStatus from the FS level 625 * (smb_cmn_oplock.c) encodes what kind of action we 626 * need to take at the SMB level. 627 */ 628 switch (status) { 629 630 case NT_STATUS_SUCCESS: 631 case NT_STATUS_CANNOT_GRANT_REQUESTED_OPLOCK: 632 test_oplock_send_brk(ofile, NewLevel, AckReq); 633 break; 634 635 case NT_STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE: 636 case NT_STATUS_OPLOCK_HANDLE_CLOSED: 637 og->og_state = OPLOCK_LEVEL_NONE; 638 break; 639 640 default: 641 ASSERT(0); 642 break; 643 } 644 } 645 646 void 647 smb_oplock_ind_break_in_ack(smb_request_t *sr, smb_ofile_t *ofile, 648 uint32_t NewLevel, boolean_t AckRequired) 649 { 650 ASSERT(sr == &test_sr); 651 smb_oplock_ind_break(ofile, NewLevel, AckRequired, STATUS_CANT_GRANT); 652 } 653 654 uint32_t 655 smb_oplock_wait_break(smb_request_t *sr, smb_node_t *node, int timeout) 656 { 657 printf("*smb_oplock_wait_break (state=0x%x)\n", 658 node->n_oplock.ol_state); 659 return (0); 660 } 661 662 int 663 smb_fem_oplock_install(smb_node_t *node) 664 { 665 return (0); 666 } 667 668 void 669 smb_fem_oplock_uninstall(smb_node_t *node) 670 { 671 } 672 673 /* 674 * There are a couple DTRACE_PROBE* in smb_cmn_oplock.c but we're 675 * not linking with the user-level dtrace support, so just 676 * stub these out. 677 */ 678 void 679 __dtrace_fksmb___probe1(char *n, unsigned long a) 680 { 681 } 682 void 683 __dtrace_fksmb___probe2(char *n, unsigned long a, unsigned long b) 684 { 685 } 686