source: trunk/essentials/app-arch/tar/src/compare.c

Last change on this file was 3342, checked in by bird, 18 years ago

tar 1.16.1

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