]> Dogcows Code - chaz/tar/blob - src/compare.c
tar: more reliable directory traversal when creating archives
[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 diff_handle = open (file_name, open_read_flags);
221
222 if (diff_handle < 0)
223 {
224 open_error (file_name);
225 skip_member ();
226 report_difference (&current_stat_info, NULL);
227 }
228 else
229 {
230 int status;
231
232 if (current_stat_info.is_sparse)
233 sparse_diff_file (diff_handle, &current_stat_info);
234 else
235 read_and_process (&current_stat_info, process_rawdata);
236
237 if (atime_preserve_option == replace_atime_preserve)
238 {
239 struct timespec ts[2];
240 ts[0] = get_stat_atime (&stat_data);
241 ts[1] = get_stat_mtime (&stat_data);
242 if (set_file_atime (diff_handle, file_name, ts) != 0)
243 utime_error (file_name);
244 }
245
246 status = close (diff_handle);
247 if (status != 0)
248 close_error (file_name);
249 }
250 }
251 }
252 }
253
254 static void
255 diff_link (void)
256 {
257 struct stat file_data;
258 struct stat link_data;
259
260 if (get_stat_data (current_stat_info.file_name, &file_data)
261 && get_stat_data (current_stat_info.link_name, &link_data)
262 && !sys_compare_links (&file_data, &link_data))
263 report_difference (&current_stat_info,
264 _("Not linked to %s"),
265 quote (current_stat_info.link_name));
266 }
267
268 #ifdef HAVE_READLINK
269 static void
270 diff_symlink (void)
271 {
272 size_t len = strlen (current_stat_info.link_name);
273 char *linkbuf = alloca (len + 1);
274
275 int status = readlink (current_stat_info.file_name, linkbuf, len + 1);
276
277 if (status < 0)
278 {
279 if (errno == ENOENT)
280 readlink_warn (current_stat_info.file_name);
281 else
282 readlink_error (current_stat_info.file_name);
283 report_difference (&current_stat_info, NULL);
284 }
285 else if (status != len
286 || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
287 report_difference (&current_stat_info, _("Symlink differs"));
288 }
289 #endif
290
291 static void
292 diff_special (void)
293 {
294 struct stat stat_data;
295
296 /* FIXME: deal with umask. */
297
298 if (!get_stat_data (current_stat_info.file_name, &stat_data))
299 return;
300
301 if (current_header->header.typeflag == CHRTYPE
302 ? !S_ISCHR (stat_data.st_mode)
303 : current_header->header.typeflag == BLKTYPE
304 ? !S_ISBLK (stat_data.st_mode)
305 : /* current_header->header.typeflag == FIFOTYPE */
306 !S_ISFIFO (stat_data.st_mode))
307 {
308 report_difference (&current_stat_info, _("File type differs"));
309 return;
310 }
311
312 if ((current_header->header.typeflag == CHRTYPE
313 || current_header->header.typeflag == BLKTYPE)
314 && current_stat_info.stat.st_rdev != stat_data.st_rdev)
315 {
316 report_difference (&current_stat_info, _("Device number differs"));
317 return;
318 }
319
320 if ((current_stat_info.stat.st_mode & MODE_ALL) !=
321 (stat_data.st_mode & MODE_ALL))
322 report_difference (&current_stat_info, _("Mode differs"));
323 }
324
325 static int
326 dumpdir_cmp (const char *a, const char *b)
327 {
328 size_t len;
329
330 while (*a)
331 switch (*a)
332 {
333 case 'Y':
334 case 'N':
335 if (!strchr ("YN", *b))
336 return 1;
337 if (strcmp(a + 1, b + 1))
338 return 1;
339 len = strlen (a) + 1;
340 a += len;
341 b += len;
342 break;
343
344 case 'D':
345 if (strcmp(a, b))
346 return 1;
347 len = strlen (a) + 1;
348 a += len;
349 b += len;
350 break;
351
352 case 'R':
353 case 'T':
354 case 'X':
355 return *b;
356 }
357 return *b;
358 }
359
360 static void
361 diff_dumpdir (void)
362 {
363 const char *dumpdir_buffer;
364 dev_t dev = 0;
365 struct stat stat_data;
366
367 if (deref_stat (true, current_stat_info.file_name, &stat_data))
368 {
369 if (errno == ENOENT)
370 stat_warn (current_stat_info.file_name);
371 else
372 stat_error (current_stat_info.file_name);
373 }
374 else
375 dev = stat_data.st_dev;
376
377 dumpdir_buffer = directory_contents (scan_directory (&current_stat_info));
378
379 if (dumpdir_buffer)
380 {
381 if (dumpdir_cmp (current_stat_info.dumpdir, dumpdir_buffer))
382 report_difference (&current_stat_info, _("Contents differ"));
383 }
384 else
385 read_and_process (&current_stat_info, process_noop);
386 }
387
388 static void
389 diff_multivol (void)
390 {
391 struct stat stat_data;
392 int fd, status;
393 off_t offset;
394
395 if (current_stat_info.had_trailing_slash)
396 {
397 diff_dir ();
398 return;
399 }
400
401 if (!get_stat_data (current_stat_info.file_name, &stat_data))
402 return;
403
404 if (!S_ISREG (stat_data.st_mode))
405 {
406 report_difference (&current_stat_info, _("File type differs"));
407 skip_member ();
408 return;
409 }
410
411 offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
412 if (stat_data.st_size != current_stat_info.stat.st_size + offset)
413 {
414 report_difference (&current_stat_info, _("Size differs"));
415 skip_member ();
416 return;
417 }
418
419 fd = open (current_stat_info.file_name, open_read_flags);
420
421 if (fd < 0)
422 {
423 open_error (current_stat_info.file_name);
424 report_difference (&current_stat_info, NULL);
425 skip_member ();
426 return;
427 }
428
429 if (lseek (fd, offset, SEEK_SET) < 0)
430 {
431 seek_error_details (current_stat_info.file_name, offset);
432 report_difference (&current_stat_info, NULL);
433 return;
434 }
435
436 read_and_process (&current_stat_info, process_rawdata);
437
438 status = close (fd);
439 if (status != 0)
440 close_error (current_stat_info.file_name);
441 }
442
443 /* Diff a file against the archive. */
444 void
445 diff_archive (void)
446 {
447
448 set_next_block_after (current_header);
449
450 /* Print the block from current_header and current_stat_info. */
451
452 if (verbose_option)
453 {
454 if (now_verifying)
455 fprintf (stdlis, _("Verify "));
456 print_header (&current_stat_info, current_header, -1);
457 }
458
459 switch (current_header->header.typeflag)
460 {
461 default:
462 ERROR ((0, 0, _("%s: Unknown file type `%c', diffed as normal file"),
463 quotearg_colon (current_stat_info.file_name),
464 current_header->header.typeflag));
465 /* Fall through. */
466
467 case AREGTYPE:
468 case REGTYPE:
469 case GNUTYPE_SPARSE:
470 case CONTTYPE:
471
472 /* Appears to be a file. See if it's really a directory. */
473
474 if (current_stat_info.had_trailing_slash)
475 diff_dir ();
476 else
477 diff_file ();
478 break;
479
480 case LNKTYPE:
481 diff_link ();
482 break;
483
484 #ifdef HAVE_READLINK
485 case SYMTYPE:
486 diff_symlink ();
487 break;
488 #endif
489
490 case CHRTYPE:
491 case BLKTYPE:
492 case FIFOTYPE:
493 diff_special ();
494 break;
495
496 case GNUTYPE_DUMPDIR:
497 case DIRTYPE:
498 if (is_dumpdir (&current_stat_info))
499 diff_dumpdir ();
500 diff_dir ();
501 break;
502
503 case GNUTYPE_VOLHDR:
504 break;
505
506 case GNUTYPE_MULTIVOL:
507 diff_multivol ();
508 }
509 }
510
511 void
512 verify_volume (void)
513 {
514 if (removed_prefixes_p ())
515 {
516 WARN((0, 0,
517 _("Archive contains file names with leading prefixes removed.")));
518 WARN((0, 0,
519 _("Verification may fail to locate original files.")));
520 }
521
522 if (!diff_buffer)
523 diff_init ();
524
525 /* Verifying an archive is meant to check if the physical media got it
526 correctly, so try to defeat clever in-memory buffering pertaining to
527 this particular media. On Linux, for example, the floppy drive would
528 not even be accessed for the whole verification.
529
530 The code was using fsync only when the ioctl is unavailable, but
531 Marty Leisner says that the ioctl does not work when not preceded by
532 fsync. So, until we know better, or maybe to please Marty, let's do it
533 the unbelievable way :-). */
534
535 #if HAVE_FSYNC
536 fsync (archive);
537 #endif
538 #ifdef FDFLUSH
539 ioctl (archive, FDFLUSH);
540 #endif
541
542 #ifdef MTIOCTOP
543 {
544 struct mtop operation;
545 int status;
546
547 operation.mt_op = MTBSF;
548 operation.mt_count = 1;
549 if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
550 {
551 if (errno != EIO
552 || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
553 status < 0))
554 {
555 #endif
556 if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
557 {
558 /* Lseek failed. Try a different method. */
559 seek_warn (archive_name_array[0]);
560 return;
561 }
562 #ifdef MTIOCTOP
563 }
564 }
565 }
566 #endif
567
568 access_mode = ACCESS_READ;
569 now_verifying = 1;
570
571 flush_read ();
572 while (1)
573 {
574 enum read_header status = read_header (&current_header,
575 &current_stat_info,
576 read_header_auto);
577
578 if (status == HEADER_FAILURE)
579 {
580 int counter = 0;
581
582 do
583 {
584 counter++;
585 set_next_block_after (current_header);
586 status = read_header (&current_header, &current_stat_info,
587 read_header_auto);
588 }
589 while (status == HEADER_FAILURE);
590
591 ERROR ((0, 0,
592 ngettext ("VERIFY FAILURE: %d invalid header detected",
593 "VERIFY FAILURE: %d invalid headers detected",
594 counter), counter));
595 }
596 if (status == HEADER_END_OF_FILE)
597 break;
598 if (status == HEADER_ZERO_BLOCK)
599 {
600 set_next_block_after (current_header);
601 if (!ignore_zeros_option)
602 {
603 char buf[UINTMAX_STRSIZE_BOUND];
604
605 status = read_header (&current_header, &current_stat_info,
606 read_header_auto);
607 if (status == HEADER_ZERO_BLOCK)
608 break;
609 WARNOPT (WARN_ALONE_ZERO_BLOCK,
610 (0, 0, _("A lone zero block at %s"),
611 STRINGIFY_BIGINT (current_block_ordinal (), buf)));
612 }
613 }
614
615 diff_archive ();
616 tar_stat_destroy (&current_stat_info);
617 }
618
619 access_mode = ACCESS_WRITE;
620 now_verifying = 0;
621 }
This page took 0.068756 seconds and 5 git commands to generate.