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