(start_header): Pass mtime as a call-specific data to xheader_store.
[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 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 2, 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 /* Directory contents, only for GNUTYPE_DUMPDIR.  */
114
115 static char *dumpdir_cursor;
116
117 static int
118 process_dumpdir (size_t bytes, char *buffer)
119 {
120   if (memcmp (buffer, dumpdir_cursor, bytes))
121     {
122       report_difference (&current_stat_info, _("Contents differ"));
123       return 0;
124     }
125
126   dumpdir_cursor += bytes;
127   return 1;
128 }
129
130 /* Some other routine wants SIZE bytes in the archive.  For each chunk
131    of the archive, call PROCESSOR with the size of the chunk, and the
132    address of the chunk it can work with.  The PROCESSOR should return
133    nonzero for success.  It it return error once, continue skipping
134    without calling PROCESSOR anymore.  */
135
136 static void
137 read_and_process (struct tar_stat_info *st, int (*processor) (size_t, char *))
138 {
139   union block *data_block;
140   size_t data_size;
141   size_t size = st->stat.st_size;
142
143   mv_begin (st);
144   while (size)
145     {
146       data_block = find_next_block ();
147       if (! data_block)
148         {
149           ERROR ((0, 0, _("Unexpected EOF in archive")));
150           return;
151         }
152
153       data_size = available_space_after (data_block);
154       if (data_size > size)
155         data_size = size;
156       if (!(*processor) (data_size, data_block->buffer))
157         processor = process_noop;
158       set_next_block_after ((union block *)
159                             (data_block->buffer + data_size - 1));
160       size -= data_size;
161       mv_size_left (size);
162     }
163   mv_end ();
164 }
165
166 /* Call either stat or lstat over STAT_DATA, depending on
167    --dereference (-h), for a file which should exist.  Diagnose any
168    problem.  Return nonzero for success, zero otherwise.  */
169 static int
170 get_stat_data (char const *file_name, struct stat *stat_data)
171 {
172   int status = deref_stat (dereference_option, file_name, stat_data);
173
174   if (status != 0)
175     {
176       if (errno == ENOENT)
177         stat_warn (file_name);
178       else
179         stat_error (file_name);
180       report_difference (&current_stat_info, NULL);
181       return 0;
182     }
183
184   return 1;
185 }
186
187 \f
188 static void
189 diff_dir (void)
190 {
191   struct stat stat_data;
192
193   if (!get_stat_data (current_stat_info.file_name, &stat_data))
194     return;
195
196   if (!S_ISDIR (stat_data.st_mode))
197     report_difference (&current_stat_info, _("File type differs"));
198   else if ((current_stat_info.stat.st_mode & MODE_ALL) !=
199            (stat_data.st_mode & MODE_ALL))
200     report_difference (&current_stat_info, _("Mode differs"));
201 }
202
203 static void
204 diff_file (void)
205 {
206   char const *file_name = current_stat_info.file_name;
207   struct stat stat_data;
208
209   if (!get_stat_data (file_name, &stat_data))
210     skip_member ();
211   else if (!S_ISREG (stat_data.st_mode))
212     {
213       report_difference (&current_stat_info, _("File type differs"));
214       skip_member ();
215     }
216   else
217     {
218       if ((current_stat_info.stat.st_mode & MODE_ALL) !=
219           (stat_data.st_mode & MODE_ALL))
220         report_difference (&current_stat_info, _("Mode differs"));
221
222       if (!sys_compare_uid (&stat_data, &current_stat_info.stat))
223         report_difference (&current_stat_info, _("Uid differs"));
224       if (!sys_compare_gid (&stat_data, &current_stat_info.stat))
225         report_difference (&current_stat_info, _("Gid differs"));
226
227       if (tar_timespec_cmp (get_stat_mtime (&stat_data),
228                             current_stat_info.mtime))
229         report_difference (&current_stat_info, _("Mod time differs"));
230       if (current_header->header.typeflag != GNUTYPE_SPARSE
231           && stat_data.st_size != current_stat_info.stat.st_size)
232         {
233           report_difference (&current_stat_info, _("Size differs"));
234           skip_member ();
235         }
236       else
237         {
238           int atime_flag =
239             (atime_preserve_option == system_atime_preserve
240              ? O_NOATIME
241              : 0);
242
243           diff_handle = open (file_name, O_RDONLY | O_BINARY | atime_flag);
244
245           if (diff_handle < 0)
246             {
247               open_error (file_name);
248               skip_member ();
249               report_difference (&current_stat_info, NULL);
250             }
251           else
252             {
253               int status;
254
255               if (current_stat_info.is_sparse)
256                 sparse_diff_file (diff_handle, &current_stat_info);
257               else
258                 read_and_process (&current_stat_info, process_rawdata);
259
260               if (atime_preserve_option == replace_atime_preserve)
261                 {
262                   struct timespec ts[2];
263                   ts[0] = get_stat_atime (&stat_data);
264                   ts[1] = get_stat_mtime (&stat_data);
265                   if (set_file_atime (diff_handle, file_name, ts) != 0)
266                     utime_error (file_name);
267                 }
268
269               status = close (diff_handle);
270               if (status != 0)
271                 close_error (file_name);
272             }
273         }
274     }
275 }
276
277 static void
278 diff_link (void)
279 {
280   struct stat file_data;
281   struct stat link_data;
282
283   if (get_stat_data (current_stat_info.file_name, &file_data)
284       && get_stat_data (current_stat_info.link_name, &link_data)
285       && !sys_compare_links (&file_data, &link_data))
286     report_difference (&current_stat_info,
287                        _("Not linked to %s"),
288                        quote (current_stat_info.link_name));
289 }
290
291 #ifdef HAVE_READLINK
292 static void
293 diff_symlink (void)
294 {
295   size_t len = strlen (current_stat_info.link_name);
296   char *linkbuf = alloca (len + 1);
297
298   int status = readlink (current_stat_info.file_name, linkbuf, len + 1);
299
300   if (status < 0)
301     {
302       if (errno == ENOENT)
303         readlink_warn (current_stat_info.file_name);
304       else
305         readlink_error (current_stat_info.file_name);
306       report_difference (&current_stat_info, NULL);
307     }
308   else if (status != len
309            || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
310     report_difference (&current_stat_info, _("Symlink differs"));
311 }
312 #endif
313
314 static void
315 diff_special (void)
316 {
317   struct stat stat_data;
318
319   /* FIXME: deal with umask.  */
320
321   if (!get_stat_data (current_stat_info.file_name, &stat_data))
322     return;
323
324   if (current_header->header.typeflag == CHRTYPE
325       ? !S_ISCHR (stat_data.st_mode)
326       : current_header->header.typeflag == BLKTYPE
327       ? !S_ISBLK (stat_data.st_mode)
328       : /* current_header->header.typeflag == FIFOTYPE */
329       !S_ISFIFO (stat_data.st_mode))
330     {
331       report_difference (&current_stat_info, _("File type differs"));
332       return;
333     }
334
335   if ((current_header->header.typeflag == CHRTYPE
336        || current_header->header.typeflag == BLKTYPE)
337       && current_stat_info.stat.st_rdev != stat_data.st_rdev)
338     {
339       report_difference (&current_stat_info, _("Device number differs"));
340       return;
341     }
342
343   if ((current_stat_info.stat.st_mode & MODE_ALL) !=
344       (stat_data.st_mode & MODE_ALL))
345     report_difference (&current_stat_info, _("Mode differs"));
346 }
347
348 static void
349 diff_dumpdir (void)
350 {
351   char *dumpdir_buffer;
352   dev_t dev = 0;
353   struct stat stat;
354
355   if (deref_stat (true, current_stat_info.file_name, &stat))
356     {
357       if (errno == ENOENT)
358         stat_warn (current_stat_info.file_name);
359       else
360         stat_error (current_stat_info.file_name);
361     }
362   else
363     dev = stat.st_dev;
364
365   dumpdir_buffer = get_directory_contents (current_stat_info.file_name, dev);
366
367   if (dumpdir_buffer)
368     {
369       dumpdir_cursor = dumpdir_buffer;
370       read_and_process (&current_stat_info, process_dumpdir);
371       free (dumpdir_buffer);
372     }
373   else
374     read_and_process (&current_stat_info, process_noop);
375 }
376
377 static void
378 diff_multivol (void)
379 {
380   struct stat stat_data;
381   int fd, status;
382   off_t offset;
383
384   if (current_stat_info.had_trailing_slash)
385     {
386       diff_dir ();
387       return;
388     }
389
390   if (!get_stat_data (current_stat_info.file_name, &stat_data))
391     return;
392
393   if (!S_ISREG (stat_data.st_mode))
394     {
395       report_difference (&current_stat_info, _("File type differs"));
396       skip_member ();
397       return;
398     }
399
400   offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
401   if (stat_data.st_size != current_stat_info.stat.st_size + offset)
402     {
403       report_difference (&current_stat_info, _("Size differs"));
404       skip_member ();
405       return;
406     }
407
408   fd = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
409
410   if (fd < 0)
411     {
412       open_error (current_stat_info.file_name);
413       report_difference (&current_stat_info, NULL);
414       skip_member ();
415       return;
416     }
417
418   if (lseek (fd, offset, SEEK_SET) < 0)
419     {
420       seek_error_details (current_stat_info.file_name, offset);
421       report_difference (&current_stat_info, NULL);
422       return;
423     }
424
425   read_and_process (&current_stat_info, process_rawdata);
426
427   status = close (fd);
428   if (status != 0)
429     close_error (current_stat_info.file_name);
430 }
431
432 /* Diff a file against the archive.  */
433 void
434 diff_archive (void)
435 {
436
437   set_next_block_after (current_header);
438   decode_header (current_header, &current_stat_info, &current_format, 1);
439
440   /* Print the block from current_header and current_stat_info.  */
441
442   if (verbose_option)
443     {
444       if (now_verifying)
445         fprintf (stdlis, _("Verify "));
446       print_header (&current_stat_info, -1);
447     }
448
449   switch (current_header->header.typeflag)
450     {
451     default:
452       ERROR ((0, 0, _("%s: Unknown file type `%c', diffed as normal file"),
453               quotearg_colon (current_stat_info.file_name),
454               current_header->header.typeflag));
455       /* Fall through.  */
456
457     case AREGTYPE:
458     case REGTYPE:
459     case GNUTYPE_SPARSE:
460     case CONTTYPE:
461
462       /* Appears to be a file.  See if it's really a directory.  */
463
464       if (current_stat_info.had_trailing_slash)
465         diff_dir ();
466       else
467         diff_file ();
468       break;
469
470     case LNKTYPE:
471       diff_link ();
472       break;
473
474 #ifdef HAVE_READLINK
475     case SYMTYPE:
476       diff_symlink ();
477       break;
478 #endif
479
480     case CHRTYPE:
481     case BLKTYPE:
482     case FIFOTYPE:
483       diff_special ();
484       break;
485
486     case GNUTYPE_DUMPDIR:
487       diff_dumpdir ();
488       /* Fall through.  */
489
490     case DIRTYPE:
491       diff_dir ();
492       break;
493
494     case GNUTYPE_VOLHDR:
495       break;
496
497     case GNUTYPE_MULTIVOL:
498       diff_multivol ();
499     }
500 }
501
502 void
503 verify_volume (void)
504 {
505   if (removed_prefixes_p ())
506     {
507       WARN((0, 0,
508             _("Archive contains file names with leading prefixes removed.")));
509       WARN((0, 0,
510             _("Verification may fail to locate original files.")));
511     }
512
513   if (!diff_buffer)
514     diff_init ();
515
516   /* Verifying an archive is meant to check if the physical media got it
517      correctly, so try to defeat clever in-memory buffering pertaining to
518      this particular media.  On Linux, for example, the floppy drive would
519      not even be accessed for the whole verification.
520
521      The code was using fsync only when the ioctl is unavailable, but
522      Marty Leisner says that the ioctl does not work when not preceded by
523      fsync.  So, until we know better, or maybe to please Marty, let's do it
524      the unbelievable way :-).  */
525
526 #if HAVE_FSYNC
527   fsync (archive);
528 #endif
529 #ifdef FDFLUSH
530   ioctl (archive, FDFLUSH);
531 #endif
532
533 #ifdef MTIOCTOP
534   {
535     struct mtop operation;
536     int status;
537
538     operation.mt_op = MTBSF;
539     operation.mt_count = 1;
540     if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
541       {
542         if (errno != EIO
543             || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
544                 status < 0))
545           {
546 #endif
547             if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
548               {
549                 /* Lseek failed.  Try a different method.  */
550                 seek_warn (archive_name_array[0]);
551                 return;
552               }
553 #ifdef MTIOCTOP
554           }
555       }
556   }
557 #endif
558
559   access_mode = ACCESS_READ;
560   now_verifying = 1;
561
562   flush_read ();
563   while (1)
564     {
565       enum read_header status = read_header (false);
566
567       if (status == HEADER_FAILURE)
568         {
569           int counter = 0;
570
571           do
572             {
573               counter++;
574               set_next_block_after (current_header);
575               status = read_header (false);
576             }
577           while (status == HEADER_FAILURE);
578
579           ERROR ((0, 0,
580                   ngettext ("VERIFY FAILURE: %d invalid header detected",
581                             "VERIFY FAILURE: %d invalid headers detected",
582                             counter), counter));
583         }
584       if (status == HEADER_ZERO_BLOCK || status == HEADER_END_OF_FILE)
585         break;
586
587       diff_archive ();
588       tar_stat_destroy (&current_stat_info);
589       xheader_destroy (&extended_header);
590     }
591
592   access_mode = ACCESS_WRITE;
593   now_verifying = 0;
594 }