]> Dogcows Code - chaz/tar/blob - src/compare.c
tar: --atime-preserve fixes for races etc.
[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, 2010 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 set_exit_status (TAREXIT_DIFFERS);
70 }
71
72 /* Take a buffer returned by read_and_process and do nothing with it. */
73 static int
74 process_noop (size_t size __attribute__ ((unused)),
75 char *data __attribute__ ((unused)))
76 {
77 return 1;
78 }
79
80 static int
81 process_rawdata (size_t bytes, char *buffer)
82 {
83 size_t status = safe_read (diff_handle, diff_buffer, bytes);
84
85 if (status != bytes)
86 {
87 if (status == SAFE_READ_ERROR)
88 {
89 read_error (current_stat_info.file_name);
90 report_difference (&current_stat_info, NULL);
91 }
92 else
93 {
94 report_difference (&current_stat_info,
95 ngettext ("Could only read %lu of %lu byte",
96 "Could only read %lu of %lu bytes",
97 bytes),
98 (unsigned long) status, (unsigned long) bytes);
99 }
100 return 0;
101 }
102
103 if (memcmp (buffer, diff_buffer, bytes))
104 {
105 report_difference (&current_stat_info, _("Contents differ"));
106 return 0;
107 }
108
109 return 1;
110 }
111
112 /* Some other routine wants SIZE bytes in the archive. For each chunk
113 of the archive, call PROCESSOR with the size of the chunk, and the
114 address of the chunk it can work with. The PROCESSOR should return
115 nonzero for success. Once it returns error, continue skipping
116 without calling PROCESSOR anymore. */
117
118 static void
119 read_and_process (struct tar_stat_info *st, int (*processor) (size_t, char *))
120 {
121 union block *data_block;
122 size_t data_size;
123 off_t size = st->stat.st_size;
124
125 mv_begin_read (st);
126 while (size)
127 {
128 data_block = find_next_block ();
129 if (! data_block)
130 {
131 ERROR ((0, 0, _("Unexpected EOF in archive")));
132 return;
133 }
134
135 data_size = available_space_after (data_block);
136 if (data_size > size)
137 data_size = size;
138 if (!(*processor) (data_size, data_block->buffer))
139 processor = process_noop;
140 set_next_block_after ((union block *)
141 (data_block->buffer + data_size - 1));
142 size -= data_size;
143 mv_size_left (size);
144 }
145 mv_end ();
146 }
147
148 /* Call either stat or lstat over STAT_DATA, depending on
149 --dereference (-h), for a file which should exist. Diagnose any
150 problem. Return nonzero for success, zero otherwise. */
151 static int
152 get_stat_data (char const *file_name, struct stat *stat_data)
153 {
154 int status = deref_stat (dereference_option, file_name, stat_data);
155
156 if (status != 0)
157 {
158 if (errno == ENOENT)
159 stat_warn (file_name);
160 else
161 stat_error (file_name);
162 report_difference (&current_stat_info, NULL);
163 return 0;
164 }
165
166 return 1;
167 }
168
169 \f
170 static void
171 diff_dir (void)
172 {
173 struct stat stat_data;
174
175 if (!get_stat_data (current_stat_info.file_name, &stat_data))
176 return;
177
178 if (!S_ISDIR (stat_data.st_mode))
179 report_difference (&current_stat_info, _("File type differs"));
180 else if ((current_stat_info.stat.st_mode & MODE_ALL) !=
181 (stat_data.st_mode & MODE_ALL))
182 report_difference (&current_stat_info, _("Mode differs"));
183 }
184
185 static void
186 diff_file (void)
187 {
188 char const *file_name = current_stat_info.file_name;
189 struct stat stat_data;
190
191 if (!get_stat_data (file_name, &stat_data))
192 skip_member ();
193 else if (!S_ISREG (stat_data.st_mode))
194 {
195 report_difference (&current_stat_info, _("File type differs"));
196 skip_member ();
197 }
198 else
199 {
200 if ((current_stat_info.stat.st_mode & MODE_ALL) !=
201 (stat_data.st_mode & MODE_ALL))
202 report_difference (&current_stat_info, _("Mode differs"));
203
204 if (!sys_compare_uid (&stat_data, &current_stat_info.stat))
205 report_difference (&current_stat_info, _("Uid differs"));
206 if (!sys_compare_gid (&stat_data, &current_stat_info.stat))
207 report_difference (&current_stat_info, _("Gid differs"));
208
209 if (tar_timespec_cmp (get_stat_mtime (&stat_data),
210 current_stat_info.mtime))
211 report_difference (&current_stat_info, _("Mod time differs"));
212 if (current_header->header.typeflag != GNUTYPE_SPARSE
213 && stat_data.st_size != current_stat_info.stat.st_size)
214 {
215 report_difference (&current_stat_info, _("Size differs"));
216 skip_member ();
217 }
218 else
219 {
220 int atime_flag =
221 (atime_preserve_option == system_atime_preserve
222 ? O_NOATIME
223 : 0);
224
225 diff_handle = open (file_name,
226 (O_RDONLY | O_BINARY | O_CLOEXEC | O_NOCTTY
227 | O_NONBLOCK | atime_flag));
228
229 if (diff_handle < 0)
230 {
231 open_error (file_name);
232 skip_member ();
233 report_difference (&current_stat_info, NULL);
234 }
235 else
236 {
237 int status;
238
239 if (current_stat_info.is_sparse)
240 sparse_diff_file (diff_handle, &current_stat_info);
241 else
242 read_and_process (&current_stat_info, process_rawdata);
243
244 if (atime_preserve_option == replace_atime_preserve)
245 {
246 struct timespec atime = get_stat_atime (&stat_data);
247 if (set_file_atime (diff_handle, file_name, atime, 0) != 0)
248 utime_error (file_name);
249 }
250
251 status = close (diff_handle);
252 if (status != 0)
253 close_error (file_name);
254 }
255 }
256 }
257 }
258
259 static void
260 diff_link (void)
261 {
262 struct stat file_data;
263 struct stat link_data;
264
265 if (get_stat_data (current_stat_info.file_name, &file_data)
266 && get_stat_data (current_stat_info.link_name, &link_data)
267 && !sys_compare_links (&file_data, &link_data))
268 report_difference (&current_stat_info,
269 _("Not linked to %s"),
270 quote (current_stat_info.link_name));
271 }
272
273 #ifdef HAVE_READLINK
274 static void
275 diff_symlink (void)
276 {
277 size_t len = strlen (current_stat_info.link_name);
278 char *linkbuf = alloca (len + 1);
279
280 int status = readlink (current_stat_info.file_name, linkbuf, len + 1);
281
282 if (status < 0)
283 {
284 if (errno == ENOENT)
285 readlink_warn (current_stat_info.file_name);
286 else
287 readlink_error (current_stat_info.file_name);
288 report_difference (&current_stat_info, NULL);
289 }
290 else if (status != len
291 || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
292 report_difference (&current_stat_info, _("Symlink differs"));
293 }
294 #endif
295
296 static void
297 diff_special (void)
298 {
299 struct stat stat_data;
300
301 /* FIXME: deal with umask. */
302
303 if (!get_stat_data (current_stat_info.file_name, &stat_data))
304 return;
305
306 if (current_header->header.typeflag == CHRTYPE
307 ? !S_ISCHR (stat_data.st_mode)
308 : current_header->header.typeflag == BLKTYPE
309 ? !S_ISBLK (stat_data.st_mode)
310 : /* current_header->header.typeflag == FIFOTYPE */
311 !S_ISFIFO (stat_data.st_mode))
312 {
313 report_difference (&current_stat_info, _("File type differs"));
314 return;
315 }
316
317 if ((current_header->header.typeflag == CHRTYPE
318 || current_header->header.typeflag == BLKTYPE)
319 && current_stat_info.stat.st_rdev != stat_data.st_rdev)
320 {
321 report_difference (&current_stat_info, _("Device number differs"));
322 return;
323 }
324
325 if ((current_stat_info.stat.st_mode & MODE_ALL) !=
326 (stat_data.st_mode & MODE_ALL))
327 report_difference (&current_stat_info, _("Mode differs"));
328 }
329
330 static int
331 dumpdir_cmp (const char *a, const char *b)
332 {
333 size_t len;
334
335 while (*a)
336 switch (*a)
337 {
338 case 'Y':
339 case 'N':
340 if (!strchr ("YN", *b))
341 return 1;
342 if (strcmp(a + 1, b + 1))
343 return 1;
344 len = strlen (a) + 1;
345 a += len;
346 b += len;
347 break;
348
349 case 'D':
350 if (strcmp(a, b))
351 return 1;
352 len = strlen (a) + 1;
353 a += len;
354 b += len;
355 break;
356
357 case 'R':
358 case 'T':
359 case 'X':
360 return *b;
361 }
362 return *b;
363 }
364
365 static void
366 diff_dumpdir (void)
367 {
368 const char *dumpdir_buffer;
369 dev_t dev = 0;
370 struct stat stat_data;
371
372 if (deref_stat (true, current_stat_info.file_name, &stat_data))
373 {
374 if (errno == ENOENT)
375 stat_warn (current_stat_info.file_name);
376 else
377 stat_error (current_stat_info.file_name);
378 }
379 else
380 dev = stat_data.st_dev;
381
382 dumpdir_buffer = directory_contents (scan_directory (&current_stat_info));
383
384 if (dumpdir_buffer)
385 {
386 if (dumpdir_cmp (current_stat_info.dumpdir, dumpdir_buffer))
387 report_difference (&current_stat_info, _("Contents differ"));
388 }
389 else
390 read_and_process (&current_stat_info, process_noop);
391 }
392
393 static void
394 diff_multivol (void)
395 {
396 struct stat stat_data;
397 int fd, status;
398 off_t offset;
399 int atime_flag =
400 (atime_preserve_option == system_atime_preserve
401 ? O_NOATIME
402 : 0);
403
404 if (current_stat_info.had_trailing_slash)
405 {
406 diff_dir ();
407 return;
408 }
409
410 if (!get_stat_data (current_stat_info.file_name, &stat_data))
411 return;
412
413 if (!S_ISREG (stat_data.st_mode))
414 {
415 report_difference (&current_stat_info, _("File type differs"));
416 skip_member ();
417 return;
418 }
419
420 offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
421 if (stat_data.st_size != current_stat_info.stat.st_size + offset)
422 {
423 report_difference (&current_stat_info, _("Size differs"));
424 skip_member ();
425 return;
426 }
427
428
429 fd = open (current_stat_info.file_name,
430 (O_RDONLY | O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK
431 | atime_flag));
432
433 if (fd < 0)
434 {
435 open_error (current_stat_info.file_name);
436 report_difference (&current_stat_info, NULL);
437 skip_member ();
438 return;
439 }
440
441 if (lseek (fd, offset, SEEK_SET) < 0)
442 {
443 seek_error_details (current_stat_info.file_name, offset);
444 report_difference (&current_stat_info, NULL);
445 return;
446 }
447
448 read_and_process (&current_stat_info, process_rawdata);
449
450 status = close (fd);
451 if (status != 0)
452 close_error (current_stat_info.file_name);
453 }
454
455 /* Diff a file against the archive. */
456 void
457 diff_archive (void)
458 {
459
460 set_next_block_after (current_header);
461
462 /* Print the block from current_header and current_stat_info. */
463
464 if (verbose_option)
465 {
466 if (now_verifying)
467 fprintf (stdlis, _("Verify "));
468 print_header (&current_stat_info, current_header, -1);
469 }
470
471 switch (current_header->header.typeflag)
472 {
473 default:
474 ERROR ((0, 0, _("%s: Unknown file type `%c', diffed as normal file"),
475 quotearg_colon (current_stat_info.file_name),
476 current_header->header.typeflag));
477 /* Fall through. */
478
479 case AREGTYPE:
480 case REGTYPE:
481 case GNUTYPE_SPARSE:
482 case CONTTYPE:
483
484 /* Appears to be a file. See if it's really a directory. */
485
486 if (current_stat_info.had_trailing_slash)
487 diff_dir ();
488 else
489 diff_file ();
490 break;
491
492 case LNKTYPE:
493 diff_link ();
494 break;
495
496 #ifdef HAVE_READLINK
497 case SYMTYPE:
498 diff_symlink ();
499 break;
500 #endif
501
502 case CHRTYPE:
503 case BLKTYPE:
504 case FIFOTYPE:
505 diff_special ();
506 break;
507
508 case GNUTYPE_DUMPDIR:
509 case DIRTYPE:
510 if (is_dumpdir (&current_stat_info))
511 diff_dumpdir ();
512 diff_dir ();
513 break;
514
515 case GNUTYPE_VOLHDR:
516 break;
517
518 case GNUTYPE_MULTIVOL:
519 diff_multivol ();
520 }
521 }
522
523 void
524 verify_volume (void)
525 {
526 if (removed_prefixes_p ())
527 {
528 WARN((0, 0,
529 _("Archive contains file names with leading prefixes removed.")));
530 WARN((0, 0,
531 _("Verification may fail to locate original files.")));
532 }
533
534 if (!diff_buffer)
535 diff_init ();
536
537 /* Verifying an archive is meant to check if the physical media got it
538 correctly, so try to defeat clever in-memory buffering pertaining to
539 this particular media. On Linux, for example, the floppy drive would
540 not even be accessed for the whole verification.
541
542 The code was using fsync only when the ioctl is unavailable, but
543 Marty Leisner says that the ioctl does not work when not preceded by
544 fsync. So, until we know better, or maybe to please Marty, let's do it
545 the unbelievable way :-). */
546
547 #if HAVE_FSYNC
548 fsync (archive);
549 #endif
550 #ifdef FDFLUSH
551 ioctl (archive, FDFLUSH);
552 #endif
553
554 #ifdef MTIOCTOP
555 {
556 struct mtop operation;
557 int status;
558
559 operation.mt_op = MTBSF;
560 operation.mt_count = 1;
561 if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
562 {
563 if (errno != EIO
564 || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
565 status < 0))
566 {
567 #endif
568 if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
569 {
570 /* Lseek failed. Try a different method. */
571 seek_warn (archive_name_array[0]);
572 return;
573 }
574 #ifdef MTIOCTOP
575 }
576 }
577 }
578 #endif
579
580 access_mode = ACCESS_READ;
581 now_verifying = 1;
582
583 flush_read ();
584 while (1)
585 {
586 enum read_header status = read_header (&current_header,
587 &current_stat_info,
588 read_header_auto);
589
590 if (status == HEADER_FAILURE)
591 {
592 int counter = 0;
593
594 do
595 {
596 counter++;
597 set_next_block_after (current_header);
598 status = read_header (&current_header, &current_stat_info,
599 read_header_auto);
600 }
601 while (status == HEADER_FAILURE);
602
603 ERROR ((0, 0,
604 ngettext ("VERIFY FAILURE: %d invalid header detected",
605 "VERIFY FAILURE: %d invalid headers detected",
606 counter), counter));
607 }
608 if (status == HEADER_END_OF_FILE)
609 break;
610 if (status == HEADER_ZERO_BLOCK)
611 {
612 set_next_block_after (current_header);
613 if (!ignore_zeros_option)
614 {
615 char buf[UINTMAX_STRSIZE_BOUND];
616
617 status = read_header (&current_header, &current_stat_info,
618 read_header_auto);
619 if (status == HEADER_ZERO_BLOCK)
620 break;
621 WARNOPT (WARN_ALONE_ZERO_BLOCK,
622 (0, 0, _("A lone zero block at %s"),
623 STRINGIFY_BIGINT (current_block_ordinal (), buf)));
624 }
625 }
626
627 diff_archive ();
628 tar_stat_destroy (&current_stat_info);
629 }
630
631 access_mode = ACCESS_WRITE;
632 now_verifying = 0;
633 }
This page took 0.066801 seconds and 5 git commands to generate.