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