]> Dogcows Code - chaz/tar/blob - src/compare.c
Optimize searches for directory structures by keeping a pointer to struct directory...
[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, 2004, 2005, 2006, 2007, 2009 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 3, 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. */
35 bool now_verifying;
36
37 /* File descriptor for the file we are diffing. */
38 static int diff_handle;
39
40 /* Area for reading file contents into. */
41 static char *diff_buffer;
42
43 /* Initialize for a diff operation. */
44 void
45 diff_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. */
55 void
56 report_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. */
74 static int
75 process_noop (size_t size __attribute__ ((unused)),
76 char *data __attribute__ ((unused)))
77 {
78 return 1;
79 }
80
81 static int
82 process_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 (&current_stat_info, NULL);
92 }
93 else
94 {
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);
100 }
101 return 0;
102 }
103
104 if (memcmp (buffer, diff_buffer, bytes))
105 {
106 report_difference (&current_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
119 static void
120 read_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. */
152 static int
153 get_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 (&current_stat_info, NULL);
164 return 0;
165 }
166
167 return 1;
168 }
169
170 \f
171 static void
172 diff_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 (&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"));
184 }
185
186 static void
187 diff_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 (&current_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 (&current_stat_info, _("Mode differs"));
204
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"));
209
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)
215 {
216 report_difference (&current_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 (&current_stat_info, NULL);
233 }
234 else
235 {
236 int status;
237
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);
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
260 static void
261 diff_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 (&current_stat_info,
270 _("Not linked to %s"),
271 quote (current_stat_info.link_name));
272 }
273
274 #ifdef HAVE_READLINK
275 static void
276 diff_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 (&current_stat_info, NULL);
290 }
291 else if (status != len
292 || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
293 report_difference (&current_stat_info, _("Symlink differs"));
294 }
295 #endif
296
297 static void
298 diff_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 (&current_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 (&current_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 (&current_stat_info, _("Mode differs"));
329 }
330
331 static int
332 dumpdir_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
366 static void
367 diff_dumpdir (void)
368 {
369 const char *dumpdir_buffer;
370 dev_t dev = 0;
371 struct stat stat_data;
372
373 if (deref_stat (true, current_stat_info.file_name, &stat_data))
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_data.st_dev;
382
383 dumpdir_buffer = directory_contents
384 (scan_directory (current_stat_info.file_name, dev, false));
385
386 if (dumpdir_buffer)
387 {
388 if (dumpdir_cmp (current_stat_info.dumpdir, dumpdir_buffer))
389 report_difference (&current_stat_info, _("Contents differ"));
390 }
391 else
392 read_and_process (&current_stat_info, process_noop);
393 }
394
395 static void
396 diff_multivol (void)
397 {
398 struct stat stat_data;
399 int fd, status;
400 off_t offset;
401
402 if (current_stat_info.had_trailing_slash)
403 {
404 diff_dir ();
405 return;
406 }
407
408 if (!get_stat_data (current_stat_info.file_name, &stat_data))
409 return;
410
411 if (!S_ISREG (stat_data.st_mode))
412 {
413 report_difference (&current_stat_info, _("File type differs"));
414 skip_member ();
415 return;
416 }
417
418 offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
419 if (stat_data.st_size != current_stat_info.stat.st_size + offset)
420 {
421 report_difference (&current_stat_info, _("Size differs"));
422 skip_member ();
423 return;
424 }
425
426 fd = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
427
428 if (fd < 0)
429 {
430 open_error (current_stat_info.file_name);
431 report_difference (&current_stat_info, NULL);
432 skip_member ();
433 return;
434 }
435
436 if (lseek (fd, offset, SEEK_SET) < 0)
437 {
438 seek_error_details (current_stat_info.file_name, offset);
439 report_difference (&current_stat_info, NULL);
440 return;
441 }
442
443 read_and_process (&current_stat_info, process_rawdata);
444
445 status = close (fd);
446 if (status != 0)
447 close_error (current_stat_info.file_name);
448 }
449
450 /* Diff a file against the archive. */
451 void
452 diff_archive (void)
453 {
454
455 set_next_block_after (current_header);
456 decode_header (current_header, &current_stat_info, &current_format, 1);
457
458 /* Print the block from current_header and current_stat_info. */
459
460 if (verbose_option)
461 {
462 if (now_verifying)
463 fprintf (stdlis, _("Verify "));
464 print_header (&current_stat_info, -1);
465 }
466
467 switch (current_header->header.typeflag)
468 {
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. */
474
475 case AREGTYPE:
476 case REGTYPE:
477 case GNUTYPE_SPARSE:
478 case CONTTYPE:
479
480 /* Appears to be a file. See if it's really a directory. */
481
482 if (current_stat_info.had_trailing_slash)
483 diff_dir ();
484 else
485 diff_file ();
486 break;
487
488 case LNKTYPE:
489 diff_link ();
490 break;
491
492 #ifdef HAVE_READLINK
493 case SYMTYPE:
494 diff_symlink ();
495 break;
496 #endif
497
498 case CHRTYPE:
499 case BLKTYPE:
500 case FIFOTYPE:
501 diff_special ();
502 break;
503
504 case GNUTYPE_DUMPDIR:
505 case DIRTYPE:
506 if (is_dumpdir (&current_stat_info))
507 diff_dumpdir ();
508 diff_dir ();
509 break;
510
511 case GNUTYPE_VOLHDR:
512 break;
513
514 case GNUTYPE_MULTIVOL:
515 diff_multivol ();
516 }
517 }
518
519 void
520 verify_volume (void)
521 {
522 if (removed_prefixes_p ())
523 {
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.")));
528 }
529
530 if (!diff_buffer)
531 diff_init ();
532
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.
537
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 :-). */
542
543 #if HAVE_FSYNC
544 fsync (archive);
545 #endif
546 #ifdef FDFLUSH
547 ioctl (archive, FDFLUSH);
548 #endif
549
550 #ifdef MTIOCTOP
551 {
552 struct mtop operation;
553 int status;
554
555 operation.mt_op = MTBSF;
556 operation.mt_count = 1;
557 if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
558 {
559 if (errno != EIO
560 || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
561 status < 0))
562 {
563 #endif
564 if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
565 {
566 /* Lseek failed. Try a different method. */
567 seek_warn (archive_name_array[0]);
568 return;
569 }
570 #ifdef MTIOCTOP
571 }
572 }
573 }
574 #endif
575
576 access_mode = ACCESS_READ;
577 now_verifying = 1;
578
579 flush_read ();
580 while (1)
581 {
582 enum read_header status = read_header (false);
583
584 if (status == HEADER_FAILURE)
585 {
586 int counter = 0;
587
588 do
589 {
590 counter++;
591 set_next_block_after (current_header);
592 status = read_header (false);
593 }
594 while (status == HEADER_FAILURE);
595
596 ERROR ((0, 0,
597 ngettext ("VERIFY FAILURE: %d invalid header detected",
598 "VERIFY FAILURE: %d invalid headers detected",
599 counter), counter));
600 }
601 if (status == HEADER_END_OF_FILE)
602 break;
603 if (status == HEADER_ZERO_BLOCK)
604 {
605 set_next_block_after (current_header);
606 if (!ignore_zeros_option)
607 {
608 char buf[UINTMAX_STRSIZE_BOUND];
609
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)));
616 }
617 }
618
619 diff_archive ();
620 tar_stat_destroy (&current_stat_info);
621 }
622
623 access_mode = ACCESS_WRITE;
624 now_verifying = 0;
625 }
This page took 0.061922 seconds and 5 git commands to generate.