2b4f0f13c7dc7be0b214eea4ace6b883b19cfd56
[gnulib.git] / tests / test-stat-time.c
1 /* Test of <stat-time.h>.
2    Copyright (C) 2007-2013 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 <sys/stat.h>
26 #include <unistd.h>
27
28 #include "macros.h"
29
30 enum { NFILES = 4 };
31
32 static int
33 force_unlink (const char *filename)
34 {
35   /* This chmod is necessary on mingw, where unlink() of a read-only file
36      fails with EPERM.  */
37   chmod (filename, 0600);
38   return unlink (filename);
39 }
40
41 static void
42 cleanup (int sig)
43 {
44   /* Remove temporary files.  */
45   force_unlink ("t-stt-stamp1");
46   force_unlink ("t-stt-testfile");
47   force_unlink ("t-stt-stamp2");
48   force_unlink ("t-stt-renamed");
49   force_unlink ("t-stt-stamp3");
50
51   if (sig != 0)
52     _exit (1);
53 }
54
55 static int
56 open_file (const char *filename, int flags)
57 {
58   int fd = open (filename, flags | O_WRONLY, 0500);
59   if (fd >= 0)
60     {
61       close (fd);
62       return 1;
63     }
64   else
65     {
66       return 0;
67     }
68 }
69
70 static void
71 create_file (const char *filename)
72 {
73   ASSERT (open_file (filename, O_CREAT | O_EXCL));
74 }
75
76 static void
77 do_stat (const char *filename, struct stat *p)
78 {
79   ASSERT (stat (filename, p) == 0);
80 }
81
82 /* Sleep long enough to notice a timestamp difference on the file
83    system in the current directory.  */
84 static void
85 nap (void)
86 {
87   static long delay;
88   if (!delay)
89     {
90       /* Initialize only once, by sleeping for 20 milliseconds (needed
91          since xfs has a quantization of about 10 milliseconds, even
92          though it has a granularity of 1 nanosecond, and since NTFS
93          has a default quantization of 15.25 milliseconds, even though
94          it has a granularity of 100 nanoseconds).  If the seconds
95          differ, repeat the test one more time (in case we crossed a
96          quantization boundary on a file system with 1 second
97          resolution).  If we can't observe a difference in only the
98          nanoseconds, then fall back to 1 second if the time is odd,
99          and 2 seconds (needed for FAT) if time is even.  */
100       struct stat st1;
101       struct stat st2;
102       ASSERT (stat ("t-stt-stamp1", &st1) == 0);
103       ASSERT (force_unlink ("t-stt-stamp1") == 0);
104       delay = 20000;
105       usleep (delay);
106       create_file ("t-stt-stamp1");
107       ASSERT (stat ("t-stt-stamp1", &st2) == 0);
108       if (st1.st_mtime != st2.st_mtime)
109         {
110           /* Seconds differ, give it one more shot.  */
111           st1 = st2;
112           ASSERT (force_unlink ("t-stt-stamp1") == 0);
113           usleep (delay);
114           create_file ("t-stt-stamp1");
115           ASSERT (stat ("t-stt-stamp1", &st2) == 0);
116         }
117       if (! (st1.st_mtime == st2.st_mtime
118              && get_stat_mtime_ns (&st1) < get_stat_mtime_ns (&st2)))
119         delay = (st1.st_mtime & 1) ? 1000000 : 2000000;
120     }
121   usleep (delay);
122 }
123
124 static void
125 prepare_test (struct stat *statinfo, struct timespec *modtimes)
126 {
127   int i;
128
129   create_file ("t-stt-stamp1");
130   nap ();
131   create_file ("t-stt-testfile");
132   nap ();
133   create_file ("t-stt-stamp2");
134   nap ();
135   ASSERT (chmod ("t-stt-testfile", 0400) == 0);
136   nap ();
137   create_file ("t-stt-stamp3");
138
139   do_stat ("t-stt-stamp1",  &statinfo[0]);
140   do_stat ("t-stt-testfile", &statinfo[1]);
141   do_stat ("t-stt-stamp2",  &statinfo[2]);
142   do_stat ("t-stt-stamp3",  &statinfo[3]);
143
144   /* Now use our access functions. */
145   for (i = 0; i < NFILES; ++i)
146     {
147       modtimes[i] = get_stat_mtime (&statinfo[i]);
148     }
149 }
150
151 static void
152 test_mtime (const struct stat *statinfo, struct timespec *modtimes)
153 {
154   int i;
155
156   /* Use the struct stat fields directly. */
157   /* mtime(stamp1) < mtime(stamp2) */
158   ASSERT (statinfo[0].st_mtime < statinfo[2].st_mtime
159           || (statinfo[0].st_mtime == statinfo[2].st_mtime
160               && (get_stat_mtime_ns (&statinfo[0])
161                   < get_stat_mtime_ns (&statinfo[2]))));
162   /* mtime(stamp2) < mtime(stamp3) */
163   ASSERT (statinfo[2].st_mtime < statinfo[3].st_mtime
164           || (statinfo[2].st_mtime == statinfo[3].st_mtime
165               && (get_stat_mtime_ns (&statinfo[2])
166                   < get_stat_mtime_ns (&statinfo[3]))));
167
168   /* Now check the result of the access functions. */
169   /* mtime(stamp1) < mtime(stamp2) */
170   ASSERT (modtimes[0].tv_sec < modtimes[2].tv_sec
171           || (modtimes[0].tv_sec == modtimes[2].tv_sec
172               && modtimes[0].tv_nsec < modtimes[2].tv_nsec));
173   /* mtime(stamp2) < mtime(stamp3) */
174   ASSERT (modtimes[2].tv_sec < modtimes[3].tv_sec
175           || (modtimes[2].tv_sec == modtimes[3].tv_sec
176               && modtimes[2].tv_nsec < modtimes[3].tv_nsec));
177
178   /* verify equivalence */
179   for (i = 0; i < NFILES; ++i)
180     {
181       struct timespec ts;
182       ts = get_stat_mtime (&statinfo[i]);
183       ASSERT (ts.tv_sec == statinfo[i].st_mtime);
184     }
185 }
186
187 #if (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__
188 /* Skip the ctime tests on native Windows platforms, because their
189    st_ctime is either the same as st_mtime (plus or minus an offset)
190    or set to the file _creation_ time, and is not influenced by rename
191    or chmod.  */
192 # define test_ctime(ignored) ((void) 0)
193 #else
194 static void
195 test_ctime (const struct stat *statinfo)
196 {
197   /* On some buggy NFS clients, mtime and ctime are disproportionately
198      skewed from one another.  Skip this test in that case.  */
199   if (statinfo[0].st_mtime != statinfo[0].st_ctime)
200     return;
201
202   /* mtime(stamp2) < ctime(renamed) */
203   ASSERT (statinfo[2].st_mtime < statinfo[1].st_ctime
204           || (statinfo[2].st_mtime == statinfo[1].st_ctime
205               && (get_stat_mtime_ns (&statinfo[2])
206                   < get_stat_ctime_ns (&statinfo[1]))));
207 }
208 #endif
209
210 static void
211 test_birthtime (const struct stat *statinfo,
212                 const struct timespec *modtimes,
213                 struct timespec *birthtimes)
214 {
215   int i;
216
217   /* Collect the birth times.  */
218   for (i = 0; i < NFILES; ++i)
219     {
220       birthtimes[i] = get_stat_birthtime (&statinfo[i]);
221       if (birthtimes[i].tv_nsec < 0)
222         return;
223     }
224
225   /* mtime(stamp1) < birthtime(renamed) */
226   ASSERT (modtimes[0].tv_sec < birthtimes[1].tv_sec
227           || (modtimes[0].tv_sec == birthtimes[1].tv_sec
228               && modtimes[0].tv_nsec < birthtimes[1].tv_nsec));
229   /* birthtime(renamed) < mtime(stamp2) */
230   ASSERT (birthtimes[1].tv_sec < modtimes[2].tv_sec
231           || (birthtimes[1].tv_sec == modtimes[2].tv_sec
232               && birthtimes[1].tv_nsec < modtimes[2].tv_nsec));
233 }
234
235 int
236 main (void)
237 {
238   struct stat statinfo[NFILES];
239   struct timespec modtimes[NFILES];
240   struct timespec birthtimes[NFILES];
241
242 #ifdef SIGHUP
243   signal (SIGHUP, cleanup);
244 #endif
245 #ifdef SIGINT
246   signal (SIGINT, cleanup);
247 #endif
248 #ifdef SIGQUIT
249   signal (SIGQUIT, cleanup);
250 #endif
251 #ifdef SIGTERM
252   signal (SIGTERM, cleanup);
253 #endif
254
255   cleanup (0);
256   prepare_test (statinfo, modtimes);
257   test_mtime (statinfo, modtimes);
258   test_ctime (statinfo);
259   test_birthtime (statinfo, modtimes, birthtimes);
260
261   cleanup (0);
262   return 0;
263 }