Optimize searches for directory structures by keeping a pointer to struct directory...
[tar.git] / src / compare.c
blob57732cccaa7fdadf51c872019252002ace7457e7
1 /* Diff files from a tar archive.
3 Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
4 2003, 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc.
6 Written by John Gilmore, on 1987-04-30.
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 3, or (at your option) any later
11 version.
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.
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. */
22 #include <system.h>
23 #include <system-ioctl.h>
25 #if HAVE_LINUX_FD_H
26 # include <linux/fd.h>
27 #endif
29 #include "common.h"
30 #include <quotearg.h>
31 #include <rmt.h>
32 #include <stdarg.h>
34 /* Nonzero if we are verifying at the moment. */
35 bool now_verifying;
37 /* File descriptor for the file we are diffing. */
38 static int diff_handle;
40 /* Area for reading file contents into. */
41 static char *diff_buffer;
43 /* Initialize for a diff operation. */
44 void
45 diff_init (void)
47 void *ptr;
48 diff_buffer = page_aligned_alloc (&ptr, record_size);
49 if (listed_incremental_option)
50 read_directory_file ();
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. */
55 void
56 report_difference (struct tar_stat_info *st, const char *fmt, ...)
58 if (fmt)
60 va_list ap;
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");
69 if (exit_status == TAREXIT_SUCCESS)
70 exit_status = TAREXIT_DIFFERS;
73 /* Take a buffer returned by read_and_process and do nothing with it. */
74 static int
75 process_noop (size_t size __attribute__ ((unused)),
76 char *data __attribute__ ((unused)))
78 return 1;
81 static int
82 process_rawdata (size_t bytes, char *buffer)
84 size_t status = safe_read (diff_handle, diff_buffer, bytes);
86 if (status != bytes)
88 if (status == SAFE_READ_ERROR)
90 read_error (current_stat_info.file_name);
91 report_difference (&current_stat_info, NULL);
93 else
95 report_difference (&current_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);
101 return 0;
104 if (memcmp (buffer, diff_buffer, bytes))
106 report_difference (&current_stat_info, _("Contents differ"));
107 return 0;
110 return 1;
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. */
119 static void
120 read_and_process (struct tar_stat_info *st, int (*processor) (size_t, char *))
122 union block *data_block;
123 size_t data_size;
124 off_t size = st->stat.st_size;
126 mv_begin (st);
127 while (size)
129 data_block = find_next_block ();
130 if (! data_block)
132 ERROR ((0, 0, _("Unexpected EOF in archive")));
133 return;
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);
146 mv_end ();
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. */
152 static int
153 get_stat_data (char const *file_name, struct stat *stat_data)
155 int status = deref_stat (dereference_option, file_name, stat_data);
157 if (status != 0)
159 if (errno == ENOENT)
160 stat_warn (file_name);
161 else
162 stat_error (file_name);
163 report_difference (&current_stat_info, NULL);
164 return 0;
167 return 1;
171 static void
172 diff_dir (void)
174 struct stat stat_data;
176 if (!get_stat_data (current_stat_info.file_name, &stat_data))
177 return;
179 if (!S_ISDIR (stat_data.st_mode))
180 report_difference (&current_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 (&current_stat_info, _("Mode differs"));
186 static void
187 diff_file (void)
189 char const *file_name = current_stat_info.file_name;
190 struct stat stat_data;
192 if (!get_stat_data (file_name, &stat_data))
193 skip_member ();
194 else if (!S_ISREG (stat_data.st_mode))
196 report_difference (&current_stat_info, _("File type differs"));
197 skip_member ();
199 else
201 if ((current_stat_info.stat.st_mode & MODE_ALL) !=
202 (stat_data.st_mode & MODE_ALL))
203 report_difference (&current_stat_info, _("Mode differs"));
205 if (!sys_compare_uid (&stat_data, &current_stat_info.stat))
206 report_difference (&current_stat_info, _("Uid differs"));
207 if (!sys_compare_gid (&stat_data, &current_stat_info.stat))
208 report_difference (&current_stat_info, _("Gid differs"));
210 if (tar_timespec_cmp (get_stat_mtime (&stat_data),
211 current_stat_info.mtime))
212 report_difference (&current_stat_info, _("Mod time differs"));
213 if (current_header->header.typeflag != GNUTYPE_SPARSE
214 && stat_data.st_size != current_stat_info.stat.st_size)
216 report_difference (&current_stat_info, _("Size differs"));
217 skip_member ();
219 else
221 int atime_flag =
222 (atime_preserve_option == system_atime_preserve
223 ? O_NOATIME
224 : 0);
226 diff_handle = open (file_name, O_RDONLY | O_BINARY | atime_flag);
228 if (diff_handle < 0)
230 open_error (file_name);
231 skip_member ();
232 report_difference (&current_stat_info, NULL);
234 else
236 int status;
238 if (current_stat_info.is_sparse)
239 sparse_diff_file (diff_handle, &current_stat_info);
240 else
241 read_and_process (&current_stat_info, process_rawdata);
243 if (atime_preserve_option == replace_atime_preserve)
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);
252 status = close (diff_handle);
253 if (status != 0)
254 close_error (file_name);
260 static void
261 diff_link (void)
263 struct stat file_data;
264 struct stat link_data;
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 (&current_stat_info,
270 _("Not linked to %s"),
271 quote (current_stat_info.link_name));
274 #ifdef HAVE_READLINK
275 static void
276 diff_symlink (void)
278 size_t len = strlen (current_stat_info.link_name);
279 char *linkbuf = alloca (len + 1);
281 int status = readlink (current_stat_info.file_name, linkbuf, len + 1);
283 if (status < 0)
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 (&current_stat_info, NULL);
291 else if (status != len
292 || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
293 report_difference (&current_stat_info, _("Symlink differs"));
295 #endif
297 static void
298 diff_special (void)
300 struct stat stat_data;
302 /* FIXME: deal with umask. */
304 if (!get_stat_data (current_stat_info.file_name, &stat_data))
305 return;
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))
314 report_difference (&current_stat_info, _("File type differs"));
315 return;
318 if ((current_header->header.typeflag == CHRTYPE
319 || current_header->header.typeflag == BLKTYPE)
320 && current_stat_info.stat.st_rdev != stat_data.st_rdev)
322 report_difference (&current_stat_info, _("Device number differs"));
323 return;
326 if ((current_stat_info.stat.st_mode & MODE_ALL) !=
327 (stat_data.st_mode & MODE_ALL))
328 report_difference (&current_stat_info, _("Mode differs"));
331 static int
332 dumpdir_cmp (const char *a, const char *b)
334 size_t len;
336 while (*a)
337 switch (*a)
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;
350 case 'D':
351 if (strcmp(a, b))
352 return 1;
353 len = strlen (a) + 1;
354 a += len;
355 b += len;
356 break;
358 case 'R':
359 case 'T':
360 case 'X':
361 return *b;
363 return *b;
366 static void
367 diff_dumpdir (void)
369 const char *dumpdir_buffer;
370 dev_t dev = 0;
371 struct stat stat_data;
373 if (deref_stat (true, current_stat_info.file_name, &stat_data))
375 if (errno == ENOENT)
376 stat_warn (current_stat_info.file_name);
377 else
378 stat_error (current_stat_info.file_name);
380 else
381 dev = stat_data.st_dev;
383 dumpdir_buffer = directory_contents
384 (scan_directory (current_stat_info.file_name, dev, false));
386 if (dumpdir_buffer)
388 if (dumpdir_cmp (current_stat_info.dumpdir, dumpdir_buffer))
389 report_difference (&current_stat_info, _("Contents differ"));
391 else
392 read_and_process (&current_stat_info, process_noop);
395 static void
396 diff_multivol (void)
398 struct stat stat_data;
399 int fd, status;
400 off_t offset;
402 if (current_stat_info.had_trailing_slash)
404 diff_dir ();
405 return;
408 if (!get_stat_data (current_stat_info.file_name, &stat_data))
409 return;
411 if (!S_ISREG (stat_data.st_mode))
413 report_difference (&current_stat_info, _("File type differs"));
414 skip_member ();
415 return;
418 offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
419 if (stat_data.st_size != current_stat_info.stat.st_size + offset)
421 report_difference (&current_stat_info, _("Size differs"));
422 skip_member ();
423 return;
426 fd = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
428 if (fd < 0)
430 open_error (current_stat_info.file_name);
431 report_difference (&current_stat_info, NULL);
432 skip_member ();
433 return;
436 if (lseek (fd, offset, SEEK_SET) < 0)
438 seek_error_details (current_stat_info.file_name, offset);
439 report_difference (&current_stat_info, NULL);
440 return;
443 read_and_process (&current_stat_info, process_rawdata);
445 status = close (fd);
446 if (status != 0)
447 close_error (current_stat_info.file_name);
450 /* Diff a file against the archive. */
451 void
452 diff_archive (void)
455 set_next_block_after (current_header);
456 decode_header (current_header, &current_stat_info, &current_format, 1);
458 /* Print the block from current_header and current_stat_info. */
460 if (verbose_option)
462 if (now_verifying)
463 fprintf (stdlis, _("Verify "));
464 print_header (&current_stat_info, -1);
467 switch (current_header->header.typeflag)
469 default:
470 ERROR ((0, 0, _("%s: Unknown file type `%c', diffed as normal file"),
471 quotearg_colon (current_stat_info.file_name),
472 current_header->header.typeflag));
473 /* Fall through. */
475 case AREGTYPE:
476 case REGTYPE:
477 case GNUTYPE_SPARSE:
478 case CONTTYPE:
480 /* Appears to be a file. See if it's really a directory. */
482 if (current_stat_info.had_trailing_slash)
483 diff_dir ();
484 else
485 diff_file ();
486 break;
488 case LNKTYPE:
489 diff_link ();
490 break;
492 #ifdef HAVE_READLINK
493 case SYMTYPE:
494 diff_symlink ();
495 break;
496 #endif
498 case CHRTYPE:
499 case BLKTYPE:
500 case FIFOTYPE:
501 diff_special ();
502 break;
504 case GNUTYPE_DUMPDIR:
505 case DIRTYPE:
506 if (is_dumpdir (&current_stat_info))
507 diff_dumpdir ();
508 diff_dir ();
509 break;
511 case GNUTYPE_VOLHDR:
512 break;
514 case GNUTYPE_MULTIVOL:
515 diff_multivol ();
519 void
520 verify_volume (void)
522 if (removed_prefixes_p ())
524 WARN((0, 0,
525 _("Archive contains file names with leading prefixes removed.")));
526 WARN((0, 0,
527 _("Verification may fail to locate original files.")));
530 if (!diff_buffer)
531 diff_init ();
533 /* Verifying an archive is meant to check if the physical media got it
534 correctly, so try to defeat clever in-memory buffering pertaining to
535 this particular media. On Linux, for example, the floppy drive would
536 not even be accessed for the whole verification.
538 The code was using fsync only when the ioctl is unavailable, but
539 Marty Leisner says that the ioctl does not work when not preceded by
540 fsync. So, until we know better, or maybe to please Marty, let's do it
541 the unbelievable way :-). */
543 #if HAVE_FSYNC
544 fsync (archive);
545 #endif
546 #ifdef FDFLUSH
547 ioctl (archive, FDFLUSH);
548 #endif
550 #ifdef MTIOCTOP
552 struct mtop operation;
553 int status;
555 operation.mt_op = MTBSF;
556 operation.mt_count = 1;
557 if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
559 if (errno != EIO
560 || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
561 status < 0))
563 #endif
564 if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
566 /* Lseek failed. Try a different method. */
567 seek_warn (archive_name_array[0]);
568 return;
570 #ifdef MTIOCTOP
574 #endif
576 access_mode = ACCESS_READ;
577 now_verifying = 1;
579 flush_read ();
580 while (1)
582 enum read_header status = read_header (false);
584 if (status == HEADER_FAILURE)
586 int counter = 0;
590 counter++;
591 set_next_block_after (current_header);
592 status = read_header (false);
594 while (status == HEADER_FAILURE);
596 ERROR ((0, 0,
597 ngettext ("VERIFY FAILURE: %d invalid header detected",
598 "VERIFY FAILURE: %d invalid headers detected",
599 counter), counter));
601 if (status == HEADER_END_OF_FILE)
602 break;
603 if (status == HEADER_ZERO_BLOCK)
605 set_next_block_after (current_header);
606 if (!ignore_zeros_option)
608 char buf[UINTMAX_STRSIZE_BOUND];
610 status = read_header (false);
611 if (status == HEADER_ZERO_BLOCK)
612 break;
613 WARNOPT (WARN_ALONE_ZERO_BLOCK,
614 (0, 0, _("A lone zero block at %s"),
615 STRINGIFY_BIGINT (current_block_ordinal (), buf)));
619 diff_archive ();
620 tar_stat_destroy (&current_stat_info);
623 access_mode = ACCESS_WRITE;
624 now_verifying = 0;