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