tar: another --atime-preserve race fix
[debian/tar] / src / compare.c
1 /* Diff files from a tar archive.
2
3    Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
4    2003, 2004, 2005, 2006, 2007, 2009, 2010 Free Software Foundation, Inc.
5
6    Written by John Gilmore, on 1987-04-30.
7
8    This program is free software; you can redistribute it and/or modify it
9    under the terms of the GNU General Public License as published by the
10    Free Software Foundation; either version 3, or (at your option) any later
11    version.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
16    Public License for more details.
17
18    You should have received a copy of the GNU General Public License along
19    with this program; if not, write to the Free Software Foundation, Inc.,
20    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
21
22 #include <system.h>
23 #include <system-ioctl.h>
24
25 #if HAVE_LINUX_FD_H
26 # include <linux/fd.h>
27 #endif
28
29 #include "common.h"
30 #include <quotearg.h>
31 #include <rmt.h>
32 #include <stdarg.h>
33
34 /* Nonzero if we are verifying at the moment.  */
35 bool now_verifying;
36
37 /* File descriptor for the file we are diffing.  */
38 static int diff_handle;
39
40 /* Area for reading file contents into.  */
41 static char *diff_buffer;
42
43 /* Initialize for a diff operation.  */
44 void
45 diff_init (void)
46 {
47   void *ptr;
48   diff_buffer = page_aligned_alloc (&ptr, record_size);
49   if (listed_incremental_option)
50     read_directory_file ();
51 }
52
53 /* Sigh about something that differs by writing a MESSAGE to stdlis,
54    given MESSAGE is nonzero.  Also set the exit status if not already.  */
55 void
56 report_difference (struct tar_stat_info *st, const char *fmt, ...)
57 {
58   if (fmt)
59     {
60       va_list ap;
61
62       fprintf (stdlis, "%s: ", quotearg_colon (st->file_name));
63       va_start (ap, fmt);
64       vfprintf (stdlis, fmt, ap);
65       va_end (ap);
66       fprintf (stdlis, "\n");
67     }
68
69   set_exit_status (TAREXIT_DIFFERS);
70 }
71
72 /* Take a buffer returned by read_and_process and do nothing with it.  */
73 static int
74 process_noop (size_t size __attribute__ ((unused)),
75               char *data __attribute__ ((unused)))
76 {
77   return 1;
78 }
79
80 static int
81 process_rawdata (size_t bytes, char *buffer)
82 {
83   size_t status = safe_read (diff_handle, diff_buffer, bytes);
84
85   if (status != bytes)
86     {
87       if (status == SAFE_READ_ERROR)
88         {
89           read_error (current_stat_info.file_name);
90           report_difference (&current_stat_info, NULL);
91         }
92       else
93         {
94           report_difference (&current_stat_info,
95                              ngettext ("Could only read %lu of %lu byte",
96                                        "Could only read %lu of %lu bytes",
97                                        bytes),
98                              (unsigned long) status, (unsigned long) bytes);
99         }
100       return 0;
101     }
102
103   if (memcmp (buffer, diff_buffer, bytes))
104     {
105       report_difference (&current_stat_info, _("Contents differ"));
106       return 0;
107     }
108
109   return 1;
110 }
111
112 /* Some other routine wants SIZE bytes in the archive.  For each chunk
113    of the archive, call PROCESSOR with the size of the chunk, and the
114    address of the chunk it can work with.  The PROCESSOR should return
115    nonzero for success.  Once it returns error, continue skipping
116    without calling PROCESSOR anymore.  */
117
118 static void
119 read_and_process (struct tar_stat_info *st, int (*processor) (size_t, char *))
120 {
121   union block *data_block;
122   size_t data_size;
123   off_t size = st->stat.st_size;
124
125   mv_begin_read (st);
126   while (size)
127     {
128       data_block = find_next_block ();
129       if (! data_block)
130         {
131           ERROR ((0, 0, _("Unexpected EOF in archive")));
132           return;
133         }
134
135       data_size = available_space_after (data_block);
136       if (data_size > size)
137         data_size = size;
138       if (!(*processor) (data_size, data_block->buffer))
139         processor = process_noop;
140       set_next_block_after ((union block *)
141                             (data_block->buffer + data_size - 1));
142       size -= data_size;
143       mv_size_left (size);
144     }
145   mv_end ();
146 }
147
148 /* Call either stat or lstat over STAT_DATA, depending on
149    --dereference (-h), for a file which should exist.  Diagnose any
150    problem.  Return nonzero for success, zero otherwise.  */
151 static int
152 get_stat_data (char const *file_name, struct stat *stat_data)
153 {
154   int status = deref_stat (dereference_option, file_name, stat_data);
155
156   if (status != 0)
157     {
158       if (errno == ENOENT)
159         stat_warn (file_name);
160       else
161         stat_error (file_name);
162       report_difference (&current_stat_info, NULL);
163       return 0;
164     }
165
166   return 1;
167 }
168
169 \f
170 static void
171 diff_dir (void)
172 {
173   struct stat stat_data;
174
175   if (!get_stat_data (current_stat_info.file_name, &stat_data))
176     return;
177
178   if (!S_ISDIR (stat_data.st_mode))
179     report_difference (&current_stat_info, _("File type differs"));
180   else if ((current_stat_info.stat.st_mode & MODE_ALL) !=
181            (stat_data.st_mode & MODE_ALL))
182     report_difference (&current_stat_info, _("Mode differs"));
183 }
184
185 static void
186 diff_file (void)
187 {
188   char const *file_name = current_stat_info.file_name;
189   struct stat stat_data;
190
191   if (!get_stat_data (file_name, &stat_data))
192     skip_member ();
193   else if (!S_ISREG (stat_data.st_mode))
194     {
195       report_difference (&current_stat_info, _("File type differs"));
196       skip_member ();
197     }
198   else
199     {
200       if ((current_stat_info.stat.st_mode & MODE_ALL) !=
201           (stat_data.st_mode & MODE_ALL))
202         report_difference (&current_stat_info, _("Mode differs"));
203
204       if (!sys_compare_uid (&stat_data, &current_stat_info.stat))
205         report_difference (&current_stat_info, _("Uid differs"));
206       if (!sys_compare_gid (&stat_data, &current_stat_info.stat))
207         report_difference (&current_stat_info, _("Gid differs"));
208
209       if (tar_timespec_cmp (get_stat_mtime (&stat_data),
210                             current_stat_info.mtime))
211         report_difference (&current_stat_info, _("Mod time differs"));
212       if (current_header->header.typeflag != GNUTYPE_SPARSE
213           && stat_data.st_size != current_stat_info.stat.st_size)
214         {
215           report_difference (&current_stat_info, _("Size differs"));
216           skip_member ();
217         }
218       else
219         {
220           int atime_flag =
221             (atime_preserve_option == system_atime_preserve
222              ? O_NOATIME
223              : 0);
224
225           diff_handle = open (file_name,
226                               (O_RDONLY | O_BINARY | O_CLOEXEC | O_NOCTTY
227                                | O_NONBLOCK | atime_flag));
228
229           if (diff_handle < 0)
230             {
231               open_error (file_name);
232               skip_member ();
233               report_difference (&current_stat_info, NULL);
234             }
235           else
236             {
237               int status;
238
239               if (current_stat_info.is_sparse)
240                 sparse_diff_file (diff_handle, &current_stat_info);
241               else
242                 read_and_process (&current_stat_info, process_rawdata);
243
244               if (atime_preserve_option == replace_atime_preserve)
245                 {
246                   struct timespec atime = get_stat_atime (&stat_data);
247                   if (set_file_atime (diff_handle, AT_FDCWD, file_name,
248                                       atime, 0)
249                       != 0)
250                     utime_error (file_name);
251                 }
252
253               status = close (diff_handle);
254               if (status != 0)
255                 close_error (file_name);
256             }
257         }
258     }
259 }
260
261 static void
262 diff_link (void)
263 {
264   struct stat file_data;
265   struct stat link_data;
266
267   if (get_stat_data (current_stat_info.file_name, &file_data)
268       && get_stat_data (current_stat_info.link_name, &link_data)
269       && !sys_compare_links (&file_data, &link_data))
270     report_difference (&current_stat_info,
271                        _("Not linked to %s"),
272                        quote (current_stat_info.link_name));
273 }
274
275 #ifdef HAVE_READLINK
276 static void
277 diff_symlink (void)
278 {
279   size_t len = strlen (current_stat_info.link_name);
280   char *linkbuf = alloca (len + 1);
281
282   int status = readlink (current_stat_info.file_name, linkbuf, len + 1);
283
284   if (status < 0)
285     {
286       if (errno == ENOENT)
287         readlink_warn (current_stat_info.file_name);
288       else
289         readlink_error (current_stat_info.file_name);
290       report_difference (&current_stat_info, NULL);
291     }
292   else if (status != len
293            || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
294     report_difference (&current_stat_info, _("Symlink differs"));
295 }
296 #endif
297
298 static void
299 diff_special (void)
300 {
301   struct stat stat_data;
302
303   /* FIXME: deal with umask.  */
304
305   if (!get_stat_data (current_stat_info.file_name, &stat_data))
306     return;
307
308   if (current_header->header.typeflag == CHRTYPE
309       ? !S_ISCHR (stat_data.st_mode)
310       : current_header->header.typeflag == BLKTYPE
311       ? !S_ISBLK (stat_data.st_mode)
312       : /* current_header->header.typeflag == FIFOTYPE */
313       !S_ISFIFO (stat_data.st_mode))
314     {
315       report_difference (&current_stat_info, _("File type differs"));
316       return;
317     }
318
319   if ((current_header->header.typeflag == CHRTYPE
320        || current_header->header.typeflag == BLKTYPE)
321       && current_stat_info.stat.st_rdev != stat_data.st_rdev)
322     {
323       report_difference (&current_stat_info, _("Device number differs"));
324       return;
325     }
326
327   if ((current_stat_info.stat.st_mode & MODE_ALL) !=
328       (stat_data.st_mode & MODE_ALL))
329     report_difference (&current_stat_info, _("Mode differs"));
330 }
331
332 static int
333 dumpdir_cmp (const char *a, const char *b)
334 {
335   size_t len;
336
337   while (*a)
338     switch (*a)
339       {
340       case 'Y':
341       case 'N':
342         if (!strchr ("YN", *b))
343           return 1;
344         if (strcmp(a + 1, b + 1))
345           return 1;
346         len = strlen (a) + 1;
347         a += len;
348         b += len;
349         break;
350
351       case 'D':
352         if (strcmp(a, b))
353           return 1;
354         len = strlen (a) + 1;
355         a += len;
356         b += len;
357         break;
358
359       case 'R':
360       case 'T':
361       case 'X':
362         return *b;
363       }
364   return *b;
365 }
366
367 static void
368 diff_dumpdir (void)
369 {
370   const char *dumpdir_buffer;
371   dev_t dev = 0;
372   struct stat stat_data;
373
374   if (deref_stat (true, current_stat_info.file_name, &stat_data))
375     {
376       if (errno == ENOENT)
377         stat_warn (current_stat_info.file_name);
378       else
379         stat_error (current_stat_info.file_name);
380     }
381   else
382     dev = stat_data.st_dev;
383
384   dumpdir_buffer = directory_contents (scan_directory (&current_stat_info));
385
386   if (dumpdir_buffer)
387     {
388       if (dumpdir_cmp (current_stat_info.dumpdir, dumpdir_buffer))
389         report_difference (&current_stat_info, _("Contents differ"));
390     }
391   else
392     read_and_process (&current_stat_info, process_noop);
393 }
394
395 static void
396 diff_multivol (void)
397 {
398   struct stat stat_data;
399   int fd, status;
400   off_t offset;
401   int atime_flag =
402     (atime_preserve_option == system_atime_preserve
403      ? O_NOATIME
404      : 0);
405
406   if (current_stat_info.had_trailing_slash)
407     {
408       diff_dir ();
409       return;
410     }
411
412   if (!get_stat_data (current_stat_info.file_name, &stat_data))
413     return;
414
415   if (!S_ISREG (stat_data.st_mode))
416     {
417       report_difference (&current_stat_info, _("File type differs"));
418       skip_member ();
419       return;
420     }
421
422   offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
423   if (stat_data.st_size != current_stat_info.stat.st_size + offset)
424     {
425       report_difference (&current_stat_info, _("Size differs"));
426       skip_member ();
427       return;
428     }
429
430
431   fd = open (current_stat_info.file_name,
432              (O_RDONLY | O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK
433               | atime_flag));
434
435   if (fd < 0)
436     {
437       open_error (current_stat_info.file_name);
438       report_difference (&current_stat_info, NULL);
439       skip_member ();
440       return;
441     }
442
443   if (lseek (fd, offset, SEEK_SET) < 0)
444     {
445       seek_error_details (current_stat_info.file_name, offset);
446       report_difference (&current_stat_info, NULL);
447       return;
448     }
449
450   read_and_process (&current_stat_info, process_rawdata);
451
452   status = close (fd);
453   if (status != 0)
454     close_error (current_stat_info.file_name);
455 }
456
457 /* Diff a file against the archive.  */
458 void
459 diff_archive (void)
460 {
461
462   set_next_block_after (current_header);
463
464   /* Print the block from current_header and current_stat_info.  */
465
466   if (verbose_option)
467     {
468       if (now_verifying)
469         fprintf (stdlis, _("Verify "));
470       print_header (&current_stat_info, current_header, -1);
471     }
472
473   switch (current_header->header.typeflag)
474     {
475     default:
476       ERROR ((0, 0, _("%s: Unknown file type `%c', diffed as normal file"),
477               quotearg_colon (current_stat_info.file_name),
478               current_header->header.typeflag));
479       /* Fall through.  */
480
481     case AREGTYPE:
482     case REGTYPE:
483     case GNUTYPE_SPARSE:
484     case CONTTYPE:
485
486       /* Appears to be a file.  See if it's really a directory.  */
487
488       if (current_stat_info.had_trailing_slash)
489         diff_dir ();
490       else
491         diff_file ();
492       break;
493
494     case LNKTYPE:
495       diff_link ();
496       break;
497
498 #ifdef HAVE_READLINK
499     case SYMTYPE:
500       diff_symlink ();
501       break;
502 #endif
503
504     case CHRTYPE:
505     case BLKTYPE:
506     case FIFOTYPE:
507       diff_special ();
508       break;
509
510     case GNUTYPE_DUMPDIR:
511     case DIRTYPE:
512       if (is_dumpdir (&current_stat_info))
513         diff_dumpdir ();
514       diff_dir ();
515       break;
516
517     case GNUTYPE_VOLHDR:
518       break;
519
520     case GNUTYPE_MULTIVOL:
521       diff_multivol ();
522     }
523 }
524
525 void
526 verify_volume (void)
527 {
528   if (removed_prefixes_p ())
529     {
530       WARN((0, 0,
531             _("Archive contains file names with leading prefixes removed.")));
532       WARN((0, 0,
533             _("Verification may fail to locate original files.")));
534     }
535
536   if (!diff_buffer)
537     diff_init ();
538
539   /* Verifying an archive is meant to check if the physical media got it
540      correctly, so try to defeat clever in-memory buffering pertaining to
541      this particular media.  On Linux, for example, the floppy drive would
542      not even be accessed for the whole verification.
543
544      The code was using fsync only when the ioctl is unavailable, but
545      Marty Leisner says that the ioctl does not work when not preceded by
546      fsync.  So, until we know better, or maybe to please Marty, let's do it
547      the unbelievable way :-).  */
548
549 #if HAVE_FSYNC
550   fsync (archive);
551 #endif
552 #ifdef FDFLUSH
553   ioctl (archive, FDFLUSH);
554 #endif
555
556 #ifdef MTIOCTOP
557   {
558     struct mtop operation;
559     int status;
560
561     operation.mt_op = MTBSF;
562     operation.mt_count = 1;
563     if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
564       {
565         if (errno != EIO
566             || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
567                 status < 0))
568           {
569 #endif
570             if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
571               {
572                 /* Lseek failed.  Try a different method.  */
573                 seek_warn (archive_name_array[0]);
574                 return;
575               }
576 #ifdef MTIOCTOP
577           }
578       }
579   }
580 #endif
581
582   access_mode = ACCESS_READ;
583   now_verifying = 1;
584
585   flush_read ();
586   while (1)
587     {
588       enum read_header status = read_header (&current_header,
589                                              &current_stat_info,
590                                              read_header_auto);
591
592       if (status == HEADER_FAILURE)
593         {
594           int counter = 0;
595
596           do
597             {
598               counter++;
599               set_next_block_after (current_header);
600               status = read_header (&current_header, &current_stat_info,
601                                     read_header_auto);
602             }
603           while (status == HEADER_FAILURE);
604
605           ERROR ((0, 0,
606                   ngettext ("VERIFY FAILURE: %d invalid header detected",
607                             "VERIFY FAILURE: %d invalid headers detected",
608                             counter), counter));
609         }
610       if (status == HEADER_END_OF_FILE)
611         break;
612       if (status == HEADER_ZERO_BLOCK)
613         {
614           set_next_block_after (current_header);
615           if (!ignore_zeros_option)
616             {
617               char buf[UINTMAX_STRSIZE_BOUND];
618
619               status = read_header (&current_header, &current_stat_info,
620                                     read_header_auto);
621               if (status == HEADER_ZERO_BLOCK)
622                 break;
623               WARNOPT (WARN_ALONE_ZERO_BLOCK,
624                        (0, 0, _("A lone zero block at %s"),
625                         STRINGIFY_BIGINT (current_block_ordinal (), buf)));
626             }
627         }
628
629       diff_archive ();
630       tar_stat_destroy (&current_stat_info);
631     }
632
633   access_mode = ACCESS_WRITE;
634   now_verifying = 0;
635 }