]> Dogcows Code - chaz/tar/blob - src/compare.c
407fd4016952a7eed858092f19f257c5e82d50fc
[chaz/tar] / src / compare.c
1 /* Diff files from a tar archive.
2
3 Copyright 1988, 1992-1994, 1996-1997, 1999-2001, 2003-2007,
4 2009-2010, 2012-2013 Free Software Foundation, Inc.
5
6 This file is part of GNU tar.
7
8 GNU tar is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
12
13 GNU tar is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 Written by John Gilmore, on 1987-04-30. */
22
23 #include <system.h>
24 #include <system-ioctl.h>
25
26 #if HAVE_LINUX_FD_H
27 # include <linux/fd.h>
28 #endif
29
30 #include "common.h"
31 #include <quotearg.h>
32 #include <rmt.h>
33 #include <stdarg.h>
34
35 /* Nonzero if we are verifying at the moment. */
36 bool now_verifying;
37
38 /* File descriptor for the file we are diffing. */
39 static int diff_handle;
40
41 /* Area for reading file contents into. */
42 static char *diff_buffer;
43
44 /* Initialize for a diff operation. */
45 void
46 diff_init (void)
47 {
48 void *ptr;
49 diff_buffer = page_aligned_alloc (&ptr, record_size);
50 if (listed_incremental_option)
51 read_directory_file ();
52 }
53
54 /* Sigh about something that differs by writing a MESSAGE to stdlis,
55 given MESSAGE is nonzero. Also set the exit status if not already. */
56 void
57 report_difference (struct tar_stat_info *st, const char *fmt, ...)
58 {
59 if (fmt)
60 {
61 va_list ap;
62
63 fprintf (stdlis, "%s: ", quotearg_colon (st->file_name));
64 va_start (ap, fmt);
65 vfprintf (stdlis, fmt, ap);
66 va_end (ap);
67 fprintf (stdlis, "\n");
68 }
69
70 set_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 = blocking_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_read (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 (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 diff_handle = openat (chdir_fd, file_name, open_read_flags);
222
223 if (diff_handle < 0)
224 {
225 open_error (file_name);
226 skip_member ();
227 report_difference (&current_stat_info, NULL);
228 }
229 else
230 {
231 int status;
232
233 if (current_stat_info.is_sparse)
234 sparse_diff_file (diff_handle, &current_stat_info);
235 else
236 read_and_process (&current_stat_info, process_rawdata);
237
238 if (atime_preserve_option == replace_atime_preserve
239 && stat_data.st_size != 0)
240 {
241 struct timespec atime = get_stat_atime (&stat_data);
242 if (set_file_atime (diff_handle, chdir_fd, file_name, atime)
243 != 0)
244 utime_error (file_name);
245 }
246
247 status = close (diff_handle);
248 if (status != 0)
249 close_error (file_name);
250 }
251 }
252 }
253 }
254
255 static void
256 diff_link (void)
257 {
258 struct stat file_data;
259 struct stat link_data;
260
261 if (get_stat_data (current_stat_info.file_name, &file_data)
262 && get_stat_data (current_stat_info.link_name, &link_data)
263 && !sys_compare_links (&file_data, &link_data))
264 report_difference (&current_stat_info,
265 _("Not linked to %s"),
266 quote (current_stat_info.link_name));
267 }
268
269 #ifdef HAVE_READLINK
270 static void
271 diff_symlink (void)
272 {
273 size_t len = strlen (current_stat_info.link_name);
274 char *linkbuf = alloca (len + 1);
275
276 int status = readlinkat (chdir_fd, current_stat_info.file_name,
277 linkbuf, len + 1);
278
279 if (status < 0)
280 {
281 if (errno == ENOENT)
282 readlink_warn (current_stat_info.file_name);
283 else
284 readlink_error (current_stat_info.file_name);
285 report_difference (&current_stat_info, NULL);
286 }
287 else if (status != len
288 || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
289 report_difference (&current_stat_info, _("Symlink differs"));
290 }
291 #endif
292
293 static void
294 diff_special (void)
295 {
296 struct stat stat_data;
297
298 /* FIXME: deal with umask. */
299
300 if (!get_stat_data (current_stat_info.file_name, &stat_data))
301 return;
302
303 if (current_header->header.typeflag == CHRTYPE
304 ? !S_ISCHR (stat_data.st_mode)
305 : current_header->header.typeflag == BLKTYPE
306 ? !S_ISBLK (stat_data.st_mode)
307 : /* current_header->header.typeflag == FIFOTYPE */
308 !S_ISFIFO (stat_data.st_mode))
309 {
310 report_difference (&current_stat_info, _("File type differs"));
311 return;
312 }
313
314 if ((current_header->header.typeflag == CHRTYPE
315 || current_header->header.typeflag == BLKTYPE)
316 && current_stat_info.stat.st_rdev != stat_data.st_rdev)
317 {
318 report_difference (&current_stat_info, _("Device number differs"));
319 return;
320 }
321
322 if ((current_stat_info.stat.st_mode & MODE_ALL) !=
323 (stat_data.st_mode & MODE_ALL))
324 report_difference (&current_stat_info, _("Mode differs"));
325 }
326
327 static int
328 dumpdir_cmp (const char *a, const char *b)
329 {
330 size_t len;
331
332 while (*a)
333 switch (*a)
334 {
335 case 'Y':
336 case 'N':
337 if (!strchr ("YN", *b))
338 return 1;
339 if (strcmp(a + 1, b + 1))
340 return 1;
341 len = strlen (a) + 1;
342 a += len;
343 b += len;
344 break;
345
346 case 'D':
347 if (strcmp(a, b))
348 return 1;
349 len = strlen (a) + 1;
350 a += len;
351 b += len;
352 break;
353
354 case 'R':
355 case 'T':
356 case 'X':
357 return *b;
358 }
359 return *b;
360 }
361
362 static void
363 diff_dumpdir (struct tar_stat_info *dir)
364 {
365 const char *dumpdir_buffer;
366
367 if (dir->fd == 0)
368 {
369 void (*diag) (char const *) = NULL;
370 int fd = subfile_open (dir->parent, dir->orig_file_name, open_read_flags);
371 if (fd < 0)
372 diag = open_diag;
373 else if (fstat (fd, &dir->stat))
374 diag = stat_diag;
375 else
376 dir->fd = fd;
377 if (diag)
378 {
379 file_removed_diag (dir->orig_file_name, false, diag);
380 return;
381 }
382 }
383 dumpdir_buffer = directory_contents (scan_directory (dir));
384
385 if (dumpdir_buffer)
386 {
387 if (dumpdir_cmp (dir->dumpdir, dumpdir_buffer))
388 report_difference (dir, _("Contents differ"));
389 }
390 else
391 read_and_process (dir, process_noop);
392 }
393
394 static void
395 diff_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 (&current_stat_info, _("File type differs"));
413 skip_member ();
414 return;
415 }
416
417 offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
418 if (offset < 0
419 || INT_ADD_OVERFLOW (current_stat_info.stat.st_size, offset)
420 || stat_data.st_size != current_stat_info.stat.st_size + offset)
421 {
422 report_difference (&current_stat_info, _("Size differs"));
423 skip_member ();
424 return;
425 }
426
427
428 fd = openat (chdir_fd, current_stat_info.file_name, open_read_flags);
429
430 if (fd < 0)
431 {
432 open_error (current_stat_info.file_name);
433 report_difference (&current_stat_info, NULL);
434 skip_member ();
435 return;
436 }
437
438 if (lseek (fd, offset, SEEK_SET) < 0)
439 {
440 seek_error_details (current_stat_info.file_name, offset);
441 report_difference (&current_stat_info, NULL);
442 return;
443 }
444
445 read_and_process (&current_stat_info, process_rawdata);
446
447 status = close (fd);
448 if (status != 0)
449 close_error (current_stat_info.file_name);
450 }
451
452 /* Diff a file against the archive. */
453 void
454 diff_archive (void)
455 {
456
457 set_next_block_after (current_header);
458
459 /* Print the block from current_header and current_stat_info. */
460
461 if (verbose_option)
462 {
463 if (now_verifying)
464 fprintf (stdlis, _("Verify "));
465 print_header (&current_stat_info, current_header, -1);
466 }
467
468 switch (current_header->header.typeflag)
469 {
470 default:
471 ERROR ((0, 0, _("%s: Unknown file type '%c', diffed as normal file"),
472 quotearg_colon (current_stat_info.file_name),
473 current_header->header.typeflag));
474 /* Fall through. */
475
476 case AREGTYPE:
477 case REGTYPE:
478 case GNUTYPE_SPARSE:
479 case CONTTYPE:
480
481 /* Appears to be a file. See if it's really a directory. */
482
483 if (current_stat_info.had_trailing_slash)
484 diff_dir ();
485 else
486 diff_file ();
487 break;
488
489 case LNKTYPE:
490 diff_link ();
491 break;
492
493 #ifdef HAVE_READLINK
494 case SYMTYPE:
495 diff_symlink ();
496 break;
497 #endif
498
499 case CHRTYPE:
500 case BLKTYPE:
501 case FIFOTYPE:
502 diff_special ();
503 break;
504
505 case GNUTYPE_DUMPDIR:
506 case DIRTYPE:
507 if (is_dumpdir (&current_stat_info))
508 diff_dumpdir (&current_stat_info);
509 diff_dir ();
510 break;
511
512 case GNUTYPE_VOLHDR:
513 break;
514
515 case GNUTYPE_MULTIVOL:
516 diff_multivol ();
517 }
518 }
519
520 void
521 verify_volume (void)
522 {
523 int may_fail = 0;
524 if (removed_prefixes_p ())
525 {
526 WARN((0, 0,
527 _("Archive contains file names with leading prefixes removed.")));
528 may_fail = 1;
529 }
530 if (transform_program_p ())
531 {
532 WARN((0, 0,
533 _("Archive contains transformed file names.")));
534 may_fail = 1;
535 }
536 if (may_fail)
537 WARN((0, 0,
538 _("Verification may fail to locate original files.")));
539
540 clear_directory_table ();
541
542 if (!diff_buffer)
543 diff_init ();
544
545 /* Verifying an archive is meant to check if the physical media got it
546 correctly, so try to defeat clever in-memory buffering pertaining to
547 this particular media. On Linux, for example, the floppy drive would
548 not even be accessed for the whole verification.
549
550 The code was using fsync only when the ioctl is unavailable, but
551 Marty Leisner says that the ioctl does not work when not preceded by
552 fsync. So, until we know better, or maybe to please Marty, let's do it
553 the unbelievable way :-). */
554
555 #if HAVE_FSYNC
556 fsync (archive);
557 #endif
558 #ifdef FDFLUSH
559 ioctl (archive, FDFLUSH);
560 #endif
561
562 #ifdef MTIOCTOP
563 {
564 struct mtop operation;
565 int status;
566
567 operation.mt_op = MTBSF;
568 operation.mt_count = 1;
569 if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
570 {
571 if (errno != EIO
572 || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
573 status < 0))
574 {
575 #endif
576 if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
577 {
578 /* Lseek failed. Try a different method. */
579 seek_warn (archive_name_array[0]);
580 return;
581 }
582 #ifdef MTIOCTOP
583 }
584 }
585 }
586 #endif
587
588 access_mode = ACCESS_READ;
589 now_verifying = 1;
590
591 flush_read ();
592 while (1)
593 {
594 enum read_header status = read_header (&current_header,
595 &current_stat_info,
596 read_header_auto);
597
598 if (status == HEADER_FAILURE)
599 {
600 int counter = 0;
601
602 do
603 {
604 counter++;
605 set_next_block_after (current_header);
606 status = read_header (&current_header, &current_stat_info,
607 read_header_auto);
608 }
609 while (status == HEADER_FAILURE);
610
611 ERROR ((0, 0,
612 ngettext ("VERIFY FAILURE: %d invalid header detected",
613 "VERIFY FAILURE: %d invalid headers detected",
614 counter), counter));
615 }
616 if (status == HEADER_END_OF_FILE)
617 break;
618 if (status == HEADER_ZERO_BLOCK)
619 {
620 set_next_block_after (current_header);
621 if (!ignore_zeros_option)
622 {
623 char buf[UINTMAX_STRSIZE_BOUND];
624
625 status = read_header (&current_header, &current_stat_info,
626 read_header_auto);
627 if (status == HEADER_ZERO_BLOCK)
628 break;
629 WARNOPT (WARN_ALONE_ZERO_BLOCK,
630 (0, 0, _("A lone zero block at %s"),
631 STRINGIFY_BIGINT (current_block_ordinal (), buf)));
632 }
633 continue;
634 }
635
636 decode_header (current_header, &current_stat_info, &current_format, 1);
637 diff_archive ();
638 tar_stat_destroy (&current_stat_info);
639 }
640
641 access_mode = ACCESS_WRITE;
642 now_verifying = 0;
643 }
This page took 0.061236 seconds and 3 git commands to generate.