85ada87737a022b6ba7d3d4a57e8293f9dd1528d
[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   static long delay;
100   if (!delay)
101     {
102       /* Initialize only once, by sleeping for 20 milliseconds (needed
103          since xfs has a quantization of about 10 milliseconds, even
104          though it has a granularity of 1 nanosecond, and since NTFS
105          has a default quantization of 15.25 milliseconds, even though
106          it has a granularity of 100 nanoseconds).  If the seconds
107          differ, repeat the test one more time (in case we crossed a
108          quantization boundary on a file system with 1 second
109          resolution).  If we can't observe a difference in only the
110          nanoseconds, then fall back to 1 second if the time is odd,
111          and 2 seconds (needed for FAT) if time is even.  */
112       struct stat st1;
113       struct stat st2;
114       ASSERT (stat ("t-stt-stamp1", &st1) == 0);
115       ASSERT (force_unlink ("t-stt-stamp1") == 0);
116       delay = 20000;
117       usleep (delay);
118       create_file ("t-stt-stamp1");
119       ASSERT (stat ("t-stt-stamp1", &st2) == 0);
120       if (st1.st_mtime != st2.st_mtime)
121         {
122           /* Seconds differ, give it one more shot.  */
123           st1 = st2;
124           ASSERT (force_unlink ("t-stt-stamp1") == 0);
125           usleep (delay);
126           create_file ("t-stt-stamp1");
127           ASSERT (stat ("t-stt-stamp1", &st2) == 0);
128         }
129       if (! (st1.st_mtime == st2.st_mtime
130              && get_stat_mtime_ns (&st1) < get_stat_mtime_ns (&st2)))
131         delay = (st1.st_mtime & 1) ? 1000000 : 2000000;
132     }
133   usleep (delay);
134 }
135
136 static void
137 prepare_test (struct stat *statinfo, struct timespec *modtimes)
138 {
139   int i;
140
141   create_file ("t-stt-stamp1");
142   nap ();
143   create_file ("t-stt-testfile");
144   nap ();
145   create_file ("t-stt-stamp2");
146   nap ();
147   ASSERT (chmod ("t-stt-testfile", 0400) == 0);
148   nap ();
149   create_file ("t-stt-stamp3");
150
151   do_stat ("t-stt-stamp1",  &statinfo[0]);
152   do_stat ("t-stt-testfile", &statinfo[1]);
153   do_stat ("t-stt-stamp2",  &statinfo[2]);
154   do_stat ("t-stt-stamp3",  &statinfo[3]);
155
156   /* Now use our access functions. */
157   for (i = 0; i < NFILES; ++i)
158     {
159       modtimes[i] = get_stat_mtime (&statinfo[i]);
160     }
161 }
162
163 static void
164 test_mtime (const struct stat *statinfo, struct timespec *modtimes)
165 {
166   int i;
167
168   /* Use the struct stat fields directly. */
169   /* mtime(stamp1) < mtime(stamp2) */
170   ASSERT (statinfo[0].st_mtime < statinfo[2].st_mtime
171           || (statinfo[0].st_mtime == statinfo[2].st_mtime
172               && (get_stat_mtime_ns (&statinfo[0])
173                   < get_stat_mtime_ns (&statinfo[2]))));
174   /* mtime(stamp2) < mtime(stamp3) */
175   ASSERT (statinfo[2].st_mtime < statinfo[3].st_mtime
176           || (statinfo[2].st_mtime == statinfo[3].st_mtime
177               && (get_stat_mtime_ns (&statinfo[2])
178                   < get_stat_mtime_ns (&statinfo[3]))));
179
180   /* Now check the result of the access functions. */
181   /* mtime(stamp1) < mtime(stamp2) */
182   ASSERT (modtimes[0].tv_sec < modtimes[2].tv_sec
183           || (modtimes[0].tv_sec == modtimes[2].tv_sec
184               && modtimes[0].tv_nsec < modtimes[2].tv_nsec));
185   /* mtime(stamp2) < mtime(stamp3) */
186   ASSERT (modtimes[2].tv_sec < modtimes[3].tv_sec
187           || (modtimes[2].tv_sec == modtimes[3].tv_sec
188               && modtimes[2].tv_nsec < modtimes[3].tv_nsec));
189
190   /* verify equivalence */
191   for (i = 0; i < NFILES; ++i)
192     {
193       struct timespec ts;
194       ts = get_stat_mtime (&statinfo[i]);
195       ASSERT (ts.tv_sec == statinfo[i].st_mtime);
196     }
197 }
198
199 #if (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__
200 /* Skip the ctime tests on native Windows platforms, because their
201    st_ctime is either the same as st_mtime (plus or minus an offset)
202    or set to the file _creation_ time, and is not influenced by rename
203    or chmod.  */
204 # define test_ctime(ignored) ((void) 0)
205 #else
206 static void
207 test_ctime (const struct stat *statinfo)
208 {
209   /* On some buggy NFS clients, mtime and ctime are disproportionately
210      skewed from one another.  Skip this test in that case.  */
211   if (statinfo[0].st_mtime != statinfo[0].st_ctime)
212     return;
213
214   /* mtime(stamp2) < ctime(renamed) */
215   ASSERT (statinfo[2].st_mtime < statinfo[1].st_ctime
216           || (statinfo[2].st_mtime == statinfo[1].st_ctime
217               && (get_stat_mtime_ns (&statinfo[2])
218                   < get_stat_ctime_ns (&statinfo[1]))));
219 }
220 #endif
221
222 static void
223 test_birthtime (const struct stat *statinfo,
224                 const struct timespec *modtimes,
225                 struct timespec *birthtimes)
226 {
227   int i;
228
229   /* Collect the birth times.  */
230   for (i = 0; i < NFILES; ++i)
231     {
232       birthtimes[i] = get_stat_birthtime (&statinfo[i]);
233       if (birthtimes[i].tv_nsec < 0)
234         return;
235     }
236
237   /* mtime(stamp1) < birthtime(renamed) */
238   ASSERT (modtimes[0].tv_sec < birthtimes[1].tv_sec
239           || (modtimes[0].tv_sec == birthtimes[1].tv_sec
240               && modtimes[0].tv_nsec < birthtimes[1].tv_nsec));
241   /* birthtime(renamed) < mtime(stamp2) */
242   ASSERT (birthtimes[1].tv_sec < modtimes[2].tv_sec
243           || (birthtimes[1].tv_sec == modtimes[2].tv_sec
244               && birthtimes[1].tv_nsec < modtimes[2].tv_nsec));
245 }
246
247 int
248 main (void)
249 {
250   struct stat statinfo[NFILES];
251   struct timespec modtimes[NFILES];
252   struct timespec birthtimes[NFILES];
253
254 #ifdef SIGHUP
255   signal (SIGHUP, cleanup);
256 #endif
257 #ifdef SIGINT
258   signal (SIGINT, cleanup);
259 #endif
260 #ifdef SIGQUIT
261   signal (SIGQUIT, cleanup);
262 #endif
263 #ifdef SIGTERM
264   signal (SIGTERM, cleanup);
265 #endif
266
267   cleanup (0);
268   prepare_test (statinfo, modtimes);
269   test_mtime (statinfo, modtimes);
270   test_ctime (statinfo);
271   test_birthtime (statinfo, modtimes, birthtimes);
272
273   cleanup (0);
274   return 0;
275 }