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