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
44smb_node_t root_node, test_node;
45smb_ofile_t  ofile_array[MAXFID];
46smb_request_t test_sr;
47uint32_t last_ind_break_level;
48char cmdbuf[100];
49
50extern const char *xlate_nt_status(uint32_t);
51
52#define	BIT_DEF(name) { name, #name }
53
54struct 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 */
80static void
81print_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 */
96const 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
116static void
117do_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
155static void
156do_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
180static void
181do_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
204static void
205do_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
225static void
226do_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
250static void
251do_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
261static void
262do_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
276static void
277do_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
288static void
289do_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
299static void
300do_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
310static void
311do_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 */
331static void
332do_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 */
356static void
357do_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
372int
373main(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
512boolean_t
513smb_node_is_file(smb_node_t *node)
514{
515	return (B_TRUE);
516}
517
518boolean_t
519smb_ofile_is_open(smb_ofile_t *ofile)
520{
521	return (ofile->f_refcnt != 0);
522}
523
524int
525smb_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 */
538static void
539test_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 */
580void
581smb_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
615void
616smb_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
623uint32_t
624smb_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 */
636void
637__dtrace_fksmb___probe1(char *n, unsigned long a)
638{
639}
640void
641__dtrace_fksmb___probe2(char *n, unsigned long a, unsigned long b)
642{
643}
644