]> Dogcows Code - chaz/tar/blob - src/compare.c
tar: prefer openat-style functions
[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 = openat (chdir_fd, 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, chdir_fd, file_name,
248 atime, 0)
249 != 0)
250 utime_error (file_name);
251 }
252
253 status = close (diff_handle);
254 if (status != 0)
255 close_error (file_name);
256 }
257 }
258 }
259 }
260
261 static void
262 diff_link (void)
263 {
264 struct stat file_data;
265 struct stat link_data;
266
267 if (get_stat_data (current_stat_info.file_name, &file_data)
268 && get_stat_data (current_stat_info.link_name, &link_data)
269 && !sys_compare_links (&file_data, &link_data))
270 report_difference (&current_stat_info,
271 _("Not linked to %s"),
272 quote (current_stat_info.link_name));
273 }
274
275 #ifdef HAVE_READLINK
276 static void
277 diff_symlink (void)
278 {
279 size_t len = strlen (current_stat_info.link_name);
280 char *linkbuf = alloca (len + 1);
281
282 int status = readlinkat (chdir_fd, current_stat_info.file_name,
283 linkbuf, len + 1);
284
285 if (status < 0)
286 {
287 if (errno == ENOENT)
288 readlink_warn (current_stat_info.file_name);
289 else
290 readlink_error (current_stat_info.file_name);
291 report_difference (&current_stat_info, NULL);
292 }
293 else if (status != len
294 || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
295 report_difference (&current_stat_info, _("Symlink differs"));
296 }
297 #endif
298
299 static void
300 diff_special (void)
301 {
302 struct stat stat_data;
303
304 /* FIXME: deal with umask. */
305
306 if (!get_stat_data (current_stat_info.file_name, &stat_data))
307 return;
308
309 if (current_header->header.typeflag == CHRTYPE
310 ? !S_ISCHR (stat_data.st_mode)
311 : current_header->header.typeflag == BLKTYPE
312 ? !S_ISBLK (stat_data.st_mode)
313 : /* current_header->header.typeflag == FIFOTYPE */
314 !S_ISFIFO (stat_data.st_mode))
315 {
316 report_difference (&current_stat_info, _("File type differs"));
317 return;
318 }
319
320 if ((current_header->header.typeflag == CHRTYPE
321 || current_header->header.typeflag == BLKTYPE)
322 && current_stat_info.stat.st_rdev != stat_data.st_rdev)
323 {
324 report_difference (&current_stat_info, _("Device number differs"));
325 return;
326 }
327
328 if ((current_stat_info.stat.st_mode & MODE_ALL) !=
329 (stat_data.st_mode & MODE_ALL))
330 report_difference (&current_stat_info, _("Mode differs"));
331 }
332
333 static int
334 dumpdir_cmp (const char *a, const char *b)
335 {
336 size_t len;
337
338 while (*a)
339 switch (*a)
340 {
341 case 'Y':
342 case 'N':
343 if (!strchr ("YN", *b))
344 return 1;
345 if (strcmp(a + 1, b + 1))
346 return 1;
347 len = strlen (a) + 1;
348 a += len;
349 b += len;
350 break;
351
352 case 'D':
353 if (strcmp(a, b))
354 return 1;
355 len = strlen (a) + 1;
356 a += len;
357 b += len;
358 break;
359
360 case 'R':
361 case 'T':
362 case 'X':
363 return *b;
364 }
365 return *b;
366 }
367
368 static void
369 diff_dumpdir (void)
370 {
371 const char *dumpdir_buffer;
372 dev_t dev = 0;
373 struct stat stat_data;
374
375 if (deref_stat (true, current_stat_info.file_name, &stat_data))
376 {
377 if (errno == ENOENT)
378 stat_warn (current_stat_info.file_name);
379 else
380 stat_error (current_stat_info.file_name);
381 }
382 else
383 dev = stat_data.st_dev;
384
385 dumpdir_buffer = directory_contents (scan_directory (&current_stat_info));
386
387 if (dumpdir_buffer)
388 {
389 if (dumpdir_cmp (current_stat_info.dumpdir, dumpdir_buffer))
390 report_difference (&current_stat_info, _("Contents differ"));
391 }
392 else
393 read_and_process (&current_stat_info, process_noop);
394 }
395
396 static void
397 diff_multivol (void)
398 {
399 struct stat stat_data;
400 int fd, status;
401 off_t offset;
402 int atime_flag =
403 (atime_preserve_option == system_atime_preserve
404 ? O_NOATIME
405 : 0);
406
407 if (current_stat_info.had_trailing_slash)
408 {
409 diff_dir ();
410 return;
411 }
412
413 if (!get_stat_data (current_stat_info.file_name, &stat_data))
414 return;
415
416 if (!S_ISREG (stat_data.st_mode))
417 {
418 report_difference (&current_stat_info, _("File type differs"));
419 skip_member ();
420 return;
421 }
422
423 offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
424 if (stat_data.st_size != current_stat_info.stat.st_size + offset)
425 {
426 report_difference (&current_stat_info, _("Size differs"));
427 skip_member ();
428 return;
429 }
430
431
432 fd = openat (chdir_fd, current_stat_info.file_name,
433 (O_RDONLY | O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK
434 | atime_flag));
435
436 if (fd < 0)
437 {
438 open_error (current_stat_info.file_name);
439 report_difference (&current_stat_info, NULL);
440 skip_member ();
441 return;
442 }
443
444 if (lseek (fd, offset, SEEK_SET) < 0)
445 {
446 seek_error_details (current_stat_info.file_name, offset);
447 report_difference (&current_stat_info, NULL);
448 return;
449 }
450
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 ();
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 if (removed_prefixes_p ())
530 {
531 WARN((0, 0,
532 _("Archive contains file names with leading prefixes removed.")));
533 WARN((0, 0,
534 _("Verification may fail to locate original files.")));
535 }
536
537 if (!diff_buffer)
538 diff_init ();
539
540 /* Verifying an archive is meant to check if the physical media got it
541 correctly, so try to defeat clever in-memory buffering pertaining to
542 this particular media. On Linux, for example, the floppy drive would
543 not even be accessed for the whole verification.
544
545 The code was using fsync only when the ioctl is unavailable, but
546 Marty Leisner says that the ioctl does not work when not preceded by
547 fsync. So, until we know better, or maybe to please Marty, let's do it
548 the unbelievable way :-). */
549
550 #if HAVE_FSYNC
551 fsync (archive);
552 #endif
553 #ifdef FDFLUSH
554 ioctl (archive, FDFLUSH);
555 #endif
556
557 #ifdef MTIOCTOP
558 {
559 struct mtop operation;
560 int status;
561
562 operation.mt_op = MTBSF;
563 operation.mt_count = 1;
564 if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
565 {
566 if (errno != EIO
567 || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
568 status < 0))
569 {
570 #endif
571 if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
572 {
573 /* Lseek failed. Try a different method. */
574 seek_warn (archive_name_array[0]);
575 return;
576 }
577 #ifdef MTIOCTOP
578 }
579 }
580 }
581 #endif
582
583 access_mode = ACCESS_READ;
584 now_verifying = 1;
585
586 flush_read ();
587 while (1)
588 {
589 enum read_header status = read_header (&current_header,
590 &current_stat_info,
591 read_header_auto);
592
593 if (status == HEADER_FAILURE)
594 {
595 int counter = 0;
596
597 do
598 {
599 counter++;
600 set_next_block_after (current_header);
601 status = read_header (&current_header, &current_stat_info,
602 read_header_auto);
603 }
604 while (status == HEADER_FAILURE);
605
606 ERROR ((0, 0,
607 ngettext ("VERIFY FAILURE: %d invalid header detected",
608 "VERIFY FAILURE: %d invalid headers detected",
609 counter), counter));
610 }
611 if (status == HEADER_END_OF_FILE)
612 break;
613 if (status == HEADER_ZERO_BLOCK)
614 {
615 set_next_block_after (current_header);
616 if (!ignore_zeros_option)
617 {
618 char buf[UINTMAX_STRSIZE_BOUND];
619
620 status = read_header (&current_header, &current_stat_info,
621 read_header_auto);
622 if (status == HEADER_ZERO_BLOCK)
623 break;
624 WARNOPT (WARN_ALONE_ZERO_BLOCK,
625 (0, 0, _("A lone zero block at %s"),
626 STRINGIFY_BIGINT (current_block_ordinal (), buf)));
627 }
628 }
629
630 diff_archive ();
631 tar_stat_destroy (&current_stat_info);
632 }
633
634 access_mode = ACCESS_WRITE;
635 now_verifying = 0;
636 }
This page took 0.059694 seconds and 4 git commands to generate.