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