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