(diff_sparse_files): Temporary placeholder.
[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 static void
175 diff_sparse_files (void)
176 {
177   /*FIXME!!*/abort();
178 }
179
180 /* Call either stat or lstat over STAT_DATA, depending on
181    --dereference (-h), for a file which should exist.  Diagnose any
182    problem.  Return nonzero for success, zero otherwise.  */
183 static int
184 get_stat_data (char const *file_name, struct stat *stat_data)
185 {
186   int status = deref_stat (dereference_option, file_name, stat_data);
187
188   if (status != 0)
189     {
190       if (errno == ENOENT)
191         stat_warn (file_name);
192       else
193         stat_error (file_name);
194       report_difference (NULL);
195       return 0;
196     }
197
198   return 1;
199 }
200
201 /* Diff a file against the archive.  */
202 void
203 diff_archive (void)
204 {
205   struct stat stat_data;
206   int status;
207   struct utimbuf restore_times;
208
209   set_next_block_after (current_header);
210   decode_header (current_header, &current_stat_info, &current_format, 1);
211
212   /* Print the block from current_header and current_stat_info.  */
213
214   if (verbose_option)
215     {
216       if (now_verifying)
217         fprintf (stdlis, _("Verify "));
218       print_header (&current_stat_info, -1);
219     }
220
221   switch (current_header->header.typeflag)
222     {
223     default:
224       ERROR ((0, 0, _("%s: Unknown file type '%c', diffed as normal file"),
225               quotearg_colon (current_stat_info.file_name),
226               current_header->header.typeflag));
227       /* Fall through.  */
228
229     case AREGTYPE:
230     case REGTYPE:
231     case GNUTYPE_SPARSE:
232     case CONTTYPE:
233
234       /* Appears to be a file.  See if it's really a directory.  */
235
236       if (current_stat_info.had_trailing_slash)
237         goto really_dir;
238
239       if (!get_stat_data (current_stat_info.file_name, &stat_data))
240         {
241           skip_member ();
242           goto quit;
243         }
244
245       if (!S_ISREG (stat_data.st_mode))
246         {
247           report_difference (_("File type differs"));
248           skip_member ();
249           goto quit;
250         }
251
252       if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
253         report_difference (_("Mode differs"));
254
255       sys_compare_uid_gid (&stat_data, &current_stat_info.stat);
256
257       if (stat_data.st_mtime != current_stat_info.stat.st_mtime)
258         report_difference (_("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 (_("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 (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         diff_sparse_files ();
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         sys_compare_links (&stat_data, &link_data);
318       }
319       break;
320       
321 #ifdef HAVE_READLINK
322     case SYMTYPE:
323       {
324         size_t len = strlen (current_stat_info.link_name);
325         char *linkbuf = alloca (len + 1);
326
327         status = readlink (current_stat_info.file_name, linkbuf, len + 1);
328
329         if (status < 0)
330           {
331             if (errno == ENOENT)
332               readlink_warn (current_stat_info.file_name);
333             else
334               readlink_error (current_stat_info.file_name);
335             report_difference (NULL);
336           }
337         else if (status != len
338                  || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
339           report_difference (_("Symlink differs"));
340
341         break;
342       }
343 #endif
344
345     case CHRTYPE:
346     case BLKTYPE:
347     case FIFOTYPE:
348
349       /* FIXME: deal with umask.  */
350
351       if (!get_stat_data (current_stat_info.file_name, &stat_data))
352         break;
353
354       if (current_header->header.typeflag == CHRTYPE
355           ? !S_ISCHR (stat_data.st_mode)
356           : current_header->header.typeflag == BLKTYPE
357           ? !S_ISBLK (stat_data.st_mode)
358           : /* current_header->header.typeflag == FIFOTYPE */
359           !S_ISFIFO (stat_data.st_mode))
360         {
361           report_difference (_("File type differs"));
362           break;
363         }
364
365       if ((current_header->header.typeflag == CHRTYPE
366            || current_header->header.typeflag == BLKTYPE)
367           && current_stat_info.stat.st_rdev != stat_data.st_rdev)
368         {
369           report_difference (_("Device number differs"));
370           break;
371         }
372
373       if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
374         {
375           report_difference (_("Mode differs"));
376           break;
377         }
378
379       break;
380
381     case GNUTYPE_DUMPDIR:
382       {
383         char *dumpdir_buffer = get_directory_contents (current_stat_info.file_name, 0);
384
385         if (multi_volume_option)
386           {
387             assign_string (&save_name, current_stat_info.file_name);
388             save_totsize = current_stat_info.stat.st_size;
389             /* save_sizeleft is set in read_and_process.  */
390           }
391
392         if (dumpdir_buffer)
393           {
394             dumpdir_cursor = dumpdir_buffer;
395             read_and_process (current_stat_info.stat.st_size, process_dumpdir);
396             free (dumpdir_buffer);
397           }
398         else
399           read_and_process (current_stat_info.stat.st_size, process_noop);
400
401         if (multi_volume_option)
402           assign_string (&save_name, 0);
403         /* Fall through.  */
404       }
405
406     case DIRTYPE:
407     really_dir:
408       if (!get_stat_data (current_stat_info.file_name, &stat_data))
409         break;
410
411       if (!S_ISDIR (stat_data.st_mode))
412         {
413           report_difference (_("File type differs"));
414           break;
415         }
416
417       if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
418         {
419           report_difference (_("Mode differs"));
420           break;
421         }
422
423       break;
424
425     case GNUTYPE_VOLHDR:
426       break;
427
428     case GNUTYPE_MULTIVOL:
429       {
430         off_t offset;
431
432         if (current_stat_info.had_trailing_slash)
433           goto really_dir;
434
435         if (!get_stat_data (current_stat_info.file_name, &stat_data))
436           break;
437
438         if (!S_ISREG (stat_data.st_mode))
439           {
440             report_difference (_("File type differs"));
441             skip_member ();
442             break;
443           }
444
445         offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
446         if (stat_data.st_size != current_stat_info.stat.st_size + offset)
447           {
448             report_difference (_("Size differs"));
449             skip_member ();
450             break;
451           }
452
453         diff_handle = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
454
455         if (diff_handle < 0)
456           {
457             open_error (current_stat_info.file_name);
458             report_difference (NULL);
459             skip_member ();
460             break;
461           }
462
463         if (lseek (diff_handle, offset, SEEK_SET) < 0)
464           {
465             seek_error_details (current_stat_info.file_name, offset);
466             report_difference (NULL);
467             break;
468           }
469
470         if (multi_volume_option)
471           {
472             assign_string (&save_name, current_stat_info.file_name);
473             save_totsize = stat_data.st_size;
474             /* save_sizeleft is set in read_and_process.  */
475           }
476
477         read_and_process (current_stat_info.stat.st_size, process_rawdata);
478
479         if (multi_volume_option)
480           assign_string (&save_name, 0);
481
482         status = close (diff_handle);
483         if (status != 0)
484           close_error (current_stat_info.file_name);
485
486         break;
487       }
488     }
489 }
490
491 void
492 verify_volume (void)
493 {
494   if (!diff_buffer)
495     diff_init ();
496
497   /* Verifying an archive is meant to check if the physical media got it
498      correctly, so try to defeat clever in-memory buffering pertaining to
499      this particular media.  On Linux, for example, the floppy drive would
500      not even be accessed for the whole verification.
501
502      The code was using fsync only when the ioctl is unavailable, but
503      Marty Leisner says that the ioctl does not work when not preceded by
504      fsync.  So, until we know better, or maybe to please Marty, let's do it
505      the unbelievable way :-).  */
506
507 #if HAVE_FSYNC
508   fsync (archive);
509 #endif
510 #ifdef FDFLUSH
511   ioctl (archive, FDFLUSH);
512 #endif
513
514 #ifdef MTIOCTOP
515   {
516     struct mtop operation;
517     int status;
518
519     operation.mt_op = MTBSF;
520     operation.mt_count = 1;
521     if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
522       {
523         if (errno != EIO
524             || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
525                 status < 0))
526           {
527 #endif
528             if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
529               {
530                 /* Lseek failed.  Try a different method.  */
531                 seek_warn (archive_name_array[0]);
532                 return;
533               }
534 #ifdef MTIOCTOP
535           }
536       }
537   }
538 #endif
539
540   access_mode = ACCESS_READ;
541   now_verifying = 1;
542
543   flush_read ();
544   while (1)
545     {
546       enum read_header status = read_header (false);
547
548       if (status == HEADER_FAILURE)
549         {
550           int counter = 0;
551
552           do
553             {
554               counter++;
555               status = read_header (false);
556             }
557           while (status == HEADER_FAILURE);
558
559           ERROR ((0, 0,
560                   ngettext ("VERIFY FAILURE: %d invalid header detected",
561                             "VERIFY FAILURE: %d invalid headers detected",
562                             counter), counter));
563         }
564       if (status == HEADER_ZERO_BLOCK || status == HEADER_END_OF_FILE)
565         break;
566
567       diff_archive ();
568     }
569
570   access_mode = ACCESS_WRITE;
571   now_verifying = 0;
572 }