maint: higher-precision checkpoint timestamps
[tar.git] / src / compare.c
blobd99fba35f145d55d421f629edcce1a4c3aba155a
1 /* Diff files from a tar archive.
3 Copyright 1988-2024 Free Software Foundation, Inc.
5 This file is part of GNU tar.
7 GNU tar is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
12 GNU tar is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 Written by John Gilmore, on 1987-04-30. */
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 enum { QUOTE_ARG, QUOTE_NAME };
55 /* Sigh about something that differs by writing a MESSAGE to stdlis,
56 given MESSAGE is nonzero. Also set the exit status if not already. */
57 void
58 report_difference (struct tar_stat_info *st, const char *fmt, ...)
60 if (fmt)
62 va_list ap;
64 fprintf (stdlis, "%s: ", quote_n_colon (QUOTE_NAME, st->file_name));
65 va_start (ap, fmt);
66 vfprintf (stdlis, fmt, ap);
67 va_end (ap);
68 fprintf (stdlis, "\n");
71 set_exit_status (TAREXIT_DIFFERS);
74 /* Take a buffer returned by read_and_process and do nothing with it. */
75 static int
76 process_noop (MAYBE_UNUSED size_t size, MAYBE_UNUSED char *data)
78 return 1;
81 static int
82 process_rawdata (size_t bytes, char *buffer)
84 size_t status = blocking_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_read (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 (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 diff_handle = openat (chdir_fd, file_name, open_read_flags);
223 if (diff_handle < 0)
225 open_error (file_name);
226 skip_member ();
227 report_difference (&current_stat_info, NULL);
229 else
231 int status;
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);
238 if (atime_preserve_option == replace_atime_preserve
239 && stat_data.st_size != 0)
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);
247 status = close (diff_handle);
248 if (status != 0)
249 close_error (file_name);
255 static void
256 diff_link (void)
258 struct stat file_data;
259 struct stat link_data;
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_n_colon (QUOTE_ARG,
267 current_stat_info.link_name));
270 #ifdef HAVE_READLINK
271 static void
272 diff_symlink (void)
274 char buf[1024];
275 size_t len = strlen (current_stat_info.link_name);
276 char *linkbuf = len < sizeof buf ? buf : xmalloc (len + 1);
278 ssize_t status = readlinkat (chdir_fd, current_stat_info.file_name,
279 linkbuf, len + 1);
281 if (status < 0)
283 if (errno == ENOENT)
284 readlink_warn (current_stat_info.file_name);
285 else
286 readlink_error (current_stat_info.file_name);
287 report_difference (&current_stat_info, NULL);
289 else if (status != len
290 || memcmp (current_stat_info.link_name, linkbuf, len) != 0)
291 report_difference (&current_stat_info, _("Symlink differs"));
293 if (linkbuf != buf)
294 free (linkbuf);
296 #endif
298 static void
299 diff_special (void)
301 struct stat stat_data;
303 /* FIXME: deal with umask. */
305 if (!get_stat_data (current_stat_info.file_name, &stat_data))
306 return;
308 if (current_header->header.typeflag == CHRTYPE
309 ? !S_ISCHR (stat_data.st_mode)
310 : current_header->header.typeflag == BLKTYPE
311 ? !S_ISBLK (stat_data.st_mode)
312 : /* current_header->header.typeflag == FIFOTYPE */
313 !S_ISFIFO (stat_data.st_mode))
315 report_difference (&current_stat_info, _("File type differs"));
316 return;
319 if ((current_header->header.typeflag == CHRTYPE
320 || current_header->header.typeflag == BLKTYPE)
321 && current_stat_info.stat.st_rdev != stat_data.st_rdev)
323 report_difference (&current_stat_info, _("Device number differs"));
324 return;
327 if ((current_stat_info.stat.st_mode & MODE_ALL) !=
328 (stat_data.st_mode & MODE_ALL))
329 report_difference (&current_stat_info, _("Mode differs"));
332 static int
333 dumpdir_cmp (const char *a, const char *b)
335 size_t len;
337 while (*a)
338 switch (*a)
340 case 'Y':
341 case 'N':
342 if (!strchr ("YN", *b))
343 return 1;
344 if (strcmp(a + 1, b + 1))
345 return 1;
346 len = strlen (a) + 1;
347 a += len;
348 b += len;
349 break;
351 case 'D':
352 if (strcmp(a, b))
353 return 1;
354 len = strlen (a) + 1;
355 a += len;
356 b += len;
357 break;
359 case 'R':
360 case 'T':
361 case 'X':
362 return *b;
364 return *b;
367 static void
368 diff_dumpdir (struct tar_stat_info *dir)
370 const char *dumpdir_buffer;
372 if (dir->fd == 0)
374 void (*diag) (char const *) = NULL;
375 int fd = subfile_open (dir->parent, dir->orig_file_name, open_read_flags);
376 if (fd < 0)
377 diag = open_diag;
378 else if (fstat (fd, &dir->stat))
380 diag = stat_diag;
381 close (fd);
383 else
384 dir->fd = fd;
385 if (diag)
387 file_removed_diag (dir->orig_file_name, false, diag);
388 return;
391 dumpdir_buffer = directory_contents (scan_directory (dir));
393 if (dumpdir_buffer)
395 if (dumpdir_cmp (dir->dumpdir, dumpdir_buffer))
396 report_difference (dir, _("Contents differ"));
398 else
399 read_and_process (dir, process_noop);
402 static void
403 diff_multivol (void)
405 struct stat stat_data;
406 int fd, status;
407 off_t offset;
409 if (current_stat_info.had_trailing_slash)
411 diff_dir ();
412 return;
415 if (!get_stat_data (current_stat_info.file_name, &stat_data))
416 return;
418 if (!S_ISREG (stat_data.st_mode))
420 report_difference (&current_stat_info, _("File type differs"));
421 skip_member ();
422 return;
425 offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
426 if (offset < 0
427 || INT_ADD_OVERFLOW (current_stat_info.stat.st_size, offset)
428 || stat_data.st_size != current_stat_info.stat.st_size + offset)
430 report_difference (&current_stat_info, _("Size differs"));
431 skip_member ();
432 return;
436 fd = openat (chdir_fd, current_stat_info.file_name, open_read_flags);
438 if (fd < 0)
440 open_error (current_stat_info.file_name);
441 report_difference (&current_stat_info, NULL);
442 skip_member ();
443 return;
446 if (lseek (fd, offset, SEEK_SET) < 0)
448 seek_error_details (current_stat_info.file_name, offset);
449 report_difference (&current_stat_info, NULL);
451 else
452 read_and_process (&current_stat_info, process_rawdata);
454 status = close (fd);
455 if (status != 0)
456 close_error (current_stat_info.file_name);
459 /* Diff a file against the archive. */
460 void
461 diff_archive (void)
464 set_next_block_after (current_header);
466 /* Print the block from current_header and current_stat_info. */
468 if (verbose_option)
470 if (now_verifying)
471 fprintf (stdlis, _("Verify "));
472 print_header (&current_stat_info, current_header, -1);
475 switch (current_header->header.typeflag)
477 default:
478 ERROR ((0, 0, _("%s: Unknown file type '%c', diffed as normal file"),
479 quotearg_colon (current_stat_info.file_name),
480 current_header->header.typeflag));
481 FALLTHROUGH;
482 case AREGTYPE:
483 case REGTYPE:
484 case GNUTYPE_SPARSE:
485 case CONTTYPE:
487 /* Appears to be a file. See if it's really a directory. */
489 if (current_stat_info.had_trailing_slash)
490 diff_dir ();
491 else
492 diff_file ();
493 break;
495 case LNKTYPE:
496 diff_link ();
497 break;
499 #ifdef HAVE_READLINK
500 case SYMTYPE:
501 diff_symlink ();
502 break;
503 #endif
505 case CHRTYPE:
506 case BLKTYPE:
507 case FIFOTYPE:
508 diff_special ();
509 break;
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;
518 case GNUTYPE_VOLHDR:
519 break;
521 case GNUTYPE_MULTIVOL:
522 diff_multivol ();
526 void
527 verify_volume (void)
529 int may_fail = 0;
530 if (removed_prefixes_p ())
532 WARN((0, 0,
533 _("Archive contains file names with leading prefixes removed.")));
534 may_fail = 1;
536 if (transform_program_p ())
538 WARN((0, 0,
539 _("Archive contains transformed file names.")));
540 may_fail = 1;
542 if (may_fail)
543 WARN((0, 0,
544 _("Verification may fail to locate original files.")));
546 clear_directory_table ();
548 if (!diff_buffer)
549 diff_init ();
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.
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 :-). */
561 #if HAVE_FSYNC
562 fsync (archive);
563 #endif
564 #ifdef FDFLUSH
565 ioctl (archive, FDFLUSH);
566 #endif
568 if (!mtioseek (true, -1) && rmtlseek (archive, 0, SEEK_SET) != 0)
570 /* Lseek failed. Try a different method. */
571 seek_warn (archive_name_array[0]);
572 return;
575 access_mode = ACCESS_READ;
576 now_verifying = 1;
578 flush_read ();
579 while (1)
581 enum read_header status = read_header (&current_header,
582 &current_stat_info,
583 read_header_auto);
585 if (status == HEADER_FAILURE)
587 int counter = 0;
591 counter++;
592 set_next_block_after (current_header);
593 status = read_header (&current_header, &current_stat_info,
594 read_header_auto);
596 while (status == HEADER_FAILURE);
598 ERROR ((0, 0,
599 ngettext ("VERIFY FAILURE: %d invalid header detected",
600 "VERIFY FAILURE: %d invalid headers detected",
601 counter), counter));
603 if (status == HEADER_END_OF_FILE)
604 break;
605 if (status == HEADER_ZERO_BLOCK)
607 set_next_block_after (current_header);
608 if (!ignore_zeros_option)
610 char buf[UINTMAX_STRSIZE_BOUND];
612 status = read_header (&current_header, &current_stat_info,
613 read_header_auto);
614 if (status == HEADER_ZERO_BLOCK)
615 break;
616 WARNOPT (WARN_ALONE_ZERO_BLOCK,
617 (0, 0, _("A lone zero block at %s"),
618 STRINGIFY_BIGINT (current_block_ordinal (), buf)));
620 continue;
623 decode_header (current_header, &current_stat_info, &current_format, 1);
624 diff_archive ();
625 tar_stat_destroy (&current_stat_info);
628 access_mode = ACCESS_WRITE;
629 now_verifying = 0;