Merge recent gnulib changes, and remove some lint.
[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 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    59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
21
22 #include "system.h"
23
24 #if HAVE_UTIME_H
25 # include <utime.h>
26 #else
27 struct utimbuf
28   {
29     long actime;
30     long modtime;
31   };
32 #endif
33
34 #if HAVE_LINUX_FD_H
35 # include <linux/fd.h>
36 #endif
37
38 #include <quotearg.h>
39
40 #include "common.h"
41 #include "rmt.h"
42 #include <stdarg.h>
43
44 /* Nonzero if we are verifying at the moment.  */
45 bool now_verifying;
46
47 /* File descriptor for the file we are diffing.  */
48 static int diff_handle;
49
50 /* Area for reading file contents into.  */
51 static char *diff_buffer;
52
53 /* Initialize for a diff operation.  */
54 void
55 diff_init (void)
56 {
57   diff_buffer = valloc (record_size);
58   if (!diff_buffer)
59     xalloc_die ();
60 }
61
62 /* Sigh about something that differs by writing a MESSAGE to stdlis,
63    given MESSAGE is nonzero.  Also set the exit status if not already.  */
64 void
65 report_difference (struct tar_stat_info *st __attribute__ ((unused)),
66                    const char *fmt, ...)
67 {
68   if (fmt)
69     {
70       va_list ap;
71
72       fprintf (stdlis, "%s: ", quotearg_colon (current_stat_info.file_name));
73       va_start (ap, fmt);
74       vfprintf (stdlis, fmt, ap);
75       va_end (ap);
76       fprintf (stdlis, "\n");
77     }
78
79   if (exit_status == TAREXIT_SUCCESS)
80     exit_status = TAREXIT_DIFFERS;
81 }
82
83 /* Take a buffer returned by read_and_process and do nothing with it.  */
84 static int
85 process_noop (size_t size __attribute__ ((unused)),
86               char *data __attribute__ ((unused)))
87 {
88   return 1;
89 }
90
91 static int
92 process_rawdata (size_t bytes, char *buffer)
93 {
94   size_t status = safe_read (diff_handle, diff_buffer, bytes);
95
96   if (status != bytes)
97     {
98       if (status == SAFE_READ_ERROR)
99         {
100           read_error (current_stat_info.file_name);
101           report_difference (&current_stat_info, NULL);
102         }
103       else
104         {
105           report_difference (&current_stat_info,
106                              ngettext ("Could only read %lu of %lu byte",
107                                        "Could only read %lu of %lu bytes",
108                                        bytes),
109                              (unsigned long) status, (unsigned long) bytes);
110         }
111       return 0;
112     }
113
114   if (memcmp (buffer, diff_buffer, bytes))
115     {
116       report_difference (&current_stat_info,
117                          _("Contents differ"));
118       return 0;
119     }
120
121   return 1;
122 }
123
124 /* Directory contents, only for GNUTYPE_DUMPDIR.  */
125
126 static char *dumpdir_cursor;
127
128 static int
129 process_dumpdir (size_t bytes, char *buffer)
130 {
131   if (memcmp (buffer, dumpdir_cursor, bytes))
132     {
133       report_difference (&current_stat_info, _("Contents differ"));
134       return 0;
135     }
136
137   dumpdir_cursor += bytes;
138   return 1;
139 }
140
141 /* Some other routine wants SIZE bytes in the archive.  For each chunk
142    of the archive, call PROCESSOR with the size of the chunk, and the
143    address of the chunk it can work with.  The PROCESSOR should return
144    nonzero for success.  It it return error once, continue skipping
145    without calling PROCESSOR anymore.  */
146 static void
147 read_and_process (off_t size, int (*processor) (size_t, char *))
148 {
149   union block *data_block;
150   size_t data_size;
151
152   if (multi_volume_option)
153     save_sizeleft = size;
154   while (size)
155     {
156       data_block = find_next_block ();
157       if (! data_block)
158         {
159           ERROR ((0, 0, _("Unexpected EOF in archive")));
160           return;
161         }
162
163       data_size = available_space_after (data_block);
164       if (data_size > size)
165         data_size = size;
166       if (!(*processor) (data_size, data_block->buffer))
167         processor = process_noop;
168       set_next_block_after ((union block *)
169                             (data_block->buffer + data_size - 1));
170       size -= data_size;
171       if (multi_volume_option)
172         save_sizeleft -= data_size;
173     }
174 }
175
176 /* Call either stat or lstat over STAT_DATA, depending on
177    --dereference (-h), for a file which should exist.  Diagnose any
178    problem.  Return nonzero for success, zero otherwise.  */
179 static int
180 get_stat_data (char const *file_name, struct stat *stat_data)
181 {
182   int status = deref_stat (dereference_option, file_name, stat_data);
183
184   if (status != 0)
185     {
186       if (errno == ENOENT)
187         stat_warn (file_name);
188       else
189         stat_error (file_name);
190       report_difference (&current_stat_info, NULL);
191       return 0;
192     }
193
194   return 1;
195 }
196
197 /* Diff a file against the archive.  */
198 void
199 diff_archive (void)
200 {
201   struct stat stat_data;
202   int status;
203   struct utimbuf restore_times;
204
205   set_next_block_after (current_header);
206   decode_header (current_header, &current_stat_info, &current_format, 1);
207
208   /* Print the block from current_header and current_stat_info.  */
209
210   if (verbose_option)
211     {
212       if (now_verifying)
213         fprintf (stdlis, _("Verify "));
214       print_header (&current_stat_info, -1);
215     }
216
217   switch (current_header->header.typeflag)
218     {
219     default:
220       ERROR ((0, 0, _("%s: Unknown file type '%c', diffed as normal file"),
221               quotearg_colon (current_stat_info.file_name),
222               current_header->header.typeflag));
223       /* Fall through.  */
224
225     case AREGTYPE:
226     case REGTYPE:
227     case GNUTYPE_SPARSE:
228     case CONTTYPE:
229
230       /* Appears to be a file.  See if it's really a directory.  */
231
232       if (current_stat_info.had_trailing_slash)
233         goto really_dir;
234
235       if (!get_stat_data (current_stat_info.file_name, &stat_data))
236         {
237           skip_member ();
238           goto quit;
239         }
240
241       if (!S_ISREG (stat_data.st_mode))
242         {
243           report_difference (&current_stat_info, _("File type differs"));
244           skip_member ();
245           goto quit;
246         }
247
248       if ((current_stat_info.stat.st_mode & MODE_ALL) !=
249           (stat_data.st_mode & MODE_ALL))
250         report_difference (&current_stat_info, _("Mode differs"));
251
252       if (!sys_compare_uid (&stat_data, &current_stat_info.stat))
253         report_difference (&current_stat_info, _("Uid differs"));
254       if (!sys_compare_gid (&stat_data, &current_stat_info.stat))
255         report_difference (&current_stat_info, _("Gid differs"));
256
257       if (stat_data.st_mtime != current_stat_info.stat.st_mtime)
258         report_difference (&current_stat_info, _("Mod time differs"));
259       if (current_header->header.typeflag != GNUTYPE_SPARSE &&
260           stat_data.st_size != current_stat_info.stat.st_size)
261         {
262           report_difference (&current_stat_info, _("Size differs"));
263           skip_member ();
264           goto quit;
265         }
266
267       diff_handle = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
268
269       if (diff_handle < 0)
270         {
271           open_error (current_stat_info.file_name);
272           skip_member ();
273           report_difference (&current_stat_info, NULL);
274           goto quit;
275         }
276
277       restore_times.actime = stat_data.st_atime;
278       restore_times.modtime = stat_data.st_mtime;
279
280       /* Need to treat sparse files completely differently here.  */
281
282       if (current_stat_info.is_sparse)
283         sparse_diff_file (diff_handle, &current_stat_info);
284       else
285         {
286           if (multi_volume_option)
287             {
288               assign_string (&save_name, current_stat_info.file_name);
289               save_totsize = current_stat_info.stat.st_size;
290               /* save_sizeleft is set in read_and_process.  */
291             }
292
293           read_and_process (current_stat_info.stat.st_size, process_rawdata);
294
295           if (multi_volume_option)
296             assign_string (&save_name, 0);
297         }
298
299       status = close (diff_handle);
300       if (status != 0)
301         close_error (current_stat_info.file_name);
302
303       if (atime_preserve_option)
304         utime (current_stat_info.file_name, &restore_times);
305
306     quit:
307       break;
308
309     case LNKTYPE:
310       {
311         struct stat file_data;
312         struct stat link_data;
313
314         if (!get_stat_data (current_stat_info.file_name, &file_data))
315           break;
316         if (!get_stat_data (current_stat_info.link_name, &link_data))
317           break;
318         if (!sys_compare_links (&file_data, &link_data))
319           report_difference (&current_stat_info,
320                              _("Not linked to %s"),
321                              quote (current_stat_info.link_name));
322       }
323       break;
324
325 #ifdef HAVE_READLINK
326     case SYMTYPE:
327       {
328         size_t len = strlen (current_stat_info.link_name);
329         char *linkbuf = alloca (len + 1);
330
331         status = readlink (current_stat_info.file_name, linkbuf, len + 1);
332
333         if (status < 0)
334           {
335             if (errno == ENOENT)
336               readlink_warn (current_stat_info.file_name);
337             else
338               readlink_error (current_stat_info.file_name);
339             report_difference (&current_stat_info, NULL);
340           }
341         else if (status != len
342                  || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
343           report_difference (&current_stat_info, _("Symlink differs"));
344
345         break;
346       }
347 #endif
348
349     case CHRTYPE:
350     case BLKTYPE:
351     case FIFOTYPE:
352
353       /* FIXME: deal with umask.  */
354
355       if (!get_stat_data (current_stat_info.file_name, &stat_data))
356         break;
357
358       if (current_header->header.typeflag == CHRTYPE
359           ? !S_ISCHR (stat_data.st_mode)
360           : current_header->header.typeflag == BLKTYPE
361           ? !S_ISBLK (stat_data.st_mode)
362           : /* current_header->header.typeflag == FIFOTYPE */
363           !S_ISFIFO (stat_data.st_mode))
364         {
365           report_difference (&current_stat_info, _("File type differs"));
366           break;
367         }
368
369       if ((current_header->header.typeflag == CHRTYPE
370            || current_header->header.typeflag == BLKTYPE)
371           && current_stat_info.stat.st_rdev != stat_data.st_rdev)
372         {
373           report_difference (&current_stat_info, _("Device number differs"));
374           break;
375         }
376
377       if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
378         {
379           report_difference (&current_stat_info, _("Mode differs"));
380           break;
381         }
382
383       break;
384
385     case GNUTYPE_DUMPDIR:
386       {
387         char *dumpdir_buffer = get_directory_contents (current_stat_info.file_name, 0);
388
389         if (multi_volume_option)
390           {
391             assign_string (&save_name, current_stat_info.file_name);
392             save_totsize = current_stat_info.stat.st_size;
393             /* save_sizeleft is set in read_and_process.  */
394           }
395
396         if (dumpdir_buffer)
397           {
398             dumpdir_cursor = dumpdir_buffer;
399             read_and_process (current_stat_info.stat.st_size, process_dumpdir);
400             free (dumpdir_buffer);
401           }
402         else
403           read_and_process (current_stat_info.stat.st_size, process_noop);
404
405         if (multi_volume_option)
406           assign_string (&save_name, 0);
407         /* Fall through.  */
408       }
409
410     case DIRTYPE:
411     really_dir:
412       if (!get_stat_data (current_stat_info.file_name, &stat_data))
413         break;
414
415       if (!S_ISDIR (stat_data.st_mode))
416         {
417           report_difference (&current_stat_info, _("File type differs"));
418           break;
419         }
420
421       if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
422         {
423           report_difference (&current_stat_info, _("Mode differs"));
424           break;
425         }
426
427       break;
428
429     case GNUTYPE_VOLHDR:
430       break;
431
432     case GNUTYPE_MULTIVOL:
433       {
434         off_t offset;
435
436         if (current_stat_info.had_trailing_slash)
437           goto really_dir;
438
439         if (!get_stat_data (current_stat_info.file_name, &stat_data))
440           break;
441
442         if (!S_ISREG (stat_data.st_mode))
443           {
444             report_difference (&current_stat_info, _("File type differs"));
445             skip_member ();
446             break;
447           }
448
449         offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
450         if (stat_data.st_size != current_stat_info.stat.st_size + offset)
451           {
452             report_difference (&current_stat_info, _("Size differs"));
453             skip_member ();
454             break;
455           }
456
457         diff_handle = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
458
459         if (diff_handle < 0)
460           {
461             open_error (current_stat_info.file_name);
462             report_difference (&current_stat_info, NULL);
463             skip_member ();
464             break;
465           }
466
467         if (lseek (diff_handle, offset, SEEK_SET) < 0)
468           {
469             seek_error_details (current_stat_info.file_name, offset);
470             report_difference (&current_stat_info, NULL);
471             break;
472           }
473
474         if (multi_volume_option)
475           {
476             assign_string (&save_name, current_stat_info.file_name);
477             save_totsize = stat_data.st_size;
478             /* save_sizeleft is set in read_and_process.  */
479           }
480
481         read_and_process (current_stat_info.stat.st_size, process_rawdata);
482
483         if (multi_volume_option)
484           assign_string (&save_name, 0);
485
486         status = close (diff_handle);
487         if (status != 0)
488           close_error (current_stat_info.file_name);
489
490         break;
491       }
492     }
493 }
494
495 void
496 verify_volume (void)
497 {
498   if (!diff_buffer)
499     diff_init ();
500
501   /* Verifying an archive is meant to check if the physical media got it
502      correctly, so try to defeat clever in-memory buffering pertaining to
503      this particular media.  On Linux, for example, the floppy drive would
504      not even be accessed for the whole verification.
505
506      The code was using fsync only when the ioctl is unavailable, but
507      Marty Leisner says that the ioctl does not work when not preceded by
508      fsync.  So, until we know better, or maybe to please Marty, let's do it
509      the unbelievable way :-).  */
510
511 #if HAVE_FSYNC
512   fsync (archive);
513 #endif
514 #ifdef FDFLUSH
515   ioctl (archive, FDFLUSH);
516 #endif
517
518 #ifdef MTIOCTOP
519   {
520     struct mtop operation;
521     int status;
522
523     operation.mt_op = MTBSF;
524     operation.mt_count = 1;
525     if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
526       {
527         if (errno != EIO
528             || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
529                 status < 0))
530           {
531 #endif
532             if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
533               {
534                 /* Lseek failed.  Try a different method.  */
535                 seek_warn (archive_name_array[0]);
536                 return;
537               }
538 #ifdef MTIOCTOP
539           }
540       }
541   }
542 #endif
543
544   access_mode = ACCESS_READ;
545   now_verifying = 1;
546
547   flush_read ();
548   while (1)
549     {
550       enum read_header status = read_header (false);
551
552       if (status == HEADER_FAILURE)
553         {
554           int counter = 0;
555
556           do
557             {
558               counter++;
559               status = read_header (false);
560             }
561           while (status == HEADER_FAILURE);
562
563           ERROR ((0, 0,
564                   ngettext ("VERIFY FAILURE: %d invalid header detected",
565                             "VERIFY FAILURE: %d invalid headers detected",
566                             counter), counter));
567         }
568       if (status == HEADER_ZERO_BLOCK || status == HEADER_END_OF_FILE)
569         break;
570
571       diff_archive ();
572     }
573
574   access_mode = ACCESS_WRITE;
575   now_verifying = 0;
576 }