r23792: convert Samba4 to GPLv3
[gd/samba-autobuild/.git] / source4 / torture / basic / delaywrite.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    test suite for delayed write update 
5
6    Copyright (C) Volker Lendecke 2004
7    Copyright (C) Andrew Tridgell 2004
8    Copyright (C) Jeremy Allison 2004
9    
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 3 of the License, or
13    (at your option) any later version.
14    
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.
19    
20    You should have received a copy of the GNU General Public License
21    along with this program.  If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "includes.h"
25 #include "torture/torture.h"
26 #include "libcli/raw/libcliraw.h"
27 #include "system/time.h"
28 #include "system/filesys.h"
29 #include "libcli/libcli.h"
30 #include "torture/util.h"
31
32 #define BASEDIR "\\delaywrite"
33
34 static BOOL test_delayed_write_update(struct torture_context *tctx, struct smbcli_state *cli)
35 {
36         union smb_fileinfo finfo1, finfo2;
37         const char *fname = BASEDIR "\\torture_file.txt";
38         NTSTATUS status;
39         int fnum1 = -1;
40         BOOL ret = True;
41         ssize_t written;
42         time_t t;
43
44         if (!torture_setup_dir(cli, BASEDIR)) {
45                 return False;
46         }
47
48         fnum1 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE);
49         if (fnum1 == -1) {
50                 torture_comment(tctx, "Failed to open %s\n", fname);
51                 return False;
52         }
53
54         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
55         finfo1.basic_info.in.file.fnum = fnum1;
56         finfo2 = finfo1;
57
58         status = smb_raw_fileinfo(cli->tree, tctx, &finfo1);
59
60         if (!NT_STATUS_IS_OK(status)) {
61                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
62                 return False;
63         }
64         
65         torture_comment(tctx, "Initial write time %s\n", 
66                nt_time_string(tctx, finfo1.basic_info.out.write_time));
67
68         /* 3 second delay to ensure we get past any 2 second time
69            granularity (older systems may have that) */
70         sleep(3);
71
72         written =  smbcli_write(cli->tree, fnum1, 0, "x", 0, 1);
73
74         if (written != 1) {
75                 torture_comment(tctx, "write failed - wrote %d bytes (%s)\n", 
76                        (int)written, __location__);
77                 return False;
78         }
79
80         t = time(NULL);
81
82         while (time(NULL) < t+120) {
83                 status = smb_raw_fileinfo(cli->tree, tctx, &finfo2);
84
85                 if (!NT_STATUS_IS_OK(status)) {
86                         DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
87                         ret = False;
88                         break;
89                 }
90                 torture_comment(tctx, "write time %s\n", 
91                        nt_time_string(tctx, finfo2.basic_info.out.write_time));
92                 if (finfo1.basic_info.out.write_time != finfo2.basic_info.out.write_time) {
93                         torture_comment(tctx, "Server updated write_time after %d seconds\n",
94                                (int)(time(NULL) - t));
95                         break;
96                 }
97                 sleep(1);
98                 fflush(stdout);
99         }
100         
101         if (finfo1.basic_info.out.write_time == finfo2.basic_info.out.write_time) {
102                 torture_comment(tctx, "Server did not update write time?!\n");
103                 ret = False;
104         }
105
106
107         if (fnum1 != -1)
108                 smbcli_close(cli->tree, fnum1);
109         smbcli_unlink(cli->tree, fname);
110         smbcli_deltree(cli->tree, BASEDIR);
111
112         return ret;
113 }
114
115 /* 
116  * Do as above, but using 2 connections.
117  */
118
119 static BOOL test_delayed_write_update2(struct torture_context *tctx, struct smbcli_state *cli, 
120                                                                            struct smbcli_state *cli2)
121 {
122         union smb_fileinfo finfo1, finfo2;
123         const char *fname = BASEDIR "\\torture_file.txt";
124         NTSTATUS status;
125         int fnum1 = -1;
126         int fnum2 = -1;
127         BOOL ret = True;
128         ssize_t written;
129         time_t t;
130         union smb_flush flsh;
131
132         if (!torture_setup_dir(cli, BASEDIR)) {
133                 return False;
134         }
135
136         fnum1 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE);
137         if (fnum1 == -1) {
138                 torture_comment(tctx, "Failed to open %s\n", fname);
139                 return False;
140         }
141
142         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
143         finfo1.basic_info.in.file.fnum = fnum1;
144         finfo2 = finfo1;
145
146         status = smb_raw_fileinfo(cli->tree, tctx, &finfo1);
147
148         if (!NT_STATUS_IS_OK(status)) {
149                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
150                 return False;
151         }
152         
153         torture_comment(tctx, "Initial write time %s\n", 
154                nt_time_string(tctx, finfo1.basic_info.out.write_time));
155
156         /* 3 second delay to ensure we get past any 2 second time
157            granularity (older systems may have that) */
158         sleep(3);
159
160         {
161                 /* Try using setfileinfo instead of write to update write time. */
162                 union smb_setfileinfo sfinfo;
163                 time_t t_set = time(NULL);
164                 sfinfo.basic_info.level = RAW_SFILEINFO_BASIC_INFO;
165                 sfinfo.basic_info.in.file.fnum = fnum1;
166                 sfinfo.basic_info.in.create_time = finfo1.basic_info.out.create_time;
167                 sfinfo.basic_info.in.access_time = finfo1.basic_info.out.access_time;
168
169                 /* I tried this with both + and - ve to see if it makes a different.
170                    It doesn't - once the filetime is set via setfileinfo it stays that way. */
171 #if 1
172                 unix_to_nt_time(&sfinfo.basic_info.in.write_time, t_set - 30000);
173 #else
174                 unix_to_nt_time(&sfinfo.basic_info.in.write_time, t_set + 30000);
175 #endif
176                 sfinfo.basic_info.in.change_time = finfo1.basic_info.out.change_time;
177                 sfinfo.basic_info.in.attrib = finfo1.basic_info.out.attrib;
178
179                 status = smb_raw_setfileinfo(cli->tree, &sfinfo);
180
181                 if (!NT_STATUS_IS_OK(status)) {
182                         DEBUG(0, ("sfileinfo failed: %s\n", nt_errstr(status)));
183                         return False;
184                 }
185         }
186
187         t = time(NULL);
188
189         while (time(NULL) < t+120) {
190                 finfo2.basic_info.in.file.path = fname;
191         
192                 status = smb_raw_pathinfo(cli2->tree, tctx, &finfo2);
193
194                 if (!NT_STATUS_IS_OK(status)) {
195                         DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
196                         ret = False;
197                         break;
198                 }
199                 torture_comment(tctx, "write time %s\n", 
200                        nt_time_string(tctx, finfo2.basic_info.out.write_time));
201                 if (finfo1.basic_info.out.write_time != finfo2.basic_info.out.write_time) {
202                         torture_comment(tctx, "Server updated write_time after %d seconds\n",
203                                (int)(time(NULL) - t));
204                         break;
205                 }
206                 sleep(1);
207                 fflush(stdout);
208         }
209         
210         if (finfo1.basic_info.out.write_time == finfo2.basic_info.out.write_time) {
211                 torture_comment(tctx, "Server did not update write time?!\n");
212                 ret = False;
213         }
214
215         /* Now try a write to see if the write time gets reset. */
216
217         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
218         finfo1.basic_info.in.file.fnum = fnum1;
219         finfo2 = finfo1;
220
221         status = smb_raw_fileinfo(cli->tree, tctx, &finfo1);
222
223         if (!NT_STATUS_IS_OK(status)) {
224                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
225                 return False;
226         }
227         
228         torture_comment(tctx, "Modified write time %s\n", 
229                nt_time_string(tctx, finfo1.basic_info.out.write_time));
230
231
232         torture_comment(tctx, "Doing a 10 byte write to extend the file and see if this changes the last write time.\n");
233
234         written =  smbcli_write(cli->tree, fnum1, 0, "0123456789", 1, 10);
235
236         if (written != 10) {
237                 torture_comment(tctx, "write failed - wrote %d bytes (%s)\n", 
238                        (int)written, __location__);
239                 return False;
240         }
241
242         /* Just to prove to tridge that the an smbflush has no effect on
243            the write time :-). The setfileinfo IS STICKY. JRA. */
244
245         torture_comment(tctx, "Doing flush after write\n");
246
247         flsh.flush.level        = RAW_FLUSH_FLUSH;
248         flsh.flush.in.file.fnum = fnum1;
249         status = smb_raw_flush(cli->tree, &flsh);
250         if (!NT_STATUS_IS_OK(status)) {
251                 DEBUG(0, ("smbflush failed: %s\n", nt_errstr(status)));
252                 return False;
253         }
254
255         t = time(NULL);
256
257         /* Once the time was set using setfileinfo then it stays set - writes
258            don't have any effect. But make sure. */
259
260         while (time(NULL) < t+15) {
261                 status = smb_raw_fileinfo(cli->tree, tctx, &finfo2);
262
263                 if (!NT_STATUS_IS_OK(status)) {
264                         DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
265                         ret = False;
266                         break;
267                 }
268                 torture_comment(tctx, "write time %s\n", 
269                        nt_time_string(tctx, finfo2.basic_info.out.write_time));
270                 if (finfo1.basic_info.out.write_time != finfo2.basic_info.out.write_time) {
271                         torture_comment(tctx, "Server updated write_time after %d seconds\n",
272                                (int)(time(NULL) - t));
273                         break;
274                 }
275                 sleep(1);
276                 fflush(stdout);
277         }
278         
279         if (finfo1.basic_info.out.write_time == finfo2.basic_info.out.write_time) {
280                 torture_comment(tctx, "Server did not update write time\n");
281         }
282
283         fnum2 = smbcli_open(cli->tree, fname, O_RDWR, DENY_NONE);
284         if (fnum2 == -1) {
285                 torture_comment(tctx, "Failed to open %s\n", fname);
286                 return False;
287         }
288         
289         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");
290
291         written =  smbcli_write(cli->tree, fnum2, 0, "0123456789", 11, 10);
292
293         if (written != 10) {
294                 torture_comment(tctx, "write failed - wrote %d bytes (%s)\n", 
295                        (int)written, __location__);
296                 return False;
297         }
298
299         status = smb_raw_fileinfo(cli->tree, tctx, &finfo2);
300
301         if (!NT_STATUS_IS_OK(status)) {
302                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
303                 return False;
304         }
305         torture_comment(tctx, "write time %s\n", 
306                nt_time_string(tctx, finfo2.basic_info.out.write_time));
307         if (finfo1.basic_info.out.write_time != finfo2.basic_info.out.write_time) {
308                 torture_comment(tctx, "Server updated write_time\n");
309         }
310
311         torture_comment(tctx, "Closing the first fd to see if write time updated.\n");
312         smbcli_close(cli->tree, fnum1);
313         fnum1 = -1;
314
315         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");
316
317         written =  smbcli_write(cli->tree, fnum2, 0, "0123456789", 21, 10);
318
319         if (written != 10) {
320                 torture_comment(tctx, "write failed - wrote %d bytes (%s)\n", 
321                        (int)written, __location__);
322                 return False;
323         }
324
325         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
326         finfo1.basic_info.in.file.fnum = fnum2;
327         finfo2 = finfo1;
328         status = smb_raw_fileinfo(cli->tree, tctx, &finfo2);
329
330         if (!NT_STATUS_IS_OK(status)) {
331                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
332                 return False;
333         }
334         torture_comment(tctx, "write time %s\n", 
335                nt_time_string(tctx, finfo2.basic_info.out.write_time));
336         if (finfo1.basic_info.out.write_time != finfo2.basic_info.out.write_time) {
337                 torture_comment(tctx, "Server updated write_time\n");
338         }
339
340         t = time(NULL);
341
342         /* Once the time was set using setfileinfo then it stays set - writes
343            don't have any effect. But make sure. */
344
345         while (time(NULL) < t+15) {
346                 status = smb_raw_fileinfo(cli->tree, tctx, &finfo2);
347
348                 if (!NT_STATUS_IS_OK(status)) {
349                         DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
350                         ret = False;
351                         break;
352                 }
353                 torture_comment(tctx, "write time %s\n", 
354                        nt_time_string(tctx, finfo2.basic_info.out.write_time));
355                 if (finfo1.basic_info.out.write_time != finfo2.basic_info.out.write_time) {
356                         torture_comment(tctx, "Server updated write_time after %d seconds\n",
357                                (int)(time(NULL) - t));
358                         break;
359                 }
360                 sleep(1);
361                 fflush(stdout);
362         }
363         
364         if (finfo1.basic_info.out.write_time == finfo2.basic_info.out.write_time) {
365                 torture_comment(tctx, "Server did not update write time\n");
366         }
367
368         torture_comment(tctx, "Closing both fd's to see if write time updated.\n");
369
370         smbcli_close(cli->tree, fnum2);
371         fnum2 = -1;
372
373         fnum1 = smbcli_open(cli->tree, fname, O_RDWR, DENY_NONE);
374         if (fnum1 == -1) {
375                 torture_comment(tctx, "Failed to open %s\n", fname);
376                 return False;
377         }
378
379         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
380         finfo1.basic_info.in.file.fnum = fnum1;
381         finfo2 = finfo1;
382
383         status = smb_raw_fileinfo(cli->tree, tctx, &finfo1);
384
385         if (!NT_STATUS_IS_OK(status)) {
386                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
387                 return False;
388         }
389         
390         torture_comment(tctx, "Second open initial write time %s\n", 
391                nt_time_string(tctx, finfo1.basic_info.out.write_time));
392
393         sleep(10);
394         torture_comment(tctx, "Doing a 10 byte write to extend the file to see if this changes the last write time.\n");
395
396         written =  smbcli_write(cli->tree, fnum1, 0, "0123456789", 31, 10);
397
398         if (written != 10) {
399                 torture_comment(tctx, "write failed - wrote %d bytes (%s)\n", 
400                        (int)written, __location__);
401                 return False;
402         }
403
404         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
405         finfo1.basic_info.in.file.fnum = fnum1;
406         finfo2 = finfo1;
407         status = smb_raw_fileinfo(cli->tree, tctx, &finfo2);
408
409         if (!NT_STATUS_IS_OK(status)) {
410                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
411                 return False;
412         }
413         torture_comment(tctx, "write time %s\n", 
414                nt_time_string(tctx, finfo2.basic_info.out.write_time));
415         if (finfo1.basic_info.out.write_time != finfo2.basic_info.out.write_time) {
416                 torture_comment(tctx, "Server updated write_time\n");
417         }
418
419         t = time(NULL);
420
421         /* Once the time was set using setfileinfo then it stays set - writes
422            don't have any effect. But make sure. */
423
424         while (time(NULL) < t+15) {
425                 status = smb_raw_fileinfo(cli->tree, tctx, &finfo2);
426
427                 if (!NT_STATUS_IS_OK(status)) {
428                         DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
429                         ret = False;
430                         break;
431                 }
432                 torture_comment(tctx, "write time %s\n", 
433                        nt_time_string(tctx, finfo2.basic_info.out.write_time));
434                 if (finfo1.basic_info.out.write_time != finfo2.basic_info.out.write_time) {
435                         torture_comment(tctx, "Server updated write_time after %d seconds\n",
436                                (int)(time(NULL) - t));
437                         break;
438                 }
439                 sleep(1);
440                 fflush(stdout);
441         }
442         
443         if (finfo1.basic_info.out.write_time == finfo2.basic_info.out.write_time) {
444                 torture_comment(tctx, "Server did not update write time\n");
445         }
446
447
448         /* One more test to do. We should read the filetime via findfirst on the
449            second connection to ensure it's the same. This is very easy for a Windows
450            server but a bastard to get right on a POSIX server. JRA. */
451
452         if (fnum1 != -1)
453                 smbcli_close(cli->tree, fnum1);
454         smbcli_unlink(cli->tree, fname);
455         smbcli_deltree(cli->tree, BASEDIR);
456
457         return ret;
458 }
459
460
461 /* Windows does obviously not update the stat info during a write call. I
462  * *think* this is the problem causing a spurious Excel 2003 on XP error
463  * message when saving a file. Excel does a setfileinfo, writes, and then does
464  * a getpath(!)info. Or so... For Samba sometimes it displays an error message
465  * that the file might have been changed in between. What i've been able to
466  * trace down is that this happens if the getpathinfo after the write shows a
467  * different last write time than the setfileinfo showed. This is really
468  * nasty....
469  */
470
471 static BOOL test_finfo_after_write(struct torture_context *tctx, struct smbcli_state *cli, 
472                                                                    struct smbcli_state *cli2)
473 {
474         union smb_fileinfo finfo1, finfo2;
475         const char *fname = BASEDIR "\\torture_file.txt";
476         NTSTATUS status;
477         int fnum1 = -1;
478         int fnum2;
479         BOOL ret = True;
480         ssize_t written;
481
482         if (!torture_setup_dir(cli, BASEDIR)) {
483                 return False;
484         }
485
486         fnum1 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE);
487         if (fnum1 == -1) {
488                 ret = False;
489                 torture_result(tctx, TORTURE_FAIL, __location__": unable to open %s", fname);
490                 goto done;
491         }
492
493         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
494         finfo1.basic_info.in.file.fnum = fnum1;
495
496         status = smb_raw_fileinfo(cli->tree, tctx, &finfo1);
497
498         if (!NT_STATUS_IS_OK(status)) {
499                 ret = False;
500                 torture_result(tctx, TORTURE_FAIL, __location__": fileinfo failed: %s", nt_errstr(status));
501                 goto done;
502         }
503
504         msleep(1000);
505
506         written =  smbcli_write(cli->tree, fnum1, 0, "x", 0, 1);
507
508         if (written != 1) {
509                 torture_result(tctx, TORTURE_FAIL, __location__": written gave %d - should have been 1", (int)written);
510                 ret = False;
511                 goto done;
512         }
513
514         fnum2 = smbcli_open(cli2->tree, fname, O_RDWR, DENY_NONE);
515         if (fnum2 == -1) {
516                 torture_result(tctx, TORTURE_FAIL, __location__": failed to open 2nd time - %s", 
517                        smbcli_errstr(cli2->tree));
518                 ret = False;
519                 goto done;
520         }
521         
522         written =  smbcli_write(cli2->tree, fnum2, 0, "x", 0, 1);
523         
524         if (written != 1) {
525                 torture_result(tctx, TORTURE_FAIL, __location__": written gave %d - should have been 1", 
526                        (int)written);
527                 ret = False;
528                 goto done;
529         }
530         
531         finfo2.basic_info.level = RAW_FILEINFO_BASIC_INFO;
532         finfo2.basic_info.in.file.path = fname;
533         
534         status = smb_raw_pathinfo(cli2->tree, tctx, &finfo2);
535         
536         if (!NT_STATUS_IS_OK(status)) {
537                 torture_result(tctx, TORTURE_FAIL, __location__": fileinfo failed: %s", 
538                           nt_errstr(status));
539                 ret = False;
540                 goto done;
541         }
542         
543         if (finfo1.basic_info.out.create_time !=
544             finfo2.basic_info.out.create_time) {
545                 torture_result(tctx, TORTURE_FAIL, __location__": create_time changed");
546                 ret = False;
547                 goto done;
548         }
549         
550         if (finfo1.basic_info.out.access_time !=
551             finfo2.basic_info.out.access_time) {
552                 torture_result(tctx, TORTURE_FAIL, __location__": access_time changed");
553                 ret = False;
554                 goto done;
555         }
556         
557         if (finfo1.basic_info.out.write_time !=
558             finfo2.basic_info.out.write_time) {
559                 torture_result(tctx, TORTURE_FAIL, __location__": write_time changed:\n"
560                                            "write time conn 1 = %s, conn 2 = %s", 
561                        nt_time_string(tctx, finfo1.basic_info.out.write_time),
562                        nt_time_string(tctx, finfo2.basic_info.out.write_time));
563                 ret = False;
564                 goto done;
565         }
566         
567         if (finfo1.basic_info.out.change_time !=
568             finfo2.basic_info.out.change_time) {
569                 torture_result(tctx, TORTURE_FAIL, __location__": change_time changed");
570                 ret = False;
571                 goto done;
572         }
573         
574         /* One of the two following calls updates the qpathinfo. */
575         
576         /* If you had skipped the smbcli_write on fnum2, it would
577          * *not* have updated the stat on disk */
578         
579         smbcli_close(cli2->tree, fnum2);
580         cli2 = NULL;
581
582         /* This call is only for the people looking at ethereal :-) */
583         finfo2.basic_info.level = RAW_FILEINFO_BASIC_INFO;
584         finfo2.basic_info.in.file.path = fname;
585
586         status = smb_raw_pathinfo(cli->tree, tctx, &finfo2);
587
588         if (!NT_STATUS_IS_OK(status)) {
589                 torture_result(tctx, TORTURE_FAIL, __location__": fileinfo failed: %s", nt_errstr(status));
590                 ret = False;
591                 goto done;
592         }
593
594  done:
595         if (fnum1 != -1)
596                 smbcli_close(cli->tree, fnum1);
597         smbcli_unlink(cli->tree, fname);
598         smbcli_deltree(cli->tree, BASEDIR);
599
600         return ret;
601 }
602
603
604 /* 
605    testing of delayed update of write_time
606 */
607 struct torture_suite *torture_delay_write(void)
608 {
609         struct torture_suite *suite = torture_suite_create(talloc_autofree_context(), "DELAYWRITE");
610
611         torture_suite_add_2smb_test(suite, "finfo update on close", test_finfo_after_write);
612         torture_suite_add_1smb_test(suite, "delayed update of write time", test_delayed_write_update);
613         torture_suite_add_2smb_test(suite, "delayed update of write time using 2 connections", test_delayed_write_update2);
614
615         return suite;
616 }