]> Dogcows Code - chaz/tar/blob - src/compare.c
* NEWS: New option --atime-preserve=system, which uses O_NOATIME.
[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 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
24 #if HAVE_LINUX_FD_H
25 # include <linux/fd.h>
26 #endif
27
28 #include "common.h"
29 #include <quotearg.h>
30 #include <rmt.h>
31 #include <stdarg.h>
32
33 /* Nonzero if we are verifying at the moment. */
34 bool now_verifying;
35
36 /* File descriptor for the file we are diffing. */
37 static int diff_handle;
38
39 /* Area for reading file contents into. */
40 static char *diff_buffer;
41
42 /* Initialize for a diff operation. */
43 void
44 diff_init (void)
45 {
46 void *ptr;
47 diff_buffer = page_aligned_alloc (&ptr, record_size);
48 if (listed_incremental_option)
49 read_directory_file ();
50 }
51
52 /* Sigh about something that differs by writing a MESSAGE to stdlis,
53 given MESSAGE is nonzero. Also set the exit status if not already. */
54 void
55 report_difference (struct tar_stat_info *st __attribute__ ((unused)),
56 const char *fmt, ...)
57 {
58 if (fmt)
59 {
60 va_list ap;
61
62 fprintf (stdlis, "%s: ", quotearg_colon (current_stat_info.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. */
74 static int
75 process_noop (size_t size __attribute__ ((unused)),
76 char *data __attribute__ ((unused)))
77 {
78 return 1;
79 }
80
81 static int
82 process_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
115 static char *dumpdir_cursor;
116
117 static int
118 process_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
136 static void
137 read_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. */
169 static int
170 get_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 \f
188 static void
189 diff_dir (void)
190 {
191 struct stat stat_data;
192
193 if (!get_stat_data (current_stat_info.file_name, &stat_data))
194 return;
195
196 if (!S_ISDIR (stat_data.st_mode))
197 report_difference (&current_stat_info, _("File type differs"));
198 else if ((current_stat_info.stat.st_mode & MODE_ALL) !=
199 (stat_data.st_mode & MODE_ALL))
200 report_difference (&current_stat_info, _("Mode differs"));
201 }
202
203 static void
204 diff_file (void)
205 {
206 char const *file_name = current_stat_info.file_name;
207 struct stat stat_data;
208
209 if (!get_stat_data (file_name, &stat_data))
210 skip_member ();
211 else if (!S_ISREG (stat_data.st_mode))
212 {
213 report_difference (&current_stat_info, _("File type differs"));
214 skip_member ();
215 }
216 else
217 {
218 if ((current_stat_info.stat.st_mode & MODE_ALL) !=
219 (stat_data.st_mode & MODE_ALL))
220 report_difference (&current_stat_info, _("Mode differs"));
221
222 if (!sys_compare_uid (&stat_data, &current_stat_info.stat))
223 report_difference (&current_stat_info, _("Uid differs"));
224 if (!sys_compare_gid (&stat_data, &current_stat_info.stat))
225 report_difference (&current_stat_info, _("Gid differs"));
226
227 if (tar_timespec_cmp (get_stat_mtime (&stat_data),
228 current_stat_info.mtime))
229 report_difference (&current_stat_info, _("Mod time differs"));
230 if (current_header->header.typeflag != GNUTYPE_SPARSE
231 && stat_data.st_size != current_stat_info.stat.st_size)
232 {
233 report_difference (&current_stat_info, _("Size differs"));
234 skip_member ();
235 }
236 else
237 {
238 int atime_flag =
239 (atime_preserve_option == system_atime_preserve
240 ? O_NOATIME
241 : 0);
242
243 diff_handle = open (file_name, O_RDONLY | O_BINARY | atime_flag);
244
245 if (diff_handle < 0)
246 {
247 open_error (file_name);
248 skip_member ();
249 report_difference (&current_stat_info, NULL);
250 }
251 else
252 {
253 int status;
254
255 if (current_stat_info.is_sparse)
256 sparse_diff_file (diff_handle, &current_stat_info);
257 else
258 read_and_process (&current_stat_info, process_rawdata);
259
260 if (atime_preserve_option == replace_atime_preserve)
261 {
262 struct timespec ts[2];
263 ts[0] = get_stat_atime (&stat_data);
264 ts[1] = get_stat_mtime (&stat_data);
265 if (set_file_atime (diff_handle, file_name, ts) != 0)
266 utime_error (file_name);
267 }
268
269 status = close (diff_handle);
270 if (status != 0)
271 close_error (file_name);
272 }
273 }
274 }
275 }
276
277 static void
278 diff_link (void)
279 {
280 struct stat file_data;
281 struct stat link_data;
282
283 if (get_stat_data (current_stat_info.file_name, &file_data)
284 && get_stat_data (current_stat_info.link_name, &link_data)
285 && !sys_compare_links (&file_data, &link_data))
286 report_difference (&current_stat_info,
287 _("Not linked to %s"),
288 quote (current_stat_info.link_name));
289 }
290
291 #ifdef HAVE_READLINK
292 static void
293 diff_symlink (void)
294 {
295 size_t len = strlen (current_stat_info.link_name);
296 char *linkbuf = alloca (len + 1);
297
298 int status = readlink (current_stat_info.file_name, linkbuf, len + 1);
299
300 if (status < 0)
301 {
302 if (errno == ENOENT)
303 readlink_warn (current_stat_info.file_name);
304 else
305 readlink_error (current_stat_info.file_name);
306 report_difference (&current_stat_info, NULL);
307 }
308 else if (status != len
309 || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
310 report_difference (&current_stat_info, _("Symlink differs"));
311 }
312 #endif
313
314 static void
315 diff_special (void)
316 {
317 struct stat stat_data;
318
319 /* FIXME: deal with umask. */
320
321 if (!get_stat_data (current_stat_info.file_name, &stat_data))
322 return;
323
324 if (current_header->header.typeflag == CHRTYPE
325 ? !S_ISCHR (stat_data.st_mode)
326 : current_header->header.typeflag == BLKTYPE
327 ? !S_ISBLK (stat_data.st_mode)
328 : /* current_header->header.typeflag == FIFOTYPE */
329 !S_ISFIFO (stat_data.st_mode))
330 {
331 report_difference (&current_stat_info, _("File type differs"));
332 return;
333 }
334
335 if ((current_header->header.typeflag == CHRTYPE
336 || current_header->header.typeflag == BLKTYPE)
337 && current_stat_info.stat.st_rdev != stat_data.st_rdev)
338 {
339 report_difference (&current_stat_info, _("Device number differs"));
340 return;
341 }
342
343 if ((current_stat_info.stat.st_mode & MODE_ALL) !=
344 (stat_data.st_mode & MODE_ALL))
345 report_difference (&current_stat_info, _("Mode differs"));
346 }
347
348 static void
349 diff_dumpdir (void)
350 {
351 char *dumpdir_buffer;
352 dev_t dev = 0;
353 struct stat stat;
354
355 if (deref_stat (true, current_stat_info.file_name, &stat))
356 {
357 if (errno == ENOENT)
358 stat_warn (current_stat_info.file_name);
359 else
360 stat_error (current_stat_info.file_name);
361 }
362 else
363 dev = stat.st_dev;
364
365 dumpdir_buffer = get_directory_contents (current_stat_info.file_name, dev);
366
367 if (dumpdir_buffer)
368 {
369 dumpdir_cursor = dumpdir_buffer;
370 read_and_process (&current_stat_info, process_dumpdir);
371 free (dumpdir_buffer);
372 }
373 else
374 read_and_process (&current_stat_info, process_noop);
375 }
376
377 static void
378 diff_multivol (void)
379 {
380 struct stat stat_data;
381 int fd, status;
382 off_t offset;
383
384 if (current_stat_info.had_trailing_slash)
385 {
386 diff_dir ();
387 return;
388 }
389
390 if (!get_stat_data (current_stat_info.file_name, &stat_data))
391 return;
392
393 if (!S_ISREG (stat_data.st_mode))
394 {
395 report_difference (&current_stat_info, _("File type differs"));
396 skip_member ();
397 return;
398 }
399
400 offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
401 if (stat_data.st_size != current_stat_info.stat.st_size + offset)
402 {
403 report_difference (&current_stat_info, _("Size differs"));
404 skip_member ();
405 return;
406 }
407
408 fd = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
409
410 if (fd < 0)
411 {
412 open_error (current_stat_info.file_name);
413 report_difference (&current_stat_info, NULL);
414 skip_member ();
415 return;
416 }
417
418 if (lseek (fd, offset, SEEK_SET) < 0)
419 {
420 seek_error_details (current_stat_info.file_name, offset);
421 report_difference (&current_stat_info, NULL);
422 return;
423 }
424
425 read_and_process (&current_stat_info, process_rawdata);
426
427 status = close (fd);
428 if (status != 0)
429 close_error (current_stat_info.file_name);
430 }
431
432 /* Diff a file against the archive. */
433 void
434 diff_archive (void)
435 {
436
437 set_next_block_after (current_header);
438 decode_header (current_header, &current_stat_info, &current_format, 1);
439
440 /* Print the block from current_header and current_stat_info. */
441
442 if (verbose_option)
443 {
444 if (now_verifying)
445 fprintf (stdlis, _("Verify "));
446 print_header (&current_stat_info, -1);
447 }
448
449 switch (current_header->header.typeflag)
450 {
451 default:
452 ERROR ((0, 0, _("%s: Unknown file type `%c', diffed as normal file"),
453 quotearg_colon (current_stat_info.file_name),
454 current_header->header.typeflag));
455 /* Fall through. */
456
457 case AREGTYPE:
458 case REGTYPE:
459 case GNUTYPE_SPARSE:
460 case CONTTYPE:
461
462 /* Appears to be a file. See if it's really a directory. */
463
464 if (current_stat_info.had_trailing_slash)
465 diff_dir ();
466 else
467 diff_file ();
468 break;
469
470 case LNKTYPE:
471 diff_link ();
472 break;
473
474 #ifdef HAVE_READLINK
475 case SYMTYPE:
476 diff_symlink ();
477 break;
478 #endif
479
480 case CHRTYPE:
481 case BLKTYPE:
482 case FIFOTYPE:
483 diff_special ();
484 break;
485
486 case GNUTYPE_DUMPDIR:
487 diff_dumpdir ();
488 /* Fall through. */
489
490 case DIRTYPE:
491 diff_dir ();
492 break;
493
494 case GNUTYPE_VOLHDR:
495 break;
496
497 case GNUTYPE_MULTIVOL:
498 diff_multivol ();
499 }
500 }
501
502 void
503 verify_volume (void)
504 {
505 if (removed_prefixes_p ())
506 {
507 WARN((0, 0,
508 _("Archive contains file names with leading prefixes removed.")));
509 WARN((0, 0,
510 _("Verification may fail to locate original files.")));
511 }
512
513 if (!diff_buffer)
514 diff_init ();
515
516 /* Verifying an archive is meant to check if the physical media got it
517 correctly, so try to defeat clever in-memory buffering pertaining to
518 this particular media. On Linux, for example, the floppy drive would
519 not even be accessed for the whole verification.
520
521 The code was using fsync only when the ioctl is unavailable, but
522 Marty Leisner says that the ioctl does not work when not preceded by
523 fsync. So, until we know better, or maybe to please Marty, let's do it
524 the unbelievable way :-). */
525
526 #if HAVE_FSYNC
527 fsync (archive);
528 #endif
529 #ifdef FDFLUSH
530 ioctl (archive, FDFLUSH);
531 #endif
532
533 #ifdef MTIOCTOP
534 {
535 struct mtop operation;
536 int status;
537
538 operation.mt_op = MTBSF;
539 operation.mt_count = 1;
540 if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
541 {
542 if (errno != EIO
543 || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
544 status < 0))
545 {
546 #endif
547 if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
548 {
549 /* Lseek failed. Try a different method. */
550 seek_warn (archive_name_array[0]);
551 return;
552 }
553 #ifdef MTIOCTOP
554 }
555 }
556 }
557 #endif
558
559 access_mode = ACCESS_READ;
560 now_verifying = 1;
561
562 flush_read ();
563 while (1)
564 {
565 enum read_header status = read_header (false);
566
567 if (status == HEADER_FAILURE)
568 {
569 int counter = 0;
570
571 do
572 {
573 counter++;
574 set_next_block_after (current_header);
575 status = read_header (false);
576 }
577 while (status == HEADER_FAILURE);
578
579 ERROR ((0, 0,
580 ngettext ("VERIFY FAILURE: %d invalid header detected",
581 "VERIFY FAILURE: %d invalid headers detected",
582 counter), counter));
583 }
584 if (status == HEADER_ZERO_BLOCK || status == HEADER_END_OF_FILE)
585 break;
586
587 diff_archive ();
588 tar_stat_destroy (&current_stat_info);
589 xheader_destroy (&extended_header);
590 }
591
592 access_mode = ACCESS_WRITE;
593 now_verifying = 0;
594 }
This page took 0.072407 seconds and 5 git commands to generate.