ignore-value: handle pointer types, too
[gnulib.git] / lib / utimecmp.c
1 /* utimecmp.c -- compare file time stamps
2
3    Copyright (C) 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 /* Written by Paul Eggert.  */
19
20 #include <config.h>
21
22 #include "utimecmp.h"
23
24 #include <limits.h>
25 #include <stdbool.h>
26 #include <stdint.h>
27 #include <stdlib.h>
28 #include <time.h>
29 #include "hash.h"
30 #include "intprops.h"
31 #include "stat-time.h"
32 #include "utimens.h"
33 #include "verify.h"
34 #include "xalloc.h"
35
36 #ifndef MAX
37 # define MAX(a, b) ((a) > (b) ? (a) : (b))
38 #endif
39
40 enum { BILLION = 1000 * 1000 * 1000 };
41
42 /* Best possible resolution that utimens can set and stat can return,
43    due to system-call limitations.  It must be a power of 10 that is
44    no greater than 1 billion.  */
45 #if (HAVE_WORKING_UTIMES                                        \
46      && (defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC               \
47          || defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC       \
48          || defined HAVE_STRUCT_STAT_ST_ATIMENSEC               \
49          || defined HAVE_STRUCT_STAT_ST_ATIM_ST__TIM_TV_NSEC    \
50          || defined HAVE_STRUCT_STAT_ST_SPARE1))
51 enum { SYSCALL_RESOLUTION = 1000 };
52 #else
53 enum { SYSCALL_RESOLUTION = BILLION };
54 #endif
55
56 /* Describe a file system and its time stamp resolution in nanoseconds.  */
57 struct fs_res
58 {
59   /* Device number of file system.  */
60   dev_t dev;
61
62   /* An upper bound on the time stamp resolution of this file system,
63      ignoring any resolution that cannot be set via utimens.  It is
64      represented by an integer count of nanoseconds.  It must be
65      either 2 billion, or a power of 10 that is no greater than a
66      billion and is no less than SYSCALL_RESOLUTION.  */
67   int resolution;
68
69   /* True if RESOLUTION is known to be exact, and is not merely an
70      upper bound on the true resolution.  */
71   bool exact;
72 };
73
74 /* Hash some device info.  */
75 static size_t
76 dev_info_hash (void const *x, size_t table_size)
77 {
78   struct fs_res const *p = x;
79
80   /* Beware signed arithmetic gotchas.  */
81   if (TYPE_SIGNED (dev_t) && SIZE_MAX < MAX (INT_MAX, TYPE_MAXIMUM (dev_t)))
82     {
83       uintmax_t dev = p->dev;
84       return dev % table_size;
85     }
86
87   return p->dev % table_size;
88 }
89
90 /* Compare two dev_info structs.  */
91 static bool
92 dev_info_compare (void const *x, void const *y)
93 {
94   struct fs_res const *a = x;
95   struct fs_res const *b = y;
96   return a->dev == b->dev;
97 }
98
99 /* Return -1, 0, 1 based on whether the destination file (with name
100    DST_NAME and status DST_STAT) is older than SRC_STAT, the same age
101    as SRC_STAT, or newer than SRC_STAT, respectively.
102
103    If OPTIONS & UTIMECMP_TRUNCATE_SOURCE, do the comparison after SRC is
104    converted to the destination's timestamp resolution as filtered through
105    utimens.  In this case, return -2 if the exact answer cannot be
106    determined; this can happen only if the time stamps are very close and
107    there is some trouble accessing the file system (e.g., the user does not
108    have permission to futz with the destination's time stamps).  */
109
110 int
111 utimecmp (char const *dst_name,
112           struct stat const *dst_stat,
113           struct stat const *src_stat,
114           int options)
115 {
116   /* Things to watch out for:
117
118      The code uses a static hash table internally and is not safe in the
119      presence of signals, multiple threads, etc.
120
121      int and long int might be 32 bits.  Many of the calculations store
122      numbers up to 2 billion, and multiply by 10; they have to avoid
123      multiplying 2 billion by 10, as this exceeds 32-bit capabilities.
124
125      time_t might be unsigned.  */
126
127   verify (TYPE_IS_INTEGER (time_t));
128   verify (TYPE_TWOS_COMPLEMENT (int));
129
130   /* Destination and source time stamps.  */
131   time_t dst_s = dst_stat->st_mtime;
132   time_t src_s = src_stat->st_mtime;
133   int dst_ns = get_stat_mtime_ns (dst_stat);
134   int src_ns = get_stat_mtime_ns (src_stat);
135
136   if (options & UTIMECMP_TRUNCATE_SOURCE)
137     {
138       /* Look up the time stamp resolution for the destination device.  */
139
140       /* Hash table for devices.  */
141       static Hash_table *ht;
142
143       /* Information about the destination file system.  */
144       static struct fs_res *new_dst_res;
145       struct fs_res *dst_res;
146
147       /* Time stamp resolution in nanoseconds.  */
148       int res;
149
150       if (! ht)
151         ht = hash_initialize (16, NULL, dev_info_hash, dev_info_compare, free);
152       if (! new_dst_res)
153         {
154           new_dst_res = xmalloc (sizeof *new_dst_res);
155           new_dst_res->resolution = 2 * BILLION;
156           new_dst_res->exact = false;
157         }
158       new_dst_res->dev = dst_stat->st_dev;
159       dst_res = hash_insert (ht, new_dst_res);
160       if (! dst_res)
161         xalloc_die ();
162
163       if (dst_res == new_dst_res)
164         {
165           /* NEW_DST_RES is now in use in the hash table, so allocate a
166              new entry next time.  */
167           new_dst_res = NULL;
168         }
169
170       res = dst_res->resolution;
171
172       if (! dst_res->exact)
173         {
174           /* This file system's resolution is not known exactly.
175              Deduce it, and store the result in the hash table.  */
176
177           time_t dst_a_s = dst_stat->st_atime;
178           time_t dst_c_s = dst_stat->st_ctime;
179           time_t dst_m_s = dst_s;
180           int dst_a_ns = get_stat_atime_ns (dst_stat);
181           int dst_c_ns = get_stat_ctime_ns (dst_stat);
182           int dst_m_ns = dst_ns;
183
184           /* Set RES to an upper bound on the file system resolution
185              (after truncation due to SYSCALL_RESOLUTION) by inspecting
186              the atime, ctime and mtime of the existing destination.
187              We don't know of any file system that stores atime or
188              ctime with a higher precision than mtime, so it's valid to
189              look at them too.  */
190           {
191             bool odd_second = (dst_a_s | dst_c_s | dst_m_s) & 1;
192
193             if (SYSCALL_RESOLUTION == BILLION)
194               {
195                 if (odd_second | dst_a_ns | dst_c_ns | dst_m_ns)
196                   res = BILLION;
197               }
198             else
199               {
200                 int a = dst_a_ns;
201                 int c = dst_c_ns;
202                 int m = dst_m_ns;
203
204                 /* Write it this way to avoid mistaken GCC warning
205                    about integer overflow in constant expression.  */
206                 int SR10 = SYSCALL_RESOLUTION;  SR10 *= 10;
207
208                 if ((a % SR10 | c % SR10 | m % SR10) != 0)
209                   res = SYSCALL_RESOLUTION;
210                 else
211                   for (res = SR10, a /= SR10, c /= SR10, m /= SR10;
212                        (res < dst_res->resolution
213                         && (a % 10 | c % 10 | m % 10) == 0);
214                        res *= 10, a /= 10, c /= 10, m /= 10)
215                     if (res == BILLION)
216                       {
217                         if (! odd_second)
218                           res *= 2;
219                         break;
220                       }
221               }
222
223             dst_res->resolution = res;
224           }
225
226           if (SYSCALL_RESOLUTION < res)
227             {
228               struct timespec timespec[2];
229               struct stat dst_status;
230
231               /* Ignore source time stamp information that must necessarily
232                  be lost when filtered through utimens.  */
233               src_ns -= src_ns % SYSCALL_RESOLUTION;
234
235               /* If the time stamps disagree widely enough, there's no need
236                  to interrogate the file system to deduce the exact time
237                  stamp resolution; return the answer directly.  */
238               {
239                 time_t s = src_s & ~ (res == 2 * BILLION);
240                 if (src_s < dst_s || (src_s == dst_s && src_ns <= dst_ns))
241                   return 1;
242                 if (dst_s < s
243                     || (dst_s == s && dst_ns < src_ns - src_ns % res))
244                   return -1;
245               }
246
247               /* Determine the actual time stamp resolution for the
248                  destination file system (after truncation due to
249                  SYSCALL_RESOLUTION) by setting the access time stamp of the
250                  destination to the existing access time, except with
251                  trailing nonzero digits.  */
252
253               timespec[0].tv_sec = dst_a_s;
254               timespec[0].tv_nsec = dst_a_ns;
255               timespec[1].tv_sec = dst_m_s | (res == 2 * BILLION);
256               timespec[1].tv_nsec = dst_m_ns + res / 9;
257
258               /* Set the modification time.  But don't try to set the
259                  modification time of symbolic links; on many hosts this sets
260                  the time of the pointed-to file.  */
261               if (S_ISLNK (dst_stat->st_mode)
262                   || utimens (dst_name, timespec) != 0)
263                 return -2;
264
265               /* Read the modification time that was set.  It's safe to call
266                  'stat' here instead of worrying about 'lstat'; either the
267                  caller used 'stat', or the caller used 'lstat' and found
268                  something other than a symbolic link.  */
269               {
270                 int stat_result = stat (dst_name, &dst_status);
271
272                 if (stat_result
273                     | (dst_status.st_mtime ^ dst_m_s)
274                     | (get_stat_mtime_ns (&dst_status) ^ dst_m_ns))
275                   {
276                     /* The modification time changed, or we can't tell whether
277                        it changed.  Change it back as best we can.  */
278                     timespec[1].tv_sec = dst_m_s;
279                     timespec[1].tv_nsec = dst_m_ns;
280                     utimens (dst_name, timespec);
281                   }
282
283                 if (stat_result != 0)
284                   return -2;
285               }
286
287               /* Determine the exact resolution from the modification time
288                  that was read back.  */
289               {
290                 int old_res = res;
291                 int a = (BILLION * (dst_status.st_mtime & 1)
292                          + get_stat_mtime_ns (&dst_status));
293
294                 res = SYSCALL_RESOLUTION;
295
296                 for (a /= res; a % 10 != 0; a /= 10)
297                   {
298                     if (res == BILLION)
299                       {
300                         res *= 2;
301                         break;
302                       }
303                     res *= 10;
304                     if (res == old_res)
305                       break;
306                   }
307               }
308             }
309
310           dst_res->resolution = res;
311           dst_res->exact = true;
312         }
313
314       /* Truncate the source's time stamp according to the resolution.  */
315       src_s &= ~ (res == 2 * BILLION);
316       src_ns -= src_ns % res;
317     }
318
319   /* Compare the time stamps and return -1, 0, 1 accordingly.  */
320   return (dst_s < src_s ? -1
321           : dst_s > src_s ? 1
322           : dst_ns < src_ns ? -1
323           : dst_ns > src_ns);
324 }