2 Unix SMB/CIFS implementation.
4 test suite for delayed write update
6 Copyright (C) Volker Lendecke 2004
7 Copyright (C) Andrew Tridgell 2004
8 Copyright (C) Jeremy Allison 2004
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include "torture/torture.h"
27 #include "libcli/raw/libcliraw.h"
28 #include "system/time.h"
29 #include "system/filesys.h"
30 #include "libcli/libcli.h"
31 #include "torture/util.h"
33 #define BASEDIR "\\delaywrite"
35 static BOOL
test_delayed_write_update(struct torture_context
*tctx
, struct smbcli_state
*cli
)
37 union smb_fileinfo finfo1
, finfo2
;
38 const char *fname
= BASEDIR
"\\torture_file.txt";
45 if (!torture_setup_dir(cli
, BASEDIR
)) {
49 fnum1
= smbcli_open(cli
->tree
, fname
, O_RDWR
|O_CREAT
, DENY_NONE
);
51 torture_comment(tctx
, "Failed to open %s\n", fname
);
55 finfo1
.basic_info
.level
= RAW_FILEINFO_BASIC_INFO
;
56 finfo1
.basic_info
.in
.file
.fnum
= fnum1
;
59 status
= smb_raw_fileinfo(cli
->tree
, tctx
, &finfo1
);
61 if (!NT_STATUS_IS_OK(status
)) {
62 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
66 torture_comment(tctx
, "Initial write time %s\n",
67 nt_time_string(tctx
, finfo1
.basic_info
.out
.write_time
));
69 /* 3 second delay to ensure we get past any 2 second time
70 granularity (older systems may have that) */
73 written
= smbcli_write(cli
->tree
, fnum1
, 0, "x", 0, 1);
76 torture_comment(tctx
, "write failed - wrote %d bytes (%s)\n",
77 (int)written
, __location__
);
83 while (time(NULL
) < t
+120) {
84 status
= smb_raw_fileinfo(cli
->tree
, tctx
, &finfo2
);
86 if (!NT_STATUS_IS_OK(status
)) {
87 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
91 torture_comment(tctx
, "write time %s\n",
92 nt_time_string(tctx
, finfo2
.basic_info
.out
.write_time
));
93 if (finfo1
.basic_info
.out
.write_time
!= finfo2
.basic_info
.out
.write_time
) {
94 torture_comment(tctx
, "Server updated write_time after %d seconds\n",
95 (int)(time(NULL
) - t
));
102 if (finfo1
.basic_info
.out
.write_time
== finfo2
.basic_info
.out
.write_time
) {
103 torture_comment(tctx
, "Server did not update write time?!\n");
109 smbcli_close(cli
->tree
, fnum1
);
110 smbcli_unlink(cli
->tree
, fname
);
111 smbcli_deltree(cli
->tree
, BASEDIR
);
117 * Do as above, but using 2 connections.
120 static BOOL
test_delayed_write_update2(struct torture_context
*tctx
, struct smbcli_state
*cli
,
121 struct smbcli_state
*cli2
)
123 union smb_fileinfo finfo1
, finfo2
;
124 const char *fname
= BASEDIR
"\\torture_file.txt";
131 union smb_flush flsh
;
133 if (!torture_setup_dir(cli
, BASEDIR
)) {
137 fnum1
= smbcli_open(cli
->tree
, fname
, O_RDWR
|O_CREAT
, DENY_NONE
);
139 torture_comment(tctx
, "Failed to open %s\n", fname
);
143 finfo1
.basic_info
.level
= RAW_FILEINFO_BASIC_INFO
;
144 finfo1
.basic_info
.in
.file
.fnum
= fnum1
;
147 status
= smb_raw_fileinfo(cli
->tree
, tctx
, &finfo1
);
149 if (!NT_STATUS_IS_OK(status
)) {
150 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
154 torture_comment(tctx
, "Initial write time %s\n",
155 nt_time_string(tctx
, finfo1
.basic_info
.out
.write_time
));
157 /* 3 second delay to ensure we get past any 2 second time
158 granularity (older systems may have that) */
162 /* Try using setfileinfo instead of write to update write time. */
163 union smb_setfileinfo sfinfo
;
164 time_t t_set
= time(NULL
);
165 sfinfo
.basic_info
.level
= RAW_SFILEINFO_BASIC_INFO
;
166 sfinfo
.basic_info
.in
.file
.fnum
= fnum1
;
167 sfinfo
.basic_info
.in
.create_time
= finfo1
.basic_info
.out
.create_time
;
168 sfinfo
.basic_info
.in
.access_time
= finfo1
.basic_info
.out
.access_time
;
170 /* I tried this with both + and - ve to see if it makes a different.
171 It doesn't - once the filetime is set via setfileinfo it stays that way. */
173 unix_to_nt_time(&sfinfo
.basic_info
.in
.write_time
, t_set
- 30000);
175 unix_to_nt_time(&sfinfo
.basic_info
.in
.write_time
, t_set
+ 30000);
177 sfinfo
.basic_info
.in
.change_time
= finfo1
.basic_info
.out
.change_time
;
178 sfinfo
.basic_info
.in
.attrib
= finfo1
.basic_info
.out
.attrib
;
180 status
= smb_raw_setfileinfo(cli
->tree
, &sfinfo
);
182 if (!NT_STATUS_IS_OK(status
)) {
183 DEBUG(0, ("sfileinfo failed: %s\n", nt_errstr(status
)));
190 while (time(NULL
) < t
+120) {
191 finfo2
.basic_info
.in
.file
.path
= fname
;
193 status
= smb_raw_pathinfo(cli2
->tree
, tctx
, &finfo2
);
195 if (!NT_STATUS_IS_OK(status
)) {
196 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
200 torture_comment(tctx
, "write time %s\n",
201 nt_time_string(tctx
, finfo2
.basic_info
.out
.write_time
));
202 if (finfo1
.basic_info
.out
.write_time
!= finfo2
.basic_info
.out
.write_time
) {
203 torture_comment(tctx
, "Server updated write_time after %d seconds\n",
204 (int)(time(NULL
) - t
));
211 if (finfo1
.basic_info
.out
.write_time
== finfo2
.basic_info
.out
.write_time
) {
212 torture_comment(tctx
, "Server did not update write time?!\n");
216 /* Now try a write to see if the write time gets reset. */
218 finfo1
.basic_info
.level
= RAW_FILEINFO_BASIC_INFO
;
219 finfo1
.basic_info
.in
.file
.fnum
= fnum1
;
222 status
= smb_raw_fileinfo(cli
->tree
, tctx
, &finfo1
);
224 if (!NT_STATUS_IS_OK(status
)) {
225 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
229 torture_comment(tctx
, "Modified write time %s\n",
230 nt_time_string(tctx
, finfo1
.basic_info
.out
.write_time
));
233 torture_comment(tctx
, "Doing a 10 byte write to extend the file and see if this changes the last write time.\n");
235 written
= smbcli_write(cli
->tree
, fnum1
, 0, "0123456789", 1, 10);
238 torture_comment(tctx
, "write failed - wrote %d bytes (%s)\n",
239 (int)written
, __location__
);
243 /* Just to prove to tridge that the an smbflush has no effect on
244 the write time :-). The setfileinfo IS STICKY. JRA. */
246 torture_comment(tctx
, "Doing flush after write\n");
248 flsh
.flush
.level
= RAW_FLUSH_FLUSH
;
249 flsh
.flush
.in
.file
.fnum
= fnum1
;
250 status
= smb_raw_flush(cli
->tree
, &flsh
);
251 if (!NT_STATUS_IS_OK(status
)) {
252 DEBUG(0, ("smbflush failed: %s\n", nt_errstr(status
)));
258 /* Once the time was set using setfileinfo then it stays set - writes
259 don't have any effect. But make sure. */
261 while (time(NULL
) < t
+15) {
262 status
= smb_raw_fileinfo(cli
->tree
, tctx
, &finfo2
);
264 if (!NT_STATUS_IS_OK(status
)) {
265 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
269 torture_comment(tctx
, "write time %s\n",
270 nt_time_string(tctx
, finfo2
.basic_info
.out
.write_time
));
271 if (finfo1
.basic_info
.out
.write_time
!= finfo2
.basic_info
.out
.write_time
) {
272 torture_comment(tctx
, "Server updated write_time after %d seconds\n",
273 (int)(time(NULL
) - t
));
280 if (finfo1
.basic_info
.out
.write_time
== finfo2
.basic_info
.out
.write_time
) {
281 torture_comment(tctx
, "Server did not update write time\n");
284 fnum2
= smbcli_open(cli
->tree
, fname
, O_RDWR
, DENY_NONE
);
286 torture_comment(tctx
, "Failed to open %s\n", fname
);
290 torture_comment(tctx
, "Doing a 10 byte write to extend the file via second fd and see if this changes the last write time.\n");
292 written
= smbcli_write(cli
->tree
, fnum2
, 0, "0123456789", 11, 10);
295 torture_comment(tctx
, "write failed - wrote %d bytes (%s)\n",
296 (int)written
, __location__
);
300 status
= smb_raw_fileinfo(cli
->tree
, tctx
, &finfo2
);
302 if (!NT_STATUS_IS_OK(status
)) {
303 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
306 torture_comment(tctx
, "write time %s\n",
307 nt_time_string(tctx
, finfo2
.basic_info
.out
.write_time
));
308 if (finfo1
.basic_info
.out
.write_time
!= finfo2
.basic_info
.out
.write_time
) {
309 torture_comment(tctx
, "Server updated write_time\n");
312 torture_comment(tctx
, "Closing the first fd to see if write time updated.\n");
313 smbcli_close(cli
->tree
, fnum1
);
316 torture_comment(tctx
, "Doing a 10 byte write to extend the file via second fd and see if this changes the last write time.\n");
318 written
= smbcli_write(cli
->tree
, fnum2
, 0, "0123456789", 21, 10);
321 torture_comment(tctx
, "write failed - wrote %d bytes (%s)\n",
322 (int)written
, __location__
);
326 finfo1
.basic_info
.level
= RAW_FILEINFO_BASIC_INFO
;
327 finfo1
.basic_info
.in
.file
.fnum
= fnum2
;
329 status
= smb_raw_fileinfo(cli
->tree
, tctx
, &finfo2
);
331 if (!NT_STATUS_IS_OK(status
)) {
332 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
335 torture_comment(tctx
, "write time %s\n",
336 nt_time_string(tctx
, finfo2
.basic_info
.out
.write_time
));
337 if (finfo1
.basic_info
.out
.write_time
!= finfo2
.basic_info
.out
.write_time
) {
338 torture_comment(tctx
, "Server updated write_time\n");
343 /* Once the time was set using setfileinfo then it stays set - writes
344 don't have any effect. But make sure. */
346 while (time(NULL
) < t
+15) {
347 status
= smb_raw_fileinfo(cli
->tree
, tctx
, &finfo2
);
349 if (!NT_STATUS_IS_OK(status
)) {
350 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
354 torture_comment(tctx
, "write time %s\n",
355 nt_time_string(tctx
, finfo2
.basic_info
.out
.write_time
));
356 if (finfo1
.basic_info
.out
.write_time
!= finfo2
.basic_info
.out
.write_time
) {
357 torture_comment(tctx
, "Server updated write_time after %d seconds\n",
358 (int)(time(NULL
) - t
));
365 if (finfo1
.basic_info
.out
.write_time
== finfo2
.basic_info
.out
.write_time
) {
366 torture_comment(tctx
, "Server did not update write time\n");
369 torture_comment(tctx
, "Closing both fd's to see if write time updated.\n");
371 smbcli_close(cli
->tree
, fnum2
);
374 fnum1
= smbcli_open(cli
->tree
, fname
, O_RDWR
, DENY_NONE
);
376 torture_comment(tctx
, "Failed to open %s\n", fname
);
380 finfo1
.basic_info
.level
= RAW_FILEINFO_BASIC_INFO
;
381 finfo1
.basic_info
.in
.file
.fnum
= fnum1
;
384 status
= smb_raw_fileinfo(cli
->tree
, tctx
, &finfo1
);
386 if (!NT_STATUS_IS_OK(status
)) {
387 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
391 torture_comment(tctx
, "Second open initial write time %s\n",
392 nt_time_string(tctx
, finfo1
.basic_info
.out
.write_time
));
395 torture_comment(tctx
, "Doing a 10 byte write to extend the file to see if this changes the last write time.\n");
397 written
= smbcli_write(cli
->tree
, fnum1
, 0, "0123456789", 31, 10);
400 torture_comment(tctx
, "write failed - wrote %d bytes (%s)\n",
401 (int)written
, __location__
);
405 finfo1
.basic_info
.level
= RAW_FILEINFO_BASIC_INFO
;
406 finfo1
.basic_info
.in
.file
.fnum
= fnum1
;
408 status
= smb_raw_fileinfo(cli
->tree
, tctx
, &finfo2
);
410 if (!NT_STATUS_IS_OK(status
)) {
411 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
414 torture_comment(tctx
, "write time %s\n",
415 nt_time_string(tctx
, finfo2
.basic_info
.out
.write_time
));
416 if (finfo1
.basic_info
.out
.write_time
!= finfo2
.basic_info
.out
.write_time
) {
417 torture_comment(tctx
, "Server updated write_time\n");
422 /* Once the time was set using setfileinfo then it stays set - writes
423 don't have any effect. But make sure. */
425 while (time(NULL
) < t
+15) {
426 status
= smb_raw_fileinfo(cli
->tree
, tctx
, &finfo2
);
428 if (!NT_STATUS_IS_OK(status
)) {
429 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
433 torture_comment(tctx
, "write time %s\n",
434 nt_time_string(tctx
, finfo2
.basic_info
.out
.write_time
));
435 if (finfo1
.basic_info
.out
.write_time
!= finfo2
.basic_info
.out
.write_time
) {
436 torture_comment(tctx
, "Server updated write_time after %d seconds\n",
437 (int)(time(NULL
) - t
));
444 if (finfo1
.basic_info
.out
.write_time
== finfo2
.basic_info
.out
.write_time
) {
445 torture_comment(tctx
, "Server did not update write time\n");
449 /* One more test to do. We should read the filetime via findfirst on the
450 second connection to ensure it's the same. This is very easy for a Windows
451 server but a bastard to get right on a POSIX server. JRA. */
454 smbcli_close(cli
->tree
, fnum1
);
455 smbcli_unlink(cli
->tree
, fname
);
456 smbcli_deltree(cli
->tree
, BASEDIR
);
462 /* Windows does obviously not update the stat info during a write call. I
463 * *think* this is the problem causing a spurious Excel 2003 on XP error
464 * message when saving a file. Excel does a setfileinfo, writes, and then does
465 * a getpath(!)info. Or so... For Samba sometimes it displays an error message
466 * that the file might have been changed in between. What i've been able to
467 * trace down is that this happens if the getpathinfo after the write shows a
468 * different last write time than the setfileinfo showed. This is really
472 static BOOL
test_finfo_after_write(struct torture_context
*tctx
, struct smbcli_state
*cli
,
473 struct smbcli_state
*cli2
)
475 union smb_fileinfo finfo1
, finfo2
;
476 const char *fname
= BASEDIR
"\\torture_file.txt";
483 if (!torture_setup_dir(cli
, BASEDIR
)) {
487 fnum1
= smbcli_open(cli
->tree
, fname
, O_RDWR
|O_CREAT
, DENY_NONE
);
493 finfo1
.basic_info
.level
= RAW_FILEINFO_BASIC_INFO
;
494 finfo1
.basic_info
.in
.file
.fnum
= fnum1
;
496 status
= smb_raw_fileinfo(cli
->tree
, tctx
, &finfo1
);
498 if (!NT_STATUS_IS_OK(status
)) {
499 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
506 written
= smbcli_write(cli
->tree
, fnum1
, 0, "x", 0, 1);
509 torture_comment(tctx
, "(%s) written gave %d - should have been 1\n",
510 __location__
, (int)written
);
515 fnum2
= smbcli_open(cli2
->tree
, fname
, O_RDWR
, DENY_NONE
);
517 torture_comment(tctx
, "(%s) failed to open 2nd time - %s\n",
518 __location__
, smbcli_errstr(cli2
->tree
));
523 written
= smbcli_write(cli2
->tree
, fnum2
, 0, "x", 0, 1);
526 torture_comment(tctx
, "(%s) written gave %d - should have been 1\n",
527 __location__
, (int)written
);
532 finfo2
.basic_info
.level
= RAW_FILEINFO_BASIC_INFO
;
533 finfo2
.basic_info
.in
.file
.path
= fname
;
535 status
= smb_raw_pathinfo(cli2
->tree
, tctx
, &finfo2
);
537 if (!NT_STATUS_IS_OK(status
)) {
538 DEBUG(0, ("(%s) fileinfo failed: %s\n",
539 __location__
, nt_errstr(status
)));
544 if (finfo1
.basic_info
.out
.create_time
!=
545 finfo2
.basic_info
.out
.create_time
) {
546 torture_comment(tctx
, "(%s) create_time changed\n", __location__
);
551 if (finfo1
.basic_info
.out
.access_time
!=
552 finfo2
.basic_info
.out
.access_time
) {
553 torture_comment(tctx
, "(%s) access_time changed\n", __location__
);
558 if (finfo1
.basic_info
.out
.write_time
!=
559 finfo2
.basic_info
.out
.write_time
) {
560 torture_comment(tctx
, "(%s) write_time changed\n", __location__
);
561 torture_comment(tctx
, "write time conn 1 = %s, conn 2 = %s\n",
562 nt_time_string(tctx
, finfo1
.basic_info
.out
.write_time
),
563 nt_time_string(tctx
, finfo2
.basic_info
.out
.write_time
));
568 if (finfo1
.basic_info
.out
.change_time
!=
569 finfo2
.basic_info
.out
.change_time
) {
570 torture_comment(tctx
, "(%s) change_time changed\n", __location__
);
575 /* One of the two following calls updates the qpathinfo. */
577 /* If you had skipped the smbcli_write on fnum2, it would
578 * *not* have updated the stat on disk */
580 smbcli_close(cli2
->tree
, fnum2
);
583 /* This call is only for the people looking at ethereal :-) */
584 finfo2
.basic_info
.level
= RAW_FILEINFO_BASIC_INFO
;
585 finfo2
.basic_info
.in
.file
.path
= fname
;
587 status
= smb_raw_pathinfo(cli
->tree
, tctx
, &finfo2
);
589 if (!NT_STATUS_IS_OK(status
)) {
590 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status
)));
597 smbcli_close(cli
->tree
, fnum1
);
598 smbcli_unlink(cli
->tree
, fname
);
599 smbcli_deltree(cli
->tree
, BASEDIR
);
606 testing of delayed update of write_time
608 struct torture_suite
*torture_delay_write(void)
610 struct torture_suite
*suite
= torture_suite_create(talloc_autofree_context(), "DELAYWRITE");
612 torture_suite_add_2smb_test(suite
, "finfo update on close", test_finfo_after_write
);
613 torture_suite_add_1smb_test(suite
, "delayed update of write time", test_delayed_write_update
);
614 torture_suite_add_2smb_test(suite
, "delayed update of write time using 2 connections", test_delayed_write_update2
);