r3886: Trying to understand delayed file write update times. Added another
[kai/samba.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    
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 2 of the License, or
12    (at your option) any later version.
13    
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18    
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 */
23
24 #include "includes.h"
25 #include "libcli/raw/libcliraw.h"
26 #include "system/time.h"
27
28 #define BASEDIR "\\delaywrite"
29
30 static BOOL test_delayed_write_update(struct smbcli_state *cli, TALLOC_CTX *mem_ctx)
31 {
32         union smb_fileinfo finfo1, finfo2;
33         const char *fname = BASEDIR "\\torture_file.txt";
34         NTSTATUS status;
35         int fnum1 = -1;
36         BOOL ret = True;
37         ssize_t written;
38         time_t t;
39
40         printf("Testing delayed update of write time\n");
41
42         if (!torture_setup_dir(cli, BASEDIR)) {
43                 return False;
44         }
45
46         fnum1 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE);
47         if (fnum1 == -1) {
48                 printf("Failed to open %s\n", fname);
49                 return False;
50         }
51
52         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
53         finfo1.basic_info.in.fnum = fnum1;
54         finfo2 = finfo1;
55
56         status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo1);
57
58         if (!NT_STATUS_IS_OK(status)) {
59                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
60                 return False;
61         }
62         
63         printf("Initial write time %s\n", 
64                nt_time_string(mem_ctx, finfo1.basic_info.out.write_time));
65
66         /* 3 second delay to ensure we get past any 2 second time
67            granularity (older systems may have that) */
68         sleep(3);
69
70         written =  smbcli_write(cli->tree, fnum1, 0, "x", 0, 1);
71
72         if (written != 1) {
73                 printf("write failed - wrote %d bytes (%s)\n", written, __location__);
74                 return False;
75         }
76
77         t = time(NULL);
78
79         while (time(NULL) < t+120) {
80                 status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo2);
81
82                 if (!NT_STATUS_IS_OK(status)) {
83                         DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
84                         ret = False;
85                         break;
86                 }
87                 printf("write time %s\n", 
88                        nt_time_string(mem_ctx, finfo2.basic_info.out.write_time));
89                 if (finfo1.basic_info.out.write_time != finfo2.basic_info.out.write_time) {
90                         printf("Server updated write_time after %d seconds\n",
91                                (int)(time(NULL) - t));
92                         break;
93                 }
94                 sleep(1);
95                 fflush(stdout);
96         }
97         
98         if (finfo1.basic_info.out.write_time == finfo2.basic_info.out.write_time) {
99                 printf("Server did not update write time?!\n");
100                 ret = False;
101         }
102
103
104         if (fnum1 != -1)
105                 smbcli_close(cli->tree, fnum1);
106         smbcli_unlink(cli->tree, fname);
107         smbcli_deltree(cli->tree, BASEDIR);
108
109         return ret;
110 }
111
112 /* 
113  * Do as above, but using 2 connections.
114  */
115
116 static BOOL test_delayed_write_update2(struct smbcli_state *cli, TALLOC_CTX *mem_ctx)
117 {
118         struct smbcli_state *cli2=NULL;
119         union smb_fileinfo finfo1, finfo2;
120         const char *fname = BASEDIR "\\torture_file.txt";
121         NTSTATUS status;
122         int fnum1 = -1;
123         BOOL ret = True;
124         ssize_t written;
125         time_t t;
126
127         printf("Testing delayed update of write time using 2 connections\n");
128
129         if (!torture_open_connection(&cli2)) {
130                 return False;
131         }
132
133         if (!torture_setup_dir(cli, BASEDIR)) {
134                 return False;
135         }
136
137         fnum1 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE);
138         if (fnum1 == -1) {
139                 printf("Failed to open %s\n", fname);
140                 return False;
141         }
142
143         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
144         finfo1.basic_info.in.fnum = fnum1;
145         finfo2 = finfo1;
146
147         status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo1);
148
149         if (!NT_STATUS_IS_OK(status)) {
150                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
151                 return False;
152         }
153         
154         printf("Initial write time %s\n", 
155                nt_time_string(mem_ctx, finfo1.basic_info.out.write_time));
156
157         /* 3 second delay to ensure we get past any 2 second time
158            granularity (older systems may have that) */
159         sleep(3);
160
161         written =  smbcli_write(cli->tree, fnum1, 0, "x", 0, 1);
162
163         if (written != 1) {
164                 printf("write failed - wrote %d bytes (%s)\n", written, __location__);
165                 return False;
166         }
167
168         t = time(NULL);
169
170         while (time(NULL) < t+120) {
171                 finfo2.basic_info.in.fname = fname;
172         
173                 status = smb_raw_pathinfo(cli2->tree, mem_ctx, &finfo2);
174
175                 if (!NT_STATUS_IS_OK(status)) {
176                         DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
177                         ret = False;
178                         break;
179                 }
180                 printf("write time %s\n", 
181                        nt_time_string(mem_ctx, finfo2.basic_info.out.write_time));
182                 if (finfo1.basic_info.out.write_time != finfo2.basic_info.out.write_time) {
183                         printf("Server updated write_time after %d seconds\n",
184                                (int)(time(NULL) - t));
185                         break;
186                 }
187                 sleep(1);
188                 fflush(stdout);
189         }
190         
191         if (finfo1.basic_info.out.write_time == finfo2.basic_info.out.write_time) {
192                 printf("Server did not update write time?!\n");
193                 ret = False;
194         }
195
196
197         if (cli2 != NULL) {
198                 torture_close_connection(cli2);
199         }
200         if (fnum1 != -1)
201                 smbcli_close(cli->tree, fnum1);
202         smbcli_unlink(cli->tree, fname);
203         smbcli_deltree(cli->tree, BASEDIR);
204
205         return ret;
206 }
207
208
209 /* Windows does obviously not update the stat info during a write call. I
210  * *think* this is the problem causing a spurious Excel 2003 on XP error
211  * message when saving a file. Excel does a setfileinfo, writes, and then does
212  * a getpath(!)info. Or so... For Samba sometimes it displays an error message
213  * that the file might have been changed in between. What i've been able to
214  * trace down is that this happens if the getpathinfo after the write shows a
215  * different last write time than the setfileinfo showed. This is really
216  * nasty....
217  */
218
219 static BOOL test_finfo_after_write(struct smbcli_state *cli, TALLOC_CTX *mem_ctx)
220 {
221         union smb_fileinfo finfo1, finfo2;
222         const char *fname = BASEDIR "\\torture_file.txt";
223         NTSTATUS status;
224         int fnum1 = -1;
225         int fnum2;
226         BOOL ret = True;
227         ssize_t written;
228         struct smbcli_state *cli2=NULL;
229
230         printf("Testing finfo update on close\n");
231
232         if (!torture_setup_dir(cli, BASEDIR)) {
233                 return False;
234         }
235
236         fnum1 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE);
237         if (fnum1 == -1) {
238                 ret = False;
239                 goto done;
240         }
241
242         finfo1.basic_info.level = RAW_FILEINFO_BASIC_INFO;
243         finfo1.basic_info.in.fnum = fnum1;
244
245         status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo1);
246
247         if (!NT_STATUS_IS_OK(status)) {
248                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
249                 ret = False;
250                 goto done;
251         }
252
253         msleep(1000);
254
255         written =  smbcli_write(cli->tree, fnum1, 0, "x", 0, 1);
256
257         if (written != 1) {
258                 printf("(%s) written gave %d - should have been 1\n", 
259                        __location__, written);
260                 ret = False;
261                 goto done;
262         }
263
264         if (!torture_open_connection(&cli2)) {
265                 return False;
266         }
267
268         fnum2 = smbcli_open(cli2->tree, fname, O_RDWR, DENY_NONE);
269         if (fnum2 == -1) {
270                 printf("(%s) failed to open 2nd time - %s\n", 
271                        __location__, smbcli_errstr(cli2->tree));
272                 ret = False;
273                 goto done;
274         }
275         
276         written =  smbcli_write(cli2->tree, fnum2, 0, "x", 0, 1);
277         
278         if (written != 1) {
279                 printf("(%s) written gave %d - should have been 1\n", 
280                        __location__, written);
281                 ret = False;
282                 goto done;
283         }
284         
285         finfo2.basic_info.level = RAW_FILEINFO_BASIC_INFO;
286         finfo2.basic_info.in.fname = fname;
287         
288         status = smb_raw_pathinfo(cli2->tree, mem_ctx, &finfo2);
289         
290         if (!NT_STATUS_IS_OK(status)) {
291                 DEBUG(0, ("(%s) fileinfo failed: %s\n", 
292                           __location__, nt_errstr(status)));
293                 ret = False;
294                 goto done;
295         }
296         
297         if (finfo1.basic_info.out.create_time !=
298             finfo2.basic_info.out.create_time) {
299                 printf("(%s) create_time changed\n", __location__);
300                 ret = False;
301                 goto done;
302         }
303         
304         if (finfo1.basic_info.out.access_time !=
305             finfo2.basic_info.out.access_time) {
306                 printf("(%s) access_time changed\n", __location__);
307                 ret = False;
308                 goto done;
309         }
310         
311         if (finfo1.basic_info.out.write_time !=
312             finfo2.basic_info.out.write_time) {
313                 printf("(%s) write_time changed\n", __location__);
314                 ret = False;
315                 goto done;
316         }
317         
318         if (finfo1.basic_info.out.change_time !=
319             finfo2.basic_info.out.change_time) {
320                 printf("(%s) change_time changed\n", __location__);
321                 ret = False;
322                 goto done;
323         }
324         
325         /* One of the two following calls updates the qpathinfo. */
326         
327         /* If you had skipped the smbcli_write on fnum2, it would
328          * *not* have updated the stat on disk */
329         
330         smbcli_close(cli2->tree, fnum2);
331         torture_close_connection(cli2);
332         cli2 = NULL;
333
334         /* This call is only for the people looking at ethereal :-) */
335         finfo2.basic_info.level = RAW_FILEINFO_BASIC_INFO;
336         finfo2.basic_info.in.fname = fname;
337
338         status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo2);
339
340         if (!NT_STATUS_IS_OK(status)) {
341                 DEBUG(0, ("fileinfo failed: %s\n", nt_errstr(status)));
342                 ret = False;
343                 goto done;
344         }
345
346  done:
347         if (fnum1 != -1)
348                 smbcli_close(cli->tree, fnum1);
349         smbcli_unlink(cli->tree, fname);
350         smbcli_deltree(cli->tree, BASEDIR);
351         if (cli2 != NULL) {
352                 torture_close_connection(cli2);
353         }
354
355         return ret;
356 }
357
358
359 /* 
360    testing of delayed update of write_time
361 */
362 BOOL torture_delay_write(void)
363 {
364         struct smbcli_state *cli;
365         BOOL ret = True;
366         TALLOC_CTX *mem_ctx;
367
368         if (!torture_open_connection(&cli)) {
369                 return False;
370         }
371
372         mem_ctx = talloc_init("torture_delay_write");
373
374         ret &= test_finfo_after_write(cli, mem_ctx);
375         ret &= test_delayed_write_update(cli, mem_ctx);
376         ret &= test_delayed_write_update2(cli, mem_ctx);
377
378         torture_close_connection(cli);
379         talloc_destroy(mem_ctx);
380         return ret;
381 }