1/*
2   Unix SMB/CIFS implementation.
3   byte range lock tester - with local filesystem support
4   Copyright (C) Andrew Tridgell 1999
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 3 of the License, or
9   (at your option) any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program.  If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#include "includes.h"
21
22static fstring password;
23static fstring username;
24static int got_pass;
25static int numops = 1000;
26static bool showall;
27static bool analyze;
28static bool hide_unlock_fails;
29static bool use_oplocks;
30
31extern char *optarg;
32extern int optind;
33
34#define FILENAME "\\locktest.dat"
35#define LOCKRANGE 100
36#define LOCKBASE 0
37
38/*
39#define LOCKBASE (0x40000000 - 50)
40*/
41
42#define READ_PCT 50
43#define LOCK_PCT 25
44#define UNLOCK_PCT 65
45#define RANGE_MULTIPLE 1
46
47#define NSERVERS 2
48#define NCONNECTIONS 2
49#define NUMFSTYPES 2
50#define NFILES 2
51#define LOCK_TIMEOUT 0
52
53#define FSTYPE_SMB 0
54#define FSTYPE_NFS 1
55
56struct record {
57	char r1, r2;
58	char conn, f, fstype;
59	unsigned start, len;
60	char needed;
61};
62
63static struct record *recorded;
64
65static int try_open(struct cli_state *c, char *nfs, int fstype, const char *fname, int flags)
66{
67	char *path;
68
69	switch (fstype) {
70	case FSTYPE_SMB:
71		{
72			uint16_t fd;
73			if (!NT_STATUS_IS_OK(cli_open(c, fname, flags, DENY_NONE, &fd))) {
74				return -1;
75			}
76			return fd;
77		}
78
79	case FSTYPE_NFS:
80		if (asprintf(&path, "%s%s", nfs, fname) > 0) {
81			int ret;
82			string_replace(path,'\\', '/');
83			ret = open(path, flags, 0666);
84			SAFE_FREE(path);
85			return ret;
86		}
87		break;
88	}
89
90	return -1;
91}
92
93static bool try_close(struct cli_state *c, int fstype, int fd)
94{
95	switch (fstype) {
96	case FSTYPE_SMB:
97		return NT_STATUS_IS_OK(cli_close(c, fd));
98
99	case FSTYPE_NFS:
100		return close(fd) == 0;
101	}
102
103	return False;
104}
105
106static bool try_lock(struct cli_state *c, int fstype,
107		     int fd, unsigned start, unsigned len,
108		     enum brl_type op)
109{
110	struct flock lock;
111
112	switch (fstype) {
113	case FSTYPE_SMB:
114		return cli_lock(c, fd, start, len, LOCK_TIMEOUT, op);
115
116	case FSTYPE_NFS:
117		lock.l_type = (op==READ_LOCK) ? F_RDLCK:F_WRLCK;
118		lock.l_whence = SEEK_SET;
119		lock.l_start = start;
120		lock.l_len = len;
121		lock.l_pid = getpid();
122		return fcntl(fd,F_SETLK,&lock) == 0;
123	}
124
125	return False;
126}
127
128static bool try_unlock(struct cli_state *c, int fstype,
129		       int fd, unsigned start, unsigned len)
130{
131	struct flock lock;
132
133	switch (fstype) {
134	case FSTYPE_SMB:
135		return NT_STATUS_IS_OK(cli_unlock(c, fd, start, len));
136
137	case FSTYPE_NFS:
138		lock.l_type = F_UNLCK;
139		lock.l_whence = SEEK_SET;
140		lock.l_start = start;
141		lock.l_len = len;
142		lock.l_pid = getpid();
143		return fcntl(fd,F_SETLK,&lock) == 0;
144	}
145
146	return False;
147}
148
149static void print_brl(struct file_id id, struct server_id pid,
150		      enum brl_type lock_type,
151		      enum brl_flavour lock_flav,
152		      br_off start, br_off size,
153		      void *private_data)
154{
155	printf("%6d   %s    %s  %.0f:%.0f(%.0f)\n",
156	       (int)procid_to_pid(&pid), file_id_string_tos(&id),
157	       lock_type==READ_LOCK?"R":"W",
158	       (double)start, (double)start+size-1,(double)size);
159
160}
161
162/*****************************************************
163return a connection to a server
164*******************************************************/
165static struct cli_state *connect_one(char *share)
166{
167	struct cli_state *c;
168	char *server_n;
169	fstring server;
170	fstring myname;
171	static int count;
172	NTSTATUS nt_status;
173
174	fstrcpy(server,share+2);
175	share = strchr_m(server,'\\');
176	if (!share) return NULL;
177	*share = 0;
178	share++;
179
180	server_n = server;
181
182	if (!got_pass) {
183		char *pass = getpass("Password: ");
184		if (pass) {
185			fstrcpy(password, pass);
186		}
187	}
188
189	slprintf(myname,sizeof(myname), "lock-%lu-%u", (unsigned long)getpid(), count++);
190
191	nt_status = cli_full_connection(&c, myname, server_n, NULL, 0, share, "?????",
192					username, lp_workgroup(), password, 0,
193					Undefined, NULL);
194
195	if (!NT_STATUS_IS_OK(nt_status)) {
196		DEBUG(0, ("cli_full_connection failed with error %s\n", nt_errstr(nt_status)));
197		return NULL;
198	}
199
200	c->use_oplocks = use_oplocks;
201
202	return c;
203}
204
205
206static void reconnect(struct cli_state *cli[NSERVERS][NCONNECTIONS],
207		      char *nfs[NSERVERS],
208		      int fnum[NSERVERS][NUMFSTYPES][NCONNECTIONS][NFILES],
209		      char *share1, char *share2)
210{
211	int server, conn, f, fstype;
212	char *share[2];
213	share[0] = share1;
214	share[1] = share2;
215
216	fstype = FSTYPE_SMB;
217
218	for (server=0;server<NSERVERS;server++)
219	for (conn=0;conn<NCONNECTIONS;conn++) {
220		if (cli[server][conn]) {
221			for (f=0;f<NFILES;f++) {
222				cli_close(cli[server][conn], fnum[server][fstype][conn][f]);
223			}
224			cli_ulogoff(cli[server][conn]);
225			cli_shutdown(cli[server][conn]);
226		}
227		cli[server][conn] = connect_one(share[server]);
228		if (!cli[server][conn]) {
229			DEBUG(0,("Failed to connect to %s\n", share[server]));
230			exit(1);
231		}
232	}
233}
234
235
236
237static bool test_one(struct cli_state *cli[NSERVERS][NCONNECTIONS],
238		     char *nfs[NSERVERS],
239		     int fnum[NSERVERS][NUMFSTYPES][NCONNECTIONS][NFILES],
240		     struct record *rec)
241{
242	unsigned conn = rec->conn;
243	unsigned f = rec->f;
244	unsigned fstype = rec->fstype;
245	unsigned start = rec->start;
246	unsigned len = rec->len;
247	unsigned r1 = rec->r1;
248	unsigned r2 = rec->r2;
249	enum brl_type op;
250	int server;
251	bool ret[NSERVERS];
252
253	if (r1 < READ_PCT) {
254		op = READ_LOCK;
255	} else {
256		op = WRITE_LOCK;
257	}
258
259	if (r2 < LOCK_PCT) {
260		/* set a lock */
261		for (server=0;server<NSERVERS;server++) {
262			ret[server] = try_lock(cli[server][conn], fstype,
263					       fnum[server][fstype][conn][f],
264					       start, len, op);
265		}
266		if (showall || ret[0] != ret[1]) {
267			printf("lock   conn=%u fstype=%u f=%u range=%u:%u(%u) op=%s -> %u:%u\n",
268			       conn, fstype, f,
269			       start, start+len-1, len,
270			       op==READ_LOCK?"READ_LOCK":"WRITE_LOCK",
271			       ret[0], ret[1]);
272		}
273		if (showall) brl_forall(print_brl, NULL);
274		if (ret[0] != ret[1]) return False;
275	} else if (r2 < LOCK_PCT+UNLOCK_PCT) {
276		/* unset a lock */
277		for (server=0;server<NSERVERS;server++) {
278			ret[server] = try_unlock(cli[server][conn], fstype,
279						 fnum[server][fstype][conn][f],
280						 start, len);
281		}
282		if (showall || (!hide_unlock_fails && (ret[0] != ret[1]))) {
283			printf("unlock conn=%u fstype=%u f=%u range=%u:%u(%u)       -> %u:%u\n",
284			       conn, fstype, f,
285			       start, start+len-1, len,
286			       ret[0], ret[1]);
287		}
288		if (showall) brl_forall(print_brl, NULL);
289		if (!hide_unlock_fails && ret[0] != ret[1]) return False;
290	} else {
291		/* reopen the file */
292		for (server=0;server<NSERVERS;server++) {
293			try_close(cli[server][conn], fstype, fnum[server][fstype][conn][f]);
294			fnum[server][fstype][conn][f] = try_open(cli[server][conn], nfs[server], fstype, FILENAME,
295								 O_RDWR|O_CREAT);
296			if (fnum[server][fstype][conn][f] == -1) {
297				printf("failed to reopen on share1\n");
298				return False;
299			}
300		}
301		if (showall) {
302			printf("reopen conn=%u fstype=%u f=%u\n",
303			       conn, fstype, f);
304			brl_forall(print_brl, NULL);
305		}
306	}
307	return True;
308}
309
310static void close_files(struct cli_state *cli[NSERVERS][NCONNECTIONS],
311			char *nfs[NSERVERS],
312			int fnum[NSERVERS][NUMFSTYPES][NCONNECTIONS][NFILES])
313{
314	int server, conn, f, fstype;
315
316	for (server=0;server<NSERVERS;server++)
317	for (fstype=0;fstype<NUMFSTYPES;fstype++)
318	for (conn=0;conn<NCONNECTIONS;conn++)
319	for (f=0;f<NFILES;f++) {
320		if (fnum[server][fstype][conn][f] != -1) {
321			try_close(cli[server][conn], fstype, fnum[server][fstype][conn][f]);
322			fnum[server][fstype][conn][f] = -1;
323		}
324	}
325	for (server=0;server<NSERVERS;server++) {
326		cli_unlink(cli[server][0], FILENAME, aSYSTEM | aHIDDEN);
327	}
328}
329
330static void open_files(struct cli_state *cli[NSERVERS][NCONNECTIONS],
331		       char *nfs[NSERVERS],
332		       int fnum[NSERVERS][NUMFSTYPES][NCONNECTIONS][NFILES])
333{
334	int server, fstype, conn, f;
335
336	for (server=0;server<NSERVERS;server++)
337	for (fstype=0;fstype<NUMFSTYPES;fstype++)
338	for (conn=0;conn<NCONNECTIONS;conn++)
339	for (f=0;f<NFILES;f++) {
340		fnum[server][fstype][conn][f] = try_open(cli[server][conn], nfs[server], fstype, FILENAME,
341							 O_RDWR|O_CREAT);
342		if (fnum[server][fstype][conn][f] == -1) {
343			fprintf(stderr,"Failed to open fnum[%u][%u][%u][%u]\n",
344				server, fstype, conn, f);
345			exit(1);
346		}
347	}
348}
349
350
351static int retest(struct cli_state *cli[NSERVERS][NCONNECTIONS],
352		  char *nfs[NSERVERS],
353		  int fnum[NSERVERS][NUMFSTYPES][NCONNECTIONS][NFILES],
354		  int n)
355{
356	int i;
357	printf("testing %u ...\n", n);
358	for (i=0; i<n; i++) {
359		if (i && i % 100 == 0) {
360			printf("%u\n", i);
361		}
362
363		if (recorded[i].needed &&
364		    !test_one(cli, nfs, fnum, &recorded[i])) return i;
365	}
366	return n;
367}
368
369
370/* each server has two connections open to it. Each connection has two file
371   descriptors open on the file - 8 file descriptors in total
372
373   we then do random locking ops in tamdem on the 4 fnums from each
374   server and ensure that the results match
375 */
376static void test_locks(char *share1, char *share2, char *nfspath1, char *nfspath2)
377{
378	struct cli_state *cli[NSERVERS][NCONNECTIONS];
379	char *nfs[NSERVERS];
380	int fnum[NSERVERS][NUMFSTYPES][NCONNECTIONS][NFILES];
381	int n, i, n1;
382
383	nfs[0] = nfspath1;
384	nfs[1] = nfspath2;
385
386	ZERO_STRUCT(fnum);
387	ZERO_STRUCT(cli);
388
389	recorded = SMB_MALLOC_ARRAY(struct record, numops);
390
391	for (n=0; n<numops; n++) {
392		recorded[n].conn = random() % NCONNECTIONS;
393		recorded[n].fstype = random() % NUMFSTYPES;
394		recorded[n].f = random() % NFILES;
395		recorded[n].start = LOCKBASE + ((unsigned)random() % (LOCKRANGE-1));
396		recorded[n].len = 1 +
397			random() % (LOCKRANGE-(recorded[n].start-LOCKBASE));
398		recorded[n].start *= RANGE_MULTIPLE;
399		recorded[n].len *= RANGE_MULTIPLE;
400		recorded[n].r1 = random() % 100;
401		recorded[n].r2 = random() % 100;
402		recorded[n].needed = True;
403	}
404
405	reconnect(cli, nfs, fnum, share1, share2);
406	open_files(cli, nfs, fnum);
407	n = retest(cli, nfs, fnum, numops);
408
409	if (n == numops || !analyze) return;
410	n++;
411
412	while (1) {
413		n1 = n;
414
415		close_files(cli, nfs, fnum);
416		reconnect(cli, nfs, fnum, share1, share2);
417		open_files(cli, nfs, fnum);
418
419		for (i=0;i<n-1;i++) {
420			int m;
421			recorded[i].needed = False;
422
423			close_files(cli, nfs, fnum);
424			open_files(cli, nfs, fnum);
425
426			m = retest(cli, nfs, fnum, n);
427			if (m == n) {
428				recorded[i].needed = True;
429			} else {
430				if (i < m) {
431					memmove(&recorded[i], &recorded[i+1],
432						(m-i)*sizeof(recorded[0]));
433				}
434				n = m;
435				i--;
436			}
437		}
438
439		if (n1 == n) break;
440	}
441
442	close_files(cli, nfs, fnum);
443	reconnect(cli, nfs, fnum, share1, share2);
444	open_files(cli, nfs, fnum);
445	showall = True;
446	n1 = retest(cli, nfs, fnum, n);
447	if (n1 != n-1) {
448		printf("ERROR - inconsistent result (%u %u)\n", n1, n);
449	}
450	close_files(cli, nfs, fnum);
451
452	for (i=0;i<n;i++) {
453		printf("{%u, %u, %u, %u, %u, %u, %u, %u},\n",
454		       recorded[i].r1,
455		       recorded[i].r2,
456		       recorded[i].conn,
457		       recorded[i].fstype,
458		       recorded[i].f,
459		       recorded[i].start,
460		       recorded[i].len,
461		       recorded[i].needed);
462	}
463}
464
465
466
467static void usage(void)
468{
469	printf(
470"Usage:\n\
471  locktest //server1/share1 //server2/share2 /path1 /path2 [options..]\n\
472  options:\n\
473        -U user%%pass\n\
474        -s seed\n\
475        -o numops\n\
476        -u          hide unlock fails\n\
477        -a          (show all ops)\n\
478        -O          use oplocks\n\
479");
480}
481
482/****************************************************************************
483  main program
484****************************************************************************/
485 int main(int argc,char *argv[])
486{
487	char *share1, *share2, *nfspath1, *nfspath2;
488	int opt;
489	char *p;
490	int seed;
491
492	setlinebuf(stdout);
493
494	dbf = x_stderr;
495
496	if (argc < 5 || argv[1][0] == '-') {
497		usage();
498		exit(1);
499	}
500
501	share1 = argv[1];
502	share2 = argv[2];
503	nfspath1 = argv[3];
504	nfspath2 = argv[4];
505
506	all_string_sub(share1,"/","\\",0);
507	all_string_sub(share2,"/","\\",0);
508
509	setup_logging(argv[0],True);
510
511	argc -= 4;
512	argv += 4;
513
514	lp_load(get_dyn_CONFIGFILE(),True,False,False,True);
515	load_interfaces();
516
517	if (getenv("USER")) {
518		fstrcpy(username,getenv("USER"));
519	}
520
521	seed = time(NULL);
522
523	while ((opt = getopt(argc, argv, "U:s:ho:aAW:O")) != EOF) {
524		switch (opt) {
525		case 'U':
526			fstrcpy(username,optarg);
527			p = strchr_m(username,'%');
528			if (p) {
529				*p = 0;
530				fstrcpy(password, p+1);
531				got_pass = 1;
532			}
533			break;
534		case 's':
535			seed = atoi(optarg);
536			break;
537		case 'u':
538			hide_unlock_fails = True;
539			break;
540		case 'o':
541			numops = atoi(optarg);
542			break;
543		case 'O':
544			use_oplocks = True;
545			break;
546		case 'a':
547			showall = True;
548			break;
549		case 'A':
550			analyze = True;
551			break;
552		case 'h':
553			usage();
554			exit(1);
555		default:
556			printf("Unknown option %c (%d)\n", (char)opt, opt);
557			exit(1);
558		}
559	}
560
561	argc -= optind;
562	argv += optind;
563
564	DEBUG(0,("seed=%u\n", seed));
565	srandom(seed);
566
567	locking_init_readonly();
568	test_locks(share1, share2, nfspath1, nfspath2);
569
570	return(0);
571}
572