2 Unix SMB/CIFS implementation.
4 test suite for SMB2 durable opens
6 Copyright (C) Stefan Metzmacher 2008
7 Copyright (C) Michael Adam 2011-2012
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 #include "libcli/smb2/smb2.h"
25 #include "libcli/smb2/smb2_calls.h"
26 #include "../libcli/smb/smbXcli_base.h"
27 #include "torture/torture.h"
28 #include "torture/smb2/proto.h"
29 #include "../libcli/smb/smbXcli_base.h"
31 #define CHECK_VAL(v, correct) do { \
32 if ((v) != (correct)) { \
33 torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%x - should be 0x%x\n", \
34 __location__, #v, (int)v, (int)correct); \
38 #define CHECK_STATUS(status, correct) do { \
39 if (!NT_STATUS_EQUAL(status, correct)) { \
40 torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \
41 nt_errstr(status), nt_errstr(correct)); \
46 #define CHECK_CREATED(__io, __created, __attribute) \
48 CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
49 CHECK_VAL((__io)->out.alloc_size, 0); \
50 CHECK_VAL((__io)->out.size, 0); \
51 CHECK_VAL((__io)->out.file_attr, (__attribute)); \
52 CHECK_VAL((__io)->out.reserved2, 0); \
57 * basic durable_open test.
58 * durable state should only be granted when requested
59 * along with a batch oplock or a handle lease.
61 * This test tests durable open with all possible oplock types.
64 struct durable_open_vs_oplock
{
66 const char *share_mode
;
70 #define NUM_OPLOCK_TYPES 4
71 #define NUM_SHARE_MODES 8
72 #define NUM_OPLOCK_OPEN_TESTS ( NUM_OPLOCK_TYPES * NUM_SHARE_MODES )
73 static struct durable_open_vs_oplock durable_open_vs_oplock_table
[NUM_OPLOCK_OPEN_TESTS
] =
91 { "s", "RWD", false },
100 { "x", "RWD", false },
109 { "b", "RWD", true },
112 static bool test_one_durable_open_open_oplock(struct torture_context
*tctx
,
113 struct smb2_tree
*tree
,
115 struct durable_open_vs_oplock test
)
118 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
119 struct smb2_handle _h
;
120 struct smb2_handle
*h
= NULL
;
122 struct smb2_create io
;
124 smb2_util_unlink(tree
, fname
);
126 smb2_oplock_create_share(&io
, fname
,
127 smb2_util_share_access(test
.share_mode
),
128 smb2_util_oplock_level(test
.level
));
129 io
.in
.durable_open
= true;
131 status
= smb2_create(tree
, mem_ctx
, &io
);
132 CHECK_STATUS(status
, NT_STATUS_OK
);
133 _h
= io
.out
.file
.handle
;
135 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
136 CHECK_VAL(io
.out
.durable_open
, test
.expected
);
137 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level(test
.level
));
141 smb2_util_close(tree
, *h
);
143 smb2_util_unlink(tree
, fname
);
144 talloc_free(mem_ctx
);
149 bool test_durable_open_open_oplock(struct torture_context
*tctx
,
150 struct smb2_tree
*tree
)
152 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
157 /* Choose a random name in case the state is left a little funky. */
158 snprintf(fname
, 256, "durable_open_open_oplock_%s.dat", generate_random_str(tctx
, 8));
160 smb2_util_unlink(tree
, fname
);
162 /* test various oplock levels with durable open */
164 for (i
= 0; i
< NUM_OPLOCK_OPEN_TESTS
; i
++) {
165 ret
= test_one_durable_open_open_oplock(tctx
,
168 durable_open_vs_oplock_table
[i
]);
175 smb2_util_unlink(tree
, fname
);
177 talloc_free(mem_ctx
);
183 * basic durable_open test.
184 * durable state should only be granted when requested
185 * along with a batch oplock or a handle lease.
187 * This test tests durable open with all valid lease types.
190 struct durable_open_vs_lease
{
192 const char *share_mode
;
196 #define NUM_LEASE_TYPES 5
197 #define NUM_LEASE_OPEN_TESTS ( NUM_LEASE_TYPES * NUM_SHARE_MODES )
198 static struct durable_open_vs_lease durable_open_vs_lease_table
[NUM_LEASE_OPEN_TESTS
] =
207 { "", "RWD", false },
213 { "R", "RW", false },
214 { "R", "RD", false },
215 { "R", "DW", false },
216 { "R", "RWD", false },
219 { "RW", "R", false },
220 { "RW", "W", false },
221 { "RW", "D", false },
222 { "RW", "RW", false },
223 { "RW", "RD", false },
224 { "RW", "WD", false },
225 { "RW", "RWD", false },
231 { "RH", "RW", true },
232 { "RH", "RD", true },
233 { "RH", "WD", true },
234 { "RH", "RWD", true },
237 { "RHW", "R", true },
238 { "RHW", "W", true },
239 { "RHW", "D", true },
240 { "RHW", "RW", true },
241 { "RHW", "RD", true },
242 { "RHW", "WD", true },
243 { "RHW", "RWD", true },
246 static bool test_one_durable_open_open_lease(struct torture_context
*tctx
,
247 struct smb2_tree
*tree
,
249 struct durable_open_vs_lease test
)
252 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
253 struct smb2_handle _h
;
254 struct smb2_handle
*h
= NULL
;
256 struct smb2_create io
;
257 struct smb2_lease ls
;
261 caps
= smb2cli_conn_server_capabilities(tree
->session
->transport
->conn
);
262 if (!(caps
& SMB2_CAP_LEASING
)) {
263 torture_skip(tctx
, "leases are not supported");
266 smb2_util_unlink(tree
, fname
);
270 smb2_lease_create_share(&io
, &ls
, false /* dir */, fname
,
271 smb2_util_share_access(test
.share_mode
),
273 smb2_util_lease_state(test
.type
));
274 io
.in
.durable_open
= true;
276 status
= smb2_create(tree
, mem_ctx
, &io
);
277 CHECK_STATUS(status
, NT_STATUS_OK
);
278 _h
= io
.out
.file
.handle
;
280 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
281 CHECK_VAL(io
.out
.durable_open
, test
.expected
);
282 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
283 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[0], lease
);
284 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[1], ~lease
);
285 CHECK_VAL(io
.out
.lease_response
.lease_state
,
286 smb2_util_lease_state(test
.type
));
289 smb2_util_close(tree
, *h
);
291 smb2_util_unlink(tree
, fname
);
292 talloc_free(mem_ctx
);
297 bool test_durable_open_open_lease(struct torture_context
*tctx
,
298 struct smb2_tree
*tree
)
300 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
306 caps
= smb2cli_conn_server_capabilities(tree
->session
->transport
->conn
);
307 if (!(caps
& SMB2_CAP_LEASING
)) {
308 torture_skip(tctx
, "leases are not supported");
311 /* Choose a random name in case the state is left a little funky. */
312 snprintf(fname
, 256, "durable_open_open_lease_%s.dat", generate_random_str(tctx
, 8));
314 smb2_util_unlink(tree
, fname
);
317 /* test various oplock levels with durable open */
319 for (i
= 0; i
< NUM_LEASE_OPEN_TESTS
; i
++) {
320 ret
= test_one_durable_open_open_lease(tctx
,
323 durable_open_vs_lease_table
[i
]);
330 smb2_util_unlink(tree
, fname
);
332 talloc_free(mem_ctx
);
338 * basic test for doing a durable open
339 * and do a durable reopen on the same connection
340 * while the first open is still active (fails)
342 bool test_durable_open_reopen1(struct torture_context
*tctx
,
343 struct smb2_tree
*tree
)
346 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
348 struct smb2_handle _h
;
349 struct smb2_handle
*h
= NULL
;
350 struct smb2_create io1
, io2
;
353 /* Choose a random name in case the state is left a little funky. */
354 snprintf(fname
, 256, "durable_open_reopen1_%s.dat",
355 generate_random_str(tctx
, 8));
357 smb2_util_unlink(tree
, fname
);
359 smb2_oplock_create_share(&io1
, fname
,
360 smb2_util_share_access(""),
361 smb2_util_oplock_level("b"));
362 io1
.in
.durable_open
= true;
364 status
= smb2_create(tree
, mem_ctx
, &io1
);
365 CHECK_STATUS(status
, NT_STATUS_OK
);
366 _h
= io1
.out
.file
.handle
;
368 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
369 CHECK_VAL(io1
.out
.durable_open
, true);
370 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
372 /* try a durable reconnect while the file is still open */
374 io2
.in
.fname
= fname
;
375 io2
.in
.durable_handle
= h
;
377 status
= smb2_create(tree
, mem_ctx
, &io2
);
378 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
382 smb2_util_close(tree
, *h
);
385 smb2_util_unlink(tree
, fname
);
389 talloc_free(mem_ctx
);
395 * basic test for doing a durable open
396 * tcp disconnect, reconnect, do a durable reopen (succeeds)
398 bool test_durable_open_reopen2(struct torture_context
*tctx
,
399 struct smb2_tree
*tree
)
402 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
404 struct smb2_handle _h
;
405 struct smb2_handle
*h
= NULL
;
406 struct smb2_create io1
, io2
;
409 /* Choose a random name in case the state is left a little funky. */
410 snprintf(fname
, 256, "durable_open_reopen2_%s.dat",
411 generate_random_str(tctx
, 8));
413 smb2_util_unlink(tree
, fname
);
415 smb2_oplock_create_share(&io1
, fname
,
416 smb2_util_share_access(""),
417 smb2_util_oplock_level("b"));
418 io1
.in
.durable_open
= true;
420 status
= smb2_create(tree
, mem_ctx
, &io1
);
421 CHECK_STATUS(status
, NT_STATUS_OK
);
422 _h
= io1
.out
.file
.handle
;
424 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
425 CHECK_VAL(io1
.out
.durable_open
, true);
426 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
428 /* disconnect, reconnect and then do durable reopen */
432 if (!torture_smb2_connection(tctx
, &tree
)) {
433 torture_warning(tctx
, "couldn't reconnect, bailing\n");
439 io2
.in
.fname
= fname
;
440 io2
.in
.durable_handle
= h
;
443 status
= smb2_create(tree
, mem_ctx
, &io2
);
444 CHECK_STATUS(status
, NT_STATUS_OK
);
445 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
446 CHECK_VAL(io2
.out
.durable_open
, true);
447 CHECK_VAL(io2
.out
.oplock_level
, smb2_util_oplock_level("b"));
448 _h
= io2
.out
.file
.handle
;
453 smb2_util_close(tree
, *h
);
456 smb2_util_unlink(tree
, fname
);
460 talloc_free(mem_ctx
);
466 * basic test for doing a durable open
467 * tcp disconnect, reconnect with a session reconnect and
468 * do a durable reopen (succeeds)
470 bool test_durable_open_reopen2a(struct torture_context
*tctx
,
471 struct smb2_tree
*tree
)
474 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
476 struct smb2_handle _h
;
477 struct smb2_handle
*h
= NULL
;
478 struct smb2_create io1
, io2
;
479 uint64_t previous_session_id
;
482 /* Choose a random name in case the state is left a little funky. */
483 snprintf(fname
, 256, "durable_open_reopen2_%s.dat",
484 generate_random_str(tctx
, 8));
486 smb2_util_unlink(tree
, fname
);
488 smb2_oplock_create_share(&io1
, fname
,
489 smb2_util_share_access(""),
490 smb2_util_oplock_level("b"));
491 io1
.in
.durable_open
= true;
493 status
= smb2_create(tree
, mem_ctx
, &io1
);
494 CHECK_STATUS(status
, NT_STATUS_OK
);
495 _h
= io1
.out
.file
.handle
;
497 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
498 CHECK_VAL(io1
.out
.durable_open
, true);
499 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
501 /* disconnect, reconnect and then do durable reopen */
502 previous_session_id
= smb2cli_session_current_id(tree
->session
->smbXcli
);
506 if (!torture_smb2_connection_ext(tctx
, previous_session_id
, &tree
)) {
507 torture_warning(tctx
, "couldn't reconnect, bailing\n");
513 io2
.in
.fname
= fname
;
514 io2
.in
.durable_handle
= h
;
517 status
= smb2_create(tree
, mem_ctx
, &io2
);
518 CHECK_STATUS(status
, NT_STATUS_OK
);
519 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
520 CHECK_VAL(io2
.out
.durable_open
, true);
521 CHECK_VAL(io2
.out
.oplock_level
, smb2_util_oplock_level("b"));
522 _h
= io2
.out
.file
.handle
;
527 smb2_util_close(tree
, *h
);
530 smb2_util_unlink(tree
, fname
);
534 talloc_free(mem_ctx
);
541 * basic test for doing a durable open:
542 * tdis, new tcon, try durable reopen (fails)
544 bool test_durable_open_reopen3(struct torture_context
*tctx
,
545 struct smb2_tree
*tree
)
548 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
550 struct smb2_handle _h
;
551 struct smb2_handle
*h
= NULL
;
552 struct smb2_create io1
, io2
;
554 struct smb2_tree
*tree2
;
556 /* Choose a random name in case the state is left a little funky. */
557 snprintf(fname
, 256, "durable_open_reopen3_%s.dat",
558 generate_random_str(tctx
, 8));
560 smb2_util_unlink(tree
, fname
);
562 smb2_oplock_create_share(&io1
, fname
,
563 smb2_util_share_access(""),
564 smb2_util_oplock_level("b"));
565 io1
.in
.durable_open
= true;
567 status
= smb2_create(tree
, mem_ctx
, &io1
);
568 CHECK_STATUS(status
, NT_STATUS_OK
);
569 _h
= io1
.out
.file
.handle
;
571 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
572 CHECK_VAL(io1
.out
.durable_open
, true);
573 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
575 /* disconnect, reconnect and then do durable reopen */
576 status
= smb2_tdis(tree
);
577 CHECK_STATUS(status
, NT_STATUS_OK
);
579 if (!torture_smb2_tree_connect(tctx
, tree
->session
, mem_ctx
, &tree2
)) {
580 torture_warning(tctx
, "couldn't reconnect to share, bailing\n");
587 io2
.in
.fname
= fname
;
588 io2
.in
.durable_handle
= h
;
590 status
= smb2_create(tree2
, mem_ctx
, &io2
);
591 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
595 smb2_util_close(tree
, *h
);
598 smb2_util_unlink(tree2
, fname
);
602 talloc_free(mem_ctx
);
608 * basic test for doing a durable open:
609 * logoff, create a new session, do a durable reopen (succeeds)
611 bool test_durable_open_reopen4(struct torture_context
*tctx
,
612 struct smb2_tree
*tree
)
615 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
617 struct smb2_handle _h
;
618 struct smb2_handle
*h
= NULL
;
619 struct smb2_create io1
, io2
;
621 struct smb2_transport
*transport
;
622 struct smb2_session
*session2
;
623 struct smb2_tree
*tree2
;
625 /* Choose a random name in case the state is left a little funky. */
626 snprintf(fname
, 256, "durable_open_reopen4_%s.dat",
627 generate_random_str(tctx
, 8));
629 smb2_util_unlink(tree
, fname
);
631 smb2_oplock_create_share(&io1
, fname
,
632 smb2_util_share_access(""),
633 smb2_util_oplock_level("b"));
634 io1
.in
.durable_open
= true;
635 io1
.in
.create_options
|= NTCREATEX_OPTIONS_DELETE_ON_CLOSE
;
637 status
= smb2_create(tree
, mem_ctx
, &io1
);
638 CHECK_STATUS(status
, NT_STATUS_OK
);
639 _h
= io1
.out
.file
.handle
;
641 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
642 CHECK_VAL(io1
.out
.durable_open
, true);
643 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
646 * do a session logoff, establish a new session and tree
647 * connect on the same transport, and try a durable reopen
649 transport
= tree
->session
->transport
;
650 status
= smb2_logoff(tree
->session
);
651 CHECK_STATUS(status
, NT_STATUS_OK
);
653 if (!torture_smb2_session_setup(tctx
, transport
,
654 0, /* previous_session_id */
657 torture_warning(tctx
, "session setup failed.\n");
663 * the session setup has talloc-stolen the transport,
664 * so we can safely free the old tree+session for clarity
668 if (!torture_smb2_tree_connect(tctx
, session2
, mem_ctx
, &tree2
)) {
669 torture_warning(tctx
, "tree connect failed.\n");
675 io2
.in
.fname
= fname
;
676 io2
.in
.durable_handle
= h
;
679 status
= smb2_create(tree2
, mem_ctx
, &io2
);
680 CHECK_STATUS(status
, NT_STATUS_OK
);
682 _h
= io2
.out
.file
.handle
;
684 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
685 CHECK_VAL(io2
.out
.durable_open
, true);
686 CHECK_VAL(io2
.out
.oplock_level
, smb2_util_oplock_level("b"));
690 smb2_util_close(tree2
, *h
);
693 smb2_util_unlink(tree2
, fname
);
697 talloc_free(mem_ctx
);
703 basic testing of SMB2 durable opens
704 regarding the position information on the handle
706 bool test_durable_open_file_position(struct torture_context
*tctx
,
707 struct smb2_tree
*tree1
,
708 struct smb2_tree
*tree2
)
710 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
711 struct smb2_handle h1
, h2
;
712 struct smb2_create io1
, io2
;
714 const char *fname
= "durable_open_position.dat";
715 union smb_fileinfo qfinfo
;
716 union smb_setfileinfo sfinfo
;
720 smb2_util_unlink(tree1
, fname
);
722 smb2_oplock_create(&io1
, fname
, SMB2_OPLOCK_LEVEL_BATCH
);
723 io1
.in
.durable_open
= true;
725 status
= smb2_create(tree1
, mem_ctx
, &io1
);
726 CHECK_STATUS(status
, NT_STATUS_OK
);
727 h1
= io1
.out
.file
.handle
;
728 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
729 CHECK_VAL(io1
.out
.durable_open
, true);
730 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
732 /* TODO: check extra blob content */
735 qfinfo
.generic
.level
= RAW_FILEINFO_POSITION_INFORMATION
;
736 qfinfo
.generic
.in
.file
.handle
= h1
;
737 status
= smb2_getinfo_file(tree1
, mem_ctx
, &qfinfo
);
738 CHECK_STATUS(status
, NT_STATUS_OK
);
739 CHECK_VAL(qfinfo
.position_information
.out
.position
, 0);
740 pos
= qfinfo
.position_information
.out
.position
;
741 torture_comment(tctx
, "position: %llu\n",
742 (unsigned long long)pos
);
745 sfinfo
.generic
.level
= RAW_SFILEINFO_POSITION_INFORMATION
;
746 sfinfo
.generic
.in
.file
.handle
= h1
;
747 sfinfo
.position_information
.in
.position
= 0x1000;
748 status
= smb2_setinfo_file(tree1
, &sfinfo
);
749 CHECK_STATUS(status
, NT_STATUS_OK
);
752 qfinfo
.generic
.level
= RAW_FILEINFO_POSITION_INFORMATION
;
753 qfinfo
.generic
.in
.file
.handle
= h1
;
754 status
= smb2_getinfo_file(tree1
, mem_ctx
, &qfinfo
);
755 CHECK_STATUS(status
, NT_STATUS_OK
);
756 CHECK_VAL(qfinfo
.position_information
.out
.position
, 0x1000);
757 pos
= qfinfo
.position_information
.out
.position
;
758 torture_comment(tctx
, "position: %llu\n",
759 (unsigned long long)pos
);
765 qfinfo
.generic
.level
= RAW_FILEINFO_POSITION_INFORMATION
;
766 qfinfo
.generic
.in
.file
.handle
= h1
;
767 status
= smb2_getinfo_file(tree2
, mem_ctx
, &qfinfo
);
768 CHECK_STATUS(status
, NT_STATUS_FILE_CLOSED
);
771 io2
.in
.fname
= fname
;
772 io2
.in
.durable_handle
= &h1
;
774 status
= smb2_create(tree2
, mem_ctx
, &io2
);
775 CHECK_STATUS(status
, NT_STATUS_OK
);
776 CHECK_VAL(io2
.out
.durable_open
, true);
777 CHECK_VAL(io2
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
778 CHECK_VAL(io2
.out
.reserved
, 0x00);
779 CHECK_VAL(io2
.out
.create_action
, NTCREATEX_ACTION_EXISTED
);
780 CHECK_VAL(io2
.out
.alloc_size
, 0);
781 CHECK_VAL(io2
.out
.size
, 0);
782 CHECK_VAL(io2
.out
.file_attr
, FILE_ATTRIBUTE_ARCHIVE
);
783 CHECK_VAL(io2
.out
.reserved2
, 0);
785 h2
= io2
.out
.file
.handle
;
788 qfinfo
.generic
.level
= RAW_FILEINFO_POSITION_INFORMATION
;
789 qfinfo
.generic
.in
.file
.handle
= h2
;
790 status
= smb2_getinfo_file(tree2
, mem_ctx
, &qfinfo
);
791 CHECK_STATUS(status
, NT_STATUS_OK
);
792 CHECK_VAL(qfinfo
.position_information
.out
.position
, 0x1000);
793 pos
= qfinfo
.position_information
.out
.position
;
794 torture_comment(tctx
, "position: %llu\n",
795 (unsigned long long)pos
);
797 smb2_util_close(tree2
, h2
);
799 talloc_free(mem_ctx
);
801 smb2_util_unlink(tree2
, fname
);
810 Open, disconnect, oplock break, reconnect.
812 bool test_durable_open_oplock(struct torture_context
*tctx
,
813 struct smb2_tree
*tree1
,
814 struct smb2_tree
*tree2
)
816 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
817 struct smb2_create io1
, io2
;
818 struct smb2_handle h1
, h2
;
823 /* Choose a random name in case the state is left a little funky. */
824 snprintf(fname
, 256, "durable_open_oplock_%s.dat", generate_random_str(tctx
, 8));
827 smb2_util_unlink(tree1
, fname
);
829 /* Create with batch oplock */
830 smb2_oplock_create(&io1
, fname
, SMB2_OPLOCK_LEVEL_BATCH
);
831 io1
.in
.durable_open
= true;
834 io2
.in
.create_disposition
= NTCREATEX_DISP_OPEN
;
836 status
= smb2_create(tree1
, mem_ctx
, &io1
);
837 CHECK_STATUS(status
, NT_STATUS_OK
);
838 h1
= io1
.out
.file
.handle
;
839 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
840 CHECK_VAL(io1
.out
.durable_open
, true);
841 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
843 /* Disconnect after getting the batch */
848 * Windows7 (build 7000) will break a batch oplock immediately if the
849 * original client is gone. (ZML: This seems like a bug. It should give
850 * some time for the client to reconnect!)
852 status
= smb2_create(tree2
, mem_ctx
, &io2
);
853 CHECK_STATUS(status
, NT_STATUS_OK
);
854 h2
= io2
.out
.file
.handle
;
855 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
856 CHECK_VAL(io2
.out
.durable_open
, true);
857 CHECK_VAL(io2
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
859 /* What if tree1 tries to come back and reclaim? */
860 if (!torture_smb2_connection(tctx
, &tree1
)) {
861 torture_warning(tctx
, "couldn't reconnect, bailing\n");
867 io1
.in
.fname
= fname
;
868 io1
.in
.durable_handle
= &h1
;
870 status
= smb2_create(tree1
, mem_ctx
, &io1
);
871 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
874 smb2_util_close(tree2
, h2
);
875 smb2_util_unlink(tree2
, fname
);
884 Open, disconnect, lease break, reconnect.
886 bool test_durable_open_lease(struct torture_context
*tctx
,
887 struct smb2_tree
*tree1
,
888 struct smb2_tree
*tree2
)
890 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
891 struct smb2_create io1
, io2
;
892 struct smb2_lease ls1
, ls2
;
893 struct smb2_handle h1
, h2
;
897 uint64_t lease1
, lease2
;
900 caps
= smb2cli_conn_server_capabilities(tree1
->session
->transport
->conn
);
901 if (!(caps
& SMB2_CAP_LEASING
)) {
902 torture_skip(tctx
, "leases are not supported");
906 * Choose a random name and random lease in case the state is left a
911 snprintf(fname
, 256, "durable_open_lease_%s.dat", generate_random_str(tctx
, 8));
914 smb2_util_unlink(tree1
, fname
);
916 /* Create with lease */
917 smb2_lease_create(&io1
, &ls1
, false /* dir */, fname
,
918 lease1
, smb2_util_lease_state("RHW"));
919 io1
.in
.durable_open
= true;
921 smb2_lease_create(&io2
, &ls2
, false /* dir */, fname
,
922 lease2
, smb2_util_lease_state("RHW"));
923 io2
.in
.durable_open
= true;
924 io2
.in
.create_disposition
= NTCREATEX_DISP_OPEN
;
926 status
= smb2_create(tree1
, mem_ctx
, &io1
);
927 CHECK_STATUS(status
, NT_STATUS_OK
);
928 h1
= io1
.out
.file
.handle
;
929 CHECK_VAL(io1
.out
.durable_open
, true);
930 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
932 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
933 CHECK_VAL(io1
.out
.lease_response
.lease_key
.data
[0], lease1
);
934 CHECK_VAL(io1
.out
.lease_response
.lease_key
.data
[1], ~lease1
);
935 CHECK_VAL(io1
.out
.lease_response
.lease_state
,
936 SMB2_LEASE_READ
|SMB2_LEASE_HANDLE
|SMB2_LEASE_WRITE
);
938 /* Disconnect after getting the lease */
943 * Windows7 (build 7000) will grant an RH lease immediate (not an RHW?)
944 * even if the original client is gone. (ZML: This seems like a bug. It
945 * should give some time for the client to reconnect! And why RH?)
947 * obnox: Current windows 7 and w2k8r2 grant RHW instead of RH.
948 * Test is adapted accordingly.
950 status
= smb2_create(tree2
, mem_ctx
, &io2
);
951 CHECK_STATUS(status
, NT_STATUS_OK
);
952 h2
= io2
.out
.file
.handle
;
953 CHECK_VAL(io2
.out
.durable_open
, true);
954 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
956 CHECK_VAL(io2
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
957 CHECK_VAL(io2
.out
.lease_response
.lease_key
.data
[0], lease2
);
958 CHECK_VAL(io2
.out
.lease_response
.lease_key
.data
[1], ~lease2
);
959 CHECK_VAL(io2
.out
.lease_response
.lease_state
,
960 SMB2_LEASE_READ
|SMB2_LEASE_HANDLE
|SMB2_LEASE_WRITE
);
962 /* What if tree1 tries to come back and reclaim? */
963 if (!torture_smb2_connection(tctx
, &tree1
)) {
964 torture_warning(tctx
, "couldn't reconnect, bailing\n");
970 io1
.in
.fname
= fname
;
971 io1
.in
.durable_handle
= &h1
;
972 io1
.in
.lease_request
= &ls1
;
974 status
= smb2_create(tree1
, mem_ctx
, &io1
);
975 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
978 smb2_util_close(tree2
, h2
);
979 smb2_util_unlink(tree2
, fname
);
988 Open, take BRL, disconnect, reconnect.
990 bool test_durable_open_lock(struct torture_context
*tctx
,
991 struct smb2_tree
*tree
)
993 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
994 struct smb2_create io
;
995 struct smb2_lease ls
;
996 struct smb2_handle h
;
997 struct smb2_lock lck
;
998 struct smb2_lock_element el
[2];
1005 caps
= smb2cli_conn_server_capabilities(tree
->session
->transport
->conn
);
1006 if (!(caps
& SMB2_CAP_LEASING
)) {
1007 torture_skip(tctx
, "leases are not supported");
1011 * Choose a random name and random lease in case the state is left a
1015 snprintf(fname
, 256, "durable_open_lock_%s.dat", generate_random_str(tctx
, 8));
1018 smb2_util_unlink(tree
, fname
);
1020 /* Create with lease */
1022 smb2_lease_create(&io
, &ls
, false /* dir */, fname
, lease
,
1023 smb2_util_lease_state("RWH"));
1024 io
.in
.durable_open
= true;
1026 status
= smb2_create(tree
, mem_ctx
, &io
);
1027 CHECK_STATUS(status
, NT_STATUS_OK
);
1028 h
= io
.out
.file
.handle
;
1029 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1031 CHECK_VAL(io
.out
.durable_open
, true);
1032 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
1033 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[0], lease
);
1034 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[1], ~lease
);
1035 CHECK_VAL(io
.out
.lease_response
.lease_state
,
1036 SMB2_LEASE_READ
|SMB2_LEASE_HANDLE
|SMB2_LEASE_WRITE
);
1041 lck
.in
.lock_count
= 0x0001;
1042 lck
.in
.lock_sequence
= 0x00000000;
1043 lck
.in
.file
.handle
= h
;
1046 el
[0].reserved
= 0x00000000;
1047 el
[0].flags
= SMB2_LOCK_FLAG_EXCLUSIVE
;
1048 status
= smb2_lock(tree
, &lck
);
1049 CHECK_STATUS(status
, NT_STATUS_OK
);
1051 /* Disconnect/Reconnect. */
1055 if (!torture_smb2_connection(tctx
, &tree
)) {
1056 torture_warning(tctx
, "couldn't reconnect, bailing\n");
1062 io
.in
.fname
= fname
;
1063 io
.in
.durable_handle
= &h
;
1064 io
.in
.lease_request
= &ls
;
1066 status
= smb2_create(tree
, mem_ctx
, &io
);
1067 CHECK_STATUS(status
, NT_STATUS_OK
);
1068 h
= io
.out
.file
.handle
;
1070 lck
.in
.file
.handle
= h
;
1071 el
[0].flags
= SMB2_LOCK_FLAG_UNLOCK
;
1072 status
= smb2_lock(tree
, &lck
);
1073 CHECK_STATUS(status
, NT_STATUS_OK
);
1076 smb2_util_close(tree
, h
);
1077 smb2_util_unlink(tree
, fname
);
1084 * Open with a RH lease, disconnect, open in another tree, reconnect.
1086 * This test actually demonstrates a minimum level of respect for the durable
1087 * open in the face of another open. As long as this test shows an inability to
1088 * reconnect after an open, the oplock/lease tests above will certainly
1089 * demonstrate an error on reconnect.
1091 bool test_durable_open_open2_lease(struct torture_context
*tctx
,
1092 struct smb2_tree
*tree1
,
1093 struct smb2_tree
*tree2
)
1095 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
1096 struct smb2_create io1
, io2
;
1097 struct smb2_lease ls
;
1098 struct smb2_handle h1
, h2
;
1105 caps
= smb2cli_conn_server_capabilities(tree1
->session
->transport
->conn
);
1106 if (!(caps
& SMB2_CAP_LEASING
)) {
1107 torture_skip(tctx
, "leases are not supported");
1111 * Choose a random name and random lease in case the state is left a
1115 snprintf(fname
, 256, "durable_open_open2_lease_%s.dat",
1116 generate_random_str(tctx
, 8));
1119 smb2_util_unlink(tree1
, fname
);
1121 /* Create with lease */
1122 smb2_lease_create_share(&io1
, &ls
, false /* dir */, fname
,
1123 smb2_util_share_access(""),
1125 smb2_util_lease_state("RH"));
1126 io1
.in
.durable_open
= true;
1128 status
= smb2_create(tree1
, mem_ctx
, &io1
);
1129 CHECK_STATUS(status
, NT_STATUS_OK
);
1130 h1
= io1
.out
.file
.handle
;
1131 CHECK_VAL(io1
.out
.durable_open
, true);
1132 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1134 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
1135 CHECK_VAL(io1
.out
.lease_response
.lease_key
.data
[0], lease
);
1136 CHECK_VAL(io1
.out
.lease_response
.lease_key
.data
[1], ~lease
);
1137 CHECK_VAL(io1
.out
.lease_response
.lease_state
,
1138 smb2_util_lease_state("RH"));
1144 /* Open the file in tree2 */
1145 smb2_oplock_create(&io2
, fname
, SMB2_OPLOCK_LEVEL_NONE
);
1147 status
= smb2_create(tree2
, mem_ctx
, &io2
);
1148 CHECK_STATUS(status
, NT_STATUS_OK
);
1149 h2
= io2
.out
.file
.handle
;
1150 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1153 if (!torture_smb2_connection(tctx
, &tree1
)) {
1154 torture_warning(tctx
, "couldn't reconnect, bailing\n");
1160 io1
.in
.fname
= fname
;
1161 io1
.in
.durable_handle
= &h1
;
1162 io1
.in
.lease_request
= &ls
;
1165 * Windows7 (build 7000) will give away an open immediately if the
1166 * original client is gone. (ZML: This seems like a bug. It should give
1167 * some time for the client to reconnect!)
1169 status
= smb2_create(tree1
, mem_ctx
, &io1
);
1170 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
1171 h1
= io1
.out
.file
.handle
;
1174 smb2_util_close(tree2
, h2
);
1175 smb2_util_unlink(tree2
, fname
);
1176 smb2_util_close(tree1
, h1
);
1177 smb2_util_unlink(tree1
, fname
);
1186 * Open with a batch oplock, disconnect, open in another tree, reconnect.
1188 * This test actually demonstrates a minimum level of respect for the durable
1189 * open in the face of another open. As long as this test shows an inability to
1190 * reconnect after an open, the oplock/lease tests above will certainly
1191 * demonstrate an error on reconnect.
1193 bool test_durable_open_open2_oplock(struct torture_context
*tctx
,
1194 struct smb2_tree
*tree1
,
1195 struct smb2_tree
*tree2
)
1197 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
1198 struct smb2_create io1
, io2
;
1199 struct smb2_handle h1
, h2
;
1205 * Choose a random name and random lease in case the state is left a
1208 snprintf(fname
, 256, "durable_open_open2_oplock_%s.dat",
1209 generate_random_str(tctx
, 8));
1212 smb2_util_unlink(tree1
, fname
);
1214 /* Create with batch oplock */
1215 smb2_oplock_create(&io1
, fname
, SMB2_OPLOCK_LEVEL_BATCH
);
1216 io1
.in
.durable_open
= true;
1218 status
= smb2_create(tree1
, mem_ctx
, &io1
);
1219 CHECK_STATUS(status
, NT_STATUS_OK
);
1220 h1
= io1
.out
.file
.handle
;
1221 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1222 CHECK_VAL(io1
.out
.durable_open
, true);
1223 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
1229 /* Open the file in tree2 */
1230 smb2_oplock_create(&io2
, fname
, SMB2_OPLOCK_LEVEL_NONE
);
1232 status
= smb2_create(tree2
, mem_ctx
, &io2
);
1233 CHECK_STATUS(status
, NT_STATUS_OK
);
1234 h2
= io2
.out
.file
.handle
;
1235 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1238 if (!torture_smb2_connection(tctx
, &tree1
)) {
1239 torture_warning(tctx
, "couldn't reconnect, bailing\n");
1245 io1
.in
.fname
= fname
;
1246 io1
.in
.durable_handle
= &h1
;
1248 status
= smb2_create(tree1
, mem_ctx
, &io1
);
1249 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
1250 h1
= io1
.out
.file
.handle
;
1253 smb2_util_close(tree2
, h2
);
1254 smb2_util_unlink(tree2
, fname
);
1255 smb2_util_close(tree1
, h1
);
1256 smb2_util_unlink(tree1
, fname
);
1264 struct torture_suite
*torture_smb2_durable_open_init(void)
1266 struct torture_suite
*suite
=
1267 torture_suite_create(talloc_autofree_context(), "durable-open");
1269 torture_suite_add_1smb2_test(suite
, "open-oplock", test_durable_open_open_oplock
);
1270 torture_suite_add_1smb2_test(suite
, "open-lease", test_durable_open_open_lease
);
1271 torture_suite_add_1smb2_test(suite
, "reopen1", test_durable_open_reopen1
);
1272 torture_suite_add_1smb2_test(suite
, "reopen2", test_durable_open_reopen2
);
1273 torture_suite_add_1smb2_test(suite
, "reopen2a", test_durable_open_reopen2a
);
1274 torture_suite_add_1smb2_test(suite
, "reopen3", test_durable_open_reopen3
);
1275 torture_suite_add_1smb2_test(suite
, "reopen4", test_durable_open_reopen4
);
1276 torture_suite_add_2smb2_test(suite
, "file-position",
1277 test_durable_open_file_position
);
1278 torture_suite_add_2smb2_test(suite
, "oplock", test_durable_open_oplock
);
1279 torture_suite_add_2smb2_test(suite
, "lease", test_durable_open_lease
);
1280 torture_suite_add_1smb2_test(suite
, "lock", test_durable_open_lock
);
1281 torture_suite_add_2smb2_test(suite
, "open2-lease",
1282 test_durable_open_open2_lease
);
1283 torture_suite_add_2smb2_test(suite
, "open2-oplock",
1284 test_durable_open_open2_oplock
);
1286 suite
->description
= talloc_strdup(suite
, "SMB2-DURABLE-OPEN tests");