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