/* * 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 Tintri by DDN, Inc. All rights reserved. * Copyright 2019 Joyent, Inc. * Copyright 2022 RackTop Systems, Inc. */ /* * Test & debug program for oplocks * * This implements a simple command reader which accepts * commands to simulate oplock events, and prints the * state changes and actions that would happen after * each event. */ #include #include #include #include #include #include #include #include #include #include extern const char *xlate_nt_status(uint32_t); #define OPLOCK_CACHE_RWH (READ_CACHING | HANDLE_CACHING | WRITE_CACHING) #define OPLOCK_TYPE (LEVEL_TWO_OPLOCK | LEVEL_ONE_OPLOCK |\ BATCH_OPLOCK | OPLOCK_LEVEL_GRANULAR) #define MAXFID 10 smb_node_t root_node, test_node; smb_ofile_t ofile_array[MAXFID]; smb_request_t test_sr; uint32_t last_ind_break_level; char cmdbuf[100]; static void run_ind_break_in_ack(smb_ofile_t *); #define BIT_DEF(name) { name, #name } struct bit_defs { uint32_t mask; const char *name; } state_bits[] = { BIT_DEF(NO_OPLOCK), BIT_DEF(BREAK_TO_NO_CACHING), BIT_DEF(BREAK_TO_WRITE_CACHING), BIT_DEF(BREAK_TO_HANDLE_CACHING), BIT_DEF(BREAK_TO_READ_CACHING), BIT_DEF(BREAK_TO_TWO_TO_NONE), BIT_DEF(BREAK_TO_NONE), BIT_DEF(BREAK_TO_TWO), BIT_DEF(BATCH_OPLOCK), BIT_DEF(LEVEL_ONE_OPLOCK), BIT_DEF(LEVEL_TWO_OPLOCK), BIT_DEF(MIXED_R_AND_RH), BIT_DEF(EXCLUSIVE), BIT_DEF(WRITE_CACHING), BIT_DEF(HANDLE_CACHING), BIT_DEF(READ_CACHING), { 0, NULL } }; /* * Helper to print flags fields */ static void print_bits32(char *label, struct bit_defs *bit, uint32_t state) { printf("%s0x%x (", label, state); while (bit->mask != 0) { if ((state & bit->mask) != 0) printf(" %s", bit->name); bit++; } printf(" )\n"); } /* * Command language: * */ const char helpstr[] = "Commands:\n" "help\t\tList commands\n" "show\t\tShow OpLock state etc.\n" "open FID\n" "close FID\n" "req FID [OplockLevel]\n" "ack FID [OplockLevel]\n" "brk-parent FID\n" "brk-open [OverWrite]\n" "brk-handle FID\n" "brk-read FID\n" "brk-write FID\n" "brk-setinfo FID [InfoClass]\n" "move FID1 FID2\n" "waiters FID [count]\n"; /* * Command handlers */ static void do_show(void) { smb_node_t *node = &test_node; smb_oplock_t *ol = &node->n_oplock; uint32_t state = ol->ol_state; smb_ofile_t *f; print_bits32(" ol_state=", state_bits, state); if (ol->excl_open != NULL) printf(" Excl=Y (FID=%d)", ol->excl_open->f_fid); else printf(" Excl=n"); printf(" cnt_II=%d cnt_R=%d cnt_RH=%d cnt_RHBQ=%d\n", ol->cnt_II, ol->cnt_R, ol->cnt_RH, ol->cnt_RHBQ); printf(" ofile_cnt=%d\n", node->n_ofile_list.ll_count); FOREACH_NODE_OFILE(node, f) { smb_oplock_grant_t *og = &f->f_oplock; printf(" fid=%d Lease=%s State=0x%x", f->f_fid, f->TargetOplockKey, /* lease */ og->og_state); if (og->og_breaking) printf(" BreakTo=0x%x", og->og_breakto); printf(" Excl=%s onlist:", (ol->excl_open == f) ? "Y" : "N"); if (og->onlist_II) printf(" II"); if (og->onlist_R) printf(" R"); if (og->onlist_RH) printf(" RH"); if (og->onlist_RHBQ) { printf(" RHBQ(to %s)", og->BreakingToRead ? "read" : "none"); } printf("\n"); } } static void do_open(int fid, char *arg2) { smb_node_t *node = &test_node; smb_ofile_t *ofile = &ofile_array[fid]; /* * Simulate an open (minimal init) */ if (ofile->f_refcnt) { printf("open fid %d already opened\n"); return; } if (arg2 != NULL) { (void) strlcpy((char *)ofile->TargetOplockKey, arg2, SMB_LEASE_KEY_SZ); } ofile->f_refcnt++; node->n_open_count++; smb_llist_insert_tail(&node->n_ofile_list, ofile); printf(" open %d OK\n", fid); } static void do_close(int fid) { smb_node_t *node = &test_node; smb_ofile_t *ofile = &ofile_array[fid]; /* * Simulate an close */ if (ofile->f_refcnt <= 0) { printf(" close fid %d already closed\n"); return; } smb_llist_enter(&node->n_ofile_list, RW_READER); mutex_enter(&node->n_oplock.ol_mutex); smb_oplock_break_CLOSE(ofile->f_node, ofile); smb_llist_remove(&node->n_ofile_list, ofile); node->n_open_count--; mutex_exit(&node->n_oplock.ol_mutex); smb_llist_exit(&node->n_ofile_list); ofile->f_refcnt--; bzero(ofile->TargetOplockKey, SMB_LEASE_KEY_SZ); printf(" close OK\n"); } static void do_req(int fid, char *arg2) { smb_ofile_t *ofile = &ofile_array[fid]; uint32_t oplock = BATCH_OPLOCK; uint32_t status; if (arg2 != NULL) oplock = strtol(arg2, NULL, 16); /* * Request an oplock */ status = smb_oplock_request(&test_sr, ofile, &oplock); if (status == 0 || status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { ofile->f_oplock.og_state = oplock; /* When no break pending, breakto=state */ ofile->f_oplock.og_breakto = oplock; ofile->f_oplock.og_breaking = B_FALSE; } printf(" req oplock fid=%d ret oplock=0x%x status=0x%x (%s)\n", fid, oplock, status, xlate_nt_status(status)); } static void do_ack(int fid, char *arg2) { smb_node_t *node = &test_node; smb_ofile_t *ofile = &ofile_array[fid]; uint32_t oplock; uint32_t status; /* Default to level in last smb_oplock_ind_break() */ oplock = last_ind_break_level; if (arg2 != NULL) oplock = strtol(arg2, NULL, 16); smb_llist_enter(&node->n_ofile_list, RW_READER); mutex_enter(&node->n_oplock.ol_mutex); ofile->f_oplock.og_breaking = 0; status = smb_oplock_ack_break(&test_sr, ofile, &oplock); if (status == 0) ofile->f_oplock.og_state = oplock; mutex_exit(&node->n_oplock.ol_mutex); smb_llist_exit(&node->n_ofile_list); if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { /* should not get this status */ printf(" ack: break fid=%d, break-in-progress\n", fid); ASSERT(0); } printf(" ack: break fid=%d, newstate=0x%x, status=0x%x (%s)\n", fid, oplock, status, xlate_nt_status(status)); run_ind_break_in_ack(ofile); } static void do_brk_parent(int fid) { smb_ofile_t *ofile = &ofile_array[fid]; uint32_t status; status = smb_oplock_break_PARENT(&test_node, ofile); printf(" brk-parent %d ret status=0x%x (%s)\n", fid, status, xlate_nt_status(status)); } static void do_brk_open(int fid, char *arg2) { smb_ofile_t *ofile = &ofile_array[fid]; uint32_t status; int disp = FILE_OPEN; if (arg2 != NULL) disp = strtol(arg2, NULL, 16); status = smb_oplock_break_OPEN(&test_node, ofile, 7, disp); printf(" brk-open %d ret status=0x%x (%s)\n", fid, status, xlate_nt_status(status)); } static void do_brk_handle(int fid) { smb_ofile_t *ofile = &ofile_array[fid]; uint32_t status; status = smb_oplock_break_HANDLE(&test_node, ofile); printf(" brk-handle %d ret status=0x%x (%s)\n", fid, status, xlate_nt_status(status)); } static void do_brk_read(int fid) { smb_ofile_t *ofile = &ofile_array[fid]; uint32_t status; status = smb_oplock_break_READ(ofile->f_node, ofile); printf(" brk-read %d ret status=0x%x (%s)\n", fid, status, xlate_nt_status(status)); } static void do_brk_write(int fid) { smb_ofile_t *ofile = &ofile_array[fid]; uint32_t status; status = smb_oplock_break_WRITE(ofile->f_node, ofile); printf(" brk-write %d ret status=0x%x (%s)\n", fid, status, xlate_nt_status(status)); } static void do_brk_setinfo(int fid, char *arg2) { smb_ofile_t *ofile = &ofile_array[fid]; uint32_t status; int infoclass = FileEndOfFileInformation; /* 20 */ if (arg2 != NULL) infoclass = strtol(arg2, NULL, 16); status = smb_oplock_break_SETINFO( &test_node, ofile, infoclass); printf(" brk-setinfo %d 0x%x ret status=0x%x (%s)\n", fid, infoclass, status, xlate_nt_status(status)); } /* * Move oplock to another FD, as specified, * or any other available open */ static void do_move(int fid, char *arg2) { smb_node_t *node = &test_node; smb_ofile_t *ofile = &ofile_array[fid]; smb_ofile_t *of2; int fid2; if (arg2 == NULL) { fprintf(stderr, "move: FID2 required\n"); return; } fid2 = atoi(arg2); if (fid2 <= 0 || fid2 >= MAXFID) { fprintf(stderr, "move: bad FID2 %d\n", fid2); return; } of2 = &ofile_array[fid2]; mutex_enter(&node->n_oplock.ol_mutex); smb_oplock_move(&test_node, ofile, of2); mutex_exit(&node->n_oplock.ol_mutex); printf(" move %d %d\n", fid, fid2); } /* * Set/clear oplock.waiters, which affects ack-break */ static void do_waiters(int fid, char *arg2) { smb_node_t *node = &test_node; smb_oplock_t *ol = &node->n_oplock; int old, new = 0; if (arg2 != NULL) new = atoi(arg2); old = ol->waiters; ol->waiters = new; printf(" waiters %d -> %d\n", old, new); } int main(int argc, char *argv[]) { smb_node_t *node = &test_node; char *cmd; char *arg1; char *arg2; char *savep; char *sep = " \t\n"; char *prompt = NULL; int fid; if (isatty(0)) prompt = "> "; mutex_init(&node->n_mutex, NULL, MUTEX_DEFAULT, NULL); smb_llist_constructor(&node->n_ofile_list, sizeof (smb_ofile_t), offsetof(smb_ofile_t, f_node_lnd)); for (fid = 0; fid < MAXFID; fid++) { smb_ofile_t *f = &ofile_array[fid]; f->f_magic = SMB_OFILE_MAGIC; mutex_init(&f->f_mutex, NULL, MUTEX_DEFAULT, NULL); f->f_fid = fid; f->f_ftype = SMB_FTYPE_DISK; f->f_node = &test_node; } for (;;) { if (prompt) { (void) fputs(prompt, stdout); fflush(stdout); } cmd = fgets(cmdbuf, sizeof (cmdbuf), stdin); if (cmd == NULL) break; if (cmd[0] == '#') continue; if (prompt == NULL) { /* Put commands in the output too. */ (void) fputs(cmdbuf, stdout); } cmd = strtok_r(cmd, sep, &savep); if (cmd == NULL) continue; /* * Commands with no args */ if (0 == strcmp(cmd, "help")) { (void) fputs(helpstr, stdout); continue; } if (0 == strcmp(cmd, "show")) { do_show(); continue; } /* * Commands with one arg (the FID) */ arg1 = strtok_r(NULL, sep, &savep); if (arg1 == NULL) { fprintf(stderr, "%s missing arg1\n", cmd); continue; } fid = atoi(arg1); if (fid <= 0 || fid >= MAXFID) { fprintf(stderr, "%s bad FID %d\n", cmd, fid); continue; } if (0 == strcmp(cmd, "close")) { do_close(fid); continue; } if (0 == strcmp(cmd, "brk-parent")) { do_brk_parent(fid); continue; } if (0 == strcmp(cmd, "brk-handle")) { do_brk_handle(fid); continue; } if (0 == strcmp(cmd, "brk-read")) { do_brk_read(fid); continue; } if (0 == strcmp(cmd, "brk-write")) { do_brk_write(fid); continue; } /* * Commands with an (optional) arg2. */ arg2 = strtok_r(NULL, sep, &savep); if (0 == strcmp(cmd, "open")) { do_open(fid, arg2); continue; } if (0 == strcmp(cmd, "req")) { do_req(fid, arg2); continue; } if (0 == strcmp(cmd, "ack")) { do_ack(fid, arg2); continue; } if (0 == strcmp(cmd, "brk-open")) { do_brk_open(fid, arg2); continue; } if (0 == strcmp(cmd, "brk-setinfo")) { do_brk_setinfo(fid, arg2); continue; } if (0 == strcmp(cmd, "move")) { do_move(fid, arg2); continue; } if (0 == strcmp(cmd, "waiters")) { do_waiters(fid, arg2); continue; } fprintf(stderr, "%s unknown command. Try help\n", cmd); } return (0); } /* * A few functions called by the oplock code * Stubbed out, and/or just print a message. */ boolean_t smb_node_is_file(smb_node_t *node) { return (B_TRUE); } boolean_t smb_ofile_is_open(smb_ofile_t *ofile) { return (ofile->f_refcnt != 0); } int smb_lock_range_access( smb_request_t *sr, smb_node_t *node, uint64_t start, uint64_t length, boolean_t will_write) { return (0); } /* * Test code replacement for combination of: * smb_oplock_hdl_update() * smb_oplock_send_break() * * In a real server, we would send a break to the client, * and keep track (at the SMB level) whether this oplock * was obtained via a lease or an old-style oplock. */ static void test_oplock_send_break(smb_ofile_t *ofile, uint32_t NewLevel, boolean_t AckReq) { smb_oplock_grant_t *og = &ofile->f_oplock; uint32_t OldLevel; /* Skip building a message. */ if ((og->og_state & OPLOCK_LEVEL_GRANULAR) != 0) NewLevel |= OPLOCK_LEVEL_GRANULAR; OldLevel = og->og_state; og->og_breakto = NewLevel; og->og_breaking = B_TRUE; printf("*smb_oplock_send_break fid=%d " "NewLevel=0x%x, OldLevel=0x%x, AckReq=%d)\n", ofile->f_fid, NewLevel, OldLevel, AckReq); if (!AckReq) { og->og_state = NewLevel; og->og_breaking = B_FALSE; } /* Next, smb_oplock_send_break() would send a break. */ last_ind_break_level = NewLevel; } /* * Simplified version of what's in smb_srv_oplock.c */ void smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel, boolean_t AckReq, uint32_t status) { smb_oplock_grant_t *og = &ofile->f_oplock; printf("*smb_oplock_ind_break fid=%d NewLevel=0x%x," " AckReq=%d, ComplStatus=0x%x (%s)\n", ofile->f_fid, NewLevel, AckReq, status, xlate_nt_status(status)); /* * Note that the CompletionStatus from the FS level * (smb_cmn_oplock.c) encodes what kind of action we * need to take at the SMB level. */ switch (status) { case NT_STATUS_SUCCESS: case NT_STATUS_CANNOT_GRANT_REQUESTED_OPLOCK: test_oplock_send_break(ofile, NewLevel, AckReq); break; case NT_STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE: case NT_STATUS_OPLOCK_HANDLE_CLOSED: og->og_state = OPLOCK_LEVEL_NONE; og->og_breakto = OPLOCK_LEVEL_NONE; og->og_breaking = B_FALSE; break; default: ASSERT(0); break; } } /* Arrange for break_in_ack to run after ack completes. */ static uint32_t break_in_ack_NewLevel; static boolean_t break_in_ack_AckReq; static boolean_t break_in_ack_called; void smb_oplock_ind_break_in_ack(smb_request_t *sr, smb_ofile_t *ofile, uint32_t NewLevel, boolean_t AckRequired) { ASSERT(sr == &test_sr); /* Process these after ack */ ASSERT(!break_in_ack_called); break_in_ack_called = B_TRUE; break_in_ack_NewLevel = NewLevel; break_in_ack_AckReq = AckRequired; } static void run_ind_break_in_ack(smb_ofile_t *ofile) { uint32_t NewLevel; boolean_t AckReq; /* Process these after ack */ if (!break_in_ack_called) return; break_in_ack_called = B_FALSE; NewLevel = break_in_ack_NewLevel; AckReq = break_in_ack_AckReq; printf("*smb_oplock_ind_break_in_ack fid=%d NewLevel=0x%x," " AckReq=%d\n", ofile->f_fid, NewLevel, AckReq); test_oplock_send_break(ofile, NewLevel, AckReq); } uint32_t smb_oplock_wait_break(smb_request_t *sr, smb_node_t *node, int timeout) { printf("*smb_oplock_wait_break (state=0x%x)\n", node->n_oplock.ol_state); return (0); } int smb_fem_oplock_install(smb_node_t *node) { return (0); } void smb_fem_oplock_uninstall(smb_node_t *node) { } /* * There are a couple DTRACE_PROBE* in smb_cmn_oplock.c but we're * not linking with the user-level dtrace support, so just * stub these out. */ void __dtrace_fksmb___probe1(char *n, unsigned long a) { } void __dtrace_fksmb___probe2(char *n, unsigned long a, unsigned long b) { }