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