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