1/* Diff files from a tar archive. 2 3 Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001, 4 2003, 2004, 2005, 2006, 2007 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 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ 21 22#include <system.h> 23#include <system-ioctl.h> 24 25#if HAVE_LINUX_FD_H 26# include <linux/fd.h> 27#endif 28 29#include "common.h" 30#include <quotearg.h> 31#include <rmt.h> 32#include <stdarg.h> 33 34/* Nonzero if we are verifying at the moment. */ 35bool now_verifying; 36 37/* File descriptor for the file we are diffing. */ 38static int diff_handle; 39 40/* Area for reading file contents into. */ 41static char *diff_buffer; 42 43/* Initialize for a diff operation. */ 44void 45diff_init (void) 46{ 47 void *ptr; 48 diff_buffer = page_aligned_alloc (&ptr, record_size); 49 if (listed_incremental_option) 50 read_directory_file (); 51} 52 53/* Sigh about something that differs by writing a MESSAGE to stdlis, 54 given MESSAGE is nonzero. Also set the exit status if not already. */ 55void 56report_difference (struct tar_stat_info *st, const char *fmt, ...) 57{ 58 if (fmt) 59 { 60 va_list ap; 61 62 fprintf (stdlis, "%s: ", quotearg_colon (st->file_name)); 63 va_start (ap, fmt); 64 vfprintf (stdlis, fmt, ap); 65 va_end (ap); 66 fprintf (stdlis, "\n"); 67 } 68 69 if (exit_status == TAREXIT_SUCCESS) 70 exit_status = TAREXIT_DIFFERS; 71} 72 73/* Take a buffer returned by read_and_process and do nothing with it. */ 74static int 75process_noop (size_t size __attribute__ ((unused)), 76 char *data __attribute__ ((unused))) 77{ 78 return 1; 79} 80 81static int 82process_rawdata (size_t bytes, char *buffer) 83{ 84 size_t status = safe_read (diff_handle, diff_buffer, bytes); 85 86 if (status != bytes) 87 { 88 if (status == SAFE_READ_ERROR) 89 { 90 read_error (current_stat_info.file_name); 91 report_difference (¤t_stat_info, NULL); 92 } 93 else 94 { 95 report_difference (¤t_stat_info, 96 ngettext ("Could only read %lu of %lu byte", 97 "Could only read %lu of %lu bytes", 98 bytes), 99 (unsigned long) status, (unsigned long) bytes); 100 } 101 return 0; 102 } 103 104 if (memcmp (buffer, diff_buffer, bytes)) 105 { 106 report_difference (¤t_stat_info, _("Contents differ")); 107 return 0; 108 } 109 110 return 1; 111} 112 113/* Some other routine wants SIZE bytes in the archive. For each chunk 114 of the archive, call PROCESSOR with the size of the chunk, and the 115 address of the chunk it can work with. The PROCESSOR should return 116 nonzero for success. Once it returns error, continue skipping 117 without calling PROCESSOR anymore. */ 118 119static void 120read_and_process (struct tar_stat_info *st, int (*processor) (size_t, char *)) 121{ 122 union block *data_block; 123 size_t data_size; 124 off_t size = st->stat.st_size; 125 126 mv_begin (st); 127 while (size) 128 { 129 data_block = find_next_block (); 130 if (! data_block) 131 { 132 ERROR ((0, 0, _("Unexpected EOF in archive"))); 133 return; 134 } 135 136 data_size = available_space_after (data_block); 137 if (data_size > size) 138 data_size = size; 139 if (!(*processor) (data_size, data_block->buffer)) 140 processor = process_noop; 141 set_next_block_after ((union block *) 142 (data_block->buffer + data_size - 1)); 143 size -= data_size; 144 mv_size_left (size); 145 } 146 mv_end (); 147} 148 149/* Call either stat or lstat over STAT_DATA, depending on 150 --dereference (-h), for a file which should exist. Diagnose any 151 problem. Return nonzero for success, zero otherwise. */ 152static int 153get_stat_data (char const *file_name, struct stat *stat_data) 154{ 155 int status = deref_stat (dereference_option, file_name, stat_data); 156 157 if (status != 0) 158 { 159 if (errno == ENOENT) 160 stat_warn (file_name); 161 else 162 stat_error (file_name); 163 report_difference (¤t_stat_info, NULL); 164 return 0; 165 } 166 167 return 1; 168} 169 170 171static void 172diff_dir (void) 173{ 174 struct stat stat_data; 175 176 if (!get_stat_data (current_stat_info.file_name, &stat_data)) 177 return; 178 179 if (!S_ISDIR (stat_data.st_mode)) 180 report_difference (¤t_stat_info, _("File type differs")); 181 else if ((current_stat_info.stat.st_mode & MODE_ALL) != 182 (stat_data.st_mode & MODE_ALL)) 183 report_difference (¤t_stat_info, _("Mode differs")); 184} 185 186static void 187diff_file (void) 188{ 189 char const *file_name = current_stat_info.file_name; 190 struct stat stat_data; 191 192 if (!get_stat_data (file_name, &stat_data)) 193 skip_member (); 194 else if (!S_ISREG (stat_data.st_mode)) 195 { 196 report_difference (¤t_stat_info, _("File type differs")); 197 skip_member (); 198 } 199 else 200 { 201 if ((current_stat_info.stat.st_mode & MODE_ALL) != 202 (stat_data.st_mode & MODE_ALL)) 203 report_difference (¤t_stat_info, _("Mode differs")); 204 205 if (!sys_compare_uid (&stat_data, ¤t_stat_info.stat)) 206 report_difference (¤t_stat_info, _("Uid differs")); 207 if (!sys_compare_gid (&stat_data, ¤t_stat_info.stat)) 208 report_difference (¤t_stat_info, _("Gid differs")); 209 210 if (tar_timespec_cmp (get_stat_mtime (&stat_data), 211 current_stat_info.mtime)) 212 report_difference (¤t_stat_info, _("Mod time differs")); 213 if (current_header->header.typeflag != GNUTYPE_SPARSE 214 && stat_data.st_size != current_stat_info.stat.st_size) 215 { 216 report_difference (¤t_stat_info, _("Size differs")); 217 skip_member (); 218 } 219 else 220 { 221 int atime_flag = 222 (atime_preserve_option == system_atime_preserve 223 ? O_NOATIME 224 : 0); 225 226 diff_handle = open (file_name, O_RDONLY | O_BINARY | atime_flag); 227 228 if (diff_handle < 0) 229 { 230 open_error (file_name); 231 skip_member (); 232 report_difference (¤t_stat_info, NULL); 233 } 234 else 235 { 236 int status; 237 238 if (current_stat_info.is_sparse) 239 sparse_diff_file (diff_handle, ¤t_stat_info); 240 else 241 read_and_process (¤t_stat_info, process_rawdata); 242 243 if (atime_preserve_option == replace_atime_preserve) 244 { 245 struct timespec ts[2]; 246 ts[0] = get_stat_atime (&stat_data); 247 ts[1] = get_stat_mtime (&stat_data); 248 if (set_file_atime (diff_handle, file_name, ts) != 0) 249 utime_error (file_name); 250 } 251 252 status = close (diff_handle); 253 if (status != 0) 254 close_error (file_name); 255 } 256 } 257 } 258} 259 260static void 261diff_link (void) 262{ 263 struct stat file_data; 264 struct stat link_data; 265 266 if (get_stat_data (current_stat_info.file_name, &file_data) 267 && get_stat_data (current_stat_info.link_name, &link_data) 268 && !sys_compare_links (&file_data, &link_data)) 269 report_difference (¤t_stat_info, 270 _("Not linked to %s"), 271 quote (current_stat_info.link_name)); 272} 273 274#ifdef HAVE_READLINK 275static void 276diff_symlink (void) 277{ 278 size_t len = strlen (current_stat_info.link_name); 279 char *linkbuf = alloca (len + 1); 280 281 int status = readlink (current_stat_info.file_name, linkbuf, len + 1); 282 283 if (status < 0) 284 { 285 if (errno == ENOENT) 286 readlink_warn (current_stat_info.file_name); 287 else 288 readlink_error (current_stat_info.file_name); 289 report_difference (¤t_stat_info, NULL); 290 } 291 else if (status != len 292 || strncmp (current_stat_info.link_name, linkbuf, len) != 0) 293 report_difference (¤t_stat_info, _("Symlink differs")); 294} 295#endif 296 297static void 298diff_special (void) 299{ 300 struct stat stat_data; 301 302 /* FIXME: deal with umask. */ 303 304 if (!get_stat_data (current_stat_info.file_name, &stat_data)) 305 return; 306 307 if (current_header->header.typeflag == CHRTYPE 308 ? !S_ISCHR (stat_data.st_mode) 309 : current_header->header.typeflag == BLKTYPE 310 ? !S_ISBLK (stat_data.st_mode) 311 : /* current_header->header.typeflag == FIFOTYPE */ 312 !S_ISFIFO (stat_data.st_mode)) 313 { 314 report_difference (¤t_stat_info, _("File type differs")); 315 return; 316 } 317 318 if ((current_header->header.typeflag == CHRTYPE 319 || current_header->header.typeflag == BLKTYPE) 320 && current_stat_info.stat.st_rdev != stat_data.st_rdev) 321 { 322 report_difference (¤t_stat_info, _("Device number differs")); 323 return; 324 } 325 326 if ((current_stat_info.stat.st_mode & MODE_ALL) != 327 (stat_data.st_mode & MODE_ALL)) 328 report_difference (¤t_stat_info, _("Mode differs")); 329} 330 331static int 332dumpdir_cmp (const char *a, const char *b) 333{ 334 size_t len; 335 336 while (*a) 337 switch (*a) 338 { 339 case 'Y': 340 case 'N': 341 if (!strchr ("YN", *b)) 342 return 1; 343 if (strcmp(a + 1, b + 1)) 344 return 1; 345 len = strlen (a) + 1; 346 a += len; 347 b += len; 348 break; 349 350 case 'D': 351 if (strcmp(a, b)) 352 return 1; 353 len = strlen (a) + 1; 354 a += len; 355 b += len; 356 break; 357 358 case 'R': 359 case 'T': 360 case 'X': 361 return *b; 362 } 363 return *b; 364} 365 366static void 367diff_dumpdir (void) 368{ 369 char *dumpdir_buffer; 370 dev_t dev = 0; 371 struct stat stat; 372 373 if (deref_stat (true, current_stat_info.file_name, &stat)) 374 { 375 if (errno == ENOENT) 376 stat_warn (current_stat_info.file_name); 377 else 378 stat_error (current_stat_info.file_name); 379 } 380 else 381 dev = stat.st_dev; 382 383 dumpdir_buffer = get_directory_contents (current_stat_info.file_name, dev); 384 385 if (dumpdir_buffer) 386 { 387 if (dumpdir_cmp (current_stat_info.dumpdir, dumpdir_buffer)) 388 report_difference (¤t_stat_info, _("Contents differ")); 389 } 390 else 391 read_and_process (¤t_stat_info, process_noop); 392} 393 394static void 395diff_multivol (void) 396{ 397 struct stat stat_data; 398 int fd, status; 399 off_t offset; 400 401 if (current_stat_info.had_trailing_slash) 402 { 403 diff_dir (); 404 return; 405 } 406 407 if (!get_stat_data (current_stat_info.file_name, &stat_data)) 408 return; 409 410 if (!S_ISREG (stat_data.st_mode)) 411 { 412 report_difference (¤t_stat_info, _("File type differs")); 413 skip_member (); 414 return; 415 } 416 417 offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset); 418 if (stat_data.st_size != current_stat_info.stat.st_size + offset) 419 { 420 report_difference (¤t_stat_info, _("Size differs")); 421 skip_member (); 422 return; 423 } 424 425 fd = open (current_stat_info.file_name, O_RDONLY | O_BINARY); 426 427 if (fd < 0) 428 { 429 open_error (current_stat_info.file_name); 430 report_difference (¤t_stat_info, NULL); 431 skip_member (); 432 return; 433 } 434 435 if (lseek (fd, offset, SEEK_SET) < 0) 436 { 437 seek_error_details (current_stat_info.file_name, offset); 438 report_difference (¤t_stat_info, NULL); 439 return; 440 } 441 442 read_and_process (¤t_stat_info, process_rawdata); 443 444 status = close (fd); 445 if (status != 0) 446 close_error (current_stat_info.file_name); 447} 448 449/* Diff a file against the archive. */ 450void 451diff_archive (void) 452{ 453 454 set_next_block_after (current_header); 455 decode_header (current_header, ¤t_stat_info, ¤t_format, 1); 456 457 /* Print the block from current_header and current_stat_info. */ 458 459 if (verbose_option) 460 { 461 if (now_verifying) 462 fprintf (stdlis, _("Verify ")); 463 print_header (¤t_stat_info, -1); 464 } 465 466 switch (current_header->header.typeflag) 467 { 468 default: 469 ERROR ((0, 0, _("%s: Unknown file type `%c', diffed as normal file"), 470 quotearg_colon (current_stat_info.file_name), 471 current_header->header.typeflag)); 472 /* Fall through. */ 473 474 case AREGTYPE: 475 case REGTYPE: 476 case GNUTYPE_SPARSE: 477 case CONTTYPE: 478 479 /* Appears to be a file. See if it's really a directory. */ 480 481 if (current_stat_info.had_trailing_slash) 482 diff_dir (); 483 else 484 diff_file (); 485 break; 486 487 case LNKTYPE: 488 diff_link (); 489 break; 490 491#ifdef HAVE_READLINK 492 case SYMTYPE: 493 diff_symlink (); 494 break; 495#endif 496 497 case CHRTYPE: 498 case BLKTYPE: 499 case FIFOTYPE: 500 diff_special (); 501 break; 502 503 case GNUTYPE_DUMPDIR: 504 case DIRTYPE: 505 if (is_dumpdir (¤t_stat_info)) 506 diff_dumpdir (); 507 diff_dir (); 508 break; 509 510 case GNUTYPE_VOLHDR: 511 break; 512 513 case GNUTYPE_MULTIVOL: 514 diff_multivol (); 515 } 516} 517 518void 519verify_volume (void) 520{ 521 if (removed_prefixes_p ()) 522 { 523 WARN((0, 0, 524 _("Archive contains file names with leading prefixes removed."))); 525 WARN((0, 0, 526 _("Verification may fail to locate original files."))); 527 } 528 529 if (!diff_buffer) 530 diff_init (); 531 532 /* Verifying an archive is meant to check if the physical media got it 533 correctly, so try to defeat clever in-memory buffering pertaining to 534 this particular media. On Linux, for example, the floppy drive would 535 not even be accessed for the whole verification. 536 537 The code was using fsync only when the ioctl is unavailable, but 538 Marty Leisner says that the ioctl does not work when not preceded by 539 fsync. So, until we know better, or maybe to please Marty, let's do it 540 the unbelievable way :-). */ 541 542#if HAVE_FSYNC 543 fsync (archive); 544#endif 545#ifdef FDFLUSH 546 ioctl (archive, FDFLUSH); 547#endif 548 549#ifdef MTIOCTOP 550 { 551 struct mtop operation; 552 int status; 553 554 operation.mt_op = MTBSF; 555 operation.mt_count = 1; 556 if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0) 557 { 558 if (errno != EIO 559 || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), 560 status < 0)) 561 { 562#endif 563 if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0) 564 { 565 /* Lseek failed. Try a different method. */ 566 seek_warn (archive_name_array[0]); 567 return; 568 } 569#ifdef MTIOCTOP 570 } 571 } 572 } 573#endif 574 575 access_mode = ACCESS_READ; 576 now_verifying = 1; 577 578 flush_read (); 579 while (1) 580 { 581 enum read_header status = read_header (false); 582 583 if (status == HEADER_FAILURE) 584 { 585 int counter = 0; 586 587 do 588 { 589 counter++; 590 set_next_block_after (current_header); 591 status = read_header (false); 592 } 593 while (status == HEADER_FAILURE); 594 595 ERROR ((0, 0, 596 ngettext ("VERIFY FAILURE: %d invalid header detected", 597 "VERIFY FAILURE: %d invalid headers detected", 598 counter), counter)); 599 } 600 if (status == HEADER_ZERO_BLOCK || status == HEADER_END_OF_FILE) 601 break; 602 603 diff_archive (); 604 tar_stat_destroy (¤t_stat_info); 605 } 606 607 access_mode = ACCESS_WRITE; 608 now_verifying = 0; 609} 610