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