test-stat-time: port to mingw
[gnulib.git] / tests / test-stat-time.c
1 /* Test of <stat-time.h>.
2    Copyright (C) 2007-2009 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
16
17 /* Written by James Youngman <jay@gnu.org>, 2007.  */
18
19 #include <config.h>
20
21 #include "stat-time.h"
22
23 #include <fcntl.h>
24 #include <signal.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29
30 #define ASSERT(expr) \
31   do                                                                         \
32     {                                                                        \
33       if (!(expr))                                                           \
34         {                                                                    \
35           fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
36           fflush (stderr);                                                   \
37           abort ();                                                          \
38         }                                                                    \
39     }                                                                        \
40   while (0)
41
42 enum { NFILES = 4 };
43
44 static int
45 force_unlink (const char *filename)
46 {
47   /* This chmod is necessary on mingw, where unlink() of a read-only file
48      fails with EPERM.  */
49   chmod (filename, 0600);
50   return unlink (filename);
51 }
52
53 static void
54 cleanup (int sig)
55 {
56   /* Remove temporary files.  */
57   force_unlink ("t-stt-stamp1");
58   force_unlink ("t-stt-testfile");
59   force_unlink ("t-stt-stamp2");
60   force_unlink ("t-stt-renamed");
61   force_unlink ("t-stt-stamp3");
62
63   if (sig != 0)
64     _exit (1);
65 }
66
67 static int
68 open_file (const char *filename, int flags)
69 {
70   int fd = open (filename, flags | O_WRONLY, 0500);
71   if (fd >= 0)
72     {
73       close (fd);
74       return 1;
75     }
76   else
77     {
78       return 0;
79     }
80 }
81
82 static void
83 create_file (const char *filename)
84 {
85   ASSERT (open_file (filename, O_CREAT | O_EXCL));
86 }
87
88 static void
89 do_stat (const char *filename, struct stat *p)
90 {
91   ASSERT (stat (filename, p) == 0);
92 }
93
94 /* Sleep long enough to notice a timestamp difference on the file
95    system in the current directory.  */
96 static void
97 nap (void)
98 {
99 #if !HAVE_USLEEP
100   /* Assume the worst case file system of FAT, which has a granularity
101      of 2 seconds.  */
102   sleep (2);
103 #else /* HAVE_USLEEP */
104   static long delay;
105   if (!delay)
106     {
107       /* Initialize only once, by sleeping for 15 milliseconds (needed
108          since xfs has a quantization of about 10 milliseconds, even
109          though it has a granularity of 1 nanosecond).  If the seconds
110          differ, repeat the test one more time (in case we crossed a
111          quantization boundary on a file system with 1 second
112          resolution).  If we can't observe a difference in only the
113          nanoseconds, then fall back to 2 seconds.  However, note that
114          usleep (2000000) is allowed to fail with EINVAL.  */
115       struct stat st1;
116       struct stat st2;
117       ASSERT (stat ("t-stt-stamp1", &st1) == 0);
118       ASSERT (force_unlink ("t-stt-stamp1") == 0);
119       delay = 15000;
120       usleep (delay);
121       create_file ("t-stt-stamp1");
122       ASSERT (stat ("t-stt-stamp1", &st2) == 0);
123       if (st1.st_mtime != st2.st_mtime)
124         {
125           /* Seconds differ, give it one more shot.  */
126           st1 = st2;
127           ASSERT (force_unlink ("t-stt-stamp1") == 0);
128           usleep (delay);
129           create_file ("t-stt-stamp1");
130           ASSERT (stat ("t-stt-stamp1", &st2) == 0);
131         }
132       if (! (st1.st_mtime == st2.st_mtime
133              && get_stat_mtime_ns (&st1) < get_stat_mtime_ns (&st2)))
134         delay = 2000000;
135     }
136   if (delay == 2000000)
137     sleep (2);
138   else
139     usleep (delay);
140 #endif /* HAVE_USLEEP */
141 }
142
143 static void
144 prepare_test (struct stat *statinfo, struct timespec *modtimes)
145 {
146   int i;
147
148   create_file ("t-stt-stamp1");
149   nap ();
150   create_file ("t-stt-testfile");
151   nap ();
152   create_file ("t-stt-stamp2");
153   nap ();
154   ASSERT (chmod ("t-stt-testfile", 0400) == 0);
155   nap ();
156   create_file ("t-stt-stamp3");
157
158   do_stat ("t-stt-stamp1",  &statinfo[0]);
159   do_stat ("t-stt-testfile", &statinfo[1]);
160   do_stat ("t-stt-stamp2",  &statinfo[2]);
161   do_stat ("t-stt-stamp3",  &statinfo[3]);
162
163   /* Now use our access functions. */
164   for (i = 0; i < NFILES; ++i)
165     {
166       modtimes[i] = get_stat_mtime (&statinfo[i]);
167     }
168 }
169
170 static void
171 test_mtime (const struct stat *statinfo, struct timespec *modtimes)
172 {
173   int i;
174
175   /* Use the struct stat fields directly. */
176   /* mtime(stamp1) < mtime(stamp2) */
177   ASSERT (statinfo[0].st_mtime < statinfo[2].st_mtime
178           || (statinfo[0].st_mtime == statinfo[2].st_mtime
179               && (get_stat_mtime_ns (&statinfo[0])
180                   < get_stat_mtime_ns (&statinfo[2]))));
181   /* mtime(stamp2) < mtime(stamp3) */
182   ASSERT (statinfo[2].st_mtime < statinfo[3].st_mtime
183           || (statinfo[2].st_mtime == statinfo[3].st_mtime
184               && (get_stat_mtime_ns (&statinfo[2])
185                   < get_stat_mtime_ns (&statinfo[3]))));
186
187   /* Now check the result of the access functions. */
188   /* mtime(stamp1) < mtime(stamp2) */
189   ASSERT (modtimes[0].tv_sec < modtimes[2].tv_sec
190           || (modtimes[0].tv_sec == modtimes[2].tv_sec
191               && modtimes[0].tv_nsec < modtimes[2].tv_nsec));
192   /* mtime(stamp2) < mtime(stamp3) */
193   ASSERT (modtimes[2].tv_sec < modtimes[3].tv_sec
194           || (modtimes[2].tv_sec == modtimes[3].tv_sec
195               && modtimes[2].tv_nsec < modtimes[3].tv_nsec));
196
197   /* verify equivalence */
198   for (i = 0; i < NFILES; ++i)
199     {
200       struct timespec ts;
201       ts = get_stat_mtime (&statinfo[i]);
202       ASSERT (ts.tv_sec == statinfo[i].st_mtime);
203     }
204 }
205
206 #if (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__
207 /* Skip the ctime tests on native Windows platforms, because their
208    st_ctime is either the same as st_mtime (plus or minus an offset)
209    or set to the file _creation_ time, and is not influenced by rename
210    or chmod.  */
211 # define test_ctime(ignored) ((void) 0)
212 #else
213 static void
214 test_ctime (const struct stat *statinfo)
215 {
216   /* On some buggy NFS clients, mtime and ctime are disproportionately
217      skewed from one another.  Skip this test in that case.  */
218   if (statinfo[0].st_mtime != statinfo[0].st_ctime)
219     return;
220
221   /* mtime(stamp2) < ctime(renamed) */
222   ASSERT (statinfo[2].st_mtime < statinfo[1].st_ctime
223           || (statinfo[2].st_mtime == statinfo[1].st_ctime
224               && (get_stat_mtime_ns (&statinfo[2])
225                   < get_stat_ctime_ns (&statinfo[1]))));
226 }
227 #endif
228
229 static void
230 test_birthtime (const struct stat *statinfo,
231                 const struct timespec *modtimes,
232                 struct timespec *birthtimes)
233 {
234   int i;
235
236   /* Collect the birth times.  */
237   for (i = 0; i < NFILES; ++i)
238     {
239       birthtimes[i] = get_stat_birthtime (&statinfo[i]);
240       if (birthtimes[i].tv_nsec < 0)
241         return;
242     }
243
244   /* mtime(stamp1) < birthtime(renamed) */
245   ASSERT (modtimes[0].tv_sec < birthtimes[1].tv_sec
246           || (modtimes[0].tv_sec == birthtimes[1].tv_sec
247               && modtimes[0].tv_nsec < birthtimes[1].tv_nsec));
248   /* birthtime(renamed) < mtime(stamp2) */
249   ASSERT (birthtimes[1].tv_sec < modtimes[2].tv_sec
250           || (birthtimes[1].tv_sec == modtimes[2].tv_sec
251               && birthtimes[1].tv_nsec < modtimes[2].tv_nsec));
252 }
253
254 int
255 main ()
256 {
257   struct stat statinfo[NFILES];
258   struct timespec modtimes[NFILES];
259   struct timespec birthtimes[NFILES];
260
261 #ifdef SIGHUP
262   signal (SIGHUP, cleanup);
263 #endif
264 #ifdef SIGINT
265   signal (SIGINT, cleanup);
266 #endif
267 #ifdef SIGQUIT
268   signal (SIGQUIT, cleanup);
269 #endif
270 #ifdef SIGTERM
271   signal (SIGTERM, cleanup);
272 #endif
273
274   cleanup (0);
275   prepare_test (statinfo, modtimes);
276   test_mtime (statinfo, modtimes);
277   test_ctime (statinfo);
278   test_birthtime (statinfo, modtimes, birthtimes);
279
280   cleanup (0);
281   return 0;
282 }