407fd4016952a7eed858092f19f257c5e82d50fc
[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-2013 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         diag = stat_diag;
375       else
376         dir->fd = fd;
377       if (diag)
378         {
379           file_removed_diag (dir->orig_file_name, false, diag);
380           return;
381         }
382     }
383   dumpdir_buffer = directory_contents (scan_directory (dir));
384
385   if (dumpdir_buffer)
386     {
387       if (dumpdir_cmp (dir->dumpdir, dumpdir_buffer))
388         report_difference (dir, _("Contents differ"));
389     }
390   else
391     read_and_process (dir, process_noop);
392 }
393
394 static void
395 diff_multivol (void)
396 {
397   struct stat stat_data;
398   int fd, status;
399   off_t offset;
400
401   if (current_stat_info.had_trailing_slash)
402     {
403       diff_dir ();
404       return;
405     }
406
407   if (!get_stat_data (current_stat_info.file_name, &stat_data))
408     return;
409
410   if (!S_ISREG (stat_data.st_mode))
411     {
412       report_difference (&current_stat_info, _("File type differs"));
413       skip_member ();
414       return;
415     }
416
417   offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
418   if (offset < 0
419       || INT_ADD_OVERFLOW (current_stat_info.stat.st_size, offset)
420       || stat_data.st_size != current_stat_info.stat.st_size + offset)
421     {
422       report_difference (&current_stat_info, _("Size differs"));
423       skip_member ();
424       return;
425     }
426
427
428   fd = openat (chdir_fd, current_stat_info.file_name, open_read_flags);
429
430   if (fd < 0)
431     {
432       open_error (current_stat_info.file_name);
433       report_difference (&current_stat_info, NULL);
434       skip_member ();
435       return;
436     }
437
438   if (lseek (fd, offset, SEEK_SET) < 0)
439     {
440       seek_error_details (current_stat_info.file_name, offset);
441       report_difference (&current_stat_info, NULL);
442       return;
443     }
444
445   read_and_process (&current_stat_info, process_rawdata);
446
447   status = close (fd);
448   if (status != 0)
449     close_error (current_stat_info.file_name);
450 }
451
452 /* Diff a file against the archive.  */
453 void
454 diff_archive (void)
455 {
456
457   set_next_block_after (current_header);
458
459   /* Print the block from current_header and current_stat_info.  */
460
461   if (verbose_option)
462     {
463       if (now_verifying)
464         fprintf (stdlis, _("Verify "));
465       print_header (&current_stat_info, current_header, -1);
466     }
467
468   switch (current_header->header.typeflag)
469     {
470     default:
471       ERROR ((0, 0, _("%s: Unknown file type '%c', diffed as normal file"),
472               quotearg_colon (current_stat_info.file_name),
473               current_header->header.typeflag));
474       /* Fall through.  */
475
476     case AREGTYPE:
477     case REGTYPE:
478     case GNUTYPE_SPARSE:
479     case CONTTYPE:
480
481       /* Appears to be a file.  See if it's really a directory.  */
482
483       if (current_stat_info.had_trailing_slash)
484         diff_dir ();
485       else
486         diff_file ();
487       break;
488
489     case LNKTYPE:
490       diff_link ();
491       break;
492
493 #ifdef HAVE_READLINK
494     case SYMTYPE:
495       diff_symlink ();
496       break;
497 #endif
498
499     case CHRTYPE:
500     case BLKTYPE:
501     case FIFOTYPE:
502       diff_special ();
503       break;
504
505     case GNUTYPE_DUMPDIR:
506     case DIRTYPE:
507       if (is_dumpdir (&current_stat_info))
508         diff_dumpdir (&current_stat_info);
509       diff_dir ();
510       break;
511
512     case GNUTYPE_VOLHDR:
513       break;
514
515     case GNUTYPE_MULTIVOL:
516       diff_multivol ();
517     }
518 }
519
520 void
521 verify_volume (void)
522 {
523   int may_fail = 0;
524   if (removed_prefixes_p ())
525     {
526       WARN((0, 0,
527             _("Archive contains file names with leading prefixes removed.")));
528       may_fail = 1;
529     }
530   if (transform_program_p ())
531     {
532       WARN((0, 0,
533             _("Archive contains transformed file names.")));
534       may_fail = 1;
535     }
536   if (may_fail)
537     WARN((0, 0,
538           _("Verification may fail to locate original files.")));
539
540   clear_directory_table ();
541
542   if (!diff_buffer)
543     diff_init ();
544
545   /* Verifying an archive is meant to check if the physical media got it
546      correctly, so try to defeat clever in-memory buffering pertaining to
547      this particular media.  On Linux, for example, the floppy drive would
548      not even be accessed for the whole verification.
549
550      The code was using fsync only when the ioctl is unavailable, but
551      Marty Leisner says that the ioctl does not work when not preceded by
552      fsync.  So, until we know better, or maybe to please Marty, let's do it
553      the unbelievable way :-).  */
554
555 #if HAVE_FSYNC
556   fsync (archive);
557 #endif
558 #ifdef FDFLUSH
559   ioctl (archive, FDFLUSH);
560 #endif
561
562 #ifdef MTIOCTOP
563   {
564     struct mtop operation;
565     int status;
566
567     operation.mt_op = MTBSF;
568     operation.mt_count = 1;
569     if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
570       {
571         if (errno != EIO
572             || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
573                 status < 0))
574           {
575 #endif
576             if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
577               {
578                 /* Lseek failed.  Try a different method.  */
579                 seek_warn (archive_name_array[0]);
580                 return;
581               }
582 #ifdef MTIOCTOP
583           }
584       }
585   }
586 #endif
587
588   access_mode = ACCESS_READ;
589   now_verifying = 1;
590
591   flush_read ();
592   while (1)
593     {
594       enum read_header status = read_header (&current_header,
595                                              &current_stat_info,
596                                              read_header_auto);
597
598       if (status == HEADER_FAILURE)
599         {
600           int counter = 0;
601
602           do
603             {
604               counter++;
605               set_next_block_after (current_header);
606               status = read_header (&current_header, &current_stat_info,
607                                     read_header_auto);
608             }
609           while (status == HEADER_FAILURE);
610
611           ERROR ((0, 0,
612                   ngettext ("VERIFY FAILURE: %d invalid header detected",
613                             "VERIFY FAILURE: %d invalid headers detected",
614                             counter), counter));
615         }
616       if (status == HEADER_END_OF_FILE)
617         break;
618       if (status == HEADER_ZERO_BLOCK)
619         {
620           set_next_block_after (current_header);
621           if (!ignore_zeros_option)
622             {
623               char buf[UINTMAX_STRSIZE_BOUND];
624
625               status = read_header (&current_header, &current_stat_info,
626                                     read_header_auto);
627               if (status == HEADER_ZERO_BLOCK)
628                 break;
629               WARNOPT (WARN_ALONE_ZERO_BLOCK,
630                        (0, 0, _("A lone zero block at %s"),
631                         STRINGIFY_BIGINT (current_block_ordinal (), buf)));
632             }
633           continue;
634         }
635
636       decode_header (current_header, &current_stat_info, &current_format, 1);
637       diff_archive ();
638       tar_stat_destroy (&current_stat_info);
639     }
640
641   access_mode = ACCESS_WRITE;
642   now_verifying = 0;
643 }