re-mark 1.29b-2 as not yet uploaded (merge madness!)
[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, 2016 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   char buf[1024];
274   size_t len = strlen (current_stat_info.link_name);
275   char *linkbuf = len < sizeof buf ? buf : xmalloc (len + 1);
276
277   ssize_t status = readlinkat (chdir_fd, current_stat_info.file_name,
278                                linkbuf, len + 1);
279
280   if (status < 0)
281     {
282       if (errno == ENOENT)
283         readlink_warn (current_stat_info.file_name);
284       else
285         readlink_error (current_stat_info.file_name);
286       report_difference (&current_stat_info, NULL);
287     }
288   else if (status != len
289            || memcmp (current_stat_info.link_name, linkbuf, len) != 0)
290     report_difference (&current_stat_info, _("Symlink differs"));
291
292   if (linkbuf != buf)
293     free (linkbuf);
294 }
295 #endif
296
297 static void
298 diff_special (void)
299 {
300   struct stat stat_data;
301
302   /* FIXME: deal with umask.  */
303
304   if (!get_stat_data (current_stat_info.file_name, &stat_data))
305     return;
306
307   if (current_header->header.typeflag == CHRTYPE
308       ? !S_ISCHR (stat_data.st_mode)
309       : current_header->header.typeflag == BLKTYPE
310       ? !S_ISBLK (stat_data.st_mode)
311       : /* current_header->header.typeflag == FIFOTYPE */
312       !S_ISFIFO (stat_data.st_mode))
313     {
314       report_difference (&current_stat_info, _("File type differs"));
315       return;
316     }
317
318   if ((current_header->header.typeflag == CHRTYPE
319        || current_header->header.typeflag == BLKTYPE)
320       && current_stat_info.stat.st_rdev != stat_data.st_rdev)
321     {
322       report_difference (&current_stat_info, _("Device number differs"));
323       return;
324     }
325
326   if ((current_stat_info.stat.st_mode & MODE_ALL) !=
327       (stat_data.st_mode & MODE_ALL))
328     report_difference (&current_stat_info, _("Mode differs"));
329 }
330
331 static int
332 dumpdir_cmp (const char *a, const char *b)
333 {
334   size_t len;
335
336   while (*a)
337     switch (*a)
338       {
339       case 'Y':
340       case 'N':
341         if (!strchr ("YN", *b))
342           return 1;
343         if (strcmp(a + 1, b + 1))
344           return 1;
345         len = strlen (a) + 1;
346         a += len;
347         b += len;
348         break;
349
350       case 'D':
351         if (strcmp(a, b))
352           return 1;
353         len = strlen (a) + 1;
354         a += len;
355         b += len;
356         break;
357
358       case 'R':
359       case 'T':
360       case 'X':
361         return *b;
362       }
363   return *b;
364 }
365
366 static void
367 diff_dumpdir (struct tar_stat_info *dir)
368 {
369   const char *dumpdir_buffer;
370
371   if (dir->fd == 0)
372     {
373       void (*diag) (char const *) = NULL;
374       int fd = subfile_open (dir->parent, dir->orig_file_name, open_read_flags);
375       if (fd < 0)
376         diag = open_diag;
377       else if (fstat (fd, &dir->stat))
378         {
379           diag = stat_diag;
380           close (fd);
381         }
382       else
383         dir->fd = fd;
384       if (diag)
385         {
386           file_removed_diag (dir->orig_file_name, false, diag);
387           return;
388         }
389     }
390   dumpdir_buffer = directory_contents (scan_directory (dir));
391
392   if (dumpdir_buffer)
393     {
394       if (dumpdir_cmp (dir->dumpdir, dumpdir_buffer))
395         report_difference (dir, _("Contents differ"));
396     }
397   else
398     read_and_process (dir, process_noop);
399 }
400
401 static void
402 diff_multivol (void)
403 {
404   struct stat stat_data;
405   int fd, status;
406   off_t offset;
407
408   if (current_stat_info.had_trailing_slash)
409     {
410       diff_dir ();
411       return;
412     }
413
414   if (!get_stat_data (current_stat_info.file_name, &stat_data))
415     return;
416
417   if (!S_ISREG (stat_data.st_mode))
418     {
419       report_difference (&current_stat_info, _("File type differs"));
420       skip_member ();
421       return;
422     }
423
424   offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
425   if (offset < 0
426       || INT_ADD_OVERFLOW (current_stat_info.stat.st_size, offset)
427       || stat_data.st_size != current_stat_info.stat.st_size + offset)
428     {
429       report_difference (&current_stat_info, _("Size differs"));
430       skip_member ();
431       return;
432     }
433
434
435   fd = openat (chdir_fd, current_stat_info.file_name, open_read_flags);
436
437   if (fd < 0)
438     {
439       open_error (current_stat_info.file_name);
440       report_difference (&current_stat_info, NULL);
441       skip_member ();
442       return;
443     }
444
445   if (lseek (fd, offset, SEEK_SET) < 0)
446     {
447       seek_error_details (current_stat_info.file_name, offset);
448       report_difference (&current_stat_info, NULL);
449     }
450   else
451     read_and_process (&current_stat_info, process_rawdata);
452
453   status = close (fd);
454   if (status != 0)
455     close_error (current_stat_info.file_name);
456 }
457
458 /* Diff a file against the archive.  */
459 void
460 diff_archive (void)
461 {
462
463   set_next_block_after (current_header);
464
465   /* Print the block from current_header and current_stat_info.  */
466
467   if (verbose_option)
468     {
469       if (now_verifying)
470         fprintf (stdlis, _("Verify "));
471       print_header (&current_stat_info, current_header, -1);
472     }
473
474   switch (current_header->header.typeflag)
475     {
476     default:
477       ERROR ((0, 0, _("%s: Unknown file type '%c', diffed as normal file"),
478               quotearg_colon (current_stat_info.file_name),
479               current_header->header.typeflag));
480       /* Fall through.  */
481
482     case AREGTYPE:
483     case REGTYPE:
484     case GNUTYPE_SPARSE:
485     case CONTTYPE:
486
487       /* Appears to be a file.  See if it's really a directory.  */
488
489       if (current_stat_info.had_trailing_slash)
490         diff_dir ();
491       else
492         diff_file ();
493       break;
494
495     case LNKTYPE:
496       diff_link ();
497       break;
498
499 #ifdef HAVE_READLINK
500     case SYMTYPE:
501       diff_symlink ();
502       break;
503 #endif
504
505     case CHRTYPE:
506     case BLKTYPE:
507     case FIFOTYPE:
508       diff_special ();
509       break;
510
511     case GNUTYPE_DUMPDIR:
512     case DIRTYPE:
513       if (is_dumpdir (&current_stat_info))
514         diff_dumpdir (&current_stat_info);
515       diff_dir ();
516       break;
517
518     case GNUTYPE_VOLHDR:
519       break;
520
521     case GNUTYPE_MULTIVOL:
522       diff_multivol ();
523     }
524 }
525
526 void
527 verify_volume (void)
528 {
529   int may_fail = 0;
530   if (removed_prefixes_p ())
531     {
532       WARN((0, 0,
533             _("Archive contains file names with leading prefixes removed.")));
534       may_fail = 1;
535     }
536   if (transform_program_p ())
537     {
538       WARN((0, 0,
539             _("Archive contains transformed file names.")));
540       may_fail = 1;
541     }
542   if (may_fail)
543     WARN((0, 0,
544           _("Verification may fail to locate original files.")));
545
546   clear_directory_table ();
547
548   if (!diff_buffer)
549     diff_init ();
550
551   /* Verifying an archive is meant to check if the physical media got it
552      correctly, so try to defeat clever in-memory buffering pertaining to
553      this particular media.  On Linux, for example, the floppy drive would
554      not even be accessed for the whole verification.
555
556      The code was using fsync only when the ioctl is unavailable, but
557      Marty Leisner says that the ioctl does not work when not preceded by
558      fsync.  So, until we know better, or maybe to please Marty, let's do it
559      the unbelievable way :-).  */
560
561 #if HAVE_FSYNC
562   fsync (archive);
563 #endif
564 #ifdef FDFLUSH
565   ioctl (archive, FDFLUSH);
566 #endif
567
568 #ifdef MTIOCTOP
569   {
570     struct mtop operation;
571     int status;
572
573     operation.mt_op = MTBSF;
574     operation.mt_count = 1;
575     if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
576       {
577         if (errno != EIO
578             || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
579                 status < 0))
580           {
581 #endif
582             if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
583               {
584                 /* Lseek failed.  Try a different method.  */
585                 seek_warn (archive_name_array[0]);
586                 return;
587               }
588 #ifdef MTIOCTOP
589           }
590       }
591   }
592 #endif
593
594   access_mode = ACCESS_READ;
595   now_verifying = 1;
596
597   flush_read ();
598   while (1)
599     {
600       enum read_header status = read_header (&current_header,
601                                              &current_stat_info,
602                                              read_header_auto);
603
604       if (status == HEADER_FAILURE)
605         {
606           int counter = 0;
607
608           do
609             {
610               counter++;
611               set_next_block_after (current_header);
612               status = read_header (&current_header, &current_stat_info,
613                                     read_header_auto);
614             }
615           while (status == HEADER_FAILURE);
616
617           ERROR ((0, 0,
618                   ngettext ("VERIFY FAILURE: %d invalid header detected",
619                             "VERIFY FAILURE: %d invalid headers detected",
620                             counter), counter));
621         }
622       if (status == HEADER_END_OF_FILE)
623         break;
624       if (status == HEADER_ZERO_BLOCK)
625         {
626           set_next_block_after (current_header);
627           if (!ignore_zeros_option)
628             {
629               char buf[UINTMAX_STRSIZE_BOUND];
630
631               status = read_header (&current_header, &current_stat_info,
632                                     read_header_auto);
633               if (status == HEADER_ZERO_BLOCK)
634                 break;
635               WARNOPT (WARN_ALONE_ZERO_BLOCK,
636                        (0, 0, _("A lone zero block at %s"),
637                         STRINGIFY_BIGINT (current_block_ordinal (), buf)));
638             }
639           continue;
640         }
641
642       decode_header (current_header, &current_stat_info, &current_format, 1);
643       diff_archive ();
644       tar_stat_destroy (&current_stat_info);
645     }
646
647   access_mode = ACCESS_WRITE;
648   now_verifying = 0;
649 }