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