]> Dogcows Code - chaz/tar/blob - src/compare.c
tar: port -d to longer symlinks
[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-2014 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 char buf[1024];
274 size_t len = strlen (current_stat_info.link_name);
275 char *linkbuf = len < sizeof buf ? buf : xmalloc (len + 1);
276
277 ssize_t status = readlinkat (chdir_fd, current_stat_info.file_name,
278 linkbuf, len + 1);
279
280 if (status < 0)
281 {
282 if (errno == ENOENT)
283 readlink_warn (current_stat_info.file_name);
284 else
285 readlink_error (current_stat_info.file_name);
286 report_difference (&current_stat_info, NULL);
287 }
288 else if (status != len
289 || memcmp (current_stat_info.link_name, linkbuf, len) != 0)
290 report_difference (&current_stat_info, _("Symlink differs"));
291
292 if (linkbuf != buf)
293 free (linkbuf);
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 (struct tar_stat_info *dir)
368 {
369 const char *dumpdir_buffer;
370
371 if (dir->fd == 0)
372 {
373 void (*diag) (char const *) = NULL;
374 int fd = subfile_open (dir->parent, dir->orig_file_name, open_read_flags);
375 if (fd < 0)
376 diag = open_diag;
377 else if (fstat (fd, &dir->stat))
378 {
379 diag = stat_diag;
380 close (fd);
381 }
382 else
383 dir->fd = fd;
384 if (diag)
385 {
386 file_removed_diag (dir->orig_file_name, false, diag);
387 return;
388 }
389 }
390 dumpdir_buffer = directory_contents (scan_directory (dir));
391
392 if (dumpdir_buffer)
393 {
394 if (dumpdir_cmp (dir->dumpdir, dumpdir_buffer))
395 report_difference (dir, _("Contents differ"));
396 }
397 else
398 read_and_process (dir, process_noop);
399 }
400
401 static void
402 diff_multivol (void)
403 {
404 struct stat stat_data;
405 int fd, status;
406 off_t offset;
407
408 if (current_stat_info.had_trailing_slash)
409 {
410 diff_dir ();
411 return;
412 }
413
414 if (!get_stat_data (current_stat_info.file_name, &stat_data))
415 return;
416
417 if (!S_ISREG (stat_data.st_mode))
418 {
419 report_difference (&current_stat_info, _("File type differs"));
420 skip_member ();
421 return;
422 }
423
424 offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
425 if (offset < 0
426 || INT_ADD_OVERFLOW (current_stat_info.stat.st_size, offset)
427 || stat_data.st_size != current_stat_info.stat.st_size + offset)
428 {
429 report_difference (&current_stat_info, _("Size differs"));
430 skip_member ();
431 return;
432 }
433
434
435 fd = openat (chdir_fd, current_stat_info.file_name, open_read_flags);
436
437 if (fd < 0)
438 {
439 open_error (current_stat_info.file_name);
440 report_difference (&current_stat_info, NULL);
441 skip_member ();
442 return;
443 }
444
445 if (lseek (fd, offset, SEEK_SET) < 0)
446 {
447 seek_error_details (current_stat_info.file_name, offset);
448 report_difference (&current_stat_info, NULL);
449 }
450 else
451 read_and_process (&current_stat_info, process_rawdata);
452
453 status = close (fd);
454 if (status != 0)
455 close_error (current_stat_info.file_name);
456 }
457
458 /* Diff a file against the archive. */
459 void
460 diff_archive (void)
461 {
462
463 set_next_block_after (current_header);
464
465 /* Print the block from current_header and current_stat_info. */
466
467 if (verbose_option)
468 {
469 if (now_verifying)
470 fprintf (stdlis, _("Verify "));
471 print_header (&current_stat_info, current_header, -1);
472 }
473
474 switch (current_header->header.typeflag)
475 {
476 default:
477 ERROR ((0, 0, _("%s: Unknown file type '%c', diffed as normal file"),
478 quotearg_colon (current_stat_info.file_name),
479 current_header->header.typeflag));
480 /* Fall through. */
481
482 case AREGTYPE:
483 case REGTYPE:
484 case GNUTYPE_SPARSE:
485 case CONTTYPE:
486
487 /* Appears to be a file. See if it's really a directory. */
488
489 if (current_stat_info.had_trailing_slash)
490 diff_dir ();
491 else
492 diff_file ();
493 break;
494
495 case LNKTYPE:
496 diff_link ();
497 break;
498
499 #ifdef HAVE_READLINK
500 case SYMTYPE:
501 diff_symlink ();
502 break;
503 #endif
504
505 case CHRTYPE:
506 case BLKTYPE:
507 case FIFOTYPE:
508 diff_special ();
509 break;
510
511 case GNUTYPE_DUMPDIR:
512 case DIRTYPE:
513 if (is_dumpdir (&current_stat_info))
514 diff_dumpdir (&current_stat_info);
515 diff_dir ();
516 break;
517
518 case GNUTYPE_VOLHDR:
519 break;
520
521 case GNUTYPE_MULTIVOL:
522 diff_multivol ();
523 }
524 }
525
526 void
527 verify_volume (void)
528 {
529 int may_fail = 0;
530 if (removed_prefixes_p ())
531 {
532 WARN((0, 0,
533 _("Archive contains file names with leading prefixes removed.")));
534 may_fail = 1;
535 }
536 if (transform_program_p ())
537 {
538 WARN((0, 0,
539 _("Archive contains transformed file names.")));
540 may_fail = 1;
541 }
542 if (may_fail)
543 WARN((0, 0,
544 _("Verification may fail to locate original files.")));
545
546 clear_directory_table ();
547
548 if (!diff_buffer)
549 diff_init ();
550
551 /* Verifying an archive is meant to check if the physical media got it
552 correctly, so try to defeat clever in-memory buffering pertaining to
553 this particular media. On Linux, for example, the floppy drive would
554 not even be accessed for the whole verification.
555
556 The code was using fsync only when the ioctl is unavailable, but
557 Marty Leisner says that the ioctl does not work when not preceded by
558 fsync. So, until we know better, or maybe to please Marty, let's do it
559 the unbelievable way :-). */
560
561 #if HAVE_FSYNC
562 fsync (archive);
563 #endif
564 #ifdef FDFLUSH
565 ioctl (archive, FDFLUSH);
566 #endif
567
568 #ifdef MTIOCTOP
569 {
570 struct mtop operation;
571 int status;
572
573 operation.mt_op = MTBSF;
574 operation.mt_count = 1;
575 if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
576 {
577 if (errno != EIO
578 || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
579 status < 0))
580 {
581 #endif
582 if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
583 {
584 /* Lseek failed. Try a different method. */
585 seek_warn (archive_name_array[0]);
586 return;
587 }
588 #ifdef MTIOCTOP
589 }
590 }
591 }
592 #endif
593
594 access_mode = ACCESS_READ;
595 now_verifying = 1;
596
597 flush_read ();
598 while (1)
599 {
600 enum read_header status = read_header (&current_header,
601 &current_stat_info,
602 read_header_auto);
603
604 if (status == HEADER_FAILURE)
605 {
606 int counter = 0;
607
608 do
609 {
610 counter++;
611 set_next_block_after (current_header);
612 status = read_header (&current_header, &current_stat_info,
613 read_header_auto);
614 }
615 while (status == HEADER_FAILURE);
616
617 ERROR ((0, 0,
618 ngettext ("VERIFY FAILURE: %d invalid header detected",
619 "VERIFY FAILURE: %d invalid headers detected",
620 counter), counter));
621 }
622 if (status == HEADER_END_OF_FILE)
623 break;
624 if (status == HEADER_ZERO_BLOCK)
625 {
626 set_next_block_after (current_header);
627 if (!ignore_zeros_option)
628 {
629 char buf[UINTMAX_STRSIZE_BOUND];
630
631 status = read_header (&current_header, &current_stat_info,
632 read_header_auto);
633 if (status == HEADER_ZERO_BLOCK)
634 break;
635 WARNOPT (WARN_ALONE_ZERO_BLOCK,
636 (0, 0, _("A lone zero block at %s"),
637 STRINGIFY_BIGINT (current_block_ordinal (), buf)));
638 }
639 continue;
640 }
641
642 decode_header (current_header, &current_stat_info, &current_format, 1);
643 diff_archive ();
644 tar_stat_destroy (&current_stat_info);
645 }
646
647 access_mode = ACCESS_WRITE;
648 now_verifying = 0;
649 }
This page took 0.055115 seconds and 4 git commands to generate.