Moved system dependencies to system.c
[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 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 (const char *fmt, ...)
66 {
67   if (fmt)
68     {
69       va_list ap;
70
71       fprintf (stdlis, "%s: ", quotearg_colon (current_stat_info.file_name));
72       va_start (ap, fmt);
73       vfprintf (stdlis, fmt, ap);
74       va_end (ap);
75       fprintf (stdlis, "\n");
76     }
77   
78   if (exit_status == TAREXIT_SUCCESS)
79     exit_status = TAREXIT_DIFFERS;
80 }
81
82 /* Take a buffer returned by read_and_process and do nothing with it.  */
83 static int
84 process_noop (size_t size, char *data)
85 {
86   /* Yes, I know.  SIZE and DATA are unused in this function.  Some
87      compilers may even report it.  That's OK, just relax!  */
88   return 1;
89 }
90
91 static int
92 process_rawdata (size_t bytes, char *buffer)
93 {
94   ssize_t status = safe_read (diff_handle, diff_buffer, bytes);
95
96   if (status != bytes)
97     {
98       if (status < 0)
99         {
100           read_error (current_stat_info.file_name);
101           report_difference (NULL);
102         }
103       else
104         {
105           report_difference (ngettext ("Could only read %lu of %lu byte",
106                                        "Could only read %lu of %lu bytes",
107                                        bytes),
108                              (unsigned long) status, (unsigned long) bytes);
109         }
110       return 0;
111     }
112
113   if (memcmp (buffer, diff_buffer, bytes))
114     {
115       report_difference (_("Contents differ"));
116       return 0;
117     }
118
119   return 1;
120 }
121
122 /* Directory contents, only for GNUTYPE_DUMPDIR.  */
123
124 static char *dumpdir_cursor;
125
126 static int
127 process_dumpdir (size_t bytes, char *buffer)
128 {
129   if (memcmp (buffer, dumpdir_cursor, bytes))
130     {
131       report_difference (_("Contents differ"));
132       return 0;
133     }
134
135   dumpdir_cursor += bytes;
136   return 1;
137 }
138
139 /* Some other routine wants SIZE bytes in the archive.  For each chunk
140    of the archive, call PROCESSOR with the size of the chunk, and the
141    address of the chunk it can work with.  The PROCESSOR should return
142    nonzero for success.  It it return error once, continue skipping
143    without calling PROCESSOR anymore.  */
144 static void
145 read_and_process (off_t size, int (*processor) (size_t, char *))
146 {
147   union block *data_block;
148   size_t data_size;
149
150   if (multi_volume_option)
151     save_sizeleft = size;
152   while (size)
153     {
154       data_block = find_next_block ();
155       if (! data_block)
156         {
157           ERROR ((0, 0, _("Unexpected EOF in archive")));
158           return;
159         }
160
161       data_size = available_space_after (data_block);
162       if (data_size > size)
163         data_size = size;
164       if (!(*processor) (data_size, data_block->buffer))
165         processor = process_noop;
166       set_next_block_after ((union block *)
167                             (data_block->buffer + data_size - 1));
168       size -= data_size;
169       if (multi_volume_option)
170         save_sizeleft -= data_size;
171     }
172 }
173
174 /* JK Diff'ing a sparse file with its counterpart on the tar file is a
175    bit of a different story than a normal file.  First, we must know what
176    areas of the file to skip through, i.e., we need to construct a
177    sparsearray, which will hold all the information we need.  We must
178    compare small amounts of data at a time as we find it.  */
179
180 /* FIXME: This does not look very solid to me, at first glance.  Zero areas
181    are not checked, spurious sparse entries seemingly goes undetected, and
182    I'm not sure overall identical sparsity is verified.  */
183
184 static void
185 diff_sparse_files (void)
186 {
187   off_t remaining_size = current_stat_info.stat.st_size;
188   char *buffer = xmalloc (BLOCKSIZE * sizeof (char));
189   size_t buffer_size = BLOCKSIZE;
190   union block *data_block = 0;
191   int counter = 0;
192   int different = 0;
193
194   if (! fill_in_sparse_array ())
195     fatal_exit ();
196
197   while (remaining_size > 0)
198     {
199       ssize_t status;
200       size_t chunk_size;
201       off_t offset;
202
203 #if 0
204       off_t amount_read = 0;
205 #endif
206
207       data_block = find_next_block ();
208       if (!data_block)
209         FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
210       chunk_size = sparsearray[counter].numbytes;
211       if (!chunk_size)
212         break;
213
214       offset = sparsearray[counter].offset;
215       if (lseek (diff_handle, offset, SEEK_SET) < 0)
216         {
217           seek_error_details (current_stat_info.file_name, offset);
218           report_difference (NULL);
219         }
220
221       /* Take care to not run out of room in our buffer.  */
222
223       while (buffer_size < chunk_size)
224         {
225           if (buffer_size * 2 < buffer_size)
226             xalloc_die ();
227           buffer_size *= 2;
228           buffer = xrealloc (buffer, buffer_size * sizeof (char));
229         }
230
231       while (chunk_size > BLOCKSIZE)
232         {
233           if (status = safe_read (diff_handle, buffer, BLOCKSIZE),
234               status != BLOCKSIZE)
235             {
236               if (status < 0)
237                 {
238                   read_error (current_stat_info.file_name);
239                   report_difference (NULL);
240                 }
241               else
242                 {
243                   report_difference (ngettext ("Could only read %lu of %lu byte",
244                                      "Could only read %lu of %lu bytes",
245                                      chunk_size),
246                                      (unsigned long) status,
247                                      (unsigned long) chunk_size);
248                 }
249               break;
250             }
251
252           if (memcmp (buffer, data_block->buffer, BLOCKSIZE))
253             {
254               different = 1;
255               break;
256             }
257
258           chunk_size -= status;
259           remaining_size -= status;
260           set_next_block_after (data_block);
261           data_block = find_next_block ();
262           if (!data_block)
263             FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
264         }
265       if (status = safe_read (diff_handle, buffer, chunk_size),
266           status != chunk_size)
267         {
268           if (status < 0)
269             {
270               read_error (current_stat_info.file_name);
271               report_difference (NULL);
272             }
273           else
274             {
275               report_difference (ngettext ("Could only read %lu of %lu byte",
276                                  "Could only read %lu of %lu bytes",
277                                  chunk_size),
278                                  (unsigned long) status,
279                                  (unsigned long) chunk_size);
280             }
281           break;
282         }
283
284       if (memcmp (buffer, data_block->buffer, chunk_size))
285         {
286           different = 1;
287           break;
288         }
289 #if 0
290       amount_read += chunk_size;
291       if (amount_read >= BLOCKSIZE)
292         {
293           amount_read = 0;
294           set_next_block_after (data_block);
295           data_block = find_next_block ();
296           if (!data_block)
297             FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
298         }
299 #endif
300       set_next_block_after (data_block);
301       counter++;
302       remaining_size -= chunk_size;
303     }
304
305 #if 0
306   /* If the number of bytes read isn't the number of bytes supposedly in
307      the file, they're different.  */
308
309   if (amount_read != current_stat_info.stat.st_size)
310     different = 1;
311 #endif
312
313   set_next_block_after (data_block);
314   free (sparsearray);
315
316   if (different)
317     report_difference (_("Contents differ"));
318 }
319
320 /* Call either stat or lstat over STAT_DATA, depending on
321    --dereference (-h), for a file which should exist.  Diagnose any
322    problem.  Return nonzero for success, zero otherwise.  */
323 static int
324 get_stat_data (char const *file_name, struct stat *stat_data)
325 {
326   int status = deref_stat (dereference_option, file_name, stat_data);
327
328   if (status != 0)
329     {
330       if (errno == ENOENT)
331         stat_warn (file_name);
332       else
333         stat_error (file_name);
334       report_difference (NULL);
335       return 0;
336     }
337
338   return 1;
339 }
340
341 /* Diff a file against the archive.  */
342 void
343 diff_archive (void)
344 {
345   struct stat stat_data;
346   int status;
347   struct utimbuf restore_times;
348
349   set_next_block_after (current_header);
350   decode_header (current_header, &current_stat_info, &current_format, 1);
351
352   /* Print the block from current_header and current_stat_info.  */
353
354   if (verbose_option)
355     {
356       if (now_verifying)
357         fprintf (stdlis, _("Verify "));
358       print_header (-1);
359     }
360
361   switch (current_header->header.typeflag)
362     {
363     default:
364       ERROR ((0, 0, _("%s: Unknown file type '%c', diffed as normal file"),
365               quotearg_colon (current_stat_info.file_name),
366               current_header->header.typeflag));
367       /* Fall through.  */
368
369     case AREGTYPE:
370     case REGTYPE:
371     case GNUTYPE_SPARSE:
372     case CONTTYPE:
373
374       /* Appears to be a file.  See if it's really a directory.  */
375
376       if (current_stat_info.had_trailing_slash)
377         goto really_dir;
378
379       if (!get_stat_data (current_stat_info.file_name, &stat_data))
380         {
381           skip_member ();
382           goto quit;
383         }
384
385       if (!S_ISREG (stat_data.st_mode))
386         {
387           report_difference (_("File type differs"));
388           skip_member ();
389           goto quit;
390         }
391
392       if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
393         report_difference (_("Mode differs"));
394
395       sys_compare_uid_gid (&stat_data, &current_stat_info.stat);
396
397       if (stat_data.st_mtime != current_stat_info.stat.st_mtime)
398         report_difference (_("Mod time differs"));
399       if (current_header->header.typeflag != GNUTYPE_SPARSE &&
400           stat_data.st_size != current_stat_info.stat.st_size)
401         {
402           report_difference (_("Size differs"));
403           skip_member ();
404           goto quit;
405         }
406
407       diff_handle = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
408
409       if (diff_handle < 0)
410         {
411           open_error (current_stat_info.file_name);
412           skip_member ();
413           report_difference (NULL);
414           goto quit;
415         }
416
417       restore_times.actime = stat_data.st_atime;
418       restore_times.modtime = stat_data.st_mtime;
419
420       /* Need to treat sparse files completely differently here.  */
421
422       if (current_header->header.typeflag == GNUTYPE_SPARSE)
423         diff_sparse_files ();
424       else
425         {
426           if (multi_volume_option)
427             {
428               assign_string (&save_name, current_stat_info.file_name);
429               save_totsize = current_stat_info.stat.st_size;
430               /* save_sizeleft is set in read_and_process.  */
431             }
432
433           read_and_process (current_stat_info.stat.st_size, process_rawdata);
434
435           if (multi_volume_option)
436             assign_string (&save_name, 0);
437         }
438
439       status = close (diff_handle);
440       if (status != 0)
441         close_error (current_stat_info.file_name);
442
443       if (atime_preserve_option)
444         utime (current_stat_info.file_name, &restore_times);
445
446     quit:
447       break;
448
449     case LNKTYPE:
450       {
451         struct stat link_data, stat_data;
452
453         if (!get_stat_data (current_stat_info.file_name, &stat_data))
454           break;
455         if (!get_stat_data (current_stat_info.link_name, &link_data))
456           break;
457         sys_compare_links (&stat_data, &link_data);
458       }
459       break;
460       
461 #ifdef HAVE_READLINK
462     case SYMTYPE:
463       {
464         size_t len = strlen (current_stat_info.link_name);
465         char *linkbuf = alloca (len + 1);
466
467         status = readlink (current_stat_info.file_name, linkbuf, len + 1);
468
469         if (status < 0)
470           {
471             if (errno == ENOENT)
472               readlink_warn (current_stat_info.file_name);
473             else
474               readlink_error (current_stat_info.file_name);
475             report_difference (NULL);
476           }
477         else if (status != len
478                  || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
479           report_difference (_("Symlink differs"));
480
481         break;
482       }
483 #endif
484
485     case CHRTYPE:
486     case BLKTYPE:
487     case FIFOTYPE:
488
489       /* FIXME: deal with umask.  */
490
491       if (!get_stat_data (current_stat_info.file_name, &stat_data))
492         break;
493
494       if (current_header->header.typeflag == CHRTYPE
495           ? !S_ISCHR (stat_data.st_mode)
496           : current_header->header.typeflag == BLKTYPE
497           ? !S_ISBLK (stat_data.st_mode)
498           : /* current_header->header.typeflag == FIFOTYPE */
499           !S_ISFIFO (stat_data.st_mode))
500         {
501           report_difference (_("File type differs"));
502           break;
503         }
504
505       if ((current_header->header.typeflag == CHRTYPE
506            || current_header->header.typeflag == BLKTYPE)
507           && current_stat_info.stat.st_rdev != stat_data.st_rdev)
508         {
509           report_difference (_("Device number differs"));
510           break;
511         }
512
513       if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
514         {
515           report_difference (_("Mode differs"));
516           break;
517         }
518
519       break;
520
521     case GNUTYPE_DUMPDIR:
522       {
523         char *dumpdir_buffer = get_directory_contents (current_stat_info.file_name, 0);
524
525         if (multi_volume_option)
526           {
527             assign_string (&save_name, current_stat_info.file_name);
528             save_totsize = current_stat_info.stat.st_size;
529             /* save_sizeleft is set in read_and_process.  */
530           }
531
532         if (dumpdir_buffer)
533           {
534             dumpdir_cursor = dumpdir_buffer;
535             read_and_process (current_stat_info.stat.st_size, process_dumpdir);
536             free (dumpdir_buffer);
537           }
538         else
539           read_and_process (current_stat_info.stat.st_size, process_noop);
540
541         if (multi_volume_option)
542           assign_string (&save_name, 0);
543         /* Fall through.  */
544       }
545
546     case DIRTYPE:
547     really_dir:
548       if (!get_stat_data (current_stat_info.file_name, &stat_data))
549         break;
550
551       if (!S_ISDIR (stat_data.st_mode))
552         {
553           report_difference (_("File type differs"));
554           break;
555         }
556
557       if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
558         {
559           report_difference (_("Mode differs"));
560           break;
561         }
562
563       break;
564
565     case GNUTYPE_VOLHDR:
566       break;
567
568     case GNUTYPE_MULTIVOL:
569       {
570         off_t offset;
571
572         if (current_stat_info.had_trailing_slash)
573           goto really_dir;
574
575         if (!get_stat_data (current_stat_info.file_name, &stat_data))
576           break;
577
578         if (!S_ISREG (stat_data.st_mode))
579           {
580             report_difference (_("File type differs"));
581             skip_member ();
582             break;
583           }
584
585         offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
586         if (stat_data.st_size != current_stat_info.stat.st_size + offset)
587           {
588             report_difference (_("Size differs"));
589             skip_member ();
590             break;
591           }
592
593         diff_handle = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
594
595         if (diff_handle < 0)
596           {
597             open_error (current_stat_info.file_name);
598             report_difference (NULL);
599             skip_member ();
600             break;
601           }
602
603         if (lseek (diff_handle, offset, SEEK_SET) < 0)
604           {
605             seek_error_details (current_stat_info.file_name, offset);
606             report_difference (NULL);
607             break;
608           }
609
610         if (multi_volume_option)
611           {
612             assign_string (&save_name, current_stat_info.file_name);
613             save_totsize = stat_data.st_size;
614             /* save_sizeleft is set in read_and_process.  */
615           }
616
617         read_and_process (current_stat_info.stat.st_size, process_rawdata);
618
619         if (multi_volume_option)
620           assign_string (&save_name, 0);
621
622         status = close (diff_handle);
623         if (status != 0)
624           close_error (current_stat_info.file_name);
625
626         break;
627       }
628     }
629 }
630
631 void
632 verify_volume (void)
633 {
634   if (!diff_buffer)
635     diff_init ();
636
637   /* Verifying an archive is meant to check if the physical media got it
638      correctly, so try to defeat clever in-memory buffering pertaining to
639      this particular media.  On Linux, for example, the floppy drive would
640      not even be accessed for the whole verification.
641
642      The code was using fsync only when the ioctl is unavailable, but
643      Marty Leisner says that the ioctl does not work when not preceded by
644      fsync.  So, until we know better, or maybe to please Marty, let's do it
645      the unbelievable way :-).  */
646
647 #if HAVE_FSYNC
648   fsync (archive);
649 #endif
650 #ifdef FDFLUSH
651   ioctl (archive, FDFLUSH);
652 #endif
653
654 #ifdef MTIOCTOP
655   {
656     struct mtop operation;
657     int status;
658
659     operation.mt_op = MTBSF;
660     operation.mt_count = 1;
661     if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
662       {
663         if (errno != EIO
664             || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
665                 status < 0))
666           {
667 #endif
668             if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
669               {
670                 /* Lseek failed.  Try a different method.  */
671                 seek_warn (archive_name_array[0]);
672                 return;
673               }
674 #ifdef MTIOCTOP
675           }
676       }
677   }
678 #endif
679
680   access_mode = ACCESS_READ;
681   now_verifying = 1;
682
683   flush_read ();
684   while (1)
685     {
686       enum read_header status = read_header (false);
687
688       if (status == HEADER_FAILURE)
689         {
690           int counter = 0;
691
692           do
693             {
694               counter++;
695               status = read_header (false);
696             }
697           while (status == HEADER_FAILURE);
698
699           ERROR ((0, 0,
700                   ngettext ("VERIFY FAILURE: %d invalid header detected",
701                             "VERIFY FAILURE: %d invalid headers detected",
702                             counter), counter));
703         }
704       if (status == HEADER_ZERO_BLOCK || status == HEADER_END_OF_FILE)
705         break;
706
707       diff_archive ();
708     }
709
710   access_mode = ACCESS_WRITE;
711   now_verifying = 0;
712 }