2 Unix SMB/CIFS implementation.
6 Copyright (C) Ralph Boehme 2019
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "libcli/smb2/smb2.h"
24 #include "libcli/smb2/smb2_calls.h"
25 #include "torture/torture.h"
26 #include "torture/util.h"
27 #include "torture/smb2/proto.h"
29 #define BASEDIR "smb2-timestamps"
30 #define FNAME "testfile.dat"
32 static bool test_close_no_attrib(struct torture_context
*tctx
,
33 struct smb2_tree
*tree
)
35 const char *filename
= BASEDIR
"/" FNAME
;
36 struct smb2_create cr
;
37 struct smb2_handle handle
= {{0}};
38 struct smb2_handle testdirh
= {{0}};
43 smb2_deltree(tree
, BASEDIR
);
45 status
= torture_smb2_testdir(tree
, BASEDIR
, &testdirh
);
46 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
47 "torture_smb2_testdir failed\n");
48 smb2_util_close(tree
, testdirh
);
50 cr
= (struct smb2_create
) {
51 .in
.desired_access
= SEC_FLAG_MAXIMUM_ALLOWED
,
52 .in
.file_attributes
= FILE_ATTRIBUTE_NORMAL
,
53 .in
.share_access
= NTCREATEX_SHARE_ACCESS_MASK
,
54 .in
.create_disposition
= NTCREATEX_DISP_OPEN_IF
,
55 .in
.impersonation_level
= NTCREATEX_IMPERSONATION_ANONYMOUS
,
59 status
= smb2_create(tree
, tctx
, &cr
);
60 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
61 "smb2_create failed\n");
62 handle
= cr
.out
.file
.handle
;
64 c
= (struct smb2_close
) {
65 .in
.file
.handle
= handle
,
68 status
= smb2_close(tree
, &c
);
69 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
73 torture_assert_u64_equal_goto(tctx
, c
.out
.create_time
, NTTIME_OMIT
,
74 ret
, done
, "Unexpected create time\n");
75 torture_assert_u64_equal_goto(tctx
, c
.out
.access_time
, NTTIME_OMIT
,
76 ret
, done
, "Unexpected access time\n");
77 torture_assert_u64_equal_goto(tctx
, c
.out
.write_time
, NTTIME_OMIT
,
78 ret
, done
, "Unexpected write time\n");
79 torture_assert_u64_equal_goto(tctx
, c
.out
.change_time
, NTTIME_OMIT
,
80 ret
, done
, "Unexpected change time\n");
81 torture_assert_u64_equal_goto(tctx
, c
.out
.size
, 0,
82 ret
, done
, "Unexpected size\n");
83 torture_assert_u64_equal_goto(tctx
, c
.out
.file_attr
, 0,
84 ret
, done
, "Unexpected attributes\n");
87 if (!smb2_util_handle_empty(handle
)) {
88 smb2_util_close(tree
, handle
);
90 smb2_deltree(tree
, BASEDIR
);
94 static bool test_time_t(struct torture_context
*tctx
,
95 struct smb2_tree
*tree
,
99 char *filename
= NULL
;
100 struct smb2_create cr
;
101 struct smb2_handle handle
= {{0}};
102 struct smb2_handle testdirh
= {{0}};
103 struct timespec ts
= { .tv_sec
= t
};
105 union smb_fileinfo gi
;
106 union smb_setfileinfo si
;
107 struct smb2_find find
;
109 union smb_search_data
*d
;
113 smb2_deltree(tree
, BASEDIR
);
115 status
= torture_smb2_testdir(tree
, BASEDIR
, &testdirh
);
116 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
117 "torture_smb2_testdir failed\n");
119 filename
= talloc_asprintf(tctx
, "%s\\%s", BASEDIR
, fname
);
120 torture_assert_not_null_goto(tctx
, filename
, ret
, done
,
121 "talloc_asprintf failed\n");
123 cr
= (struct smb2_create
) {
124 .in
.desired_access
= SEC_FLAG_MAXIMUM_ALLOWED
,
125 .in
.file_attributes
= FILE_ATTRIBUTE_NORMAL
,
126 .in
.share_access
= NTCREATEX_SHARE_ACCESS_MASK
,
127 .in
.create_disposition
= NTCREATEX_DISP_OPEN_IF
,
128 .in
.impersonation_level
= NTCREATEX_IMPERSONATION_ANONYMOUS
,
129 .in
.fname
= filename
,
132 status
= smb2_create(tree
, tctx
, &cr
);
133 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
134 "smb2_create failed\n");
135 handle
= cr
.out
.file
.handle
;
137 si
= (union smb_setfileinfo
) {
138 .basic_info
.level
= RAW_SFILEINFO_BASIC_INFORMATION
,
139 .basic_info
.in
.file
.handle
= handle
,
142 nttime
= full_timespec_to_nt_time(&ts
);
143 si
.basic_info
.in
.create_time
= nttime
;
144 si
.basic_info
.in
.write_time
= nttime
;
145 si
.basic_info
.in
.change_time
= nttime
;
147 status
= smb2_setinfo_file(tree
, &si
);
148 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
149 "smb2_setinfo_file failed\n");
151 gi
= (union smb_fileinfo
) {
152 .generic
.level
= SMB_QFILEINFO_BASIC_INFORMATION
,
153 .generic
.in
.file
.handle
= handle
,
156 status
= smb2_getinfo_file(tree
, tctx
, &gi
);
157 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
158 "smb2_getinfo_file failed\n");
160 torture_comment(tctx
, "Got: create: %s, write: %s, change: %s\n",
161 nt_time_string(tctx
, gi
.basic_info
.out
.create_time
),
162 nt_time_string(tctx
, gi
.basic_info
.out
.write_time
),
163 nt_time_string(tctx
, gi
.basic_info
.out
.change_time
));
165 torture_assert_u64_equal_goto(tctx
,
166 gi
.basic_info
.out
.create_time
,
169 "Wrong create time\n");
170 torture_assert_u64_equal_goto(tctx
,
171 gi
.basic_info
.out
.write_time
,
174 "Wrong write time\n");
175 torture_assert_u64_equal_goto(tctx
,
176 gi
.basic_info
.out
.change_time
,
179 "Wrong change time\n");
181 find
= (struct smb2_find
) {
182 .in
.file
.handle
= testdirh
,
184 .in
.max_response_size
= 0x1000,
185 .in
.level
= SMB2_FIND_ID_BOTH_DIRECTORY_INFO
,
188 status
= smb2_find_level(tree
, tree
, &find
, &count
, &d
);
189 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
190 "smb2_find_level failed\n");
192 torture_assert_u64_equal_goto(tctx
,
193 d
[0].id_both_directory_info
.create_time
,
196 "Wrong create time\n");
197 torture_assert_u64_equal_goto(tctx
,
198 d
[0].id_both_directory_info
.write_time
,
201 "Wrong write time\n");
202 torture_assert_u64_equal_goto(tctx
,
203 d
[0].id_both_directory_info
.change_time
,
206 "Wrong change time\n");
208 status
= smb2_util_close(tree
, handle
);
209 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
210 "smb2_util_close failed\n");
213 cr
= (struct smb2_create
) {
214 .in
.desired_access
= SEC_FLAG_MAXIMUM_ALLOWED
,
215 .in
.file_attributes
= FILE_ATTRIBUTE_NORMAL
,
216 .in
.share_access
= NTCREATEX_SHARE_ACCESS_MASK
,
217 .in
.create_disposition
= NTCREATEX_DISP_OPEN
,
218 .in
.impersonation_level
= NTCREATEX_IMPERSONATION_ANONYMOUS
,
219 .in
.fname
= filename
,
222 status
= smb2_create(tree
, tctx
, &cr
);
223 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
224 "smb2_create failed\n");
225 handle
= cr
.out
.file
.handle
;
227 gi
= (union smb_fileinfo
) {
228 .generic
.level
= SMB_QFILEINFO_BASIC_INFORMATION
,
229 .generic
.in
.file
.handle
= handle
,
232 status
= smb2_getinfo_file(tree
, tctx
, &gi
);
233 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
234 "smb2_getinfo_file failed\n");
236 torture_comment(tctx
, "Got: create: %s, write: %s, change: %s\n",
237 nt_time_string(tctx
, gi
.basic_info
.out
.create_time
),
238 nt_time_string(tctx
, gi
.basic_info
.out
.write_time
),
239 nt_time_string(tctx
, gi
.basic_info
.out
.change_time
));
241 torture_assert_u64_equal_goto(tctx
,
242 gi
.basic_info
.out
.create_time
,
245 "Wrong create time\n");
246 torture_assert_u64_equal_goto(tctx
,
247 gi
.basic_info
.out
.write_time
,
250 "Wrong write time\n");
251 torture_assert_u64_equal_goto(tctx
,
252 gi
.basic_info
.out
.change_time
,
255 "Wrong change time\n");
257 find
= (struct smb2_find
) {
258 .in
.continue_flags
= SMB2_CONTINUE_FLAG_RESTART
,
259 .in
.file
.handle
= testdirh
,
261 .in
.max_response_size
= 0x1000,
262 .in
.level
= SMB2_FIND_ID_BOTH_DIRECTORY_INFO
,
265 status
= smb2_find_level(tree
, tree
, &find
, &count
, &d
);
266 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
267 "smb2_find_level failed\n");
269 torture_assert_u64_equal_goto(tctx
,
270 d
[0].id_both_directory_info
.create_time
,
273 "Wrong create time\n");
274 torture_assert_u64_equal_goto(tctx
,
275 d
[0].id_both_directory_info
.write_time
,
278 "Wrong write time\n");
279 torture_assert_u64_equal_goto(tctx
,
280 d
[0].id_both_directory_info
.change_time
,
283 "Wrong change time\n");
285 status
= smb2_util_close(tree
, handle
);
286 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
287 "smb2_util_close failed\n");
291 if (!smb2_util_handle_empty(handle
)) {
292 smb2_util_close(tree
, handle
);
294 if (!smb2_util_handle_empty(testdirh
)) {
295 smb2_util_close(tree
, testdirh
);
297 smb2_deltree(tree
, BASEDIR
);
301 static bool test_time_t_15032385535(struct torture_context
*tctx
,
302 struct smb2_tree
*tree
)
304 return test_time_t(tctx
, tree
, "test_time_t_15032385535.txt",
305 15032385535 /* >> INT32_MAX, limit on ext */);
308 static bool test_time_t_10000000000(struct torture_context
*tctx
,
309 struct smb2_tree
*tree
)
311 return test_time_t(tctx
, tree
, "test_time_t_10000000000.txt",
312 10000000000 /* >> INT32_MAX */);
315 static bool test_time_t_4294967295(struct torture_context
*tctx
,
316 struct smb2_tree
*tree
)
318 return test_time_t(tctx
, tree
, "test_time_t_4294967295.txt",
319 4294967295 /* INT32_MAX */);
322 static bool test_time_t_1(struct torture_context
*tctx
,
323 struct smb2_tree
*tree
)
325 return test_time_t(tctx
, tree
, "test_time_t_1.txt", 1);
328 static bool test_time_t_0(struct torture_context
*tctx
,
329 struct smb2_tree
*tree
)
331 return test_time_t(tctx
, tree
, "test_time_t_0.txt", 0);
334 static bool test_time_t_minus_1(struct torture_context
*tctx
,
335 struct smb2_tree
*tree
)
337 return test_time_t(tctx
, tree
, "test_time_t_-1.txt", -1);
340 static bool test_time_t_minus_2(struct torture_context
*tctx
,
341 struct smb2_tree
*tree
)
343 return test_time_t(tctx
, tree
, "test_time_t_-2.txt", -2);
346 static bool test_time_t_1968(struct torture_context
*tctx
,
347 struct smb2_tree
*tree
)
349 return test_time_t(tctx
, tree
, "test_time_t_1968.txt",
350 -63158400 /* 1968 */);
353 static bool test_freeze_thaw(struct torture_context
*tctx
,
354 struct smb2_tree
*tree
)
356 const char *filename
= BASEDIR
"\\test_freeze_thaw";
357 struct smb2_create cr
;
358 struct smb2_handle handle
= {{0}};
359 struct smb2_handle testdirh
= {{0}};
360 struct timespec ts
= { .tv_sec
= time(NULL
) };
362 union smb_fileinfo gi
;
363 union smb_setfileinfo si
;
367 smb2_deltree(tree
, BASEDIR
);
369 status
= torture_smb2_testdir(tree
, BASEDIR
, &testdirh
);
370 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
371 "torture_smb2_testdir failed\n");
373 cr
= (struct smb2_create
) {
374 .in
.desired_access
= SEC_FLAG_MAXIMUM_ALLOWED
,
375 .in
.file_attributes
= FILE_ATTRIBUTE_NORMAL
,
376 .in
.share_access
= NTCREATEX_SHARE_ACCESS_MASK
,
377 .in
.create_disposition
= NTCREATEX_DISP_OPEN_IF
,
378 .in
.impersonation_level
= NTCREATEX_IMPERSONATION_ANONYMOUS
,
379 .in
.fname
= filename
,
382 status
= smb2_create(tree
, tctx
, &cr
);
383 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
384 "smb2_create failed\n");
385 handle
= cr
.out
.file
.handle
;
387 si
= (union smb_setfileinfo
) {
388 .basic_info
.level
= RAW_SFILEINFO_BASIC_INFORMATION
,
389 .basic_info
.in
.file
.handle
= handle
,
394 * First set timestamps of testfile to current time
397 nttime
= full_timespec_to_nt_time(&ts
);
398 si
.basic_info
.in
.create_time
= nttime
;
399 si
.basic_info
.in
.write_time
= nttime
;
400 si
.basic_info
.in
.change_time
= nttime
;
402 status
= smb2_setinfo_file(tree
, &si
);
403 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
404 "smb2_setinfo_file failed\n");
406 gi
= (union smb_fileinfo
) {
407 .generic
.level
= SMB_QFILEINFO_BASIC_INFORMATION
,
408 .generic
.in
.file
.handle
= handle
,
413 * Verify timestamps are indeed set to the value in "nttime".
416 status
= smb2_getinfo_file(tree
, tctx
, &gi
);
417 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
418 "smb2_getinfo_file failed\n");
420 torture_comment(tctx
, "Got: create: %s, write: %s, change: %s\n",
421 nt_time_string(tctx
, gi
.basic_info
.out
.create_time
),
422 nt_time_string(tctx
, gi
.basic_info
.out
.write_time
),
423 nt_time_string(tctx
, gi
.basic_info
.out
.change_time
));
425 torture_assert_u64_equal_goto(tctx
,
426 gi
.basic_info
.out
.create_time
,
429 "Wrong create time\n");
430 torture_assert_u64_equal_goto(tctx
,
431 gi
.basic_info
.out
.write_time
,
434 "Wrong write time\n");
435 torture_assert_u64_equal_goto(tctx
,
436 gi
.basic_info
.out
.change_time
,
439 "Wrong change time\n");
443 * First set timestamps with NTTIME_FREEZE, must not change any
447 si
.basic_info
.in
.create_time
= NTTIME_FREEZE
;
448 si
.basic_info
.in
.write_time
= NTTIME_FREEZE
;
449 si
.basic_info
.in
.change_time
= NTTIME_FREEZE
;
451 status
= smb2_setinfo_file(tree
, &si
);
452 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
453 "smb2_setinfo_file failed\n");
455 gi
= (union smb_fileinfo
) {
456 .generic
.level
= SMB_QFILEINFO_BASIC_INFORMATION
,
457 .generic
.in
.file
.handle
= handle
,
462 * Verify timestamps are unmodified from step 2.
465 gi
= (union smb_fileinfo
) {
466 .generic
.level
= SMB_QFILEINFO_BASIC_INFORMATION
,
467 .generic
.in
.file
.handle
= handle
,
470 status
= smb2_getinfo_file(tree
, tctx
, &gi
);
471 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
472 "smb2_getinfo_file failed\n");
474 torture_comment(tctx
, "Got: create: %s, write: %s, change: %s\n",
475 nt_time_string(tctx
, gi
.basic_info
.out
.create_time
),
476 nt_time_string(tctx
, gi
.basic_info
.out
.write_time
),
477 nt_time_string(tctx
, gi
.basic_info
.out
.change_time
));
479 torture_assert_u64_equal_goto(tctx
,
480 gi
.basic_info
.out
.create_time
,
483 "Wrong create time\n");
484 torture_assert_u64_equal_goto(tctx
,
485 gi
.basic_info
.out
.write_time
,
488 "Wrong write time\n");
489 torture_assert_u64_equal_goto(tctx
,
490 gi
.basic_info
.out
.change_time
,
493 "Wrong change time\n");
497 * First set timestamps with NTTIME_THAW, must not change any timestamp
501 si
.basic_info
.in
.create_time
= NTTIME_THAW
;
502 si
.basic_info
.in
.write_time
= NTTIME_THAW
;
503 si
.basic_info
.in
.change_time
= NTTIME_THAW
;
505 status
= smb2_setinfo_file(tree
, &si
);
506 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
507 "smb2_setinfo_file failed\n");
509 gi
= (union smb_fileinfo
) {
510 .generic
.level
= SMB_QFILEINFO_BASIC_INFORMATION
,
511 .generic
.in
.file
.handle
= handle
,
516 * Verify timestamps are unmodified from step 2.
519 gi
= (union smb_fileinfo
) {
520 .generic
.level
= SMB_QFILEINFO_BASIC_INFORMATION
,
521 .generic
.in
.file
.handle
= handle
,
524 status
= smb2_getinfo_file(tree
, tctx
, &gi
);
525 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
526 "smb2_getinfo_file failed\n");
528 torture_comment(tctx
, "Got: create: %s, write: %s, change: %s\n",
529 nt_time_string(tctx
, gi
.basic_info
.out
.create_time
),
530 nt_time_string(tctx
, gi
.basic_info
.out
.write_time
),
531 nt_time_string(tctx
, gi
.basic_info
.out
.change_time
));
533 torture_assert_u64_equal_goto(tctx
,
534 gi
.basic_info
.out
.create_time
,
537 "Wrong create time\n");
538 torture_assert_u64_equal_goto(tctx
,
539 gi
.basic_info
.out
.write_time
,
542 "Wrong write time\n");
543 torture_assert_u64_equal_goto(tctx
,
544 gi
.basic_info
.out
.change_time
,
547 "Wrong change time\n");
550 if (!smb2_util_handle_empty(handle
)) {
551 smb2_util_close(tree
, handle
);
553 if (!smb2_util_handle_empty(testdirh
)) {
554 smb2_util_close(tree
, testdirh
);
556 smb2_deltree(tree
, BASEDIR
);
560 static bool test_delayed_write_vs_seteof(struct torture_context
*tctx
,
561 struct smb2_tree
*tree
)
563 struct smb2_create cr
;
564 struct smb2_handle h1
= {{0}};
565 struct smb2_handle h2
= {{0}};
568 union smb_fileinfo finfo
;
569 union smb_setfileinfo setinfo
;
574 smb2_deltree(tree
, BASEDIR
);
575 status
= torture_smb2_testdir(tree
, BASEDIR
, &h1
);
576 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
578 status
= smb2_util_close(tree
, h1
);
579 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
582 torture_comment(tctx
, "Open file-handle 1\n");
584 cr
= (struct smb2_create
) {
585 .in
.desired_access
= SEC_FLAG_MAXIMUM_ALLOWED
,
586 .in
.create_disposition
= NTCREATEX_DISP_OPEN_IF
,
587 .in
.share_access
= NTCREATEX_SHARE_ACCESS_MASK
,
588 .in
.fname
= BASEDIR
"\\" FNAME
,
590 status
= smb2_create(tree
, tctx
, &cr
);
591 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
593 h1
= cr
.out
.file
.handle
;
594 create_time
= cr
.out
.create_time
;
597 torture_comment(tctx
, "Write to file-handle 1\n");
599 status
= smb2_util_write(tree
, h1
, "s", 0, 1);
600 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
603 torture_comment(tctx
, "Check writetime hasn't been updated\n");
605 finfo
= (union smb_fileinfo
) {
606 .generic
.level
= RAW_FILEINFO_SMB2_ALL_INFORMATION
,
607 .generic
.in
.file
.handle
= h1
,
609 status
= smb2_getinfo_file(tree
, tree
, &finfo
);
610 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
613 torture_assert_nttime_equal(tctx
,
614 finfo
.all_info
.out
.write_time
,
616 "Writetime != set_time (wrong!)\n");
618 torture_comment(tctx
, "Setinfo EOF on file-handle 1,"
619 " should flush pending writetime update\n");
621 setinfo
= (union smb_setfileinfo
) {
622 .generic
.level
= RAW_SFILEINFO_END_OF_FILE_INFORMATION
,
624 setinfo
.end_of_file_info
.in
.file
.handle
= h1
;
625 setinfo
.end_of_file_info
.in
.size
= 1; /* same size! */
627 status
= smb2_setinfo_file(tree
, &setinfo
);
628 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
631 torture_comment(tctx
, "Check writetime has been updated "
632 "by the setinfo EOF\n");
634 finfo
= (union smb_fileinfo
) {
635 .generic
.level
= RAW_FILEINFO_SMB2_ALL_INFORMATION
,
636 .generic
.in
.file
.handle
= h1
,
638 status
= smb2_getinfo_file(tree
, tree
, &finfo
);
639 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
641 if (!(finfo
.all_info
.out
.write_time
> create_time
)) {
643 torture_fail_goto(tctx
, done
, "setinfo EOF hasn't updated writetime\n");
646 torture_comment(tctx
, "Open file-handle 2\n");
648 cr
= (struct smb2_create
) {
649 .in
.desired_access
= SEC_FILE_WRITE_ATTRIBUTE
,
650 .in
.create_disposition
= NTCREATEX_DISP_OPEN
,
651 .in
.share_access
= NTCREATEX_SHARE_ACCESS_MASK
,
652 .in
.fname
= BASEDIR
"\\" FNAME
,
654 status
= smb2_create(tree
, tctx
, &cr
);
655 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
657 h2
= cr
.out
.file
.handle
;
659 torture_comment(tctx
, "Set write time on file-handle 2\n");
661 setinfo
= (union smb_setfileinfo
) {
662 .generic
.level
= SMB_QFILEINFO_BASIC_INFORMATION
,
664 setinfo
.generic
.in
.file
.handle
= h2
;
665 unix_to_nt_time(&set_time
, time(NULL
) + 86400);
666 setinfo
.basic_info
.in
.write_time
= set_time
;
668 status
= smb2_setinfo_file(tree
, &setinfo
);
669 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
672 status
= smb2_util_close(tree
, h2
);
673 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
677 torture_comment(tctx
, "Close file-handle 1, write-time should not be updated\n");
679 c
= (struct smb2_close
) {
680 .in
.file
.handle
= h1
,
681 .in
.flags
= SMB2_CLOSE_FLAGS_FULL_INFORMATION
,
684 status
= smb2_close(tree
, &c
);
685 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
689 torture_assert_nttime_equal(tctx
,
692 "Writetime != set_time (wrong!)\n");
695 if (!smb2_util_handle_empty(h1
)) {
696 smb2_util_close(tree
, h1
);
698 if (!smb2_util_handle_empty(h2
)) {
699 smb2_util_close(tree
, h2
);
701 smb2_deltree(tree
, BASEDIR
);
705 static bool test_delayed_write_vs_flush(struct torture_context
*tctx
,
706 struct smb2_tree
*tree
)
708 struct smb2_create cr
;
709 struct smb2_handle h1
= {{0}};
710 union smb_fileinfo finfo
;
718 smb2_deltree(tree
, BASEDIR
);
719 status
= torture_smb2_testdir(tree
, BASEDIR
, &h1
);
720 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
722 status
= smb2_util_close(tree
, h1
);
723 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
726 torture_comment(tctx
, "Open file-handle 1\n");
728 cr
= (struct smb2_create
) {
729 .in
.desired_access
= SEC_FLAG_MAXIMUM_ALLOWED
,
730 .in
.create_disposition
= NTCREATEX_DISP_OPEN_IF
,
731 .in
.share_access
= NTCREATEX_SHARE_ACCESS_MASK
,
732 .in
.fname
= BASEDIR
"\\" FNAME
,
734 status
= smb2_create(tree
, tctx
, &cr
);
735 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
737 h1
= cr
.out
.file
.handle
;
738 create_time
= cr
.out
.create_time
;
741 torture_comment(tctx
, "Write to file-handle 1\n");
743 status
= smb2_util_write(tree
, h1
, "s", 0, 1);
744 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
747 torture_comment(tctx
, "Check writetime hasn't been updated\n");
749 finfo
= (union smb_fileinfo
) {
750 .generic
.level
= RAW_FILEINFO_SMB2_ALL_INFORMATION
,
751 .generic
.in
.file
.handle
= h1
,
753 status
= smb2_getinfo_file(tree
, tree
, &finfo
);
754 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
757 torture_assert_nttime_equal(tctx
,
758 finfo
.all_info
.out
.write_time
,
760 "Writetime != create_time (wrong!)\n");
762 torture_comment(tctx
, "Flush file, "
763 "should flush pending writetime update\n");
765 f
= (struct smb2_flush
) {
766 .in
.file
.handle
= h1
,
769 status
= smb2_flush(tree
, &f
);
770 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
773 torture_comment(tctx
, "Check writetime has been updated "
774 "by the setinfo EOF\n");
776 finfo
= (union smb_fileinfo
) {
777 .generic
.level
= RAW_FILEINFO_SMB2_ALL_INFORMATION
,
778 .generic
.in
.file
.handle
= h1
,
780 status
= smb2_getinfo_file(tree
, tree
, &finfo
);
781 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
784 flush_time
= finfo
.all_info
.out
.write_time
;
785 if (!(flush_time
> create_time
)) {
787 torture_fail_goto(tctx
, done
, "flush hasn't updated writetime\n");
790 torture_comment(tctx
, "Close file-handle 1, write-time should not be updated\n");
792 c
= (struct smb2_close
) {
793 .in
.file
.handle
= h1
,
794 .in
.flags
= SMB2_CLOSE_FLAGS_FULL_INFORMATION
,
797 status
= smb2_close(tree
, &c
);
798 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
802 torture_assert_nttime_equal(tctx
,
805 "writetime != flushtime (wrong!)\n");
808 if (!smb2_util_handle_empty(h1
)) {
809 smb2_util_close(tree
, h1
);
811 smb2_deltree(tree
, BASEDIR
);
815 static bool test_delayed_write_vs_setbasic_do(struct torture_context
*tctx
,
816 struct smb2_tree
*tree
,
817 union smb_setfileinfo
*setinfo
,
821 struct smb2_create cr
;
822 struct smb2_handle h1
= {{0}};
824 union smb_fileinfo finfo
;
828 torture_comment(tctx
, "Create testfile\n");
830 path
= talloc_asprintf(tree
, BASEDIR
"\\" FNAME
".%" PRIu32
,
832 torture_assert_not_null_goto(tctx
, path
, ret
, done
, "OOM\n");
834 cr
= (struct smb2_create
) {
835 .in
.desired_access
= SEC_FLAG_MAXIMUM_ALLOWED
,
836 .in
.create_disposition
= NTCREATEX_DISP_CREATE
,
837 .in
.share_access
= NTCREATEX_SHARE_ACCESS_MASK
,
840 status
= smb2_create(tree
, tctx
, &cr
);
841 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
843 h1
= cr
.out
.file
.handle
;
844 create_time
= cr
.out
.create_time
;
846 torture_comment(tctx
, "Write to file\n");
848 status
= smb2_util_write(tree
, h1
, "s", 0, 1);
849 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
852 torture_comment(tctx
, "Get timestamps\n");
854 finfo
= (union smb_fileinfo
) {
855 .generic
.level
= RAW_FILEINFO_SMB2_ALL_INFORMATION
,
856 .generic
.in
.file
.handle
= h1
,
858 status
= smb2_getinfo_file(tree
, tree
, &finfo
);
859 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
862 torture_assert_nttime_equal(tctx
,
863 finfo
.all_info
.out
.write_time
,
865 "Writetime != create_time (wrong!)\n");
867 torture_comment(tctx
, "Set timestamps\n");
869 setinfo
->end_of_file_info
.in
.file
.handle
= h1
;
870 status
= smb2_setinfo_file(tree
, setinfo
);
871 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
874 torture_comment(tctx
, "Check timestamps\n");
876 finfo
= (union smb_fileinfo
) {
877 .generic
.level
= RAW_FILEINFO_SMB2_ALL_INFORMATION
,
878 .generic
.in
.file
.handle
= h1
,
880 status
= smb2_getinfo_file(tree
, tree
, &finfo
);
881 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
885 if (!(finfo
.all_info
.out
.write_time
> create_time
)) {
887 torture_fail_goto(tctx
, done
, "setinfo basicinfo "
888 "hasn't updated writetime\n");
891 if (finfo
.all_info
.out
.write_time
!= create_time
) {
893 torture_fail_goto(tctx
, done
, "setinfo basicinfo "
894 "hasn't updated writetime\n");
898 status
= smb2_util_close(tree
, h1
);
899 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
903 status
= smb2_util_unlink(tree
, path
);
904 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
909 if (!smb2_util_handle_empty(h1
)) {
910 smb2_util_close(tree
, h1
);
915 static bool test_delayed_write_vs_setbasic(struct torture_context
*tctx
,
916 struct smb2_tree
*tree
)
918 struct smb2_handle h1
= {{0}};
919 union smb_setfileinfo setinfo
;
920 time_t t
= time(NULL
) - 86400;
924 smb2_deltree(tree
, BASEDIR
);
925 status
= torture_smb2_testdir(tree
, BASEDIR
, &h1
);
926 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
928 status
= smb2_util_close(tree
, h1
);
929 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
933 * Yes, this is correct, tested against Windows 2016: even if all
934 * timestamp fields are 0, a pending write time is flushed.
936 torture_comment(tctx
, "Test: setting all-0 timestamps flushes?\n");
938 setinfo
= (union smb_setfileinfo
) {
939 .generic
.level
= RAW_SFILEINFO_BASIC_INFORMATION
,
941 ret
= test_delayed_write_vs_setbasic_do(tctx
, tree
, &setinfo
, true);
946 torture_comment(tctx
, "Test: setting create_time flushes?\n");
947 unix_to_nt_time(&setinfo
.basic_info
.in
.create_time
, t
);
948 ret
= test_delayed_write_vs_setbasic_do(tctx
, tree
, &setinfo
, true);
953 torture_comment(tctx
, "Test: setting access_time flushes?\n");
954 unix_to_nt_time(&setinfo
.basic_info
.in
.access_time
, t
);
955 ret
= test_delayed_write_vs_setbasic_do(tctx
, tree
, &setinfo
, true);
960 torture_comment(tctx
, "Test: setting change_time flushes?\n");
961 unix_to_nt_time(&setinfo
.basic_info
.in
.change_time
, t
);
962 ret
= test_delayed_write_vs_setbasic_do(tctx
, tree
, &setinfo
, true);
968 smb2_deltree(tree
, BASEDIR
);
972 static bool test_delayed_1write(struct torture_context
*tctx
,
973 struct smb2_tree
*tree
)
975 struct smb2_create cr
;
976 struct smb2_handle h1
= {{0}};
977 union smb_fileinfo finfo
;
985 smb2_deltree(tree
, BASEDIR
);
986 status
= torture_smb2_testdir(tree
, BASEDIR
, &h1
);
987 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
989 status
= smb2_util_close(tree
, h1
);
990 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
993 torture_comment(tctx
, "Open file-handle 1\n");
995 cr
= (struct smb2_create
) {
996 .in
.desired_access
= SEC_FLAG_MAXIMUM_ALLOWED
,
997 .in
.create_disposition
= NTCREATEX_DISP_OPEN_IF
,
998 .in
.share_access
= NTCREATEX_SHARE_ACCESS_MASK
,
999 .in
.fname
= BASEDIR
"\\" FNAME
,
1001 status
= smb2_create(tree
, tctx
, &cr
);
1002 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1004 h1
= cr
.out
.file
.handle
;
1005 create_time
= cr
.out
.create_time
;
1008 torture_comment(tctx
, "Write to file-handle 1\n");
1010 status
= smb2_util_write(tree
, h1
, "s", 0, 1);
1011 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1015 torture_comment(tctx
, "Check writetime has been updated\n");
1017 finfo
= (union smb_fileinfo
) {
1018 .generic
.level
= RAW_FILEINFO_SMB2_ALL_INFORMATION
,
1019 .generic
.in
.file
.handle
= h1
,
1021 status
= smb2_getinfo_file(tree
, tree
, &finfo
);
1022 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1023 "getinfo failed\n");
1024 write_time
= finfo
.all_info
.out
.write_time
;
1026 if (!(write_time
> create_time
)) {
1028 torture_fail_goto(tctx
, done
,
1029 "Write-time not updated (wrong!)\n");
1032 torture_comment(tctx
, "Close file-handle 1\n");
1035 c
= (struct smb2_close
) {
1036 .in
.file
.handle
= h1
,
1037 .in
.flags
= SMB2_CLOSE_FLAGS_FULL_INFORMATION
,
1040 status
= smb2_close(tree
, &c
);
1041 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1044 close_time
= c
.out
.write_time
;
1046 torture_assert_nttime_equal(tctx
, close_time
, write_time
,
1047 "Writetime != close_time (wrong!)\n");
1050 if (!smb2_util_handle_empty(h1
)) {
1051 smb2_util_close(tree
, h1
);
1053 smb2_deltree(tree
, BASEDIR
);
1057 static bool test_delayed_2write(struct torture_context
*tctx
,
1058 struct smb2_tree
*tree
)
1060 struct smb2_create cr
;
1061 struct smb2_handle h1
= {{0}};
1062 union smb_fileinfo finfo
;
1063 struct smb2_close c
;
1071 smb2_deltree(tree
, BASEDIR
);
1072 status
= torture_smb2_testdir(tree
, BASEDIR
, &h1
);
1073 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1075 status
= smb2_util_close(tree
, h1
);
1076 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1079 torture_comment(tctx
, "Open file\n");
1081 cr
= (struct smb2_create
) {
1082 .in
.desired_access
= SEC_FLAG_MAXIMUM_ALLOWED
,
1083 .in
.create_disposition
= NTCREATEX_DISP_OPEN_IF
,
1084 .in
.share_access
= NTCREATEX_SHARE_ACCESS_MASK
,
1085 .in
.fname
= BASEDIR
"\\" FNAME
,
1087 status
= smb2_create(tree
, tctx
, &cr
);
1088 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1090 h1
= cr
.out
.file
.handle
;
1091 create_time
= cr
.out
.create_time
;
1094 torture_comment(tctx
, "Write to file\n");
1096 status
= smb2_util_write(tree
, h1
, "s", 0, 1);
1097 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1101 torture_comment(tctx
, "Check writetime has been updated\n");
1103 finfo
= (union smb_fileinfo
) {
1104 .generic
.level
= RAW_FILEINFO_SMB2_ALL_INFORMATION
,
1105 .generic
.in
.file
.handle
= h1
,
1107 status
= smb2_getinfo_file(tree
, tree
, &finfo
);
1108 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1109 "getinfo failed\n");
1110 write_time
= finfo
.all_info
.out
.write_time
;
1112 if (!(write_time
> create_time
)) {
1114 torture_fail_goto(tctx
, done
,
1115 "Write-time not updated (wrong!)\n");
1118 torture_comment(tctx
, "Write a second time\n");
1120 status
= smb2_util_write(tree
, h1
, "s", 0, 1);
1121 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1125 torture_comment(tctx
, "Check writetime has NOT been updated\n");
1127 finfo
= (union smb_fileinfo
) {
1128 .generic
.level
= RAW_FILEINFO_SMB2_ALL_INFORMATION
,
1129 .generic
.in
.file
.handle
= h1
,
1131 status
= smb2_getinfo_file(tree
, tree
, &finfo
);
1132 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1133 "getinfo failed\n");
1134 write_time2
= finfo
.all_info
.out
.write_time
;
1136 torture_assert_nttime_equal(tctx
, write_time2
, write_time
,
1137 "second write updated write-time (wrong!)\n");
1139 torture_comment(tctx
, "Close file-handle 1\n");
1142 torture_comment(tctx
, "Check writetime has been updated\n");
1144 c
= (struct smb2_close
) {
1145 .in
.file
.handle
= h1
,
1146 .in
.flags
= SMB2_CLOSE_FLAGS_FULL_INFORMATION
,
1149 status
= smb2_close(tree
, &c
);
1150 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1153 close_time
= c
.out
.write_time
;
1155 if (!(close_time
> write_time
)) {
1157 torture_fail_goto(tctx
, done
,
1158 "Write-time not updated (wrong!)\n");
1162 if (!smb2_util_handle_empty(h1
)) {
1163 smb2_util_close(tree
, h1
);
1165 smb2_deltree(tree
, BASEDIR
);
1170 basic testing of SMB2 timestamps
1172 struct torture_suite
*torture_smb2_timestamps_init(TALLOC_CTX
*ctx
)
1174 struct torture_suite
*suite
= torture_suite_create(ctx
, "timestamps");
1176 torture_suite_add_1smb2_test(suite
, "test_close_not_attrib", test_close_no_attrib
);
1177 torture_suite_add_1smb2_test(suite
, "time_t_15032385535", test_time_t_15032385535
);
1178 torture_suite_add_1smb2_test(suite
, "time_t_10000000000", test_time_t_10000000000
);
1179 torture_suite_add_1smb2_test(suite
, "time_t_4294967295", test_time_t_4294967295
);
1180 torture_suite_add_1smb2_test(suite
, "time_t_1", test_time_t_1
);
1181 torture_suite_add_1smb2_test(suite
, "time_t_0", test_time_t_0
);
1182 torture_suite_add_1smb2_test(suite
, "time_t_-1", test_time_t_minus_1
);
1183 torture_suite_add_1smb2_test(suite
, "time_t_-2", test_time_t_minus_2
);
1184 torture_suite_add_1smb2_test(suite
, "time_t_1968", test_time_t_1968
);
1185 torture_suite_add_1smb2_test(suite
, "freeze-thaw", test_freeze_thaw
);
1188 * Testing of delayed write-time updates
1190 torture_suite_add_1smb2_test(suite
, "delayed-write-vs-seteof", test_delayed_write_vs_seteof
);
1191 torture_suite_add_1smb2_test(suite
, "delayed-write-vs-flush", test_delayed_write_vs_flush
);
1192 torture_suite_add_1smb2_test(suite
, "delayed-write-vs-setbasic", test_delayed_write_vs_setbasic
);
1193 torture_suite_add_1smb2_test(suite
, "delayed-1write", test_delayed_1write
);
1194 torture_suite_add_1smb2_test(suite
, "delayed-2write", test_delayed_2write
);
1196 suite
->description
= talloc_strdup(suite
, "SMB2 timestamp tests");
1202 * This test shows that Windows has a timestamp resolution of ~15ms. When so
1203 * when a smaller amount of time than that has passed it's not necessarily
1204 * detectable on a Windows 2019 and newer who implement immediate timestamp
1207 * Note that this test relies on a low latency SMB connection. Even with a low
1208 * latency connection of eg 1m there's a chance of 1/15 that the first part of
1209 * the test expecting no timestamp change fails as the writetime is updated.
1211 * Due to this timing dependency this test is skipped in Samba CI, but it is
1212 * preserved here for future SMB2 timestamps behaviour archealogists.
1214 * See also: https://lists.samba.org/archive/cifs-protocol/2019-December/003358.html
1216 static bool test_timestamp_resolution1(struct torture_context
*tctx
,
1217 struct smb2_tree
*tree
)
1219 union smb_fileinfo finfo1
;
1220 const char *fname
= BASEDIR
"\\" FNAME
;
1221 struct smb2_create cr
;
1222 struct smb2_handle h
= {{0}};
1223 struct smb2_close cl
;
1227 smb2_deltree(tree
, BASEDIR
);
1228 status
= torture_smb2_testdir(tree
, BASEDIR
, &h
);
1229 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1231 status
= smb2_util_close(tree
, h
);
1232 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1235 torture_comment(tctx
, "Write without delay, expect no "
1236 "write-time change\n");
1238 smb2_generic_create(&cr
, NULL
, false, fname
,
1239 NTCREATEX_DISP_CREATE
,
1240 smb2_util_oplock_level(""), 0, 0);
1241 status
= smb2_create(tree
, tree
, &cr
);
1242 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1244 h
= cr
.out
.file
.handle
;
1246 finfo1
= (union smb_fileinfo
) {
1247 .generic
.level
= RAW_FILEINFO_SMB2_ALL_INFORMATION
,
1248 .generic
.in
.file
.handle
= h
,
1250 status
= smb2_getinfo_file(tree
, tree
, &finfo1
);
1251 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1252 "getinfo failed\n");
1254 status
= smb2_util_write(tree
, h
, "123456789", 0, 9);
1255 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1258 cl
= (struct smb2_close
) {
1259 .in
.file
.handle
= h
,
1260 .in
.flags
= SMB2_CLOSE_FLAGS_FULL_INFORMATION
,
1263 status
= smb2_close(tree
, &cl
);
1264 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1268 torture_comment(tctx
, "Initial: %s\nClose: %s\n",
1269 nt_time_string(tctx
, finfo1
.basic_info
.out
.write_time
),
1270 nt_time_string(tctx
, cl
.out
.write_time
));
1272 torture_assert_u64_equal_goto(tctx
,
1273 finfo1
.basic_info
.out
.write_time
,
1276 "Write time changed (wrong!)\n");
1278 torture_comment(tctx
, "Write with 20 ms delay, expect "
1279 "write-time change\n");
1281 smb2_generic_create(&cr
, NULL
, false, fname
,
1282 NTCREATEX_DISP_OPEN
,
1283 smb2_util_oplock_level(""), 0, 0);
1284 status
= smb2_create(tree
, tree
, &cr
);
1285 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1287 h
= cr
.out
.file
.handle
;
1289 finfo1
= (union smb_fileinfo
) {
1290 .generic
.level
= RAW_FILEINFO_SMB2_ALL_INFORMATION
,
1291 .generic
.in
.file
.handle
= h
,
1293 status
= smb2_getinfo_file(tree
, tree
, &finfo1
);
1294 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1295 "getinfo failed\n");
1299 status
= smb2_util_write(tree
, h
, "123456789", 0, 9);
1300 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1303 cl
= (struct smb2_close
) {
1304 .in
.file
.handle
= h
,
1305 .in
.flags
= SMB2_CLOSE_FLAGS_FULL_INFORMATION
,
1308 status
= smb2_close(tree
, &cl
);
1309 torture_assert_ntstatus_ok_goto(tctx
, status
, ret
, done
,
1313 torture_comment(tctx
, "Initial: %s\nClose: %s\n",
1314 nt_time_string(tctx
, finfo1
.basic_info
.out
.write_time
),
1315 nt_time_string(tctx
, cl
.out
.write_time
));
1317 torture_assert_u64_not_equal_goto(
1319 finfo1
.basic_info
.out
.write_time
,
1322 "Write time did not change (wrong!)\n");
1325 if (!smb2_util_handle_empty(h
)) {
1326 smb2_util_close(tree
, h
);
1328 smb2_deltree(tree
, BASEDIR
);
1333 basic testing of SMB2 timestamps
1335 struct torture_suite
*torture_smb2_timestamp_resolution_init(TALLOC_CTX
*ctx
)
1337 struct torture_suite
*suite
= torture_suite_create(ctx
, "timestamp_resolution");
1339 torture_suite_add_1smb2_test(suite
, "resolution1", test_timestamp_resolution1
);
1341 suite
->description
= talloc_strdup(suite
, "SMB2 timestamp tests");