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 "../libcli/smb/smbXcli_base.h"
26 #include "torture/torture.h"
27 #include "torture/smb2/proto.h"
28 #include "../libcli/smb/smbXcli_base.h"
30 #define CHECK_VAL(v, correct) do { \
31 if ((v) != (correct)) { \
32 torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%x - should be 0x%x\n", \
33 __location__, #v, (int)v, (int)correct); \
37 #define CHECK_STATUS(status, correct) do { \
38 if (!NT_STATUS_EQUAL(status, correct)) { \
39 torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \
40 nt_errstr(status), nt_errstr(correct)); \
45 #define CHECK_CREATED(__io, __created, __attribute) \
47 CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
48 CHECK_VAL((__io)->out.alloc_size, 0); \
49 CHECK_VAL((__io)->out.size, 0); \
50 CHECK_VAL((__io)->out.file_attr, (__attribute)); \
51 CHECK_VAL((__io)->out.reserved2, 0); \
56 * basic durable_open test.
57 * durable state should only be granted when requested
58 * along with a batch oplock or a handle lease.
60 * This test tests durable open with all possible oplock types.
63 struct durable_open_vs_oplock
{
65 const char *share_mode
;
69 #define NUM_OPLOCK_TYPES 4
70 #define NUM_SHARE_MODES 8
71 #define NUM_OPLOCK_OPEN_TESTS ( NUM_OPLOCK_TYPES * NUM_SHARE_MODES )
72 struct durable_open_vs_oplock durable_open_vs_oplock_table
[NUM_OPLOCK_OPEN_TESTS
] =
90 { "s", "RWD", false },
99 { "x", "RWD", false },
108 { "b", "RWD", true },
111 static bool test_one_durable_open_open1(struct torture_context
*tctx
,
112 struct smb2_tree
*tree
,
114 struct durable_open_vs_oplock test
)
117 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
118 struct smb2_handle _h
;
119 struct smb2_handle
*h
= NULL
;
121 struct smb2_create io
;
123 smb2_util_unlink(tree
, fname
);
125 smb2_oplock_create_share(&io
, fname
,
126 smb2_util_share_access(test
.share_mode
),
127 smb2_util_oplock_level(test
.level
));
128 io
.in
.durable_open
= true;
130 status
= smb2_create(tree
, mem_ctx
, &io
);
131 CHECK_STATUS(status
, NT_STATUS_OK
);
132 _h
= io
.out
.file
.handle
;
134 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
135 CHECK_VAL(io
.out
.durable_open
, test
.expected
);
136 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level(test
.level
));
140 smb2_util_close(tree
, *h
);
142 smb2_util_unlink(tree
, fname
);
143 talloc_free(mem_ctx
);
148 bool test_durable_open_open1(struct torture_context
*tctx
,
149 struct smb2_tree
*tree
)
151 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
156 /* Choose a random name in case the state is left a little funky. */
157 snprintf(fname
, 256, "durable_open_open1_%s.dat", generate_random_str(tctx
, 8));
159 smb2_util_unlink(tree
, fname
);
161 /* test various oplock levels with durable open */
163 for (i
= 0; i
< NUM_OPLOCK_OPEN_TESTS
; i
++) {
164 ret
= test_one_durable_open_open1(tctx
,
167 durable_open_vs_oplock_table
[i
]);
174 smb2_util_unlink(tree
, fname
);
176 talloc_free(mem_ctx
);
182 * basic durable_open test.
183 * durable state should only be granted when requested
184 * along with a batch oplock or a handle lease.
186 * This test tests durable open with all valid lease types.
189 struct durable_open_vs_lease
{
191 const char *share_mode
;
195 #define NUM_LEASE_TYPES 5
196 #define NUM_LEASE_OPEN_TESTS ( NUM_LEASE_TYPES * NUM_SHARE_MODES )
197 struct durable_open_vs_lease durable_open_vs_lease_table
[NUM_LEASE_OPEN_TESTS
] =
206 { "", "RWD", false },
212 { "R", "RW", false },
213 { "R", "RD", false },
214 { "R", "DW", false },
215 { "R", "RWD", false },
218 { "RW", "R", false },
219 { "RW", "W", false },
220 { "RW", "D", false },
221 { "RW", "RW", false },
222 { "RW", "RD", false },
223 { "RW", "WD", false },
224 { "RW", "RWD", false },
230 { "RH", "RW", true },
231 { "RH", "RD", true },
232 { "RH", "WD", true },
233 { "RH", "RWD", true },
236 { "RHW", "R", true },
237 { "RHW", "W", true },
238 { "RHW", "D", true },
239 { "RHW", "RW", true },
240 { "RHW", "RD", true },
241 { "RHW", "WD", true },
242 { "RHW", "RWD", true },
245 static bool test_one_durable_open_open2(struct torture_context
*tctx
,
246 struct smb2_tree
*tree
,
248 struct durable_open_vs_lease test
)
251 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
252 struct smb2_handle _h
;
253 struct smb2_handle
*h
= NULL
;
255 struct smb2_create io
;
256 struct smb2_lease ls
;
259 smb2_util_unlink(tree
, fname
);
263 smb2_lease_create_share(&io
, &ls
, false /* dir */, fname
,
264 smb2_util_share_access(test
.share_mode
),
266 smb2_util_lease_state(test
.type
));
267 io
.in
.durable_open
= true;
269 status
= smb2_create(tree
, mem_ctx
, &io
);
270 CHECK_STATUS(status
, NT_STATUS_OK
);
271 _h
= io
.out
.file
.handle
;
273 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
274 CHECK_VAL(io
.out
.durable_open
, test
.expected
);
275 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
276 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[0], lease
);
277 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[1], ~lease
);
278 CHECK_VAL(io
.out
.lease_response
.lease_state
,
279 smb2_util_lease_state(test
.type
));
282 smb2_util_close(tree
, *h
);
284 smb2_util_unlink(tree
, fname
);
285 talloc_free(mem_ctx
);
290 bool test_durable_open_open2(struct torture_context
*tctx
,
291 struct smb2_tree
*tree
)
293 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
299 caps
= smb2cli_conn_server_capabilities(tree
->session
->transport
->conn
);
300 if (!(caps
& SMB2_CAP_LEASING
)) {
301 torture_skip(tctx
, "leases are not supported");
304 /* Choose a random name in case the state is left a little funky. */
305 snprintf(fname
, 256, "durable_open_open2_%s.dat", generate_random_str(tctx
, 8));
307 smb2_util_unlink(tree
, fname
);
310 /* test various oplock levels with durable open */
312 for (i
= 0; i
< NUM_LEASE_OPEN_TESTS
; i
++) {
313 ret
= test_one_durable_open_open2(tctx
,
316 durable_open_vs_lease_table
[i
]);
323 smb2_util_unlink(tree
, fname
);
325 talloc_free(mem_ctx
);
331 * basic test for doing a durable open
332 * and do a durable reopen on the same connection
333 * while the first open is still active (fails)
335 bool test_durable_open_reopen1(struct torture_context
*tctx
,
336 struct smb2_tree
*tree
)
339 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
341 struct smb2_handle _h
;
342 struct smb2_handle
*h
= NULL
;
343 struct smb2_create io1
, io2
;
346 /* Choose a random name in case the state is left a little funky. */
347 snprintf(fname
, 256, "durable_open_reopen1_%s.dat",
348 generate_random_str(tctx
, 8));
350 smb2_util_unlink(tree
, fname
);
352 smb2_oplock_create_share(&io1
, fname
,
353 smb2_util_share_access(""),
354 smb2_util_oplock_level("b"));
355 io1
.in
.durable_open
= true;
357 status
= smb2_create(tree
, mem_ctx
, &io1
);
358 CHECK_STATUS(status
, NT_STATUS_OK
);
359 _h
= io1
.out
.file
.handle
;
361 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
362 CHECK_VAL(io1
.out
.durable_open
, true);
363 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
365 /* try a durable reconnect while the file is still open */
367 io2
.in
.fname
= fname
;
368 io2
.in
.durable_handle
= h
;
370 status
= smb2_create(tree
, mem_ctx
, &io2
);
371 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
375 smb2_util_close(tree
, *h
);
378 smb2_util_unlink(tree
, fname
);
382 talloc_free(mem_ctx
);
388 * basic test for doing a durable open
389 * tcp disconnect, reconnect, do a durable reopen (succeeds)
391 bool test_durable_open_reopen2(struct torture_context
*tctx
,
392 struct smb2_tree
*tree
)
395 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
397 struct smb2_handle _h
;
398 struct smb2_handle
*h
= NULL
;
399 struct smb2_create io1
, io2
;
402 /* Choose a random name in case the state is left a little funky. */
403 snprintf(fname
, 256, "durable_open_reopen2_%s.dat",
404 generate_random_str(tctx
, 8));
406 smb2_util_unlink(tree
, fname
);
408 smb2_oplock_create_share(&io1
, fname
,
409 smb2_util_share_access(""),
410 smb2_util_oplock_level("b"));
411 io1
.in
.durable_open
= true;
413 status
= smb2_create(tree
, mem_ctx
, &io1
);
414 CHECK_STATUS(status
, NT_STATUS_OK
);
415 _h
= io1
.out
.file
.handle
;
417 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
418 CHECK_VAL(io1
.out
.durable_open
, true);
419 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
421 /* disconnect, reconnect and then do durable reopen */
425 if (!torture_smb2_connection(tctx
, &tree
)) {
426 torture_warning(tctx
, "couldn't reconnect, bailing\n");
432 io2
.in
.fname
= fname
;
433 io2
.in
.durable_handle
= h
;
436 status
= smb2_create(tree
, mem_ctx
, &io2
);
437 CHECK_STATUS(status
, NT_STATUS_OK
);
438 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
439 CHECK_VAL(io2
.out
.durable_open
, true);
440 CHECK_VAL(io2
.out
.oplock_level
, smb2_util_oplock_level("b"));
441 _h
= io2
.out
.file
.handle
;
446 smb2_util_close(tree
, *h
);
449 smb2_util_unlink(tree
, fname
);
453 talloc_free(mem_ctx
);
459 * basic test for doing a durable open
460 * tcp disconnect, reconnect with a session reconnect and
461 * do a durable reopen (succeeds)
463 bool test_durable_open_reopen2a(struct torture_context
*tctx
,
464 struct smb2_tree
*tree
)
467 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
469 struct smb2_handle _h
;
470 struct smb2_handle
*h
= NULL
;
471 struct smb2_create io1
, io2
;
472 uint64_t previous_session_id
;
475 /* Choose a random name in case the state is left a little funky. */
476 snprintf(fname
, 256, "durable_open_reopen2_%s.dat",
477 generate_random_str(tctx
, 8));
479 smb2_util_unlink(tree
, fname
);
481 smb2_oplock_create_share(&io1
, fname
,
482 smb2_util_share_access(""),
483 smb2_util_oplock_level("b"));
484 io1
.in
.durable_open
= true;
486 status
= smb2_create(tree
, mem_ctx
, &io1
);
487 CHECK_STATUS(status
, NT_STATUS_OK
);
488 _h
= io1
.out
.file
.handle
;
490 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
491 CHECK_VAL(io1
.out
.durable_open
, true);
492 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
494 /* disconnect, reconnect and then do durable reopen */
495 previous_session_id
= smb2cli_session_current_id(tree
->session
->smbXcli
);
499 if (!torture_smb2_connection_ext(tctx
, previous_session_id
, &tree
)) {
500 torture_warning(tctx
, "couldn't reconnect, bailing\n");
506 io2
.in
.fname
= fname
;
507 io2
.in
.durable_handle
= h
;
510 status
= smb2_create(tree
, mem_ctx
, &io2
);
511 CHECK_STATUS(status
, NT_STATUS_OK
);
512 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
513 CHECK_VAL(io2
.out
.durable_open
, true);
514 CHECK_VAL(io2
.out
.oplock_level
, smb2_util_oplock_level("b"));
515 _h
= io2
.out
.file
.handle
;
520 smb2_util_close(tree
, *h
);
523 smb2_util_unlink(tree
, fname
);
527 talloc_free(mem_ctx
);
534 * basic test for doing a durable open:
535 * tdis, new tcon, try durable reopen (fails)
537 bool test_durable_open_reopen3(struct torture_context
*tctx
,
538 struct smb2_tree
*tree
)
541 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
543 struct smb2_handle _h
;
544 struct smb2_handle
*h
= NULL
;
545 struct smb2_create io1
, io2
;
547 struct smb2_tree
*tree2
;
549 /* Choose a random name in case the state is left a little funky. */
550 snprintf(fname
, 256, "durable_open_reopen3_%s.dat",
551 generate_random_str(tctx
, 8));
553 smb2_util_unlink(tree
, fname
);
555 smb2_oplock_create_share(&io1
, fname
,
556 smb2_util_share_access(""),
557 smb2_util_oplock_level("b"));
558 io1
.in
.durable_open
= true;
560 status
= smb2_create(tree
, mem_ctx
, &io1
);
561 CHECK_STATUS(status
, NT_STATUS_OK
);
562 _h
= io1
.out
.file
.handle
;
564 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
565 CHECK_VAL(io1
.out
.durable_open
, true);
566 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
568 /* disconnect, reconnect and then do durable reopen */
569 status
= smb2_tdis(tree
);
570 CHECK_STATUS(status
, NT_STATUS_OK
);
572 if (!torture_smb2_tree_connect(tctx
, tree
->session
, mem_ctx
, &tree2
)) {
573 torture_warning(tctx
, "couldn't reconnect to share, bailing\n");
580 io2
.in
.fname
= fname
;
581 io2
.in
.durable_handle
= h
;
583 status
= smb2_create(tree2
, mem_ctx
, &io2
);
584 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
588 smb2_util_close(tree
, *h
);
591 smb2_util_unlink(tree2
, fname
);
595 talloc_free(mem_ctx
);
601 * basic test for doing a durable open:
602 * logoff, create a new session, do a durable reopen (succeeds)
604 bool test_durable_open_reopen4(struct torture_context
*tctx
,
605 struct smb2_tree
*tree
)
608 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
610 struct smb2_handle _h
;
611 struct smb2_handle
*h
= NULL
;
612 struct smb2_create io1
, io2
;
614 struct smb2_transport
*transport
;
615 struct smb2_session
*session2
;
616 struct smb2_tree
*tree2
;
618 /* Choose a random name in case the state is left a little funky. */
619 snprintf(fname
, 256, "durable_open_reopen4_%s.dat",
620 generate_random_str(tctx
, 8));
622 smb2_util_unlink(tree
, fname
);
624 smb2_oplock_create_share(&io1
, fname
,
625 smb2_util_share_access(""),
626 smb2_util_oplock_level("b"));
627 io1
.in
.durable_open
= true;
628 io1
.in
.create_options
|= NTCREATEX_OPTIONS_DELETE_ON_CLOSE
;
630 status
= smb2_create(tree
, mem_ctx
, &io1
);
631 CHECK_STATUS(status
, NT_STATUS_OK
);
632 _h
= io1
.out
.file
.handle
;
634 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
635 CHECK_VAL(io1
.out
.durable_open
, true);
636 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
639 * do a session logoff, establish a new session and tree
640 * connect on the same transport, and try a durable reopen
642 transport
= tree
->session
->transport
;
643 status
= smb2_logoff(tree
->session
);
644 CHECK_STATUS(status
, NT_STATUS_OK
);
646 if (!torture_smb2_session_setup(tctx
, transport
,
647 0, /* previous_session_id */
650 torture_warning(tctx
, "session setup failed.\n");
656 * the session setup has talloc-stolen the transport,
657 * so we can safely free the old tree+session for clarity
661 if (!torture_smb2_tree_connect(tctx
, session2
, mem_ctx
, &tree2
)) {
662 torture_warning(tctx
, "tree connect failed.\n");
668 io2
.in
.fname
= fname
;
669 io2
.in
.durable_handle
= h
;
672 status
= smb2_create(tree2
, mem_ctx
, &io2
);
673 CHECK_STATUS(status
, NT_STATUS_OK
);
675 _h
= io2
.out
.file
.handle
;
677 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
678 CHECK_VAL(io2
.out
.durable_open
, true);
679 CHECK_VAL(io2
.out
.oplock_level
, smb2_util_oplock_level("b"));
683 smb2_util_close(tree2
, *h
);
686 smb2_util_unlink(tree2
, fname
);
690 talloc_free(mem_ctx
);
696 basic testing of SMB2 durable opens
697 regarding the position information on the handle
699 bool test_durable_open_file_position(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_handle h1
, h2
;
705 struct smb2_create io1
, io2
;
707 const char *fname
= "durable_open_position.dat";
708 union smb_fileinfo qfinfo
;
709 union smb_setfileinfo sfinfo
;
713 smb2_util_unlink(tree1
, fname
);
715 smb2_oplock_create(&io1
, fname
, SMB2_OPLOCK_LEVEL_BATCH
);
716 io1
.in
.durable_open
= true;
718 status
= smb2_create(tree1
, mem_ctx
, &io1
);
719 CHECK_STATUS(status
, NT_STATUS_OK
);
720 h1
= io1
.out
.file
.handle
;
721 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
722 CHECK_VAL(io1
.out
.durable_open
, true);
723 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
725 /* TODO: check extra blob content */
728 qfinfo
.generic
.level
= RAW_FILEINFO_POSITION_INFORMATION
;
729 qfinfo
.generic
.in
.file
.handle
= h1
;
730 status
= smb2_getinfo_file(tree1
, mem_ctx
, &qfinfo
);
731 CHECK_STATUS(status
, NT_STATUS_OK
);
732 CHECK_VAL(qfinfo
.position_information
.out
.position
, 0);
733 pos
= qfinfo
.position_information
.out
.position
;
734 torture_comment(tctx
, "position: %llu\n",
735 (unsigned long long)pos
);
738 sfinfo
.generic
.level
= RAW_SFILEINFO_POSITION_INFORMATION
;
739 sfinfo
.generic
.in
.file
.handle
= h1
;
740 sfinfo
.position_information
.in
.position
= 0x1000;
741 status
= smb2_setinfo_file(tree1
, &sfinfo
);
742 CHECK_STATUS(status
, NT_STATUS_OK
);
745 qfinfo
.generic
.level
= RAW_FILEINFO_POSITION_INFORMATION
;
746 qfinfo
.generic
.in
.file
.handle
= h1
;
747 status
= smb2_getinfo_file(tree1
, mem_ctx
, &qfinfo
);
748 CHECK_STATUS(status
, NT_STATUS_OK
);
749 CHECK_VAL(qfinfo
.position_information
.out
.position
, 0x1000);
750 pos
= qfinfo
.position_information
.out
.position
;
751 torture_comment(tctx
, "position: %llu\n",
752 (unsigned long long)pos
);
758 qfinfo
.generic
.level
= RAW_FILEINFO_POSITION_INFORMATION
;
759 qfinfo
.generic
.in
.file
.handle
= h1
;
760 status
= smb2_getinfo_file(tree2
, mem_ctx
, &qfinfo
);
761 CHECK_STATUS(status
, NT_STATUS_FILE_CLOSED
);
764 io2
.in
.fname
= fname
;
765 io2
.in
.durable_handle
= &h1
;
767 status
= smb2_create(tree2
, mem_ctx
, &io2
);
768 CHECK_STATUS(status
, NT_STATUS_OK
);
769 CHECK_VAL(io2
.out
.durable_open
, true);
770 CHECK_VAL(io2
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
771 CHECK_VAL(io2
.out
.reserved
, 0x00);
772 CHECK_VAL(io2
.out
.create_action
, NTCREATEX_ACTION_EXISTED
);
773 CHECK_VAL(io2
.out
.alloc_size
, 0);
774 CHECK_VAL(io2
.out
.size
, 0);
775 CHECK_VAL(io2
.out
.file_attr
, FILE_ATTRIBUTE_ARCHIVE
);
776 CHECK_VAL(io2
.out
.reserved2
, 0);
778 h2
= io2
.out
.file
.handle
;
781 qfinfo
.generic
.level
= RAW_FILEINFO_POSITION_INFORMATION
;
782 qfinfo
.generic
.in
.file
.handle
= h2
;
783 status
= smb2_getinfo_file(tree2
, mem_ctx
, &qfinfo
);
784 CHECK_STATUS(status
, NT_STATUS_OK
);
785 CHECK_VAL(qfinfo
.position_information
.out
.position
, 0x1000);
786 pos
= qfinfo
.position_information
.out
.position
;
787 torture_comment(tctx
, "position: %llu\n",
788 (unsigned long long)pos
);
790 smb2_util_close(tree2
, h2
);
792 talloc_free(mem_ctx
);
794 smb2_util_unlink(tree2
, fname
);
803 Open, disconnect, oplock break, reconnect.
805 bool test_durable_open_oplock(struct torture_context
*tctx
,
806 struct smb2_tree
*tree1
,
807 struct smb2_tree
*tree2
)
809 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
810 struct smb2_create io1
, io2
;
811 struct smb2_handle h1
, h2
;
816 /* Choose a random name in case the state is left a little funky. */
817 snprintf(fname
, 256, "durable_open_oplock_%s.dat", generate_random_str(tctx
, 8));
820 smb2_util_unlink(tree1
, fname
);
822 /* Create with batch oplock */
823 smb2_oplock_create(&io1
, fname
, SMB2_OPLOCK_LEVEL_BATCH
);
824 io1
.in
.durable_open
= true;
827 io2
.in
.create_disposition
= NTCREATEX_DISP_OPEN
;
829 status
= smb2_create(tree1
, mem_ctx
, &io1
);
830 CHECK_STATUS(status
, NT_STATUS_OK
);
831 h1
= io1
.out
.file
.handle
;
832 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
833 CHECK_VAL(io1
.out
.durable_open
, true);
834 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
836 /* Disconnect after getting the batch */
841 * Windows7 (build 7000) will break a batch oplock immediately if the
842 * original client is gone. (ZML: This seems like a bug. It should give
843 * some time for the client to reconnect!)
845 status
= smb2_create(tree2
, mem_ctx
, &io2
);
846 CHECK_STATUS(status
, NT_STATUS_OK
);
847 h2
= io2
.out
.file
.handle
;
848 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
849 CHECK_VAL(io2
.out
.durable_open
, true);
850 CHECK_VAL(io2
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
852 /* What if tree1 tries to come back and reclaim? */
853 if (!torture_smb2_connection(tctx
, &tree1
)) {
854 torture_warning(tctx
, "couldn't reconnect, bailing\n");
860 io1
.in
.fname
= fname
;
861 io1
.in
.durable_handle
= &h1
;
863 status
= smb2_create(tree1
, mem_ctx
, &io1
);
864 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
867 smb2_util_close(tree2
, h2
);
868 smb2_util_unlink(tree2
, fname
);
877 Open, disconnect, lease break, reconnect.
879 bool test_durable_open_lease(struct torture_context
*tctx
,
880 struct smb2_tree
*tree1
,
881 struct smb2_tree
*tree2
)
883 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
884 struct smb2_create io1
, io2
;
885 struct smb2_lease ls1
, ls2
;
886 struct smb2_handle h1
, h2
;
890 uint64_t lease1
, lease2
;
893 caps
= smb2cli_conn_server_capabilities(tree1
->session
->transport
->conn
);
894 if (!(caps
& SMB2_CAP_LEASING
)) {
895 torture_skip(tctx
, "leases are not supported");
899 * Choose a random name and random lease in case the state is left a
904 snprintf(fname
, 256, "durable_open_lease_%s.dat", generate_random_str(tctx
, 8));
907 smb2_util_unlink(tree1
, fname
);
909 /* Create with lease */
910 smb2_lease_create(&io1
, &ls1
, false /* dir */, fname
,
911 lease1
, smb2_util_lease_state("RHW"));
912 io1
.in
.durable_open
= true;
914 smb2_lease_create(&io2
, &ls2
, false /* dir */, fname
,
915 lease2
, smb2_util_lease_state("RHW"));
916 io2
.in
.durable_open
= true;
917 io2
.in
.create_disposition
= NTCREATEX_DISP_OPEN
;
919 status
= smb2_create(tree1
, mem_ctx
, &io1
);
920 CHECK_STATUS(status
, NT_STATUS_OK
);
921 h1
= io1
.out
.file
.handle
;
922 CHECK_VAL(io1
.out
.durable_open
, true);
923 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
925 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
926 CHECK_VAL(io1
.out
.lease_response
.lease_key
.data
[0], lease1
);
927 CHECK_VAL(io1
.out
.lease_response
.lease_key
.data
[1], ~lease1
);
928 CHECK_VAL(io1
.out
.lease_response
.lease_state
,
929 SMB2_LEASE_READ
|SMB2_LEASE_HANDLE
|SMB2_LEASE_WRITE
);
931 /* Disconnect after getting the lease */
936 * Windows7 (build 7000) will grant an RH lease immediate (not an RHW?)
937 * even if the original client is gone. (ZML: This seems like a bug. It
938 * should give some time for the client to reconnect! And why RH?)
940 * obnox: Current windows 7 and w2k8r2 grant RHW instead of RH.
941 * Test is adapted accordingly.
943 status
= smb2_create(tree2
, mem_ctx
, &io2
);
944 CHECK_STATUS(status
, NT_STATUS_OK
);
945 h2
= io2
.out
.file
.handle
;
946 CHECK_VAL(io2
.out
.durable_open
, true);
947 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
949 CHECK_VAL(io2
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
950 CHECK_VAL(io2
.out
.lease_response
.lease_key
.data
[0], lease2
);
951 CHECK_VAL(io2
.out
.lease_response
.lease_key
.data
[1], ~lease2
);
952 CHECK_VAL(io2
.out
.lease_response
.lease_state
,
953 SMB2_LEASE_READ
|SMB2_LEASE_HANDLE
|SMB2_LEASE_WRITE
);
955 /* What if tree1 tries to come back and reclaim? */
956 if (!torture_smb2_connection(tctx
, &tree1
)) {
957 torture_warning(tctx
, "couldn't reconnect, bailing\n");
963 io1
.in
.fname
= fname
;
964 io1
.in
.durable_handle
= &h1
;
965 io1
.in
.lease_request
= &ls1
;
967 status
= smb2_create(tree1
, mem_ctx
, &io1
);
968 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
971 smb2_util_close(tree2
, h2
);
972 smb2_util_unlink(tree2
, fname
);
981 Open, take BRL, disconnect, reconnect.
983 bool test_durable_open_lock(struct torture_context
*tctx
,
984 struct smb2_tree
*tree
)
986 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
987 struct smb2_create io
;
988 struct smb2_lease ls
;
989 struct smb2_handle h
;
990 struct smb2_lock lck
;
991 struct smb2_lock_element el
[2];
998 caps
= smb2cli_conn_server_capabilities(tree
->session
->transport
->conn
);
999 if (!(caps
& SMB2_CAP_LEASING
)) {
1000 torture_skip(tctx
, "leases are not supported");
1004 * Choose a random name and random lease in case the state is left a
1008 snprintf(fname
, 256, "durable_open_lock_%s.dat", generate_random_str(tctx
, 8));
1011 smb2_util_unlink(tree
, fname
);
1013 /* Create with lease */
1015 smb2_lease_create(&io
, &ls
, false /* dir */, fname
, lease
,
1016 smb2_util_lease_state("RWH"));
1017 io
.in
.durable_open
= true;
1019 status
= smb2_create(tree
, mem_ctx
, &io
);
1020 CHECK_STATUS(status
, NT_STATUS_OK
);
1021 h
= io
.out
.file
.handle
;
1022 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1024 CHECK_VAL(io
.out
.durable_open
, true);
1025 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
1026 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[0], lease
);
1027 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[1], ~lease
);
1028 CHECK_VAL(io
.out
.lease_response
.lease_state
,
1029 SMB2_LEASE_READ
|SMB2_LEASE_HANDLE
|SMB2_LEASE_WRITE
);
1034 lck
.in
.lock_count
= 0x0001;
1035 lck
.in
.lock_sequence
= 0x00000000;
1036 lck
.in
.file
.handle
= h
;
1039 el
[0].reserved
= 0x00000000;
1040 el
[0].flags
= SMB2_LOCK_FLAG_EXCLUSIVE
;
1041 status
= smb2_lock(tree
, &lck
);
1042 CHECK_STATUS(status
, NT_STATUS_OK
);
1044 /* Disconnect/Reconnect. */
1048 if (!torture_smb2_connection(tctx
, &tree
)) {
1049 torture_warning(tctx
, "couldn't reconnect, bailing\n");
1055 io
.in
.fname
= fname
;
1056 io
.in
.durable_handle
= &h
;
1057 io
.in
.lease_request
= &ls
;
1059 status
= smb2_create(tree
, mem_ctx
, &io
);
1060 CHECK_STATUS(status
, NT_STATUS_OK
);
1061 h
= io
.out
.file
.handle
;
1063 lck
.in
.file
.handle
= h
;
1064 el
[0].flags
= SMB2_LOCK_FLAG_UNLOCK
;
1065 status
= smb2_lock(tree
, &lck
);
1066 CHECK_STATUS(status
, NT_STATUS_OK
);
1069 smb2_util_close(tree
, h
);
1070 smb2_util_unlink(tree
, fname
);
1077 * Open with a RH lease, disconnect, open in another tree, reconnect.
1079 * This test actually demonstrates a minimum level of respect for the durable
1080 * open in the face of another open. As long as this test shows an inability to
1081 * reconnect after an open, the oplock/lease tests above will certainly
1082 * demonstrate an error on reconnect.
1084 bool test_durable_open_open_lease(struct torture_context
*tctx
,
1085 struct smb2_tree
*tree1
,
1086 struct smb2_tree
*tree2
)
1088 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
1089 struct smb2_create io1
, io2
;
1090 struct smb2_lease ls
;
1091 struct smb2_handle h1
, h2
;
1098 caps
= smb2cli_conn_server_capabilities(tree1
->session
->transport
->conn
);
1099 if (!(caps
& SMB2_CAP_LEASING
)) {
1100 torture_skip(tctx
, "leases are not supported");
1104 * Choose a random name and random lease in case the state is left a
1108 snprintf(fname
, 256, "durable_open_open_lease_%s.dat",
1109 generate_random_str(tctx
, 8));
1112 smb2_util_unlink(tree1
, fname
);
1114 /* Create with lease */
1115 smb2_lease_create_share(&io1
, &ls
, false /* dir */, fname
,
1116 smb2_util_share_access(""),
1118 smb2_util_lease_state("RH"));
1119 io1
.in
.durable_open
= true;
1121 status
= smb2_create(tree1
, mem_ctx
, &io1
);
1122 CHECK_STATUS(status
, NT_STATUS_OK
);
1123 h1
= io1
.out
.file
.handle
;
1124 CHECK_VAL(io1
.out
.durable_open
, true);
1125 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1127 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
1128 CHECK_VAL(io1
.out
.lease_response
.lease_key
.data
[0], lease
);
1129 CHECK_VAL(io1
.out
.lease_response
.lease_key
.data
[1], ~lease
);
1130 CHECK_VAL(io1
.out
.lease_response
.lease_state
,
1131 smb2_util_lease_state("RH"));
1137 /* Open the file in tree2 */
1138 smb2_oplock_create(&io2
, fname
, SMB2_OPLOCK_LEVEL_NONE
);
1140 status
= smb2_create(tree2
, mem_ctx
, &io2
);
1141 CHECK_STATUS(status
, NT_STATUS_OK
);
1142 h2
= io2
.out
.file
.handle
;
1143 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1146 if (!torture_smb2_connection(tctx
, &tree1
)) {
1147 torture_warning(tctx
, "couldn't reconnect, bailing\n");
1153 io1
.in
.fname
= fname
;
1154 io1
.in
.durable_handle
= &h1
;
1155 io1
.in
.lease_request
= &ls
;
1158 * Windows7 (build 7000) will give away an open immediately if the
1159 * original client is gone. (ZML: This seems like a bug. It should give
1160 * some time for the client to reconnect!)
1162 status
= smb2_create(tree1
, mem_ctx
, &io1
);
1163 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
1164 h1
= io1
.out
.file
.handle
;
1167 smb2_util_close(tree2
, h2
);
1168 smb2_util_unlink(tree2
, fname
);
1169 smb2_util_close(tree1
, h1
);
1170 smb2_util_unlink(tree1
, fname
);
1179 * Open with a batch oplock, disconnect, open in another tree, reconnect.
1181 * This test actually demonstrates a minimum level of respect for the durable
1182 * open in the face of another open. As long as this test shows an inability to
1183 * reconnect after an open, the oplock/lease tests above will certainly
1184 * demonstrate an error on reconnect.
1186 bool test_durable_open_open_oplock(struct torture_context
*tctx
,
1187 struct smb2_tree
*tree1
,
1188 struct smb2_tree
*tree2
)
1190 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
1191 struct smb2_create io1
, io2
;
1192 struct smb2_handle h1
, h2
;
1198 * Choose a random name and random lease in case the state is left a
1201 snprintf(fname
, 256, "durable_open_open_oplock_%s.dat",
1202 generate_random_str(tctx
, 8));
1205 smb2_util_unlink(tree1
, fname
);
1207 /* Create with batch oplock */
1208 smb2_oplock_create(&io1
, fname
, SMB2_OPLOCK_LEVEL_BATCH
);
1209 io1
.in
.durable_open
= true;
1211 status
= smb2_create(tree1
, mem_ctx
, &io1
);
1212 CHECK_STATUS(status
, NT_STATUS_OK
);
1213 h1
= io1
.out
.file
.handle
;
1214 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1215 CHECK_VAL(io1
.out
.durable_open
, true);
1216 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
1222 /* Open the file in tree2 */
1223 smb2_oplock_create(&io2
, fname
, SMB2_OPLOCK_LEVEL_NONE
);
1225 status
= smb2_create(tree2
, mem_ctx
, &io2
);
1226 CHECK_STATUS(status
, NT_STATUS_OK
);
1227 h2
= io2
.out
.file
.handle
;
1228 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1231 if (!torture_smb2_connection(tctx
, &tree1
)) {
1232 torture_warning(tctx
, "couldn't reconnect, bailing\n");
1238 io1
.in
.fname
= fname
;
1239 io1
.in
.durable_handle
= &h1
;
1241 status
= smb2_create(tree1
, mem_ctx
, &io1
);
1242 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
1243 h1
= io1
.out
.file
.handle
;
1246 smb2_util_close(tree2
, h2
);
1247 smb2_util_unlink(tree2
, fname
);
1248 smb2_util_close(tree1
, h1
);
1249 smb2_util_unlink(tree1
, fname
);
1257 struct torture_suite
*torture_smb2_durable_open_init(void)
1259 struct torture_suite
*suite
=
1260 torture_suite_create(talloc_autofree_context(), "durable-open");
1262 torture_suite_add_1smb2_test(suite
, "open1", test_durable_open_open1
);
1263 torture_suite_add_1smb2_test(suite
, "open2", test_durable_open_open2
);
1264 torture_suite_add_1smb2_test(suite
, "reopen1", test_durable_open_reopen1
);
1265 torture_suite_add_1smb2_test(suite
, "reopen2", test_durable_open_reopen2
);
1266 torture_suite_add_1smb2_test(suite
, "reopen2a", test_durable_open_reopen2a
);
1267 torture_suite_add_1smb2_test(suite
, "reopen3", test_durable_open_reopen3
);
1268 torture_suite_add_1smb2_test(suite
, "reopen4", test_durable_open_reopen4
);
1269 torture_suite_add_2smb2_test(suite
, "file-position",
1270 test_durable_open_file_position
);
1271 torture_suite_add_2smb2_test(suite
, "oplock", test_durable_open_oplock
);
1272 torture_suite_add_2smb2_test(suite
, "lease", test_durable_open_lease
);
1273 torture_suite_add_1smb2_test(suite
, "lock", test_durable_open_lock
);
1274 torture_suite_add_2smb2_test(suite
, "open-lease",
1275 test_durable_open_open_lease
);
1276 torture_suite_add_2smb2_test(suite
, "open-oplock",
1277 test_durable_open_open_oplock
);
1279 suite
->description
= talloc_strdup(suite
, "SMB2-DURABLE-OPEN tests");