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"
30 #include "lib/util/time_basic.h"
32 #define CHECK_VAL(v, correct) do { \
33 if ((v) != (correct)) { \
34 torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%llx - should be 0x%llx\n", \
35 __location__, #v, (unsigned long long)v, (unsigned long long)correct); \
39 #define CHECK_NOT_VAL(v, incorrect) do { \
40 if ((v) == (incorrect)) { \
41 torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%llx - should not be 0x%llx\n", \
42 __location__, #v, (unsigned long long)v, (unsigned long long)incorrect); \
46 #define CHECK_NOT_NULL(p) do { \
48 torture_result(tctx, TORTURE_FAIL, "(%s): %s is NULL but it should not be.\n", \
53 #define CHECK_STATUS(status, correct) do { \
54 if (!NT_STATUS_EQUAL(status, correct)) { \
55 torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \
56 nt_errstr(status), nt_errstr(correct)); \
61 #define CHECK_CREATED(__io, __created, __attribute) \
63 CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
64 CHECK_VAL((__io)->out.alloc_size, 0); \
65 CHECK_VAL((__io)->out.size, 0); \
66 CHECK_VAL((__io)->out.file_attr, (__attribute)); \
67 CHECK_VAL((__io)->out.reserved2, 0); \
70 #define CHECK_CREATED_SIZE(__io, __created, __attribute, __alloc_size, __size) \
72 CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
73 CHECK_VAL((__io)->out.alloc_size, (__alloc_size)); \
74 CHECK_VAL((__io)->out.size, (__size)); \
75 CHECK_VAL((__io)->out.file_attr, (__attribute)); \
76 CHECK_VAL((__io)->out.reserved2, 0); \
82 * basic durable_open test.
83 * durable state should only be granted when requested
84 * along with a batch oplock or a handle lease.
86 * This test tests durable open with all possible oplock types.
89 struct durable_open_vs_oplock {
91 const char *share_mode;
95 #define NUM_OPLOCK_TYPES 4
96 #define NUM_SHARE_MODES 8
97 #define NUM_OPLOCK_OPEN_TESTS ( NUM_OPLOCK_TYPES * NUM_SHARE_MODES )
98 static struct durable_open_vs_oplock durable_open_vs_oplock_table[NUM_OPLOCK_OPEN_TESTS] =
107 { "", "RWD", false },
113 { "s", "RD", false },
114 { "s", "RW", false },
115 { "s", "WD", false },
116 { "s", "RWD", false },
122 { "x", "RD", false },
123 { "x", "RW", false },
124 { "x", "WD", false },
125 { "x", "RWD", false },
134 { "b", "RWD", true },
137 static bool test_one_durable_open_open_oplock(struct torture_context *tctx,
138 struct smb2_tree *tree,
140 struct durable_open_vs_oplock test)
143 TALLOC_CTX *mem_ctx = talloc_new(tctx);
144 struct smb2_handle _h;
145 struct smb2_handle *h = NULL;
147 struct smb2_create io;
149 smb2_util_unlink(tree, fname);
151 smb2_oplock_create_share(&io, fname,
152 smb2_util_share_access(test.share_mode),
153 smb2_util_oplock_level(test.level));
154 io.in.durable_open = true;
156 status = smb2_create(tree, mem_ctx, &io);
157 CHECK_STATUS(status, NT_STATUS_OK);
158 _h = io.out.file.handle;
160 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
161 CHECK_VAL(io.out.durable_open, test.expected);
162 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level(test.level));
166 smb2_util_close(tree, *h);
168 smb2_util_unlink(tree, fname);
169 talloc_free(mem_ctx);
174 static bool test_durable_open_open_oplock(struct torture_context *tctx,
175 struct smb2_tree *tree)
177 TALLOC_CTX *mem_ctx = talloc_new(tctx);
182 /* Choose a random name in case the state is left a little funky. */
183 snprintf(fname, 256, "durable_open_open_oplock_%s.dat", generate_random_str(tctx, 8));
185 smb2_util_unlink(tree, fname);
187 /* test various oplock levels with durable open */
189 for (i = 0; i < NUM_OPLOCK_OPEN_TESTS; i++) {
190 ret = test_one_durable_open_open_oplock(tctx,
193 durable_open_vs_oplock_table[i]);
200 smb2_util_unlink(tree, fname);
202 talloc_free(mem_ctx);
208 * basic durable_open test.
209 * durable state should only be granted when requested
210 * along with a batch oplock or a handle lease.
212 * This test tests durable open with all valid lease types.
215 struct durable_open_vs_lease {
217 const char *share_mode;
221 #define NUM_LEASE_TYPES 5
222 #define NUM_LEASE_OPEN_TESTS ( NUM_LEASE_TYPES * NUM_SHARE_MODES )
223 static struct durable_open_vs_lease durable_open_vs_lease_table[NUM_LEASE_OPEN_TESTS] =
232 { "", "RWD", false },
238 { "R", "RW", false },
239 { "R", "RD", false },
240 { "R", "DW", false },
241 { "R", "RWD", false },
244 { "RW", "R", false },
245 { "RW", "W", false },
246 { "RW", "D", false },
247 { "RW", "RW", false },
248 { "RW", "RD", false },
249 { "RW", "WD", false },
250 { "RW", "RWD", false },
256 { "RH", "RW", true },
257 { "RH", "RD", true },
258 { "RH", "WD", true },
259 { "RH", "RWD", true },
262 { "RHW", "R", true },
263 { "RHW", "W", true },
264 { "RHW", "D", true },
265 { "RHW", "RW", true },
266 { "RHW", "RD", true },
267 { "RHW", "WD", true },
268 { "RHW", "RWD", true },
271 static bool test_one_durable_open_open_lease(struct torture_context *tctx,
272 struct smb2_tree *tree,
274 struct durable_open_vs_lease test)
277 TALLOC_CTX *mem_ctx = talloc_new(tctx);
278 struct smb2_handle _h;
279 struct smb2_handle *h = NULL;
281 struct smb2_create io;
282 struct smb2_lease ls;
286 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
287 if (!(caps & SMB2_CAP_LEASING)) {
288 torture_skip(tctx, "leases are not supported");
291 smb2_util_unlink(tree, fname);
295 smb2_lease_create_share(&io, &ls, false /* dir */, fname,
296 smb2_util_share_access(test.share_mode),
298 smb2_util_lease_state(test.type));
299 io.in.durable_open = true;
301 status = smb2_create(tree, mem_ctx, &io);
302 CHECK_STATUS(status, NT_STATUS_OK);
303 _h = io.out.file.handle;
305 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
306 CHECK_VAL(io.out.durable_open, test.expected);
307 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
308 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease);
309 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease);
310 CHECK_VAL(io.out.lease_response.lease_state,
311 smb2_util_lease_state(test.type));
314 smb2_util_close(tree, *h);
316 smb2_util_unlink(tree, fname);
317 talloc_free(mem_ctx);
322 static bool test_durable_open_open_lease(struct torture_context *tctx,
323 struct smb2_tree *tree)
325 TALLOC_CTX *mem_ctx = talloc_new(tctx);
331 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
332 if (!(caps & SMB2_CAP_LEASING)) {
333 torture_skip(tctx, "leases are not supported");
336 /* Choose a random name in case the state is left a little funky. */
337 snprintf(fname, 256, "durable_open_open_lease_%s.dat", generate_random_str(tctx, 8));
339 smb2_util_unlink(tree, fname);
342 /* test various oplock levels with durable open */
344 for (i = 0; i < NUM_LEASE_OPEN_TESTS; i++) {
345 ret = test_one_durable_open_open_lease(tctx,
348 durable_open_vs_lease_table[i]);
355 smb2_util_unlink(tree, fname);
357 talloc_free(mem_ctx);
363 * basic test for doing a durable open
364 * and do a durable reopen on the same connection
365 * while the first open is still active (fails)
367 static bool test_durable_open_reopen1(struct torture_context *tctx,
368 struct smb2_tree *tree)
371 TALLOC_CTX *mem_ctx = talloc_new(tctx);
373 struct smb2_handle _h;
374 struct smb2_handle *h = NULL;
375 struct smb2_create io1, io2;
378 /* Choose a random name in case the state is left a little funky. */
379 snprintf(fname, 256, "durable_open_reopen1_%s.dat",
380 generate_random_str(tctx, 8));
382 smb2_util_unlink(tree, fname);
384 smb2_oplock_create_share(&io1, fname,
385 smb2_util_share_access(""),
386 smb2_util_oplock_level("b"));
387 io1.in.durable_open = true;
389 status = smb2_create(tree, mem_ctx, &io1);
390 CHECK_STATUS(status, NT_STATUS_OK);
391 _h = io1.out.file.handle;
393 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
394 CHECK_VAL(io1.out.durable_open, true);
395 CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
397 /* try a durable reconnect while the file is still open */
399 io2.in.fname = fname;
400 io2.in.durable_handle = h;
402 status = smb2_create(tree, mem_ctx, &io2);
403 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
407 smb2_util_close(tree, *h);
410 smb2_util_unlink(tree, fname);
414 talloc_free(mem_ctx);
420 * Basic test for doing a durable open
421 * and do a session reconnect while the first
422 * session is still active and the handle is
423 * still open in the client.
424 * This closes the original session and a
425 * durable reconnect on the new session succeeds.
427 static bool test_durable_open_reopen1a(struct torture_context *tctx,
428 struct smb2_tree *tree)
431 TALLOC_CTX *mem_ctx = talloc_new(tctx);
433 struct smb2_handle _h;
434 struct smb2_handle *h = NULL;
435 struct smb2_create io;
437 struct smb2_tree *tree2 = NULL;
438 struct smb2_tree *tree3 = NULL;
439 uint64_t previous_session_id;
440 struct smbcli_options options;
441 struct GUID orig_client_guid;
443 options = tree->session->transport->options;
444 orig_client_guid = options.client_guid;
446 /* Choose a random name in case the state is left a little funky. */
447 snprintf(fname, 256, "durable_open_reopen1a_%s.dat",
448 generate_random_str(tctx, 8));
450 smb2_util_unlink(tree, fname);
452 smb2_oplock_create_share(&io, fname,
453 smb2_util_share_access(""),
454 smb2_util_oplock_level("b"));
455 io.in.durable_open = true;
457 status = smb2_create(tree, mem_ctx, &io);
458 CHECK_STATUS(status, NT_STATUS_OK);
459 _h = io.out.file.handle;
461 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
462 CHECK_VAL(io.out.durable_open, true);
463 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
466 * a session reconnect on a second tcp connection
469 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
471 /* for oplocks, the client guid can be different: */
472 options.client_guid = GUID_random();
474 ret = torture_smb2_connection_ext(tctx, previous_session_id,
476 torture_assert_goto(tctx, ret, ret, done, "could not reconnect");
479 * check that this has deleted the old session
484 io.in.durable_handle = h;
486 status = smb2_create(tree, mem_ctx, &io);
487 CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED);
492 * but a durable reconnect on the new session succeeds:
497 io.in.durable_handle = h;
499 status = smb2_create(tree2, mem_ctx, &io);
500 CHECK_STATUS(status, NT_STATUS_OK);
501 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
502 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
503 _h = io.out.file.handle;
507 * a session reconnect on a second tcp connection
510 previous_session_id = smb2cli_session_current_id(tree2->session->smbXcli);
512 /* the original client_guid works just the same */
513 options.client_guid = orig_client_guid;
515 ret = torture_smb2_connection_ext(tctx, previous_session_id,
517 torture_assert_goto(tctx, ret, ret, done, "could not reconnect");
520 * check that this has deleted the old session
525 io.in.durable_handle = h;
527 status = smb2_create(tree2, mem_ctx, &io);
528 CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED);
533 * but a durable reconnect on the new session succeeds:
538 io.in.durable_handle = h;
540 status = smb2_create(tree3, mem_ctx, &io);
541 CHECK_STATUS(status, NT_STATUS_OK);
542 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
543 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
544 _h = io.out.file.handle;
558 smb2_util_close(tree, *h);
561 smb2_util_unlink(tree, fname);
566 talloc_free(mem_ctx);
572 * lease variant of reopen1a
574 * Basic test for doing a durable open and doing a session
575 * reconnect while the first session is still active and the
576 * handle is still open in the client.
577 * This closes the original session and a durable reconnect on
578 * the new session succeeds depending on the client guid:
580 * Durable reconnect on a session with a different client guid fails.
581 * Durable reconnect on a session with the original client guid succeeds.
583 bool test_durable_open_reopen1a_lease(struct torture_context *tctx,
584 struct smb2_tree *tree)
587 TALLOC_CTX *mem_ctx = talloc_new(tctx);
589 struct smb2_handle _h;
590 struct smb2_handle *h = NULL;
591 struct smb2_create io;
592 struct smb2_lease ls;
595 struct smb2_tree *tree2 = NULL;
596 struct smb2_tree *tree3 = NULL;
597 uint64_t previous_session_id;
598 struct smbcli_options options;
599 struct GUID orig_client_guid;
601 options = tree->session->transport->options;
602 orig_client_guid = options.client_guid;
604 /* Choose a random name in case the state is left a little funky. */
605 snprintf(fname, 256, "durable_v2_open_reopen1a_lease_%s.dat",
606 generate_random_str(tctx, 8));
608 smb2_util_unlink(tree, fname);
610 lease_key = random();
611 smb2_lease_create(&io, &ls, false /* dir */, fname,
612 lease_key, smb2_util_lease_state("RWH"));
613 io.in.durable_open = true;
615 status = smb2_create(tree, mem_ctx, &io);
616 CHECK_STATUS(status, NT_STATUS_OK);
617 _h = io.out.file.handle;
619 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
620 CHECK_VAL(io.out.durable_open, true);
621 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
622 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key);
623 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key);
624 CHECK_VAL(io.out.lease_response.lease_state,
625 smb2_util_lease_state("RWH"));
626 CHECK_VAL(io.out.lease_response.lease_flags, 0);
627 CHECK_VAL(io.out.lease_response.lease_duration, 0);
629 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
632 * a session reconnect on a second tcp connection
633 * with a different client_guid does not allow
634 * the durable reconnect.
637 options.client_guid = GUID_random();
639 ret = torture_smb2_connection_ext(tctx, previous_session_id,
641 torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect");
644 * check that this has deleted the old session
649 io.in.durable_handle = h;
650 io.in.lease_request = &ls;
651 status = smb2_create(tree, mem_ctx, &io);
652 CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED);
657 * but a durable reconnect on the new session with the wrong
663 io.in.durable_handle = h;
664 io.in.lease_request = &ls;
665 status = smb2_create(tree2, mem_ctx, &io);
666 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
669 * now a session reconnect on a second tcp connection
670 * with original client_guid allows the durable reconnect.
673 options.client_guid = orig_client_guid;
675 ret = torture_smb2_connection_ext(tctx, previous_session_id,
677 torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect");
680 * check that this has deleted the old session
681 * In this case, a durable reconnect attempt with the
682 * correct client_guid yields a different error code.
687 io.in.durable_handle = h;
688 io.in.lease_request = &ls;
689 status = smb2_create(tree2, mem_ctx, &io);
690 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
694 * but a durable reconnect on the new session succeeds:
699 io.in.durable_handle = h;
700 io.in.lease_request = &ls;
701 status = smb2_create(tree3, mem_ctx, &io);
702 CHECK_STATUS(status, NT_STATUS_OK);
703 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
704 CHECK_VAL(io.out.durable_open, false); /* no dh response context... */
705 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
706 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key);
707 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key);
708 CHECK_VAL(io.out.lease_response.lease_state,
709 smb2_util_lease_state("RWH"));
710 CHECK_VAL(io.out.lease_response.lease_flags, 0);
711 CHECK_VAL(io.out.lease_response.lease_duration, 0);
712 _h = io.out.file.handle;
726 smb2_util_close(tree, *h);
729 smb2_util_unlink(tree, fname);
734 talloc_free(mem_ctx);
741 * basic test for doing a durable open
742 * tcp disconnect, reconnect, do a durable reopen (succeeds)
744 static bool test_durable_open_reopen2(struct torture_context *tctx,
745 struct smb2_tree *tree)
748 TALLOC_CTX *mem_ctx = talloc_new(tctx);
750 struct smb2_handle _h;
751 struct smb2_handle *h = NULL;
752 struct smb2_create io;
755 /* Choose a random name in case the state is left a little funky. */
756 snprintf(fname, 256, "durable_open_reopen2_%s.dat",
757 generate_random_str(tctx, 8));
759 smb2_util_unlink(tree, fname);
761 smb2_oplock_create_share(&io, fname,
762 smb2_util_share_access(""),
763 smb2_util_oplock_level("b"));
764 io.in.durable_open = true;
766 status = smb2_create(tree, mem_ctx, &io);
767 CHECK_STATUS(status, NT_STATUS_OK);
768 _h = io.out.file.handle;
770 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
771 CHECK_VAL(io.out.durable_open, true);
772 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
774 /* disconnect, leaving the durable in place */
777 if (!torture_smb2_connection(tctx, &tree)) {
778 torture_warning(tctx, "couldn't reconnect, bailing\n");
784 /* the path name is ignored by the server */
786 io.in.durable_handle = h; /* durable v1 reconnect request */
789 status = smb2_create(tree, mem_ctx, &io);
790 CHECK_STATUS(status, NT_STATUS_OK);
791 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
792 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
793 _h = io.out.file.handle;
796 /* disconnect again, leaving the durable in place */
799 if (!torture_smb2_connection(tctx, &tree)) {
800 torture_warning(tctx, "couldn't reconnect, bailing\n");
806 * show that the filename and many other fields
807 * are ignored. only the reconnect request blob
811 /* the path name is ignored by the server */
812 io.in.security_flags = 0x78;
813 io.in.oplock_level = 0x78;
814 io.in.impersonation_level = 0x12345678;
815 io.in.create_flags = 0x12345678;
816 io.in.reserved = 0x12345678;
817 io.in.desired_access = 0x12345678;
818 io.in.file_attributes = 0x12345678;
819 io.in.share_access = 0x12345678;
820 io.in.create_disposition = 0x12345678;
821 io.in.create_options = 0x12345678;
822 io.in.fname = "__non_existing_fname__";
823 io.in.durable_handle = h; /* durable v1 reconnect request */
826 status = smb2_create(tree, mem_ctx, &io);
827 CHECK_STATUS(status, NT_STATUS_OK);
828 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
829 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
830 _h = io.out.file.handle;
833 /* disconnect, leaving the durable in place */
836 if (!torture_smb2_connection(tctx, &tree)) {
837 torture_warning(tctx, "couldn't reconnect, bailing\n");
843 * show that an additionally specified durable v1 request
844 * is ignored by the server.
845 * See MS-SMB2, 3.3.5.9.7
846 * Handling the SMB2_CREATE_DURABLE_HANDLE_RECONNECT Create Context
849 /* the path name is ignored by the server */
851 io.in.durable_handle = h; /* durable v1 reconnect request */
852 io.in.durable_open = true; /* durable v1 handle request */
855 status = smb2_create(tree, mem_ctx, &io);
856 CHECK_STATUS(status, NT_STATUS_OK);
857 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
858 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
859 _h = io.out.file.handle;
865 smb2_util_close(tree, *h);
868 smb2_util_unlink(tree, fname);
873 talloc_free(mem_ctx);
879 * lease variant of reopen2
880 * basic test for doing a durable open
881 * tcp disconnect, reconnect, do a durable reopen (succeeds)
883 static bool test_durable_open_reopen2_lease(struct torture_context *tctx,
884 struct smb2_tree *tree)
887 TALLOC_CTX *mem_ctx = talloc_new(tctx);
889 struct smb2_handle _h;
890 struct smb2_handle *h = NULL;
891 struct smb2_create io;
892 struct smb2_lease ls;
895 struct smbcli_options options;
898 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
899 if (!(caps & SMB2_CAP_LEASING)) {
900 torture_skip(tctx, "leases are not supported");
903 options = tree->session->transport->options;
905 /* Choose a random name in case the state is left a little funky. */
906 snprintf(fname, 256, "durable_open_reopen2_%s.dat",
907 generate_random_str(tctx, 8));
909 smb2_util_unlink(tree, fname);
911 lease_key = random();
912 smb2_lease_create(&io, &ls, false /* dir */, fname, lease_key,
913 smb2_util_lease_state("RWH"));
914 io.in.durable_open = true;
916 status = smb2_create(tree, mem_ctx, &io);
917 CHECK_STATUS(status, NT_STATUS_OK);
918 _h = io.out.file.handle;
920 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
922 CHECK_VAL(io.out.durable_open, true);
923 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
924 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key);
925 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key);
926 CHECK_VAL(io.out.lease_response.lease_state,
927 smb2_util_lease_state("RWH"));
928 CHECK_VAL(io.out.lease_response.lease_flags, 0);
929 CHECK_VAL(io.out.lease_response.lease_duration, 0);
931 /* disconnect, reconnect and then do durable reopen */
934 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
935 torture_warning(tctx, "couldn't reconnect, bailing\n");
941 /* a few failure tests: */
944 * several attempts without lease attached:
945 * all fail with NT_STATUS_OBJECT_NAME_NOT_FOUND
946 * irrespective of file name provided
951 io.in.durable_handle = h;
952 status = smb2_create(tree, mem_ctx, &io);
953 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
956 io.in.fname = "__non_existing_fname__";
957 io.in.durable_handle = h;
958 status = smb2_create(tree, mem_ctx, &io);
959 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
963 io.in.durable_handle = h;
964 status = smb2_create(tree, mem_ctx, &io);
965 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
968 * attempt with lease provided, but
969 * with a changed lease key. => fails
973 io.in.durable_open = false;
974 io.in.durable_handle = h;
975 io.in.lease_request = &ls;
976 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
977 /* a wrong lease key lets the request fail */
978 ls.lease_key.data[0]++;
980 status = smb2_create(tree, mem_ctx, &io);
981 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
983 /* restore the correct lease key */
984 ls.lease_key.data[0]--;
987 * this last failing attempt is almost correct:
988 * only problem is: we use the wrong filename...
989 * Note that this gives INVALID_PARAMETER.
990 * This is different from oplocks!
993 io.in.fname = "__non_existing_fname__";
994 io.in.durable_open = false;
995 io.in.durable_handle = h;
996 io.in.lease_request = &ls;
997 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
999 status = smb2_create(tree, mem_ctx, &io);
1000 CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER);
1003 * Now for a succeeding reconnect:
1007 io.in.fname = fname;
1008 io.in.durable_open = false;
1009 io.in.durable_handle = h;
1010 io.in.lease_request = &ls;
1011 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
1013 /* the requested lease state is irrelevant */
1014 ls.lease_state = smb2_util_lease_state("");
1018 status = smb2_create(tree, mem_ctx, &io);
1019 CHECK_STATUS(status, NT_STATUS_OK);
1021 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1022 CHECK_VAL(io.out.durable_open, false);
1023 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
1024 CHECK_VAL(io.out.persistent_open, false);
1025 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
1026 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key);
1027 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key);
1028 CHECK_VAL(io.out.lease_response.lease_state,
1029 smb2_util_lease_state("RWH"));
1030 CHECK_VAL(io.out.lease_response.lease_flags, 0);
1031 CHECK_VAL(io.out.lease_response.lease_duration, 0);
1032 _h = io.out.file.handle;
1035 /* disconnect one more time */
1038 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
1039 torture_warning(tctx, "couldn't reconnect, bailing\n");
1045 * demonstrate that various parameters are ignored
1051 * These are completely ignored by the server
1053 io.in.security_flags = 0x78;
1054 io.in.oplock_level = 0x78;
1055 io.in.impersonation_level = 0x12345678;
1056 io.in.create_flags = 0x12345678;
1057 io.in.reserved = 0x12345678;
1058 io.in.desired_access = 0x12345678;
1059 io.in.file_attributes = 0x12345678;
1060 io.in.share_access = 0x12345678;
1061 io.in.create_disposition = 0x12345678;
1062 io.in.create_options = 0x12345678;
1065 * only these are checked:
1067 * - io.in.durable_handle,
1068 * - io.in.lease_request->lease_key
1071 io.in.fname = fname;
1072 io.in.durable_open_v2 = false;
1073 io.in.durable_handle_v2 = h;
1074 io.in.lease_request = &ls;
1076 /* the requested lease state is irrelevant */
1077 ls.lease_state = smb2_util_lease_state("");
1081 status = smb2_create(tree, mem_ctx, &io);
1082 CHECK_STATUS(status, NT_STATUS_OK);
1084 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1085 CHECK_VAL(io.out.durable_open, false);
1086 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
1087 CHECK_VAL(io.out.persistent_open, false);
1088 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
1089 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key);
1090 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key);
1091 CHECK_VAL(io.out.lease_response.lease_state,
1092 smb2_util_lease_state("RWH"));
1093 CHECK_VAL(io.out.lease_response.lease_flags, 0);
1094 CHECK_VAL(io.out.lease_response.lease_duration, 0);
1096 _h = io.out.file.handle;
1102 smb2_util_close(tree, *h);
1105 smb2_util_unlink(tree, fname);
1110 talloc_free(mem_ctx);
1116 * lease v2 variant of reopen2
1117 * basic test for doing a durable open
1118 * tcp disconnect, reconnect, do a durable reopen (succeeds)
1120 static bool test_durable_open_reopen2_lease_v2(struct torture_context *tctx,
1121 struct smb2_tree *tree)
1124 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1126 struct smb2_handle _h;
1127 struct smb2_handle *h = NULL;
1128 struct smb2_create io;
1129 struct smb2_lease ls;
1132 struct smbcli_options options;
1135 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
1136 if (!(caps & SMB2_CAP_LEASING)) {
1137 torture_skip(tctx, "leases are not supported");
1140 options = tree->session->transport->options;
1142 /* Choose a random name in case the state is left a little funky. */
1143 snprintf(fname, 256, "durable_open_reopen2_%s.dat",
1144 generate_random_str(tctx, 8));
1146 smb2_util_unlink(tree, fname);
1148 lease_key = random();
1149 smb2_lease_v2_create(&io, &ls, false /* dir */, fname,
1150 lease_key, 0, /* parent lease key */
1151 smb2_util_lease_state("RWH"), 0 /* lease epoch */);
1152 io.in.durable_open = true;
1154 status = smb2_create(tree, mem_ctx, &io);
1155 CHECK_STATUS(status, NT_STATUS_OK);
1156 _h = io.out.file.handle;
1158 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1160 CHECK_VAL(io.out.durable_open, true);
1161 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
1162 CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key);
1163 CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key);
1164 CHECK_VAL(io.out.lease_response_v2.lease_state,
1165 smb2_util_lease_state("RWH"));
1166 CHECK_VAL(io.out.lease_response_v2.lease_flags, 0);
1167 CHECK_VAL(io.out.lease_response_v2.lease_duration, 0);
1169 /* disconnect, reconnect and then do durable reopen */
1172 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
1173 torture_warning(tctx, "couldn't reconnect, bailing\n");
1178 /* a few failure tests: */
1181 * several attempts without lease attached:
1182 * all fail with NT_STATUS_OBJECT_NAME_NOT_FOUND
1183 * irrespective of file name provided
1188 io.in.durable_handle = h;
1189 status = smb2_create(tree, mem_ctx, &io);
1190 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
1193 io.in.fname = "__non_existing_fname__";
1194 io.in.durable_handle = h;
1195 status = smb2_create(tree, mem_ctx, &io);
1196 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
1199 io.in.fname = fname;
1200 io.in.durable_handle = h;
1201 status = smb2_create(tree, mem_ctx, &io);
1202 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
1205 * attempt with lease provided, but
1206 * with a changed lease key. => fails
1209 io.in.fname = fname;
1210 io.in.durable_open = false;
1211 io.in.durable_handle = h;
1212 io.in.lease_request_v2 = &ls;
1213 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
1214 /* a wrong lease key lets the request fail */
1215 ls.lease_key.data[0]++;
1217 status = smb2_create(tree, mem_ctx, &io);
1218 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
1220 /* restore the correct lease key */
1221 ls.lease_key.data[0]--;
1224 * this last failing attempt is almost correct:
1225 * only problem is: we use the wrong filename...
1226 * Note that this gives INVALID_PARAMETER.
1227 * This is different from oplocks!
1230 io.in.fname = "__non_existing_fname__";
1231 io.in.durable_open = false;
1232 io.in.durable_handle = h;
1233 io.in.lease_request_v2 = &ls;
1234 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
1236 status = smb2_create(tree, mem_ctx, &io);
1237 CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER);
1240 * Now for a succeeding reconnect:
1244 io.in.fname = fname;
1245 io.in.durable_open = false;
1246 io.in.durable_handle = h;
1247 io.in.lease_request_v2 = &ls;
1248 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
1250 /* the requested lease state is irrelevant */
1251 ls.lease_state = smb2_util_lease_state("");
1255 status = smb2_create(tree, mem_ctx, &io);
1256 CHECK_STATUS(status, NT_STATUS_OK);
1258 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1259 CHECK_VAL(io.out.durable_open, false);
1260 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
1261 CHECK_VAL(io.out.persistent_open, false);
1262 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
1263 CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key);
1264 CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key);
1265 CHECK_VAL(io.out.lease_response_v2.lease_state,
1266 smb2_util_lease_state("RWH"));
1267 CHECK_VAL(io.out.lease_response_v2.lease_flags, 0);
1268 CHECK_VAL(io.out.lease_response_v2.lease_duration, 0);
1269 _h = io.out.file.handle;
1272 /* disconnect one more time */
1275 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
1276 torture_warning(tctx, "couldn't reconnect, bailing\n");
1282 * demonstrate that various parameters are ignored
1288 * These are completely ignored by the server
1290 io.in.security_flags = 0x78;
1291 io.in.oplock_level = 0x78;
1292 io.in.impersonation_level = 0x12345678;
1293 io.in.create_flags = 0x12345678;
1294 io.in.reserved = 0x12345678;
1295 io.in.desired_access = 0x12345678;
1296 io.in.file_attributes = 0x12345678;
1297 io.in.share_access = 0x12345678;
1298 io.in.create_disposition = 0x12345678;
1299 io.in.create_options = 0x12345678;
1302 * only these are checked:
1304 * - io.in.durable_handle,
1305 * - io.in.lease_request->lease_key
1308 io.in.fname = fname;
1309 io.in.durable_open_v2 = false;
1310 io.in.durable_handle_v2 = h;
1311 io.in.lease_request_v2 = &ls;
1313 /* the requested lease state is irrelevant */
1314 ls.lease_state = smb2_util_lease_state("");
1318 status = smb2_create(tree, mem_ctx, &io);
1319 CHECK_STATUS(status, NT_STATUS_OK);
1321 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1322 CHECK_VAL(io.out.durable_open, false);
1323 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
1324 CHECK_VAL(io.out.persistent_open, false);
1325 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
1326 CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key);
1327 CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key);
1328 CHECK_VAL(io.out.lease_response_v2.lease_state,
1329 smb2_util_lease_state("RWH"));
1330 CHECK_VAL(io.out.lease_response_v2.lease_flags, 0);
1331 CHECK_VAL(io.out.lease_response_v2.lease_duration, 0);
1333 _h = io.out.file.handle;
1339 smb2_util_close(tree, *h);
1342 smb2_util_unlink(tree, fname);
1347 talloc_free(mem_ctx);
1353 * basic test for doing a durable open
1354 * tcp disconnect, reconnect with a session reconnect and
1355 * do a durable reopen (succeeds)
1357 static bool test_durable_open_reopen2a(struct torture_context *tctx,
1358 struct smb2_tree *tree)
1361 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1363 struct smb2_handle _h;
1364 struct smb2_handle *h = NULL;
1365 struct smb2_create io1, io2;
1366 uint64_t previous_session_id;
1368 struct smbcli_options options;
1370 options = tree->session->transport->options;
1372 /* Choose a random name in case the state is left a little funky. */
1373 snprintf(fname, 256, "durable_open_reopen2_%s.dat",
1374 generate_random_str(tctx, 8));
1376 smb2_util_unlink(tree, fname);
1378 smb2_oplock_create_share(&io1, fname,
1379 smb2_util_share_access(""),
1380 smb2_util_oplock_level("b"));
1381 io1.in.durable_open = true;
1383 status = smb2_create(tree, mem_ctx, &io1);
1384 CHECK_STATUS(status, NT_STATUS_OK);
1385 _h = io1.out.file.handle;
1387 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1388 CHECK_VAL(io1.out.durable_open, true);
1389 CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
1391 /* disconnect, reconnect and then do durable reopen */
1392 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
1396 if (!torture_smb2_connection_ext(tctx, previous_session_id,
1399 torture_warning(tctx, "couldn't reconnect, bailing\n");
1405 io2.in.fname = fname;
1406 io2.in.durable_handle = h;
1409 status = smb2_create(tree, mem_ctx, &io2);
1410 CHECK_STATUS(status, NT_STATUS_OK);
1411 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1412 CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b"));
1413 _h = io2.out.file.handle;
1419 smb2_util_close(tree, *h);
1422 smb2_util_unlink(tree, fname);
1427 talloc_free(mem_ctx);
1434 * basic test for doing a durable open:
1435 * tdis, new tcon, try durable reopen (fails)
1437 static bool test_durable_open_reopen3(struct torture_context *tctx,
1438 struct smb2_tree *tree)
1441 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1443 struct smb2_handle _h;
1444 struct smb2_handle *h = NULL;
1445 struct smb2_create io1, io2;
1447 struct smb2_tree *tree2;
1449 /* Choose a random name in case the state is left a little funky. */
1450 snprintf(fname, 256, "durable_open_reopen3_%s.dat",
1451 generate_random_str(tctx, 8));
1453 smb2_util_unlink(tree, fname);
1455 smb2_oplock_create_share(&io1, fname,
1456 smb2_util_share_access(""),
1457 smb2_util_oplock_level("b"));
1458 io1.in.durable_open = true;
1460 status = smb2_create(tree, mem_ctx, &io1);
1461 CHECK_STATUS(status, NT_STATUS_OK);
1462 _h = io1.out.file.handle;
1464 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1465 CHECK_VAL(io1.out.durable_open, true);
1466 CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
1468 /* disconnect, reconnect and then do durable reopen */
1469 status = smb2_tdis(tree);
1470 CHECK_STATUS(status, NT_STATUS_OK);
1472 if (!torture_smb2_tree_connect(tctx, tree->session, mem_ctx, &tree2)) {
1473 torture_warning(tctx, "couldn't reconnect to share, bailing\n");
1480 io2.in.fname = fname;
1481 io2.in.durable_handle = h;
1483 status = smb2_create(tree2, mem_ctx, &io2);
1484 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
1489 smb2_util_close(tree, *h);
1492 smb2_util_unlink(tree2, fname);
1497 talloc_free(mem_ctx);
1503 * basic test for doing a durable open:
1504 * logoff, create a new session, do a durable reopen (succeeds)
1506 static bool test_durable_open_reopen4(struct torture_context *tctx,
1507 struct smb2_tree *tree)
1510 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1512 struct smb2_handle _h;
1513 struct smb2_handle *h = NULL;
1514 struct smb2_create io1, io2;
1516 struct smb2_transport *transport;
1517 struct smb2_session *session2;
1518 struct smb2_tree *tree2;
1520 /* Choose a random name in case the state is left a little funky. */
1521 snprintf(fname, 256, "durable_open_reopen4_%s.dat",
1522 generate_random_str(tctx, 8));
1524 smb2_util_unlink(tree, fname);
1526 smb2_oplock_create_share(&io1, fname,
1527 smb2_util_share_access(""),
1528 smb2_util_oplock_level("b"));
1529 io1.in.durable_open = true;
1531 status = smb2_create(tree, mem_ctx, &io1);
1532 CHECK_STATUS(status, NT_STATUS_OK);
1533 _h = io1.out.file.handle;
1535 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1536 CHECK_VAL(io1.out.durable_open, true);
1537 CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
1540 * do a session logoff, establish a new session and tree
1541 * connect on the same transport, and try a durable reopen
1543 transport = tree->session->transport;
1544 status = smb2_logoff(tree->session);
1545 CHECK_STATUS(status, NT_STATUS_OK);
1547 if (!torture_smb2_session_setup(tctx, transport,
1548 0, /* previous_session_id */
1549 mem_ctx, &session2))
1551 torture_warning(tctx, "session setup failed.\n");
1557 * the session setup has talloc-stolen the transport,
1558 * so we can safely free the old tree+session for clarity
1562 if (!torture_smb2_tree_connect(tctx, session2, mem_ctx, &tree2)) {
1563 torture_warning(tctx, "tree connect failed.\n");
1569 io2.in.fname = fname;
1570 io2.in.durable_handle = h;
1573 status = smb2_create(tree2, mem_ctx, &io2);
1574 CHECK_STATUS(status, NT_STATUS_OK);
1576 _h = io2.out.file.handle;
1578 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1579 CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b"));
1584 smb2_util_close(tree2, *h);
1587 smb2_util_unlink(tree2, fname);
1592 talloc_free(mem_ctx);
1597 static bool test_durable_open_delete_on_close1(struct torture_context *tctx,
1598 struct smb2_tree *tree)
1601 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1603 struct smb2_handle _h;
1604 struct smb2_handle *h = NULL;
1605 struct smb2_create io1, io2;
1609 /* Choose a random name in case the state is left a little funky. */
1610 snprintf(fname, 256, "durable_open_delete_on_close1_%s.dat",
1611 generate_random_str(tctx, 8));
1613 smb2_util_unlink(tree, fname);
1615 smb2_oplock_create_share(&io1, fname,
1616 smb2_util_share_access(""),
1617 smb2_util_oplock_level("b"));
1618 io1.in.durable_open = true;
1619 io1.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
1621 status = smb2_create(tree, mem_ctx, &io1);
1622 CHECK_STATUS(status, NT_STATUS_OK);
1623 _h = io1.out.file.handle;
1625 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1626 CHECK_VAL(io1.out.durable_open, true);
1627 CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
1629 status = smb2_util_write(tree, *h, &b, 0, 1);
1630 CHECK_STATUS(status, NT_STATUS_OK);
1632 /* disconnect, leaving the durable handle in place */
1635 if (!torture_smb2_connection(tctx, &tree)) {
1636 torture_warning(tctx, "could not reconnect, bailing\n");
1642 * Open the file on the new connection again
1643 * and check that it has been newly created,
1644 * i.e. delete on close was effective on the disconnected handle.
1645 * Also check that the file is really empty,
1646 * the previously written byte gone.
1648 smb2_oplock_create_share(&io2, fname,
1649 smb2_util_share_access(""),
1650 smb2_util_oplock_level("b"));
1651 io2.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
1653 status = smb2_create(tree, mem_ctx, &io2);
1654 CHECK_STATUS(status, NT_STATUS_OK);
1655 _h = io2.out.file.handle;
1657 CHECK_CREATED_SIZE(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE, 0, 0);
1658 CHECK_VAL(io2.out.durable_open, false);
1659 CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b"));
1664 smb2_util_close(tree, *h);
1667 smb2_util_unlink(tree, fname);
1672 talloc_free(mem_ctx);
1678 static bool test_durable_open_delete_on_close2(struct torture_context *tctx,
1679 struct smb2_tree *tree)
1682 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1684 struct smb2_handle _h;
1685 struct smb2_handle *h = NULL;
1686 struct smb2_create io;
1689 uint64_t previous_session_id;
1690 uint64_t alloc_size_step;
1691 struct smbcli_options options;
1693 options = tree->session->transport->options;
1695 /* Choose a random name in case the state is left a little funky. */
1696 snprintf(fname, 256, "durable_open_delete_on_close2_%s.dat",
1697 generate_random_str(tctx, 8));
1699 smb2_util_unlink(tree, fname);
1701 smb2_oplock_create_share(&io, fname,
1702 smb2_util_share_access(""),
1703 smb2_util_oplock_level("b"));
1704 io.in.durable_open = true;
1705 io.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
1707 status = smb2_create(tree, mem_ctx, &io);
1708 CHECK_STATUS(status, NT_STATUS_OK);
1709 _h = io.out.file.handle;
1711 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1712 CHECK_VAL(io.out.durable_open, true);
1713 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
1715 status = smb2_util_write(tree, *h, &b, 0, 1);
1716 CHECK_STATUS(status, NT_STATUS_OK);
1718 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
1720 /* disconnect, leaving the durable handle in place */
1723 if (!torture_smb2_connection_ext(tctx, previous_session_id,
1726 torture_warning(tctx, "could not reconnect, bailing\n");
1732 io.in.fname = fname;
1733 io.in.durable_handle = h;
1735 status = smb2_create(tree, mem_ctx, &io);
1736 CHECK_STATUS(status, NT_STATUS_OK);
1737 _h = io.out.file.handle;
1739 alloc_size_step = io.out.alloc_size;
1740 CHECK_CREATED_SIZE(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE, alloc_size_step, 1);
1741 CHECK_VAL(io.out.durable_open, false);
1742 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
1744 /* close the file, thereby deleting it */
1745 smb2_util_close(tree, *h);
1746 status = smb2_logoff(tree->session);
1749 if (!torture_smb2_connection(tctx, &tree)) {
1750 torture_warning(tctx, "could not reconnect, bailing\n");
1756 * Open the file on the new connection again
1757 * and check that it has been newly created,
1758 * i.e. delete on close was effective on the reconnected handle.
1759 * Also check that the file is really empty,
1760 * the previously written byte gone.
1762 smb2_oplock_create_share(&io, fname,
1763 smb2_util_share_access(""),
1764 smb2_util_oplock_level("b"));
1765 io.in.durable_open = true;
1766 io.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
1768 status = smb2_create(tree, mem_ctx, &io);
1769 CHECK_STATUS(status, NT_STATUS_OK);
1770 _h = io.out.file.handle;
1772 CHECK_CREATED_SIZE(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE, 0, 0);
1773 CHECK_VAL(io.out.durable_open, true);
1774 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
1779 smb2_util_close(tree, *h);
1782 smb2_util_unlink(tree, fname);
1787 talloc_free(mem_ctx);
1793 basic testing of SMB2 durable opens
1794 regarding the position information on the handle
1796 static bool test_durable_open_file_position(struct torture_context *tctx,
1797 struct smb2_tree *tree)
1799 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1800 struct smb2_handle h;
1801 struct smb2_create io;
1803 const char *fname = "durable_open_position.dat";
1804 union smb_fileinfo qfinfo;
1805 union smb_setfileinfo sfinfo;
1808 uint64_t previous_session_id;
1809 struct smbcli_options options;
1811 options = tree->session->transport->options;
1813 smb2_util_unlink(tree, fname);
1815 smb2_oplock_create(&io, fname, SMB2_OPLOCK_LEVEL_BATCH);
1816 io.in.durable_open = true;
1818 status = smb2_create(tree, mem_ctx, &io);
1819 CHECK_STATUS(status, NT_STATUS_OK);
1820 h = io.out.file.handle;
1821 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1822 CHECK_VAL(io.out.durable_open, true);
1823 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
1825 /* TODO: check extra blob content */
1827 ZERO_STRUCT(qfinfo);
1828 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
1829 qfinfo.generic.in.file.handle = h;
1830 status = smb2_getinfo_file(tree, mem_ctx, &qfinfo);
1831 CHECK_STATUS(status, NT_STATUS_OK);
1832 CHECK_VAL(qfinfo.position_information.out.position, 0);
1833 pos = qfinfo.position_information.out.position;
1834 torture_comment(tctx, "position: %llu\n",
1835 (unsigned long long)pos);
1837 ZERO_STRUCT(sfinfo);
1838 sfinfo.generic.level = RAW_SFILEINFO_POSITION_INFORMATION;
1839 sfinfo.generic.in.file.handle = h;
1840 sfinfo.position_information.in.position = 0x1000;
1841 status = smb2_setinfo_file(tree, &sfinfo);
1842 CHECK_STATUS(status, NT_STATUS_OK);
1844 ZERO_STRUCT(qfinfo);
1845 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
1846 qfinfo.generic.in.file.handle = h;
1847 status = smb2_getinfo_file(tree, mem_ctx, &qfinfo);
1848 CHECK_STATUS(status, NT_STATUS_OK);
1849 CHECK_VAL(qfinfo.position_information.out.position, 0x1000);
1850 pos = qfinfo.position_information.out.position;
1851 torture_comment(tctx, "position: %llu\n",
1852 (unsigned long long)pos);
1854 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
1856 /* tcp disconnect */
1860 /* do a session reconnect */
1861 if (!torture_smb2_connection_ext(tctx, previous_session_id,
1864 torture_warning(tctx, "couldn't reconnect, bailing\n");
1869 ZERO_STRUCT(qfinfo);
1870 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
1871 qfinfo.generic.in.file.handle = h;
1872 status = smb2_getinfo_file(tree, mem_ctx, &qfinfo);
1873 CHECK_STATUS(status, NT_STATUS_FILE_CLOSED);
1876 io.in.fname = fname;
1877 io.in.durable_handle = &h;
1879 status = smb2_create(tree, mem_ctx, &io);
1880 CHECK_STATUS(status, NT_STATUS_OK);
1881 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
1882 CHECK_VAL(io.out.reserved, 0x00);
1883 CHECK_VAL(io.out.create_action, NTCREATEX_ACTION_EXISTED);
1884 CHECK_VAL(io.out.alloc_size, 0);
1885 CHECK_VAL(io.out.size, 0);
1886 CHECK_VAL(io.out.file_attr, FILE_ATTRIBUTE_ARCHIVE);
1887 CHECK_VAL(io.out.reserved2, 0);
1889 h = io.out.file.handle;
1891 ZERO_STRUCT(qfinfo);
1892 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
1893 qfinfo.generic.in.file.handle = h;
1894 status = smb2_getinfo_file(tree, mem_ctx, &qfinfo);
1895 CHECK_STATUS(status, NT_STATUS_OK);
1896 CHECK_VAL(qfinfo.position_information.out.position, 0x1000);
1897 pos = qfinfo.position_information.out.position;
1898 torture_comment(tctx, "position: %llu\n",
1899 (unsigned long long)pos);
1901 smb2_util_close(tree, h);
1903 talloc_free(mem_ctx);
1905 smb2_util_unlink(tree, fname);
1914 Open, disconnect, oplock break, reconnect.
1916 static bool test_durable_open_oplock(struct torture_context *tctx,
1917 struct smb2_tree *tree1,
1918 struct smb2_tree *tree2)
1920 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1921 struct smb2_create io1, io2;
1922 struct smb2_handle h1 = {{0}};
1923 struct smb2_handle h2 = {{0}};
1928 /* Choose a random name in case the state is left a little funky. */
1929 snprintf(fname, 256, "durable_open_oplock_%s.dat", generate_random_str(tctx, 8));
1932 smb2_util_unlink(tree1, fname);
1934 /* Create with batch oplock */
1935 smb2_oplock_create(&io1, fname, SMB2_OPLOCK_LEVEL_BATCH);
1936 io1.in.durable_open = true;
1939 io2.in.create_disposition = NTCREATEX_DISP_OPEN;
1941 status = smb2_create(tree1, mem_ctx, &io1);
1942 CHECK_STATUS(status, NT_STATUS_OK);
1943 h1 = io1.out.file.handle;
1944 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1945 CHECK_VAL(io1.out.durable_open, true);
1946 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
1948 /* Disconnect after getting the batch */
1953 * Windows7 (build 7000) will break a batch oplock immediately if the
1954 * original client is gone. (ZML: This seems like a bug. It should give
1955 * some time for the client to reconnect!)
1957 status = smb2_create(tree2, mem_ctx, &io2);
1958 CHECK_STATUS(status, NT_STATUS_OK);
1959 h2 = io2.out.file.handle;
1960 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1961 CHECK_VAL(io2.out.durable_open, true);
1962 CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
1964 /* What if tree1 tries to come back and reclaim? */
1965 if (!torture_smb2_connection(tctx, &tree1)) {
1966 torture_warning(tctx, "couldn't reconnect, bailing\n");
1972 io1.in.fname = fname;
1973 io1.in.durable_handle = &h1;
1975 status = smb2_create(tree1, mem_ctx, &io1);
1976 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
1979 smb2_util_close(tree2, h2);
1980 smb2_util_unlink(tree2, fname);
1989 Open, disconnect, lease break, reconnect.
1991 static bool test_durable_open_lease(struct torture_context *tctx,
1992 struct smb2_tree *tree1,
1993 struct smb2_tree *tree2)
1995 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1996 struct smb2_create io1, io2;
1997 struct smb2_lease ls1, ls2;
1998 struct smb2_handle h1 = {{0}};
1999 struct smb2_handle h2 = {{0}};
2003 uint64_t lease1, lease2;
2006 caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
2007 if (!(caps & SMB2_CAP_LEASING)) {
2008 torture_skip(tctx, "leases are not supported");
2012 * Choose a random name and random lease in case the state is left a
2017 snprintf(fname, 256, "durable_open_lease_%s.dat", generate_random_str(tctx, 8));
2020 smb2_util_unlink(tree1, fname);
2022 /* Create with lease */
2023 smb2_lease_create(&io1, &ls1, false /* dir */, fname,
2024 lease1, smb2_util_lease_state("RHW"));
2025 io1.in.durable_open = true;
2027 smb2_lease_create(&io2, &ls2, false /* dir */, fname,
2028 lease2, smb2_util_lease_state("RHW"));
2029 io2.in.durable_open = true;
2030 io2.in.create_disposition = NTCREATEX_DISP_OPEN;
2032 status = smb2_create(tree1, mem_ctx, &io1);
2033 CHECK_STATUS(status, NT_STATUS_OK);
2034 h1 = io1.out.file.handle;
2035 CHECK_VAL(io1.out.durable_open, true);
2036 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
2038 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
2039 CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease1);
2040 CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease1);
2041 CHECK_VAL(io1.out.lease_response.lease_state,
2042 SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
2044 /* Disconnect after getting the lease */
2049 * Windows7 (build 7000) will grant an RH lease immediate (not an RHW?)
2050 * even if the original client is gone. (ZML: This seems like a bug. It
2051 * should give some time for the client to reconnect! And why RH?)
2053 * obnox: Current windows 7 and w2k8r2 grant RHW instead of RH.
2054 * Test is adapted accordingly.
2056 status = smb2_create(tree2, mem_ctx, &io2);
2057 CHECK_STATUS(status, NT_STATUS_OK);
2058 h2 = io2.out.file.handle;
2059 CHECK_VAL(io2.out.durable_open, true);
2060 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
2062 CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
2063 CHECK_VAL(io2.out.lease_response.lease_key.data[0], lease2);
2064 CHECK_VAL(io2.out.lease_response.lease_key.data[1], ~lease2);
2065 CHECK_VAL(io2.out.lease_response.lease_state,
2066 SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
2068 /* What if tree1 tries to come back and reclaim? */
2069 if (!torture_smb2_connection(tctx, &tree1)) {
2070 torture_warning(tctx, "couldn't reconnect, bailing\n");
2076 io1.in.fname = fname;
2077 io1.in.durable_handle = &h1;
2078 io1.in.lease_request = &ls1;
2080 status = smb2_create(tree1, mem_ctx, &io1);
2081 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
2084 smb2_util_close(tree2, h2);
2085 smb2_util_unlink(tree2, fname);
2093 static bool test_durable_open_lock_oplock(struct torture_context *tctx,
2094 struct smb2_tree *tree)
2096 TALLOC_CTX *mem_ctx = talloc_new(tctx);
2097 struct smb2_create io;
2098 struct smb2_handle h = {{0}};
2099 struct smb2_lock lck;
2100 struct smb2_lock_element el[2];
2107 snprintf(fname, 256, "durable_open_oplock_lock_%s.dat", generate_random_str(tctx, 8));
2110 smb2_util_unlink(tree, fname);
2112 /* Create with oplock */
2114 smb2_oplock_create_share(&io, fname,
2115 smb2_util_share_access(""),
2116 smb2_util_oplock_level("b"));
2117 io.in.durable_open = true;
2119 status = smb2_create(tree, mem_ctx, &io);
2120 CHECK_STATUS(status, NT_STATUS_OK);
2121 h = io.out.file.handle;
2122 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
2124 CHECK_VAL(io.out.durable_open, true);
2125 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
2130 lck.in.lock_count = 0x0001;
2131 lck.in.lock_sequence = 0x00000000;
2132 lck.in.file.handle = h;
2135 el[0].reserved = 0x00000000;
2136 el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE;
2137 status = smb2_lock(tree, &lck);
2138 CHECK_STATUS(status, NT_STATUS_OK);
2140 /* Disconnect/Reconnect. */
2144 if (!torture_smb2_connection(tctx, &tree)) {
2145 torture_warning(tctx, "couldn't reconnect, bailing\n");
2151 io.in.fname = fname;
2152 io.in.durable_handle = &h;
2154 status = smb2_create(tree, mem_ctx, &io);
2155 CHECK_STATUS(status, NT_STATUS_OK);
2156 h = io.out.file.handle;
2158 lck.in.file.handle = h;
2159 el[0].flags = SMB2_LOCK_FLAG_UNLOCK;
2160 status = smb2_lock(tree, &lck);
2161 CHECK_STATUS(status, NT_STATUS_OK);
2164 smb2_util_close(tree, h);
2165 smb2_util_unlink(tree, fname);
2172 Open, take BRL, disconnect, reconnect.
2174 static bool test_durable_open_lock_lease(struct torture_context *tctx,
2175 struct smb2_tree *tree)
2177 TALLOC_CTX *mem_ctx = talloc_new(tctx);
2178 struct smb2_create io;
2179 struct smb2_lease ls;
2180 struct smb2_handle h = {{0}};
2181 struct smb2_lock lck;
2182 struct smb2_lock_element el[2];
2188 struct smbcli_options options;
2190 options = tree->session->transport->options;
2192 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
2193 if (!(caps & SMB2_CAP_LEASING)) {
2194 torture_skip(tctx, "leases are not supported");
2198 * Choose a random name and random lease in case the state is left a
2202 snprintf(fname, 256, "durable_open_lease_lock_%s.dat", generate_random_str(tctx, 8));
2205 smb2_util_unlink(tree, fname);
2207 /* Create with lease */
2209 smb2_lease_create(&io, &ls, false /* dir */, fname, lease,
2210 smb2_util_lease_state("RWH"));
2211 io.in.durable_open = true;
2213 status = smb2_create(tree, mem_ctx, &io);
2214 CHECK_STATUS(status, NT_STATUS_OK);
2215 h = io.out.file.handle;
2216 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
2218 CHECK_VAL(io.out.durable_open, true);
2219 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
2220 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease);
2221 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease);
2222 CHECK_VAL(io.out.lease_response.lease_state,
2223 SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
2228 lck.in.lock_count = 0x0001;
2229 lck.in.lock_sequence = 0x00000000;
2230 lck.in.file.handle = h;
2233 el[0].reserved = 0x00000000;
2234 el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE;
2235 status = smb2_lock(tree, &lck);
2236 CHECK_STATUS(status, NT_STATUS_OK);
2238 /* Disconnect/Reconnect. */
2242 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
2243 torture_warning(tctx, "couldn't reconnect, bailing\n");
2249 io.in.fname = fname;
2250 io.in.durable_handle = &h;
2251 io.in.lease_request = &ls;
2253 status = smb2_create(tree, mem_ctx, &io);
2254 CHECK_STATUS(status, NT_STATUS_OK);
2255 h = io.out.file.handle;
2257 lck.in.file.handle = h;
2258 el[0].flags = SMB2_LOCK_FLAG_UNLOCK;
2259 status = smb2_lock(tree, &lck);
2260 CHECK_STATUS(status, NT_STATUS_OK);
2263 smb2_util_close(tree, h);
2264 smb2_util_unlink(tree, fname);
2271 * Open with a RH lease, disconnect, open in another tree, reconnect.
2273 * This test actually demonstrates a minimum level of respect for the durable
2274 * open in the face of another open. As long as this test shows an inability to
2275 * reconnect after an open, the oplock/lease tests above will certainly
2276 * demonstrate an error on reconnect.
2278 static bool test_durable_open_open2_lease(struct torture_context *tctx,
2279 struct smb2_tree *tree1,
2280 struct smb2_tree *tree2)
2282 TALLOC_CTX *mem_ctx = talloc_new(tctx);
2283 struct smb2_create io1, io2;
2284 struct smb2_lease ls;
2285 struct smb2_handle h1 = {{0}};
2286 struct smb2_handle h2 = {{0}};
2292 struct smbcli_options options;
2294 options = tree1->session->transport->options;
2296 caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
2297 if (!(caps & SMB2_CAP_LEASING)) {
2298 torture_skip(tctx, "leases are not supported");
2302 * Choose a random name and random lease in case the state is left a
2306 snprintf(fname, 256, "durable_open_open2_lease_%s.dat",
2307 generate_random_str(tctx, 8));
2310 smb2_util_unlink(tree1, fname);
2312 /* Create with lease */
2313 smb2_lease_create_share(&io1, &ls, false /* dir */, fname,
2314 smb2_util_share_access(""),
2316 smb2_util_lease_state("RH"));
2317 io1.in.durable_open = true;
2319 status = smb2_create(tree1, mem_ctx, &io1);
2320 CHECK_STATUS(status, NT_STATUS_OK);
2321 h1 = io1.out.file.handle;
2322 CHECK_VAL(io1.out.durable_open, true);
2323 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
2325 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
2326 CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease);
2327 CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease);
2328 CHECK_VAL(io1.out.lease_response.lease_state,
2329 smb2_util_lease_state("RH"));
2335 /* Open the file in tree2 */
2336 smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE);
2338 status = smb2_create(tree2, mem_ctx, &io2);
2339 CHECK_STATUS(status, NT_STATUS_OK);
2340 h2 = io2.out.file.handle;
2341 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
2344 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree1)) {
2345 torture_warning(tctx, "couldn't reconnect, bailing\n");
2351 io1.in.fname = fname;
2352 io1.in.durable_handle = &h1;
2353 io1.in.lease_request = &ls;
2356 * Windows7 (build 7000) will give away an open immediately if the
2357 * original client is gone. (ZML: This seems like a bug. It should give
2358 * some time for the client to reconnect!)
2360 status = smb2_create(tree1, mem_ctx, &io1);
2361 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
2362 h1 = io1.out.file.handle;
2366 smb2_util_close(tree1, h1);
2367 smb2_util_unlink(tree1, fname);
2371 smb2_util_close(tree2, h2);
2372 smb2_util_unlink(tree2, fname);
2379 * Open with a batch oplock, disconnect, open in another tree, reconnect.
2381 * This test actually demonstrates a minimum level of respect for the durable
2382 * open in the face of another open. As long as this test shows an inability to
2383 * reconnect after an open, the oplock/lease tests above will certainly
2384 * demonstrate an error on reconnect.
2386 static bool test_durable_open_open2_oplock(struct torture_context *tctx,
2387 struct smb2_tree *tree1,
2388 struct smb2_tree *tree2)
2390 TALLOC_CTX *mem_ctx = talloc_new(tctx);
2391 struct smb2_create io1, io2;
2392 struct smb2_handle h1 = {{0}};
2393 struct smb2_handle h2 = {{0}};
2399 * Choose a random name and random lease in case the state is left a
2402 snprintf(fname, 256, "durable_open_open2_oplock_%s.dat",
2403 generate_random_str(tctx, 8));
2406 smb2_util_unlink(tree1, fname);
2408 /* Create with batch oplock */
2409 smb2_oplock_create(&io1, fname, SMB2_OPLOCK_LEVEL_BATCH);
2410 io1.in.durable_open = true;
2412 status = smb2_create(tree1, mem_ctx, &io1);
2413 CHECK_STATUS(status, NT_STATUS_OK);
2414 h1 = io1.out.file.handle;
2415 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
2416 CHECK_VAL(io1.out.durable_open, true);
2417 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
2423 /* Open the file in tree2 */
2424 smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE);
2426 status = smb2_create(tree2, mem_ctx, &io2);
2427 CHECK_STATUS(status, NT_STATUS_OK);
2428 h2 = io2.out.file.handle;
2429 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
2432 if (!torture_smb2_connection(tctx, &tree1)) {
2433 torture_warning(tctx, "couldn't reconnect, bailing\n");
2439 io1.in.fname = fname;
2440 io1.in.durable_handle = &h1;
2442 status = smb2_create(tree1, mem_ctx, &io1);
2443 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
2444 h1 = io1.out.file.handle;
2447 smb2_util_close(tree2, h2);
2448 smb2_util_unlink(tree2, fname);
2449 if (tree1 != NULL) {
2450 smb2_util_close(tree1, h1);
2451 smb2_util_unlink(tree1, fname);
2461 * test behaviour with initial allocation size
2463 static bool test_durable_open_alloc_size(struct torture_context *tctx,
2464 struct smb2_tree *tree)
2467 TALLOC_CTX *mem_ctx = talloc_new(tctx);
2469 struct smb2_handle _h;
2470 struct smb2_handle *h = NULL;
2471 struct smb2_create io;
2473 uint64_t previous_session_id;
2474 uint64_t alloc_size_step;
2475 uint64_t initial_alloc_size = 0x100;
2476 const uint8_t *b = NULL;
2477 struct smbcli_options options;
2479 options = tree->session->transport->options;
2481 /* Choose a random name in case the state is left a little funky. */
2482 snprintf(fname, 256, "durable_open_alloc_size_%s.dat",
2483 generate_random_str(tctx, 8));
2485 smb2_util_unlink(tree, fname);
2487 smb2_oplock_create_share(&io, fname,
2488 smb2_util_share_access(""),
2489 smb2_util_oplock_level("b"));
2490 io.in.durable_open = true;
2491 io.in.alloc_size = initial_alloc_size;
2493 status = smb2_create(tree, mem_ctx, &io);
2494 CHECK_STATUS(status, NT_STATUS_OK);
2495 _h = io.out.file.handle;
2497 CHECK_NOT_VAL(io.out.alloc_size, 0);
2498 alloc_size_step = io.out.alloc_size;
2499 CHECK_CREATED_SIZE(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE,
2500 alloc_size_step, 0);
2501 CHECK_VAL(io.out.durable_open, true);
2502 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
2504 /* prepare buffer */
2505 b = talloc_zero_size(mem_ctx, alloc_size_step);
2508 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
2510 /* disconnect, reconnect and then do durable reopen */
2514 if (!torture_smb2_connection_ext(tctx, previous_session_id,
2517 torture_warning(tctx, "couldn't reconnect, bailing\n");
2523 io.in.fname = fname;
2524 io.in.durable_handle = h;
2527 status = smb2_create(tree, mem_ctx, &io);
2528 CHECK_STATUS(status, NT_STATUS_OK);
2529 CHECK_CREATED_SIZE(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE,
2530 alloc_size_step, 0);
2531 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
2532 _h = io.out.file.handle;
2535 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
2537 /* write one byte */
2538 status = smb2_util_write(tree, *h, b, 0, 1);
2539 CHECK_STATUS(status, NT_STATUS_OK);
2541 /* disconnect, reconnect and then do durable reopen */
2545 if (!torture_smb2_connection_ext(tctx, previous_session_id,
2548 torture_warning(tctx, "couldn't reconnect, bailing\n");
2554 io.in.fname = fname;
2555 io.in.durable_handle = h;
2558 status = smb2_create(tree, mem_ctx, &io);
2559 CHECK_STATUS(status, NT_STATUS_OK);
2560 CHECK_CREATED_SIZE(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE,
2561 alloc_size_step, 1);
2562 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
2563 _h = io.out.file.handle;
2566 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
2568 /* write more byte than initial allocation size */
2569 status = smb2_util_write(tree, *h, b, 1, alloc_size_step);
2571 /* disconnect, reconnect and then do durable reopen */
2575 if (!torture_smb2_connection_ext(tctx, previous_session_id,
2578 torture_warning(tctx, "couldn't reconnect, bailing\n");
2584 io.in.fname = fname;
2585 io.in.durable_handle = h;
2588 status = smb2_create(tree, mem_ctx, &io);
2589 CHECK_STATUS(status, NT_STATUS_OK);
2590 CHECK_CREATED_SIZE(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE,
2591 alloc_size_step * 2, alloc_size_step + 1);
2592 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
2593 _h = io.out.file.handle;
2598 smb2_util_close(tree, *h);
2601 smb2_util_unlink(tree, fname);
2605 talloc_free(mem_ctx);
2611 * test behaviour when a disconnect happens while creating a read-only file
2613 static bool test_durable_open_read_only(struct torture_context *tctx,
2614 struct smb2_tree *tree)
2617 TALLOC_CTX *mem_ctx = talloc_new(tctx);
2619 struct smb2_handle _h;
2620 struct smb2_handle *h = NULL;
2621 struct smb2_create io;
2623 uint64_t previous_session_id;
2624 const uint8_t b = 0;
2625 uint64_t alloc_size = 0;
2626 struct smbcli_options options;
2628 options = tree->session->transport->options;
2630 /* Choose a random name in case the state is left a little funky. */
2631 snprintf(fname, 256, "durable_open_initial_alloc_%s.dat",
2632 generate_random_str(tctx, 8));
2634 smb2_util_unlink(tree, fname);
2636 smb2_oplock_create_share(&io, fname,
2637 smb2_util_share_access(""),
2638 smb2_util_oplock_level("b"));
2639 io.in.durable_open = true;
2640 io.in.file_attributes = FILE_ATTRIBUTE_READONLY;
2642 status = smb2_create(tree, mem_ctx, &io);
2643 CHECK_STATUS(status, NT_STATUS_OK);
2644 _h = io.out.file.handle;
2646 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_ARCHIVE);
2647 CHECK_VAL(io.out.durable_open, true);
2648 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
2650 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
2652 /* write one byte */
2653 status = smb2_util_write(tree, *h, &b, 0, 1);
2654 CHECK_STATUS(status, NT_STATUS_OK);
2656 /* disconnect, reconnect and then do durable reopen */
2660 if (!torture_smb2_connection_ext(tctx, previous_session_id,
2663 torture_warning(tctx, "couldn't reconnect, bailing\n");
2669 io.in.fname = fname;
2670 io.in.durable_handle = h;
2673 status = smb2_create(tree, mem_ctx, &io);
2674 CHECK_STATUS(status, NT_STATUS_OK);
2675 alloc_size = io.out.alloc_size;
2676 CHECK_CREATED_SIZE(&io, EXISTED,
2677 FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_ARCHIVE,
2679 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
2680 _h = io.out.file.handle;
2683 /* write one byte */
2684 status = smb2_util_write(tree, *h, &b, 1, 1);
2685 CHECK_STATUS(status, NT_STATUS_OK);
2689 union smb_setfileinfo sfinfo;
2691 ZERO_STRUCT(sfinfo);
2692 sfinfo.basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION;
2693 sfinfo.basic_info.in.file.handle = *h;
2694 sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_NORMAL;
2695 smb2_setinfo_file(tree, &sfinfo);
2697 smb2_util_close(tree, *h);
2700 smb2_util_unlink(tree, fname);
2704 talloc_free(mem_ctx);
2710 * durable open with oplock, disconnect, exit
2712 static bool test_durable_open_oplock_disconnect(struct torture_context *tctx,
2713 struct smb2_tree *tree)
2715 TALLOC_CTX *mem_ctx = talloc_new(tctx);
2716 struct smb2_create io;
2717 struct smb2_handle _h;
2718 struct smb2_handle *h = NULL;
2723 snprintf(fname, 256, "durable_open_oplock_disconnect_%s.dat",
2724 generate_random_str(mem_ctx, 8));
2726 smb2_util_unlink(tree, fname);
2728 smb2_oplock_create(&io, fname, SMB2_OPLOCK_LEVEL_BATCH);
2729 io.in.durable_open = true;
2731 status = smb2_create(tree, mem_ctx, &io);
2732 CHECK_STATUS(status, NT_STATUS_OK);
2734 _h = io.out.file.handle;
2737 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
2738 CHECK_VAL(io.out.durable_open, true);
2739 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
2748 smb2_util_close(tree, *h);
2750 smb2_util_unlink(tree, fname);
2752 talloc_free(mem_ctx);
2756 #define COMPARE_TIME_CMP(given, gelem, correct, celem, cmp) do { \
2757 const uint64_t _r = 10*1000*1000; \
2758 NTTIME _g = (given).basic_info.out.gelem; \
2759 NTTIME _gr = (_g / _r) * _r; \
2760 NTTIME _c = (correct).basic_info.out.celem; \
2761 NTTIME _cr = (_c / _r) * _r; \
2762 bool _strict = torture_setting_bool(tctx, "strict mode", false); \
2763 const char *_err = NULL; \
2764 if (_strict && (_g cmp _c)) { \
2766 } else if ((_g cmp _c) && (_gr cmp _cr)) { \
2767 /* handle filesystem without high resolution timestamps */ \
2770 if (_err != NULL) { \
2771 struct timeval _gtv; \
2772 struct timeval _ctv; \
2773 struct timeval_buf _gtvb; \
2774 struct timeval_buf _ctvb; \
2775 nttime_to_timeval(&_gtv, _g); \
2776 nttime_to_timeval(&_ctv, _c); \
2777 torture_result(tctx, TORTURE_FAIL, \
2778 __location__": %s wrong (%s.%s)%s %s (%s.%s)%s", \
2781 timeval_str_buf(&_gtv, false, true, &_gtvb), \
2784 timeval_str_buf(&_ctv, false, true, &_ctvb)); \
2789 #define COMPARE_WRITE_TIME_CMP(given, correct, cmp) do { \
2790 COMPARE_TIME_CMP(given, write_time, correct, write_time, cmp); \
2792 #define COMPARE_WRITE_TIME_EQUAL(given,correct) \
2793 COMPARE_WRITE_TIME_CMP(given,correct,!=)
2794 #define COMPARE_WRITE_TIME_GREATER(given,correct) \
2795 COMPARE_WRITE_TIME_CMP(given,correct,<=)
2796 #define COMPARE_WRITE_TIME_LESS(given,correct) \
2797 COMPARE_WRITE_TIME_CMP(given,correct,>=)
2799 #define COMPARE_ACCESS_TIME_CMP(given, correct, cmp) do { \
2800 COMPARE_TIME_CMP(given, access_time, correct, access_time, cmp); \
2802 #define COMPARE_ACCESS_TIME_EQUAL(given,correct) \
2803 COMPARE_ACCESS_TIME_CMP(given,correct,!=)
2805 #define COMPARE_CHANGE_TIME_CMP(given, correct, cmp) do { \
2806 COMPARE_TIME_CMP(given, change_time, correct, change_time, cmp); \
2808 #define COMPARE_CHANGE_TIME_EQUAL(given,correct) \
2809 COMPARE_CHANGE_TIME_CMP(given,correct,!=)
2811 #define COMPARE_CREATE_TIME_CMP(given, correct, cmp) do { \
2812 COMPARE_TIME_CMP(given, create_time, correct, create_time, cmp); \
2814 #define COMPARE_CREATE_TIME_EQUAL(given,correct) \
2815 COMPARE_CREATE_TIME_CMP(given,correct,!=)
2817 #define COMPARE_ALL_TIMES_EQUAL(given,correct) do { \
2818 COMPARE_ACCESS_TIME_EQUAL(given,correct); \
2819 COMPARE_WRITE_TIME_EQUAL(given,correct); \
2820 COMPARE_CHANGE_TIME_EQUAL(given,correct); \
2821 COMPARE_CREATE_TIME_EQUAL(given,correct); \
2824 #define COMPARE_TIMES_AFTER_WRITE(given,correct) do { \
2825 COMPARE_ACCESS_TIME_GREATER(given,correct); \
2826 COMPARE_WRITE_TIME_GREATER(given,correct); \
2827 COMPARE_CHANGE_TIME_GREATER(given,correct); \
2828 COMPARE_CREATE_TIME_EQUAL(given,correct); \
2831 #define GET_INFO_FILE(tree, finfo) do { \
2832 struct timeval _atv; \
2833 struct timeval _wtv; \
2834 struct timeval_buf _atvb; \
2835 struct timeval_buf _wtvb; \
2837 _status = smb2_getinfo_file(tree, tctx, &finfo); \
2838 if (!NT_STATUS_IS_OK(_status)) { \
2840 torture_result(tctx, TORTURE_FAIL, __location__": fileinfo failed: %s", \
2841 nt_errstr(_status)); \
2844 nttime_to_timeval(&_atv, finfo.basic_info.out.access_time); \
2845 nttime_to_timeval(&_wtv, finfo.basic_info.out.write_time); \
2846 torture_comment(tctx, "fileinfo(%s,%s): Access(%s) Write(%s)\n", \
2848 timeval_str_buf(&_atv, false, true, &_atvb), \
2849 timeval_str_buf(&_wtv, false, true, &_wtvb)); \
2852 #define GET_INFO_BOTH(finfo1, finfo2) do { \
2853 GET_INFO_FILE(tree2, finfo2); \
2854 GET_INFO_FILE(tree1, finfo1); \
2855 COMPARE_ALL_TIMES_EQUAL(finfo1, finfo2); \
2858 #define SET_INFO_FILE_EX(finfo, wrtime, tree, tfnum) do { \
2860 union smb_setfileinfo sfinfo; \
2861 sfinfo.basic_info.level = RAW_SFILEINFO_BASIC_INFO; \
2862 sfinfo.basic_info.in.file.fnum = tfnum; \
2863 sfinfo.basic_info.in.create_time = 0; \
2864 sfinfo.basic_info.in.access_time = 0; \
2865 unix_to_nt_time(&sfinfo.basic_info.in.write_time, (wrtime)); \
2866 sfinfo.basic_info.in.change_time = 0; \
2867 sfinfo.basic_info.in.attrib = finfo1.basic_info.out.attrib; \
2868 _status = smb_raw_setfileinfo(tree, &sfinfo); \
2869 if (!NT_STATUS_IS_OK(_status)) { \
2870 torture_result(tctx, TORTURE_FAIL, __location__": setfileinfo failed: %s", \
2871 nt_errstr(_status)); \
2876 #define SET_INFO_FILE(finfo, wrtime) \
2877 SET_INFO_FILE_EX(finfo, wrtime, cli->tree, fnum1)
2879 #define SET_INFO_FILE_NS(finfo, wrtime, ns, tree, tfnum) do { \
2881 union smb_setfileinfo sfinfo; \
2882 sfinfo.basic_info.level = RAW_SFILEINFO_BASIC_INFO; \
2883 sfinfo.basic_info.in.file.fnum = tfnum; \
2884 sfinfo.basic_info.in.create_time = 0; \
2885 sfinfo.basic_info.in.access_time = 0; \
2886 unix_to_nt_time(&sfinfo.basic_info.in.write_time, (wrtime)); \
2887 sfinfo.basic_info.in.write_time += (ns); \
2888 sfinfo.basic_info.in.change_time = 0; \
2889 sfinfo.basic_info.in.attrib = finfo1.basic_info.out.attrib; \
2890 _status = smb_raw_setfileinfo(tree, &sfinfo); \
2891 if (!NT_STATUS_IS_OK(_status)) { \
2892 torture_result(tctx, TORTURE_FAIL, __location__": setfileinfo failed: %s", \
2893 nt_errstr(_status)); \
2899 static bool test_delay_writetime(struct torture_context *tctx,
2901 double normal_delay,
2902 const char *description,
2903 bool (*get_basic_info_cb)(void *private_data,
2904 union smb_fileinfo *finfo),
2905 bool (*write_data_cb)(void *private_data),
2908 union smb_fileinfo finfo0, finfo1, finfoT;
2909 struct timeval before_write;
2910 struct timeval after_write;
2911 struct timeval start;
2913 double sec = used_delay / normal_delay;
2914 double msec = 1000 * sec;
2915 double nsec = 1000 * msec;
2916 double expected_delay = used_delay / nsec;
2917 double min_delay = expected_delay * 0.1;
2918 double max_delay = 5 * expected_delay;
2922 torture_comment(tctx, "START: %s\n", description);
2924 /* get the initial times */
2925 ok = get_basic_info_cb(private_data, &finfo0);
2926 torture_assert(tctx, ok, "get_basic_info_cb: finfo0");
2929 * Make sure the time doesn't change during the next 5 seconds
2931 start = timeval_current();
2932 end = timeval_add(&start, max_delay * 1.25, 0);
2933 while (!timeval_expired(&end)) {
2934 smb_msleep(1 * msec);
2935 torture_comment(tctx, "Check for no change\n");
2936 ok = get_basic_info_cb(private_data, &finfoT);
2937 torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
2938 COMPARE_ALL_TIMES_EQUAL(finfoT, finfo0);
2941 ok = get_basic_info_cb(private_data, &finfoT);
2942 torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
2943 COMPARE_WRITE_TIME_EQUAL(finfoT, finfo0);
2946 * make sure the write time is updated 2 seconds later
2947 * calcuated from the first write
2948 * (but expect upto 5 seconds extra time for a busy server)
2950 torture_comment(tctx, "Do a write on the file handle\n");
2951 before_write = timeval_current();
2952 ok = write_data_cb(private_data);
2953 after_write = timeval_current();
2954 torture_assert(tctx, ok, "write_data_cb");
2955 (void)(&before_write == &after_write);
2957 start = timeval_current();
2958 end = timeval_add(&start, max_delay * 2, 0);
2959 while (!timeval_expired(&end)) {
2960 struct timeval before_get;
2961 struct timeval after_get;
2963 torture_comment(tctx, "Wait for change\n");
2964 before_get = timeval_current();
2965 ok = get_basic_info_cb(private_data, &finfoT);
2966 after_get = timeval_current();
2967 torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
2968 (void)(&before_get == &after_get);
2970 if (finfoT.basic_info.out.write_time > finfo0.basic_info.out.write_time) {
2971 double delayS = timeval_elapsed2(&after_write, &before_get);
2972 double delayL = timeval_elapsed2(&before_write, &after_get);
2974 torture_comment(tctx, "Server updated write_time after %.2f/%.2f seconds "
2975 "(min delay == %.2f, max delay == %.2f)\n",
2976 delayS, delayL, min_delay, max_delay);
2977 torture_assert(tctx, (delayL >= min_delay),
2978 "Server updated write_time to early!");
2979 torture_assert(tctx, (delayS <= max_delay),
2980 "Server didn't update write_time!");
2982 COMPARE_WRITE_TIME_GREATER(finfoT, finfo0);
2986 COMPARE_ALL_TIMES_EQUAL(finfoT, finfo0);
2987 smb_msleep(0.01 * msec);
2990 ok = get_basic_info_cb(private_data, &finfo1);
2991 torture_assert(tctx, ok, "get_basic_info_cb: finfo1");
2992 COMPARE_WRITE_TIME_GREATER(finfo1, finfo0);
2995 * Make sure the time doesn't change during the next 5 seconds
2997 start = timeval_current();
2998 end = timeval_add(&start, max_delay * 1.25, 0);
2999 while (!timeval_expired(&end)) {
3000 smb_msleep(1 * msec);
3001 torture_comment(tctx, "Check for no additional change\n");
3002 ok = get_basic_info_cb(private_data, &finfoT);
3003 torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
3004 COMPARE_ALL_TIMES_EQUAL(finfoT, finfo1);
3007 ok = get_basic_info_cb(private_data, &finfoT);
3008 torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
3009 COMPARE_ALL_TIMES_EQUAL(finfoT, finfo1);
3011 start = timeval_current();
3012 end = timeval_add(&start, 7 * sec, 0);
3013 while (!timeval_expired(&end)) {
3014 union smb_fileinfo finfoT0;
3015 union smb_fileinfo finfoT1;
3017 ok = get_basic_info_cb(private_data, &finfoT0);
3018 torture_assert(tctx, ok, "get_basic_info_cb: finfoT0");
3020 torture_comment(tctx, "Do a write on the file handle\n");
3021 ok = write_data_cb(private_data);
3022 torture_assert_ntstatus_ok(tctx, status);
3024 ok = get_basic_info_cb(private_data, &finfoT1);
3025 torture_assert(tctx, ok, "get_basic_info_cb: finfoT1");
3026 COMPARE_WRITE_TIME_EQUAL(finfoT1, finfoT0);
3028 if (finfo.basic_info.out.write_time > finfo0.basic_info.out.write_time) {
3029 double diff = timeval_elapsed(&start);
3030 if (diff < (used_delay / (double)1000000)) {
3031 torture_result(tctx, TORTURE_FAIL, __location__": Server updated write_time after %.2f seconds "
3032 "(write time update delay == %.2f) (wrong!)\n",
3033 diff, used_delay / (double)1000000);
3038 torture_comment(tctx, "Server updated write_time after %.2f seconds "
3044 ok = get_basic_info_cb(private_data, &finfo1);
3045 torture_assert_ntstatus_ok(tctx, status);
3047 COMPARE_WRITE_TIME_EQUAL(finfo1, finfoT);
3048 smb_msleep(0.5 * msec);
3051 GET_INFO_BOTH(finfo1, c2finfo1);
3052 COMPARE_WRITE_TIME_GREATER(finfo1, finfo0);
3058 struct test_durable_open_delaywrite1_state {
3059 struct torture_context *tctx;
3060 struct smb2_tree *tree1;
3061 struct smb2_tree *tree2;
3062 struct smb2_handle *h1;
3063 struct smb2_handle *h2;
3066 static bool test_durable_open_delaywrite1_get_info(void *private_data,
3067 union smb_fileinfo *finfo)
3069 struct test_durable_open_delaywrite1_state *state =
3070 (struct test_durable_open_delaywrite1_state *)private_data;
3071 struct torture_context *tctx = state->tctx;
3072 union smb_fileinfo t1finfo;
3073 union smb_fileinfo t2finfo;
3076 ZERO_STRUCTP(finfo);
3078 ZERO_STRUCT(t1finfo);
3079 t1finfo.basic_info.level = RAW_FILEINFO_BASIC_INFORMATION;
3080 t1finfo.basic_info.in.file.handle = *state->h1;
3082 ZERO_STRUCT(t2finfo);
3083 t2finfo.basic_info.level = RAW_FILEINFO_BASIC_INFORMATION;
3084 t2finfo.basic_info.in.file.handle = *state->h2;
3086 GET_INFO_FILE(state->tree2, t2finfo);
3087 GET_INFO_FILE(state->tree1, t1finfo);
3088 COMPARE_ALL_TIMES_EQUAL(t1finfo, t2finfo);
3090 finfo->basic_info.out = t1finfo.basic_info.out;
3095 static bool test_durable_open_delaywrite1_write_data(void *private_data)
3097 struct test_durable_open_delaywrite1_state *state =
3098 (struct test_durable_open_delaywrite1_state *)private_data;
3099 struct torture_context *tctx = state->tctx;
3100 struct smb2_write wr;
3105 wr.in.file.handle = *state->h1;
3107 wr.in.data = data_blob_const("x", 1);
3108 status = smb2_write(state->tree1, &wr);
3109 CHECK_STATUS(status, NT_STATUS_OK);
3110 torture_assert_int_equal_goto(tctx, wr.out.nwritten, 1,
3111 ret, done, "smb2_write");
3117 static bool test_durable_open_delaywrite1(struct torture_context *tctx,
3118 struct smb2_tree *tree1,
3119 struct smb2_tree *tree2)
3121 struct test_durable_open_delaywrite1_state state = {
3127 TALLOC_CTX *mem_ctx = talloc_new(tctx);
3129 struct smb2_handle _h;
3130 struct smb2_handle *h = NULL;
3131 struct smb2_handle _h2;
3132 struct smb2_handle *h2 = NULL;
3133 struct smb2_create cr;
3134 struct smb2_create cr2;
3135 union smb_fileinfo finfoCR, finfo0, finfo1, finfo2, finfo3, finfo4, finfoT, finfoCL;
3136 union smb_fileinfo c2finfoCR, c2finfo0, c2finfo1, c2finfo2, c2finfo3, c2finfo4, c2finfoT, c2finfoCL;
3137 struct smb2_close cl;
3138 struct smb2_close cl2;
3139 struct smb2_write wr;
3141 struct timeval start;
3143 //double used_delay = torture_setting_int(tctx, "writetimeupdatedelay", 2000000);
3144 //double used_delay = torture_setting_int(tctx, "writetimeupdatedelay", 1000000);
3145 double normal_delay = 1000000;
3146 double used_delay = torture_setting_int(tctx, "writetimeupdatedelay", normal_delay);
3147 //double normal_delay = 1000000;
3148 //int normal_delay = 2000000;
3149 double sec = ((double)used_delay) / ((double)normal_delay);
3150 int msec = 1000 * sec;
3152 bool changed = false;
3155 /* Choose a random name in case the state is left a little funky. */
3156 snprintf(fname, 256, "durable_open_delaywrite1_%s.dat",
3157 generate_random_str(tctx, 8));
3159 smb2_util_unlink(tree1, fname);
3161 smb2_oplock_create_share(&cr, fname,
3162 smb2_util_share_access(""),
3163 smb2_util_oplock_level("b"));
3164 cr.in.durable_open = true;
3166 status = smb2_create(tree1, mem_ctx, &cr);
3167 CHECK_STATUS(status, NT_STATUS_OK);
3168 _h = cr.out.file.handle;
3170 CHECK_CREATED(&cr, CREATED, FILE_ATTRIBUTE_ARCHIVE);
3171 CHECK_VAL(cr.out.oplock_level, smb2_util_oplock_level("b"));
3172 CHECK_VAL(cr.out.durable_open, true);
3173 CHECK_VAL(cr.out.durable_open_v2, false);
3174 CHECK_VAL(cr.out.persistent_open, false);
3175 //CHECK_VAL(io.out.timeout, io.in.timeout);
3178 // NTTIME create_time;
3179 // NTTIME access_time;
3180 // NTTIME write_time;
3181 // NTTIME change_time;
3185 ZERO_STRUCT(finfoCR);
3186 finfoCR.basic_info.out.create_time = cr.out.create_time;
3187 finfoCR.basic_info.out.access_time = cr.out.access_time;
3188 finfoCR.basic_info.out.write_time = cr.out.write_time;
3189 finfoCR.basic_info.out.change_time = cr.out.change_time;
3190 finfoCR.basic_info.out.attrib = cr.out.file_attr;
3192 ZERO_STRUCT(finfo0);
3193 finfo0.basic_info.level = RAW_FILEINFO_BASIC_INFORMATION;
3194 finfo0.basic_info.in.file.handle = *h;
3201 /* get the initial times */
3202 GET_INFO_FILE(tree1, finfo0);
3203 COMPARE_WRITE_TIME_EQUAL(finfoCR, finfo0);
3205 ///* static body buffer 56 (0x38) bytes */
3206 //uint8_t security_flags; /* SMB2_SECURITY_* */
3207 //uint8_t oplock_level; /* SMB2_OPLOCK_LEVEL_* */
3208 //uint32_t impersonation_level; /* SMB2_IMPERSONATION_* */
3209 //uint64_t create_flags;
3210 //uint64_t reserved;
3211 //uint32_t desired_access;
3212 //uint32_t file_attributes;
3213 //uint32_t share_access; /* NTCREATEX_SHARE_ACCESS_* */
3214 //uint32_t create_disposition; /* NTCREATEX_DISP_* */
3215 //uint32_t create_options; /* NTCREATEX_OPTIONS_* */
3217 ///* uint16_t fname_ofs */
3218 ///* uint16_t fname_size */
3219 ///* uint32_t blob_ofs; */
3220 ///* uint32_t blob_size; */
3222 ///* dynamic body */
3223 //const char *fname;
3225 ///* now some optional parameters - encoded as tagged blobs */
3226 //struct smb_ea_list eas;
3227 //uint64_t alloc_size;
3228 //struct security_descriptor *sec_desc;
3229 //bool durable_open;
3230 //struct smb2_handle *durable_handle;
3232 ///* data for durable handle v2 */
3233 //bool durable_open_v2;
3234 //struct GUID create_guid;
3235 //bool persistent_open;
3237 //struct smb2_handle *durable_handle_v2;
3239 //bool query_maximal_access;
3241 //bool query_on_disk_id;
3242 //struct smb2_lease *lease_request;
3243 //struct smb2_lease *lease_request_v2;
3245 //struct GUID *app_instance_id;
3247 ///* and any additional blobs the caller wants */
3248 //struct smb2_create_blobs blobs;
3251 //union smb_handle file;
3253 ///* static body buffer 88 (0x58) bytes */
3254 ///* uint16_t buffer_code; 0x59 = 0x58 + 1 */
3255 //uint8_t oplock_level;
3257 //uint32_t create_action;
3258 //NTTIME create_time;
3259 //NTTIME access_time;
3260 //NTTIME write_time;
3261 //NTTIME change_time;
3262 //uint64_t alloc_size;
3264 //uint32_t file_attr;
3265 //uint32_t reserved2;
3266 ///* struct smb2_handle handle;*/
3267 ///* uint32_t blob_ofs; */
3268 ///* uint32_t blob_size; */
3270 ///* optional return values matching tagged values in the call */
3271 //uint32_t maximal_access;
3272 //uint8_t on_disk_id[32];
3273 //struct smb2_lease lease_response;
3274 //struct smb2_lease lease_response_v2;
3275 //bool durable_open;
3277 ///* durable handle v2 */
3278 //bool durable_open_v2;
3279 //bool persistent_open;
3282 ///* tagged blobs in the reply */
3283 //struct smb2_create_blobs blobs;
3286 cr2.in.desired_access = SEC_FILE_READ_ATTRIBUTE;//FILE_READ_ATTRIBUTES;
3287 cr2.in.durable_open = false;
3288 cr2.in.oplock_level = 0;
3289 cr2.in.create_disposition = NTCREATEX_DISP_OPEN;
3290 status = smb2_create(tree2, mem_ctx, &cr2);
3291 CHECK_STATUS(status, NT_STATUS_OK);
3292 _h2 = cr2.out.file.handle;
3294 CHECK_CREATED(&cr2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
3295 CHECK_VAL(cr2.out.oplock_level, 0);
3296 CHECK_VAL(cr2.out.durable_open, false);
3297 CHECK_VAL(cr2.out.durable_open_v2, false);
3298 CHECK_VAL(cr2.out.persistent_open, false);
3300 ZERO_STRUCT(c2finfoCR);
3301 c2finfoCR.basic_info.out.create_time = cr2.out.create_time;
3302 c2finfoCR.basic_info.out.access_time = cr2.out.access_time;
3303 c2finfoCR.basic_info.out.write_time = cr2.out.write_time;
3304 c2finfoCR.basic_info.out.change_time = cr2.out.change_time;
3305 c2finfoCR.basic_info.out.attrib = cr2.out.file_attr;
3307 ZERO_STRUCT(c2finfo0);
3308 c2finfo0.basic_info.level = RAW_FILEINFO_BASIC_INFORMATION;
3309 c2finfo0.basic_info.in.file.handle = *h2;
3310 c2finfo1 = c2finfo0;
3311 c2finfo2 = c2finfo0;
3312 c2finfo3 = c2finfo0;
3313 c2finfo4 = c2finfo0;
3314 c2finfoT = c2finfo0;
3316 /* get the initial times */
3317 GET_INFO_FILE(tree2, c2finfo0);
3318 COMPARE_WRITE_TIME_EQUAL(c2finfoCR, c2finfo0);
3323 ok = test_delay_writetime(tctx, used_delay, normal_delay,
3325 test_durable_open_delaywrite1_get_info,
3326 test_durable_open_delaywrite1_write_data,
3328 torture_assert(tctx, ok, "test_delay_writetime");
3331 * make sure the write time is updated 2 seconds later
3332 * calcuated from the first write
3333 * (but expect upto 5 seconds extra time for a busy server)
3335 start = timeval_current();
3336 end = timeval_add(&start, 7 * sec, 0);
3337 while (!timeval_expired(&end)) {
3339 torture_comment(tctx, "Do a write on the file handle\n");
3340 GET_INFO_BOTH(finfoT, c2finfoT);
3342 wr.in.file.handle = *h;
3344 wr.in.data = data_blob_const("x", 1);
3345 status = smb2_write(tree1, &wr);
3346 CHECK_STATUS(status, NT_STATUS_OK);
3347 written = wr.out.nwritten;
3349 torture_result(tctx, TORTURE_FAIL, __location__": written gave %d - should have been 1", (int)written);
3353 /* get the times after the write */
3354 GET_INFO_BOTH(finfo1, c2finfo1);
3356 COMPARE_WRITE_TIME_GREATER(finfo1, finfoT);
3359 if (finfo1.basic_info.out.write_time > finfo0.basic_info.out.write_time) {
3360 double diff = timeval_elapsed(&start);
3361 if (diff < (used_delay / (double)1000000)) {
3362 torture_result(tctx, TORTURE_FAIL, __location__": Server updated write_time after %.2f seconds "
3363 "(write time update delay == %.2f) (wrong!)\n",
3364 diff, used_delay / (double)1000000);
3369 torture_comment(tctx, "Server updated write_time after %.2f seconds "
3375 COMPARE_WRITE_TIME_EQUAL(finfo1, finfoT);
3376 smb_msleep(0.5 * msec);
3380 GET_INFO_BOTH(finfo1, c2finfo1);
3381 COMPARE_WRITE_TIME_GREATER(finfo1, finfo0);
3383 /* sure any further write doesn't update the write time */
3384 start = timeval_current();
3385 end = timeval_add(&start, 15 * sec, 0);
3386 while (!timeval_expired(&end)) {
3388 torture_comment(tctx, "Do a write on the file handle\n");
3390 wr.in.file.handle = *h;
3392 wr.in.data = data_blob_const("x", 1);
3393 status = smb2_write(tree1, &wr);
3394 CHECK_STATUS(status, NT_STATUS_OK);
3395 written = wr.out.nwritten;
3397 torture_result(tctx, TORTURE_FAIL, __location__": written gave %d - should have been 1", (int)written);
3401 /* get the times after the write */
3402 GET_INFO_BOTH(finfo2, c2finfo2);
3404 if (finfo2.basic_info.out.write_time > finfo1.basic_info.out.write_time) {
3405 double diff = timeval_elapsed(&start);
3406 torture_result(tctx, TORTURE_FAIL, __location__": Server updated write_time after %.2f seconds "
3412 smb_msleep(0.1 * msec);
3413 //smb_msleep(1 * msec);
3416 GET_INFO_BOTH(finfo2, c2finfo2);
3417 //COMPARE_WRITE_TIME_EQUAL(finfo2, finfo1);
3418 COMPARE_WRITE_TIME_GREATER(finfo2, finfo1);
3419 if (finfo2.basic_info.out.write_time == finfo1.basic_info.out.write_time) {
3420 torture_comment(tctx, "Server did not update write_time (correct)\n");
3423 /* sure any further write doesn't update the write time */
3424 start = timeval_current();
3425 end = timeval_add(&start, 15 * sec, 0);
3426 while (!timeval_expired(&end)) {
3428 torture_comment(tctx, "Check on both file handles\n");
3429 GET_INFO_BOTH(finfo3, c2finfo3);
3431 if (finfo3.basic_info.out.write_time > finfo2.basic_info.out.write_time) {
3432 double diff = timeval_elapsed(&start);
3433 torture_result(tctx, TORTURE_FAIL, __location__": Server updated write_time after %.2f seconds "
3439 smb_msleep(0.1 * msec);
3440 //smb_msleep(1 * msec);
3442 GET_INFO_BOTH(finfo3, c2finfo3);
3443 //COMPARE_WRITE_TIME_EQUAL(finfo3, finfo2);
3446 torture_comment(tctx, "Sleep ...\n");
3447 smb_msleep(5 * msec);
3448 torture_comment(tctx, "... continue\n");
3450 GET_INFO_BOTH(finfo4, c2finfo4);
3451 COMPARE_WRITE_TIME_EQUAL(finfo4, finfo3);
3454 * the close updates the write time to the time of the close
3455 * and not to the time of the last write!
3457 torture_comment(tctx, "Close the file handle\n");
3460 cl.in.file.handle = *h;
3461 cl.in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION;
3462 status = smb2_close(tree1, &cl);
3463 CHECK_STATUS(status, NT_STATUS_OK);
3465 ZERO_STRUCT(finfoCL);
3466 finfoCL.basic_info.out.create_time = cl.out.create_time;
3467 finfoCL.basic_info.out.access_time = cl.out.access_time;
3468 finfoCL.basic_info.out.write_time = cl.out.write_time;
3469 finfoCL.basic_info.out.change_time = cl.out.change_time;
3470 finfoCL.basic_info.out.attrib = cl.out.file_attr;
3471 COMPARE_WRITE_TIME_GREATER(finfoCL, finfo3);
3474 cl2.in.file.handle = *h2;
3475 cl2.in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION;
3476 status = smb2_close(tree2, &cl2);
3477 CHECK_STATUS(status, NT_STATUS_OK);
3479 ZERO_STRUCT(c2finfoCL);
3480 c2finfoCL.basic_info.out.create_time = cl2.out.create_time;
3481 c2finfoCL.basic_info.out.access_time = cl2.out.access_time;
3482 c2finfoCL.basic_info.out.write_time = cl2.out.write_time;
3483 c2finfoCL.basic_info.out.change_time = cl2.out.change_time;
3484 c2finfoCL.basic_info.out.attrib = cl2.out.file_attr;
3485 COMPARE_WRITE_TIME_EQUAL(c2finfoCL, finfoCL);
3487 // /* static body buffer 60 (0x3C) bytes */
3488 // /* uint16_t buffer_code; 0x3C */
3491 // NTTIME create_time;
3492 // NTTIME access_time;
3493 // NTTIME write_time;
3494 // NTTIME change_time;
3495 // uint64_t alloc_size;
3497 // uint32_t file_attr;
3500 //if (pinfo4.basic_info.out.write_time > pinfo3.basic_info.out.write_time) {
3501 // torture_comment(tctx, "Server updated the write_time on close (correct)\n");
3503 ///* try a durable reconnect while the file is still open */
3506 //io.in.durable_handle_v2 = h;
3507 //io.in.create_guid = create_guid;
3508 //status = smb2_create(tree1, mem_ctx, &io);
3509 //CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
3513 smb2_util_close(tree1, *h);
3516 smb2_util_unlink(tree1, fname);
3521 talloc_free(mem_ctx);
3527 struct torture_suite *torture_smb2_durable_open_init(TALLOC_CTX *ctx)
3529 struct torture_suite *suite =
3530 torture_suite_create(ctx, "durable-open");
3532 torture_suite_add_1smb2_test(suite, "open-oplock", test_durable_open_open_oplock);
3533 torture_suite_add_1smb2_test(suite, "open-lease", test_durable_open_open_lease);
3534 torture_suite_add_1smb2_test(suite, "reopen1", test_durable_open_reopen1);
3535 torture_suite_add_1smb2_test(suite, "reopen1a", test_durable_open_reopen1a);
3536 torture_suite_add_1smb2_test(suite, "reopen1a-lease", test_durable_open_reopen1a_lease);
3537 torture_suite_add_1smb2_test(suite, "reopen2", test_durable_open_reopen2);
3538 torture_suite_add_1smb2_test(suite, "reopen2-lease", test_durable_open_reopen2_lease);
3539 torture_suite_add_1smb2_test(suite, "reopen2-lease-v2", test_durable_open_reopen2_lease_v2);
3540 torture_suite_add_1smb2_test(suite, "reopen2a", test_durable_open_reopen2a);
3541 torture_suite_add_1smb2_test(suite, "reopen3", test_durable_open_reopen3);
3542 torture_suite_add_1smb2_test(suite, "reopen4", test_durable_open_reopen4);
3543 torture_suite_add_1smb2_test(suite, "delete_on_close1",
3544 test_durable_open_delete_on_close1);
3545 torture_suite_add_1smb2_test(suite, "delete_on_close2",
3546 test_durable_open_delete_on_close2);
3547 torture_suite_add_1smb2_test(suite, "file-position",
3548 test_durable_open_file_position);
3549 torture_suite_add_2smb2_test(suite, "oplock", test_durable_open_oplock);
3550 torture_suite_add_2smb2_test(suite, "lease", test_durable_open_lease);
3551 torture_suite_add_1smb2_test(suite, "lock-oplock", test_durable_open_lock_oplock);
3552 torture_suite_add_1smb2_test(suite, "lock-lease", test_durable_open_lock_lease);
3553 torture_suite_add_2smb2_test(suite, "open2-lease",
3554 test_durable_open_open2_lease);
3555 torture_suite_add_2smb2_test(suite, "open2-oplock",
3556 test_durable_open_open2_oplock);
3557 torture_suite_add_1smb2_test(suite, "alloc-size",
3558 test_durable_open_alloc_size);
3559 torture_suite_add_1smb2_test(suite, "read-only",
3560 test_durable_open_read_only);
3561 torture_suite_add_2smb2_test(suite, "delaywrite1",
3562 test_durable_open_delaywrite1);
3564 suite->description = talloc_strdup(suite, "SMB2-DURABLE-OPEN tests");
3569 struct torture_suite *torture_smb2_durable_open_disconnect_init(TALLOC_CTX *ctx)
3571 struct torture_suite *suite =
3572 torture_suite_create(ctx,
3573 "durable-open-disconnect");
3575 torture_suite_add_1smb2_test(suite, "open-oplock-disconnect",
3576 test_durable_open_oplock_disconnect);
3578 suite->description = talloc_strdup(suite,
3579 "SMB2-DURABLE-OPEN-DISCONNECT tests");