Removed accumulator stuff in favor of obstack.
[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 (struct tar_stat_info *st, 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 (&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_header->header.typeflag == GNUTYPE_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 link_data, stat_data;
312
313         if (!get_stat_data (current_stat_info.file_name, &stat_data))
314           break;
315         if (!get_stat_data (current_stat_info.link_name, &link_data))
316           break;
317         if (!sys_compare_links (&stat_data, &link_data))
318           report_difference (&current_stat_info,
319                              _("Not linked to %s"),
320                              quote (current_stat_info.link_name));
321       }
322       break;
323       
324 #ifdef HAVE_READLINK
325     case SYMTYPE:
326       {
327         size_t len = strlen (current_stat_info.link_name);
328         char *linkbuf = alloca (len + 1);
329
330         status = readlink (current_stat_info.file_name, linkbuf, len + 1);
331
332         if (status < 0)
333           {
334             if (errno == ENOENT)
335               readlink_warn (current_stat_info.file_name);
336             else
337               readlink_error (current_stat_info.file_name);
338             report_difference (&current_stat_info, NULL);
339           }
340         else if (status != len
341                  || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
342           report_difference (&current_stat_info, _("Symlink differs"));
343
344         break;
345       }
346 #endif
347
348     case CHRTYPE:
349     case BLKTYPE:
350     case FIFOTYPE:
351
352       /* FIXME: deal with umask.  */
353
354       if (!get_stat_data (current_stat_info.file_name, &stat_data))
355         break;
356
357       if (current_header->header.typeflag == CHRTYPE
358           ? !S_ISCHR (stat_data.st_mode)
359           : current_header->header.typeflag == BLKTYPE
360           ? !S_ISBLK (stat_data.st_mode)
361           : /* current_header->header.typeflag == FIFOTYPE */
362           !S_ISFIFO (stat_data.st_mode))
363         {
364           report_difference (&current_stat_info, _("File type differs"));
365           break;
366         }
367
368       if ((current_header->header.typeflag == CHRTYPE
369            || current_header->header.typeflag == BLKTYPE)
370           && current_stat_info.stat.st_rdev != stat_data.st_rdev)
371         {
372           report_difference (&current_stat_info, _("Device number differs"));
373           break;
374         }
375
376       if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
377         {
378           report_difference (&current_stat_info, _("Mode differs"));
379           break;
380         }
381
382       break;
383
384     case GNUTYPE_DUMPDIR:
385       {
386         char *dumpdir_buffer = get_directory_contents (current_stat_info.file_name, 0);
387
388         if (multi_volume_option)
389           {
390             assign_string (&save_name, current_stat_info.file_name);
391             save_totsize = current_stat_info.stat.st_size;
392             /* save_sizeleft is set in read_and_process.  */
393           }
394
395         if (dumpdir_buffer)
396           {
397             dumpdir_cursor = dumpdir_buffer;
398             read_and_process (current_stat_info.stat.st_size, process_dumpdir);
399             free (dumpdir_buffer);
400           }
401         else
402           read_and_process (current_stat_info.stat.st_size, process_noop);
403
404         if (multi_volume_option)
405           assign_string (&save_name, 0);
406         /* Fall through.  */
407       }
408
409     case DIRTYPE:
410     really_dir:
411       if (!get_stat_data (current_stat_info.file_name, &stat_data))
412         break;
413
414       if (!S_ISDIR (stat_data.st_mode))
415         {
416           report_difference (&current_stat_info, _("File type differs"));
417           break;
418         }
419
420       if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
421         {
422           report_difference (&current_stat_info, _("Mode differs"));
423           break;
424         }
425
426       break;
427
428     case GNUTYPE_VOLHDR:
429       break;
430
431     case GNUTYPE_MULTIVOL:
432       {
433         off_t offset;
434
435         if (current_stat_info.had_trailing_slash)
436           goto really_dir;
437
438         if (!get_stat_data (current_stat_info.file_name, &stat_data))
439           break;
440
441         if (!S_ISREG (stat_data.st_mode))
442           {
443             report_difference (&current_stat_info, _("File type differs"));
444             skip_member ();
445             break;
446           }
447
448         offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
449         if (stat_data.st_size != current_stat_info.stat.st_size + offset)
450           {
451             report_difference (&current_stat_info, _("Size differs"));
452             skip_member ();
453             break;
454           }
455
456         diff_handle = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
457
458         if (diff_handle < 0)
459           {
460             open_error (current_stat_info.file_name);
461             report_difference (&current_stat_info, NULL);
462             skip_member ();
463             break;
464           }
465
466         if (lseek (diff_handle, offset, SEEK_SET) < 0)
467           {
468             seek_error_details (current_stat_info.file_name, offset);
469             report_difference (&current_stat_info, NULL);
470             break;
471           }
472
473         if (multi_volume_option)
474           {
475             assign_string (&save_name, current_stat_info.file_name);
476             save_totsize = stat_data.st_size;
477             /* save_sizeleft is set in read_and_process.  */
478           }
479
480         read_and_process (current_stat_info.stat.st_size, process_rawdata);
481
482         if (multi_volume_option)
483           assign_string (&save_name, 0);
484
485         status = close (diff_handle);
486         if (status != 0)
487           close_error (current_stat_info.file_name);
488
489         break;
490       }
491     }
492 }
493
494 void
495 verify_volume (void)
496 {
497   if (!diff_buffer)
498     diff_init ();
499
500   /* Verifying an archive is meant to check if the physical media got it
501      correctly, so try to defeat clever in-memory buffering pertaining to
502      this particular media.  On Linux, for example, the floppy drive would
503      not even be accessed for the whole verification.
504
505      The code was using fsync only when the ioctl is unavailable, but
506      Marty Leisner says that the ioctl does not work when not preceded by
507      fsync.  So, until we know better, or maybe to please Marty, let's do it
508      the unbelievable way :-).  */
509
510 #if HAVE_FSYNC
511   fsync (archive);
512 #endif
513 #ifdef FDFLUSH
514   ioctl (archive, FDFLUSH);
515 #endif
516
517 #ifdef MTIOCTOP
518   {
519     struct mtop operation;
520     int status;
521
522     operation.mt_op = MTBSF;
523     operation.mt_count = 1;
524     if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
525       {
526         if (errno != EIO
527             || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
528                 status < 0))
529           {
530 #endif
531             if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
532               {
533                 /* Lseek failed.  Try a different method.  */
534                 seek_warn (archive_name_array[0]);
535                 return;
536               }
537 #ifdef MTIOCTOP
538           }
539       }
540   }
541 #endif
542
543   access_mode = ACCESS_READ;
544   now_verifying = 1;
545
546   flush_read ();
547   while (1)
548     {
549       enum read_header status = read_header (false);
550
551       if (status == HEADER_FAILURE)
552         {
553           int counter = 0;
554
555           do
556             {
557               counter++;
558               status = read_header (false);
559             }
560           while (status == HEADER_FAILURE);
561
562           ERROR ((0, 0,
563                   ngettext ("VERIFY FAILURE: %d invalid header detected",
564                             "VERIFY FAILURE: %d invalid headers detected",
565                             counter), counter));
566         }
567       if (status == HEADER_ZERO_BLOCK || status == HEADER_END_OF_FILE)
568         break;
569
570       diff_archive ();
571     }
572
573   access_mode = ACCESS_WRITE;
574   now_verifying = 0;
575 }