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