2 Unix SMB/CIFS implementation.
4 test suite for SMB2 durable opens
6 Copyright (C) Stefan Metzmacher 2008
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "libcli/smb2/smb2.h"
24 #include "libcli/smb2/smb2_calls.h"
25 #include "torture/torture.h"
26 #include "torture/smb2/proto.h"
28 #define CHECK_VAL(v, correct) do { \
29 if ((v) != (correct)) { \
30 torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%x - should be 0x%x\n", \
31 __location__, #v, (int)v, (int)correct); \
35 #define CHECK_STATUS(status, correct) do { \
36 if (!NT_STATUS_EQUAL(status, correct)) { \
37 torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \
38 nt_errstr(status), nt_errstr(correct)); \
43 #define CHECK_CREATED(__io, __created, __attribute) \
45 CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
46 CHECK_VAL((__io)->out.alloc_size, 0); \
47 CHECK_VAL((__io)->out.size, 0); \
48 CHECK_VAL((__io)->out.file_attr, (__attribute)); \
49 CHECK_VAL((__io)->out.reserved2, 0); \
54 * basic durable_open test.
55 * durable state should only be granted when requested
56 * along with a batch oplock or a handle lease.
58 * This test tests durable open with all possible oplock types.
61 struct durable_open_vs_oplock {
63 const char *share_mode;
67 #define NUM_OPLOCK_TYPES 4
68 #define NUM_SHARE_MODES 8
69 #define NUM_OPLOCK_OPEN_TESTS ( NUM_OPLOCK_TYPES * NUM_SHARE_MODES )
70 struct durable_open_vs_oplock durable_open_vs_oplock_table[NUM_OPLOCK_OPEN_TESTS] =
88 { "s", "RWD", false },
97 { "x", "RWD", false },
106 { "b", "RWD", true },
109 static bool test_one_durable_open_open1(struct torture_context *tctx,
110 struct smb2_tree *tree,
112 struct durable_open_vs_oplock test)
115 TALLOC_CTX *mem_ctx = talloc_new(tctx);
116 struct smb2_handle _h;
117 struct smb2_handle *h = NULL;
119 struct smb2_create io;
121 smb2_util_unlink(tree, fname);
123 smb2_oplock_create_share(&io, fname,
124 smb2_util_share_access(test.share_mode),
125 smb2_util_oplock_level(test.level));
126 io.in.durable_open = true;
128 status = smb2_create(tree, mem_ctx, &io);
129 CHECK_STATUS(status, NT_STATUS_OK);
130 _h = io.out.file.handle;
132 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
133 CHECK_VAL(io.out.durable_open, test.expected);
134 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level(test.level));
138 smb2_util_close(tree, *h);
140 smb2_util_unlink(tree, fname);
141 talloc_free(mem_ctx);
146 bool test_durable_open_open1(struct torture_context *tctx,
147 struct smb2_tree *tree)
149 TALLOC_CTX *mem_ctx = talloc_new(tctx);
154 /* Choose a random name in case the state is left a little funky. */
155 snprintf(fname, 256, "durable_open_open1_%s.dat", generate_random_str(tctx, 8));
157 smb2_util_unlink(tree, fname);
159 /* test various oplock levels with durable open */
161 for (i = 0; i < NUM_OPLOCK_OPEN_TESTS; i++) {
162 ret = test_one_durable_open_open1(tctx,
165 durable_open_vs_oplock_table[i]);
172 smb2_util_unlink(tree, fname);
174 talloc_free(mem_ctx);
180 * basic durable_open test.
181 * durable state should only be granted when requested
182 * along with a batch oplock or a handle lease.
184 * This test tests durable open with all valid lease types.
187 struct durable_open_vs_lease {
189 const char *share_mode;
193 #define NUM_LEASE_TYPES 5
194 #define NUM_LEASE_OPEN_TESTS ( NUM_LEASE_TYPES * NUM_SHARE_MODES )
195 struct durable_open_vs_lease durable_open_vs_lease_table[NUM_LEASE_OPEN_TESTS] =
204 { "", "RWD", false },
210 { "R", "RW", false },
211 { "R", "RD", false },
212 { "R", "DW", false },
213 { "R", "RWD", false },
216 { "RW", "R", false },
217 { "RW", "W", false },
218 { "RW", "D", false },
219 { "RW", "RW", false },
220 { "RW", "RD", false },
221 { "RW", "WD", false },
222 { "RW", "RWD", false },
228 { "RH", "RW", true },
229 { "RH", "RD", true },
230 { "RH", "WD", true },
231 { "RH", "RWD", true },
234 { "RHW", "R", true },
235 { "RHW", "W", true },
236 { "RHW", "D", true },
237 { "RHW", "RW", true },
238 { "RHW", "RD", true },
239 { "RHW", "WD", true },
240 { "RHW", "RWD", true },
243 static bool test_one_durable_open_open2(struct torture_context *tctx,
244 struct smb2_tree *tree,
246 struct durable_open_vs_lease test)
249 TALLOC_CTX *mem_ctx = talloc_new(tctx);
250 struct smb2_handle _h;
251 struct smb2_handle *h = NULL;
253 struct smb2_create io;
254 struct smb2_lease ls;
257 smb2_util_unlink(tree, fname);
261 smb2_lease_create_share(&io, &ls, false /* dir */, fname,
262 smb2_util_share_access(test.share_mode),
264 smb2_util_lease_state(test.type));
265 io.in.durable_open = true;
267 status = smb2_create(tree, mem_ctx, &io);
268 CHECK_STATUS(status, NT_STATUS_OK);
269 _h = io.out.file.handle;
271 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
272 CHECK_VAL(io.out.durable_open, test.expected);
273 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
274 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease);
275 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease);
276 CHECK_VAL(io.out.lease_response.lease_state,
277 smb2_util_lease_state(test.type));
280 smb2_util_close(tree, *h);
282 smb2_util_unlink(tree, fname);
283 talloc_free(mem_ctx);
288 bool test_durable_open_open2(struct torture_context *tctx,
289 struct smb2_tree *tree)
291 TALLOC_CTX *mem_ctx = talloc_new(tctx);
296 /* Choose a random name in case the state is left a little funky. */
297 snprintf(fname, 256, "durable_open_open2_%s.dat", generate_random_str(tctx, 8));
299 smb2_util_unlink(tree, fname);
302 /* test various oplock levels with durable open */
304 for (i = 0; i < NUM_LEASE_OPEN_TESTS; i++) {
305 ret = test_one_durable_open_open2(tctx,
308 durable_open_vs_lease_table[i]);
315 smb2_util_unlink(tree, fname);
317 talloc_free(mem_ctx);
323 basic testing of SMB2 durable opens
324 regarding the position information on the handle
326 bool test_durable_open_file_position(struct torture_context *tctx,
327 struct smb2_tree *tree1,
328 struct smb2_tree *tree2)
330 TALLOC_CTX *mem_ctx = talloc_new(tctx);
331 struct smb2_handle h1, h2;
332 struct smb2_create io1, io2;
334 const char *fname = "durable_open_position.dat";
335 union smb_fileinfo qfinfo;
336 union smb_setfileinfo sfinfo;
340 smb2_util_unlink(tree1, fname);
342 smb2_oplock_create(&io1, fname, SMB2_OPLOCK_LEVEL_BATCH);
343 io1.in.durable_open = true;
345 status = smb2_create(tree1, mem_ctx, &io1);
346 CHECK_STATUS(status, NT_STATUS_OK);
347 h1 = io1.out.file.handle;
348 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
349 CHECK_VAL(io1.out.durable_open, true);
350 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
352 /* TODO: check extra blob content */
355 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
356 qfinfo.generic.in.file.handle = h1;
357 status = smb2_getinfo_file(tree1, mem_ctx, &qfinfo);
358 CHECK_STATUS(status, NT_STATUS_OK);
359 CHECK_VAL(qfinfo.position_information.out.position, 0);
360 pos = qfinfo.position_information.out.position;
361 torture_comment(tctx, "position: %llu\n",
362 (unsigned long long)pos);
365 sfinfo.generic.level = RAW_SFILEINFO_POSITION_INFORMATION;
366 sfinfo.generic.in.file.handle = h1;
367 sfinfo.position_information.in.position = 0x1000;
368 status = smb2_setinfo_file(tree1, &sfinfo);
369 CHECK_STATUS(status, NT_STATUS_OK);
372 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
373 qfinfo.generic.in.file.handle = h1;
374 status = smb2_getinfo_file(tree1, mem_ctx, &qfinfo);
375 CHECK_STATUS(status, NT_STATUS_OK);
376 CHECK_VAL(qfinfo.position_information.out.position, 0x1000);
377 pos = qfinfo.position_information.out.position;
378 torture_comment(tctx, "position: %llu\n",
379 (unsigned long long)pos);
385 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
386 qfinfo.generic.in.file.handle = h1;
387 status = smb2_getinfo_file(tree2, mem_ctx, &qfinfo);
388 CHECK_STATUS(status, NT_STATUS_FILE_CLOSED);
391 io2.in.fname = fname;
392 io2.in.durable_handle = &h1;
394 status = smb2_create(tree2, mem_ctx, &io2);
395 CHECK_STATUS(status, NT_STATUS_OK);
396 CHECK_VAL(io2.out.durable_open, true);
397 CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
398 CHECK_VAL(io2.out.reserved, 0x00);
399 CHECK_VAL(io2.out.create_action, NTCREATEX_ACTION_EXISTED);
400 CHECK_VAL(io2.out.alloc_size, 0);
401 CHECK_VAL(io2.out.size, 0);
402 CHECK_VAL(io2.out.file_attr, FILE_ATTRIBUTE_ARCHIVE);
403 CHECK_VAL(io2.out.reserved2, 0);
405 h2 = io2.out.file.handle;
408 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
409 qfinfo.generic.in.file.handle = h2;
410 status = smb2_getinfo_file(tree2, mem_ctx, &qfinfo);
411 CHECK_STATUS(status, NT_STATUS_OK);
412 CHECK_VAL(qfinfo.position_information.out.position, 0x1000);
413 pos = qfinfo.position_information.out.position;
414 torture_comment(tctx, "position: %llu\n",
415 (unsigned long long)pos);
417 smb2_util_close(tree2, h2);
419 talloc_free(mem_ctx);
421 smb2_util_unlink(tree2, fname);
430 Open, disconnect, oplock break, reconnect.
432 bool test_durable_open_oplock(struct torture_context *tctx,
433 struct smb2_tree *tree1,
434 struct smb2_tree *tree2)
436 TALLOC_CTX *mem_ctx = talloc_new(tctx);
437 struct smb2_create io1, io2;
438 struct smb2_handle h1, h2;
443 /* Choose a random name in case the state is left a little funky. */
444 snprintf(fname, 256, "durable_open_oplock_%s.dat", generate_random_str(tctx, 8));
447 smb2_util_unlink(tree1, fname);
449 /* Create with batch oplock */
450 smb2_oplock_create(&io1, fname, SMB2_OPLOCK_LEVEL_BATCH);
451 io1.in.durable_open = true;
454 io2.in.create_disposition = NTCREATEX_DISP_OPEN;
456 status = smb2_create(tree1, mem_ctx, &io1);
457 CHECK_STATUS(status, NT_STATUS_OK);
458 h1 = io1.out.file.handle;
459 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
460 CHECK_VAL(io1.out.durable_open, true);
461 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
463 /* Disconnect after getting the batch */
468 * Windows7 (build 7000) will break a batch oplock immediately if the
469 * original client is gone. (ZML: This seems like a bug. It should give
470 * some time for the client to reconnect!)
472 status = smb2_create(tree2, mem_ctx, &io2);
473 CHECK_STATUS(status, NT_STATUS_OK);
474 h2 = io2.out.file.handle;
475 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
476 CHECK_VAL(io2.out.durable_open, true);
477 CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
479 /* What if tree1 tries to come back and reclaim? */
480 if (!torture_smb2_connection(tctx, &tree1)) {
481 torture_warning(tctx, "couldn't reconnect, bailing\n");
487 io1.in.fname = fname;
488 io1.in.durable_handle = &h1;
490 status = smb2_create(tree1, mem_ctx, &io1);
491 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
494 smb2_util_close(tree2, h2);
495 smb2_util_unlink(tree2, fname);
504 Open, disconnect, lease break, reconnect.
506 bool test_durable_open_lease(struct torture_context *tctx,
507 struct smb2_tree *tree1,
508 struct smb2_tree *tree2)
510 TALLOC_CTX *mem_ctx = talloc_new(tctx);
511 struct smb2_create io1, io2;
512 struct smb2_lease ls1, ls2;
513 struct smb2_handle h1, h2;
517 uint64_t lease1, lease2;
520 * Choose a random name and random lease in case the state is left a
525 snprintf(fname, 256, "durable_open_lease_%s.dat", generate_random_str(tctx, 8));
528 smb2_util_unlink(tree1, fname);
530 /* Create with lease */
531 smb2_lease_create(&io1, &ls1, false /* dir */, fname,
532 lease1, smb2_util_lease_state("RHW"));
533 io1.in.durable_open = true;
535 smb2_lease_create(&io2, &ls2, false /* dir */, fname,
536 lease2, smb2_util_lease_state("RHW"));
537 io2.in.durable_open = true;
538 io2.in.create_disposition = NTCREATEX_DISP_OPEN;
540 status = smb2_create(tree1, mem_ctx, &io1);
541 CHECK_STATUS(status, NT_STATUS_OK);
542 h1 = io1.out.file.handle;
543 CHECK_VAL(io1.out.durable_open, true);
544 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
546 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
547 CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease1);
548 CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease1);
549 CHECK_VAL(io1.out.lease_response.lease_state,
550 SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
552 /* Disconnect after getting the lease */
557 * Windows7 (build 7000) will grant an RH lease immediate (not an RHW?)
558 * even if the original client is gone. (ZML: This seems like a bug. It
559 * should give some time for the client to reconnect! And why RH?)
561 * obnox: Current windows 7 and w2k8r2 grant RHW instead of RH.
562 * Test is adapted accordingly.
564 status = smb2_create(tree2, mem_ctx, &io2);
565 CHECK_STATUS(status, NT_STATUS_OK);
566 h2 = io2.out.file.handle;
567 CHECK_VAL(io2.out.durable_open, true);
568 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
570 CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
571 CHECK_VAL(io2.out.lease_response.lease_key.data[0], lease2);
572 CHECK_VAL(io2.out.lease_response.lease_key.data[1], ~lease2);
573 CHECK_VAL(io2.out.lease_response.lease_state,
574 SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
576 /* What if tree1 tries to come back and reclaim? */
577 if (!torture_smb2_connection(tctx, &tree1)) {
578 torture_warning(tctx, "couldn't reconnect, bailing\n");
584 io1.in.fname = fname;
585 io1.in.durable_handle = &h1;
586 io1.in.lease_request = &ls1;
588 status = smb2_create(tree1, mem_ctx, &io1);
589 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
592 smb2_util_close(tree2, h2);
593 smb2_util_unlink(tree2, fname);
602 Open, take BRL, disconnect, reconnect.
604 bool test_durable_open_lock(struct torture_context *tctx,
605 struct smb2_tree *tree)
607 TALLOC_CTX *mem_ctx = talloc_new(tctx);
608 struct smb2_create io;
609 struct smb2_lease ls;
610 struct smb2_handle h;
611 struct smb2_lock lck;
612 struct smb2_lock_element el[2];
619 * Choose a random name and random lease in case the state is left a
623 snprintf(fname, 256, "durable_open_lock_%s.dat", generate_random_str(tctx, 8));
626 smb2_util_unlink(tree, fname);
628 /* Create with lease */
630 smb2_lease_create(&io, &ls, false /* dir */, fname, lease,
631 smb2_util_lease_state("RWH"));
632 io.in.durable_open = true;
634 status = smb2_create(tree, mem_ctx, &io);
635 CHECK_STATUS(status, NT_STATUS_OK);
636 h = io.out.file.handle;
637 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
639 CHECK_VAL(io.out.durable_open, true);
640 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
641 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease);
642 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease);
643 CHECK_VAL(io.out.lease_response.lease_state,
644 SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
649 lck.in.lock_count = 0x0001;
650 lck.in.lock_sequence = 0x00000000;
651 lck.in.file.handle = h;
654 el[0].reserved = 0x00000000;
655 el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE;
656 status = smb2_lock(tree, &lck);
657 CHECK_STATUS(status, NT_STATUS_OK);
659 /* Disconnect/Reconnect. */
663 if (!torture_smb2_connection(tctx, &tree)) {
664 torture_warning(tctx, "couldn't reconnect, bailing\n");
671 io.in.durable_handle = &h;
672 io.in.lease_request = &ls;
674 status = smb2_create(tree, mem_ctx, &io);
675 CHECK_STATUS(status, NT_STATUS_OK);
676 h = io.out.file.handle;
678 lck.in.file.handle = h;
679 el[0].flags = SMB2_LOCK_FLAG_UNLOCK;
680 status = smb2_lock(tree, &lck);
681 CHECK_STATUS(status, NT_STATUS_OK);
684 smb2_util_close(tree, h);
685 smb2_util_unlink(tree, fname);
692 Open, disconnect, open in another tree, reconnect.
694 This test actually demonstrates a minimum level of respect for the durable
695 open in the face of another open. As long as this test shows an inability to
696 reconnect after an open, the oplock/lease tests above will certainly
697 demonstrate an error on reconnect.
699 bool test_durable_open_open(struct torture_context *tctx,
700 struct smb2_tree *tree1,
701 struct smb2_tree *tree2)
703 TALLOC_CTX *mem_ctx = talloc_new(tctx);
704 struct smb2_create io1, io2;
705 struct smb2_lease ls;
706 struct smb2_handle h1, h2;
713 * Choose a random name and random lease in case the state is left a
717 snprintf(fname, 256, "durable_open_lock_%s.dat", generate_random_str(tctx, 8));
720 smb2_util_unlink(tree1, fname);
722 /* Create with lease */
723 smb2_lease_create_share(&io1, &ls, false /* dir */, fname,
724 smb2_util_share_access(""),
726 smb2_util_lease_state("RH"));
727 io1.in.durable_open = true;
729 smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE);
731 status = smb2_create(tree1, mem_ctx, &io1);
732 CHECK_STATUS(status, NT_STATUS_OK);
733 h1 = io1.out.file.handle;
734 CHECK_VAL(io1.out.durable_open, true);
735 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
737 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
738 CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease);
739 CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease);
740 CHECK_VAL(io1.out.lease_response.lease_state,
741 smb2_util_lease_state("RH"));
747 /* Open the file in tree2 */
748 status = smb2_create(tree2, mem_ctx, &io2);
749 CHECK_STATUS(status, NT_STATUS_OK);
750 h2 = io2.out.file.handle;
751 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
754 if (!torture_smb2_connection(tctx, &tree1)) {
755 torture_warning(tctx, "couldn't reconnect, bailing\n");
761 io1.in.fname = fname;
762 io1.in.durable_handle = &h1;
763 io1.in.lease_request = &ls;
766 * Windows7 (build 7000) will give away an open immediately if the
767 * original client is gone. (ZML: This seems like a bug. It should give
768 * some time for the client to reconnect!)
770 status = smb2_create(tree1, mem_ctx, &io1);
771 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
772 h1 = io1.out.file.handle;
775 smb2_util_close(tree2, h2);
776 smb2_util_unlink(tree2, fname);
777 smb2_util_close(tree1, h1);
778 smb2_util_unlink(tree1, fname);
786 struct torture_suite *torture_smb2_durable_open_init(void)
788 struct torture_suite *suite =
789 torture_suite_create(talloc_autofree_context(), "durable-open");
791 torture_suite_add_1smb2_test(suite, "open1", test_durable_open_open1);
792 torture_suite_add_1smb2_test(suite, "open2", test_durable_open_open2);
793 torture_suite_add_2smb2_test(suite, "file-position",
794 test_durable_open_file_position);
795 torture_suite_add_2smb2_test(suite, "oplock", test_durable_open_oplock);
796 torture_suite_add_2smb2_test(suite, "lease", test_durable_open_lease);
797 torture_suite_add_1smb2_test(suite, "lock", test_durable_open_lock);
798 torture_suite_add_2smb2_test(suite, "open", test_durable_open_open);
800 suite->description = talloc_strdup(suite, "SMB2-DURABLE-OPEN tests");