]> git.cworth.org Git - tar/blob - src/compare.c
Imported Upstream version 1.20
[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 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   if (exit_status == TAREXIT_SUCCESS)
70     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 = safe_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 (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 (dereference_option, 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           int atime_flag =
222             (atime_preserve_option == system_atime_preserve
223              ? O_NOATIME
224              : 0);
225
226           diff_handle = open (file_name, O_RDONLY | O_BINARY | atime_flag);
227
228           if (diff_handle < 0)
229             {
230               open_error (file_name);
231               skip_member ();
232               report_difference (&current_stat_info, NULL);
233             }
234           else
235             {
236               int status;
237
238               if (current_stat_info.is_sparse)
239                 sparse_diff_file (diff_handle, &current_stat_info);
240               else
241                 read_and_process (&current_stat_info, process_rawdata);
242
243               if (atime_preserve_option == replace_atime_preserve)
244                 {
245                   struct timespec ts[2];
246                   ts[0] = get_stat_atime (&stat_data);
247                   ts[1] = get_stat_mtime (&stat_data);
248                   if (set_file_atime (diff_handle, file_name, ts) != 0)
249                     utime_error (file_name);
250                 }
251
252               status = close (diff_handle);
253               if (status != 0)
254                 close_error (file_name);
255             }
256         }
257     }
258 }
259
260 static void
261 diff_link (void)
262 {
263   struct stat file_data;
264   struct stat link_data;
265
266   if (get_stat_data (current_stat_info.file_name, &file_data)
267       && get_stat_data (current_stat_info.link_name, &link_data)
268       && !sys_compare_links (&file_data, &link_data))
269     report_difference (&current_stat_info,
270                        _("Not linked to %s"),
271                        quote (current_stat_info.link_name));
272 }
273
274 #ifdef HAVE_READLINK
275 static void
276 diff_symlink (void)
277 {
278   size_t len = strlen (current_stat_info.link_name);
279   char *linkbuf = alloca (len + 1);
280
281   int status = readlink (current_stat_info.file_name, linkbuf, len + 1);
282
283   if (status < 0)
284     {
285       if (errno == ENOENT)
286         readlink_warn (current_stat_info.file_name);
287       else
288         readlink_error (current_stat_info.file_name);
289       report_difference (&current_stat_info, NULL);
290     }
291   else if (status != len
292            || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
293     report_difference (&current_stat_info, _("Symlink differs"));
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 (void)
368 {
369   const char *dumpdir_buffer;
370   dev_t dev = 0;
371   struct stat stat_data;
372
373   if (deref_stat (true, current_stat_info.file_name, &stat_data))
374     {
375       if (errno == ENOENT)
376         stat_warn (current_stat_info.file_name);
377       else
378         stat_error (current_stat_info.file_name);
379     }
380   else
381     dev = stat_data.st_dev;
382
383   dumpdir_buffer = get_directory_contents (current_stat_info.file_name, dev);
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, -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 (false);
582
583       if (status == HEADER_FAILURE)
584         {
585           int counter = 0;
586
587           do
588             {
589               counter++;
590               set_next_block_after (current_header);
591               status = read_header (false);
592             }
593           while (status == HEADER_FAILURE);
594
595           ERROR ((0, 0,
596                   ngettext ("VERIFY FAILURE: %d invalid header detected",
597                             "VERIFY FAILURE: %d invalid headers detected",
598                             counter), counter));
599         }
600       if (status == HEADER_END_OF_FILE)
601         break;
602       if (status == HEADER_ZERO_BLOCK)
603         {
604           set_next_block_after (current_header);
605           if (!ignore_zeros_option)
606             {
607               char buf[UINTMAX_STRSIZE_BOUND];
608
609               status = read_header (false);
610               if (status == HEADER_ZERO_BLOCK)
611                 break;
612               WARN ((0, 0, _("A lone zero block at %s"),
613                     STRINGIFY_BIGINT (current_block_ordinal (), buf)));
614             }
615         }
616       
617       diff_archive ();
618       tar_stat_destroy (&current_stat_info);
619     }
620
621   access_mode = ACCESS_WRITE;
622   now_verifying = 0;
623 }