r3887: Much better understanding of delayed write time, and the interaction
[metze/samba/wb-ndr.git] / source / 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 2 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, write to the Free Software
22    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 */
24
25 #include "includes.h"
26 #include "libcli/raw/libcliraw.h"
27 #include "system/time.h"
28
29 #define BASEDIR "\\delaywrite"
30
31 static BOOL test_delayed_write_update(struct smbcli_state *cli, TALLOC_CTX *mem_ctx)
32 {
33         union smb_fileinfo finfo1, finfo2;
34         const char *fname = BASEDIR "\\torture_file.txt";
35         NTSTATUS status;
36         int fnum1 = -1;
37         BOOL ret = True;
38         ssize_t written;
39         time_t t;
40
41         printf("Testing delayed update of write time\n");
42
43         if (!torture_setup_dir(cli, BASEDIR)) {
44                 return False;
45         }
46
47         fnum1 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE);
48         if (fnum1 == -1) {
49                 printf("Failed to open %s\n", fname);
50                 return False;
51         }
52
53         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
54         finfo1.basic_info.in.fnum = fnum1;
55         finfo2 = finfo1;
56
57         status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo1);
58
59         if (!NT_STATUS_IS_OK(status)) {
60                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
61                 return False;
62         }
63         
64         printf("Initial write time %s\n", 
65                nt_time_string(mem_ctx, finfo1.basic_info.out.write_time));
66
67         /* 3 second delay to ensure we get past any 2 second time
68            granularity (older systems may have that) */
69         sleep(3);
70
71         written =  smbcli_write(cli->tree, fnum1, 0, "x", 0, 1);
72
73         if (written != 1) {
74                 printf("write failed - wrote %d bytes (%s)\n", written, __location__);
75                 return False;
76         }
77
78         t = time(NULL);
79
80         while (time(NULL) < t+120) {
81                 status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo2);
82
83                 if (!NT_STATUS_IS_OK(status)) {
84                         DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
85                         ret = False;
86                         break;
87                 }
88                 printf("write time %s\n", 
89                        nt_time_string(mem_ctx, finfo2.basic_info.out.write_time));
90                 if (finfo1.basic_info.out.write_time != finfo2.basic_info.out.write_time) {
91                         printf("Server updated write_time after %d seconds\n",
92                                (int)(time(NULL) - t));
93                         break;
94                 }
95                 sleep(1);
96                 fflush(stdout);
97         }
98         
99         if (finfo1.basic_info.out.write_time == finfo2.basic_info.out.write_time) {
100                 printf("Server did not update write time?!\n");
101                 ret = False;
102         }
103
104
105         if (fnum1 != -1)
106                 smbcli_close(cli->tree, fnum1);
107         smbcli_unlink(cli->tree, fname);
108         smbcli_deltree(cli->tree, BASEDIR);
109
110         return ret;
111 }
112
113 /* 
114  * Do as above, but using 2 connections.
115  */
116
117 static BOOL test_delayed_write_update2(struct smbcli_state *cli, TALLOC_CTX *mem_ctx)
118 {
119         struct smbcli_state *cli2=NULL;
120         union smb_fileinfo finfo1, finfo2;
121         const char *fname = BASEDIR "\\torture_file.txt";
122         NTSTATUS status;
123         int fnum1 = -1;
124         BOOL ret = True;
125         ssize_t written;
126         time_t t;
127
128         printf("Testing delayed update of write time using 2 connections\n");
129
130         if (!torture_open_connection(&cli2)) {
131                 return False;
132         }
133
134         if (!torture_setup_dir(cli, BASEDIR)) {
135                 return False;
136         }
137
138         fnum1 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE);
139         if (fnum1 == -1) {
140                 printf("Failed to open %s\n", fname);
141                 return False;
142         }
143
144         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
145         finfo1.basic_info.in.fnum = fnum1;
146         finfo2 = finfo1;
147
148         status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo1);
149
150         if (!NT_STATUS_IS_OK(status)) {
151                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
152                 return False;
153         }
154         
155         printf("Initial write time %s\n", 
156                nt_time_string(mem_ctx, finfo1.basic_info.out.write_time));
157
158         /* 3 second delay to ensure we get past any 2 second time
159            granularity (older systems may have that) */
160         sleep(3);
161
162         {
163                 /* Try using setfileinfo instead of write to update write time. */
164                 union smb_setfileinfo sfinfo;
165                 time_t t_set = time(NULL);
166                 sfinfo.basic_info.level = RAW_SFILEINFO_BASIC_INFO;
167                 sfinfo.basic_info.file.fnum = fnum1;
168                 sfinfo.basic_info.in.create_time = finfo1.basic_info.out.create_time;
169                 sfinfo.basic_info.in.access_time = finfo1.basic_info.out.access_time;
170
171                 /* I tried this with both + and - ve to see if it makes a different.
172                    It doesn't - once the filetime is set via setfileinfo it stays that way. */
173 #if 1
174                 unix_to_nt_time(&sfinfo.basic_info.in.write_time, t_set - 30000);
175 #else
176                 unix_to_nt_time(&sfinfo.basic_info.in.write_time, t_set + 30000);
177 #endif
178                 sfinfo.basic_info.in.change_time = finfo1.basic_info.out.change_time;
179                 sfinfo.basic_info.in.attrib = finfo1.basic_info.out.attrib;
180
181                 status = smb_raw_setfileinfo(cli->tree, &sfinfo);
182
183                 if (!NT_STATUS_IS_OK(status)) {
184                         DEBUG(0, ("sfileinfo failed: %s\n", nt_errstr(status)));
185                         return False;
186                 }
187         }
188
189         t = time(NULL);
190
191         while (time(NULL) < t+120) {
192                 finfo2.basic_info.in.fname = fname;
193         
194                 status = smb_raw_pathinfo(cli2->tree, mem_ctx, &finfo2);
195
196                 if (!NT_STATUS_IS_OK(status)) {
197                         DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
198                         ret = False;
199                         break;
200                 }
201                 printf("write time %s\n", 
202                        nt_time_string(mem_ctx, finfo2.basic_info.out.write_time));
203                 if (finfo1.basic_info.out.write_time != finfo2.basic_info.out.write_time) {
204                         printf("Server updated write_time after %d seconds\n",
205                                (int)(time(NULL) - t));
206                         break;
207                 }
208                 sleep(1);
209                 fflush(stdout);
210         }
211         
212         if (finfo1.basic_info.out.write_time == finfo2.basic_info.out.write_time) {
213                 printf("Server did not update write time?!\n");
214                 ret = False;
215         }
216
217         /* Now try a write to see if the write time gets reset. */
218
219         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
220         finfo1.basic_info.in.fnum = fnum1;
221         finfo2 = finfo1;
222
223         status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo1);
224
225         if (!NT_STATUS_IS_OK(status)) {
226                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
227                 return False;
228         }
229         
230         printf("Modified write time %s\n", 
231                nt_time_string(mem_ctx, finfo1.basic_info.out.write_time));
232
233
234         written =  smbcli_write(cli->tree, fnum1, 0, "x", 0, 1);
235
236         if (written != 1) {
237                 printf("write failed - wrote %d bytes (%s)\n", written, __location__);
238                 return False;
239         }
240
241         t = time(NULL);
242
243         /* Once the time was set using setfileinfo then it stays set - writes
244            don't have any effect. But make sure. */
245
246         while (time(NULL) < t+40) {
247                 status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo2);
248
249                 if (!NT_STATUS_IS_OK(status)) {
250                         DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
251                         ret = False;
252                         break;
253                 }
254                 printf("write time %s\n", 
255                        nt_time_string(mem_ctx, finfo2.basic_info.out.write_time));
256                 if (finfo1.basic_info.out.write_time != finfo2.basic_info.out.write_time) {
257                         printf("Server updated write_time after %d seconds\n",
258                                (int)(time(NULL) - t));
259                         break;
260                 }
261                 sleep(1);
262                 fflush(stdout);
263         }
264         
265         if (finfo1.basic_info.out.write_time == finfo2.basic_info.out.write_time) {
266                 printf("Server did not update write time?!\n");
267         }
268
269         /* One more test to do. We should read the filetime via findfirst on the
270            second connection to ensure it's the same. This is very easy for a Windows
271            server but a bastard to get right on a POSIX server. JRA. */
272
273         if (cli2 != NULL) {
274                 torture_close_connection(cli2);
275         }
276         if (fnum1 != -1)
277                 smbcli_close(cli->tree, fnum1);
278         smbcli_unlink(cli->tree, fname);
279         smbcli_deltree(cli->tree, BASEDIR);
280
281         return ret;
282 }
283
284
285 /* Windows does obviously not update the stat info during a write call. I
286  * *think* this is the problem causing a spurious Excel 2003 on XP error
287  * message when saving a file. Excel does a setfileinfo, writes, and then does
288  * a getpath(!)info. Or so... For Samba sometimes it displays an error message
289  * that the file might have been changed in between. What i've been able to
290  * trace down is that this happens if the getpathinfo after the write shows a
291  * different last write time than the setfileinfo showed. This is really
292  * nasty....
293  */
294
295 static BOOL test_finfo_after_write(struct smbcli_state *cli, TALLOC_CTX *mem_ctx)
296 {
297         union smb_fileinfo finfo1, finfo2;
298         const char *fname = BASEDIR "\\torture_file.txt";
299         NTSTATUS status;
300         int fnum1 = -1;
301         int fnum2;
302         BOOL ret = True;
303         ssize_t written;
304         struct smbcli_state *cli2=NULL;
305
306         printf("Testing finfo update on close\n");
307
308         if (!torture_setup_dir(cli, BASEDIR)) {
309                 return False;
310         }
311
312         fnum1 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE);
313         if (fnum1 == -1) {
314                 ret = False;
315                 goto done;
316         }
317
318         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
319         finfo1.basic_info.in.fnum = fnum1;
320
321         status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo1);
322
323         if (!NT_STATUS_IS_OK(status)) {
324                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
325                 ret = False;
326                 goto done;
327         }
328
329         msleep(1000);
330
331         written =  smbcli_write(cli->tree, fnum1, 0, "x", 0, 1);
332
333         if (written != 1) {
334                 printf("(%s) written gave %d - should have been 1\n", 
335                        __location__, written);
336                 ret = False;
337                 goto done;
338         }
339
340         if (!torture_open_connection(&cli2)) {
341                 return False;
342         }
343
344         fnum2 = smbcli_open(cli2->tree, fname, O_RDWR, DENY_NONE);
345         if (fnum2 == -1) {
346                 printf("(%s) failed to open 2nd time - %s\n", 
347                        __location__, smbcli_errstr(cli2->tree));
348                 ret = False;
349                 goto done;
350         }
351         
352         written =  smbcli_write(cli2->tree, fnum2, 0, "x", 0, 1);
353         
354         if (written != 1) {
355                 printf("(%s) written gave %d - should have been 1\n", 
356                        __location__, written);
357                 ret = False;
358                 goto done;
359         }
360         
361         finfo2.basic_info.level = RAW_FILEINFO_BASIC_INFO;
362         finfo2.basic_info.in.fname = fname;
363         
364         status = smb_raw_pathinfo(cli2->tree, mem_ctx, &finfo2);
365         
366         if (!NT_STATUS_IS_OK(status)) {
367                 DEBUG(0, ("(%s) fileinfo failed: %s\n", 
368                           __location__, nt_errstr(status)));
369                 ret = False;
370                 goto done;
371         }
372         
373         if (finfo1.basic_info.out.create_time !=
374             finfo2.basic_info.out.create_time) {
375                 printf("(%s) create_time changed\n", __location__);
376                 ret = False;
377                 goto done;
378         }
379         
380         if (finfo1.basic_info.out.access_time !=
381             finfo2.basic_info.out.access_time) {
382                 printf("(%s) access_time changed\n", __location__);
383                 ret = False;
384                 goto done;
385         }
386         
387         if (finfo1.basic_info.out.write_time !=
388             finfo2.basic_info.out.write_time) {
389                 printf("(%s) write_time changed\n", __location__);
390                 ret = False;
391                 goto done;
392         }
393         
394         if (finfo1.basic_info.out.change_time !=
395             finfo2.basic_info.out.change_time) {
396                 printf("(%s) change_time changed\n", __location__);
397                 ret = False;
398                 goto done;
399         }
400         
401         /* One of the two following calls updates the qpathinfo. */
402         
403         /* If you had skipped the smbcli_write on fnum2, it would
404          * *not* have updated the stat on disk */
405         
406         smbcli_close(cli2->tree, fnum2);
407         torture_close_connection(cli2);
408         cli2 = NULL;
409
410         /* This call is only for the people looking at ethereal :-) */
411         finfo2.basic_info.level = RAW_FILEINFO_BASIC_INFO;
412         finfo2.basic_info.in.fname = fname;
413
414         status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo2);
415
416         if (!NT_STATUS_IS_OK(status)) {
417                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
418                 ret = False;
419                 goto done;
420         }
421
422  done:
423         if (fnum1 != -1)
424                 smbcli_close(cli->tree, fnum1);
425         smbcli_unlink(cli->tree, fname);
426         smbcli_deltree(cli->tree, BASEDIR);
427         if (cli2 != NULL) {
428                 torture_close_connection(cli2);
429         }
430
431         return ret;
432 }
433
434
435 /* 
436    testing of delayed update of write_time
437 */
438 BOOL torture_delay_write(void)
439 {
440         struct smbcli_state *cli;
441         BOOL ret = True;
442         TALLOC_CTX *mem_ctx;
443
444         if (!torture_open_connection(&cli)) {
445                 return False;
446         }
447
448         mem_ctx = talloc_init("torture_delay_write");
449
450         ret &= test_finfo_after_write(cli, mem_ctx);
451         ret &= test_delayed_write_update(cli, mem_ctx);
452         ret &= test_delayed_write_update2(cli, mem_ctx);
453
454         torture_close_connection(cli);
455         talloc_destroy(mem_ctx);
456         return ret;
457 }