(root)/
tar-1.35/
src/
compare.c
       1  /* Diff files from a tar archive.
       2  
       3     Copyright 1988-2023 Free Software Foundation, Inc.
       4  
       5     This file is part of GNU tar.
       6  
       7     GNU tar is free software; you can redistribute it and/or modify
       8     it under the terms of the GNU General Public License as published by
       9     the Free Software Foundation; either version 3 of the License, or
      10     (at your option) any later version.
      11  
      12     GNU tar is distributed in the hope that it will be useful,
      13     but WITHOUT ANY WARRANTY; without even the implied warranty of
      14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      15     GNU General Public License for more details.
      16  
      17     You should have received a copy of the GNU General Public License
      18     along with this program.  If not, see <http://www.gnu.org/licenses/>.
      19  
      20     Written by John Gilmore, on 1987-04-30.  */
      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  enum { QUOTE_ARG, QUOTE_NAME };
      54  
      55  /* Sigh about something that differs by writing a MESSAGE to stdlis,
      56     given MESSAGE is nonzero.  Also set the exit status if not already.  */
      57  void
      58  report_difference (struct tar_stat_info *st, const char *fmt, ...)
      59  {
      60    if (fmt)
      61      {
      62        va_list ap;
      63  
      64        fprintf (stdlis, "%s: ", quote_n_colon (QUOTE_NAME, st->file_name));
      65        va_start (ap, fmt);
      66        vfprintf (stdlis, fmt, ap);
      67        va_end (ap);
      68        fprintf (stdlis, "\n");
      69      }
      70  
      71    set_exit_status (TAREXIT_DIFFERS);
      72  }
      73  
      74  /* Take a buffer returned by read_and_process and do nothing with it.  */
      75  static int
      76  process_noop (MAYBE_UNUSED size_t size, MAYBE_UNUSED char *data)
      77  {
      78    return 1;
      79  }
      80  
      81  static int
      82  process_rawdata (size_t bytes, char *buffer)
      83  {
      84    size_t status = blocking_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  
     119  static void
     120  read_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_read (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.  */
     152  static int
     153  get_stat_data (char const *file_name, struct stat *stat_data)
     154  {
     155    int status = deref_stat (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  
     171  static void
     172  diff_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  
     186  static void
     187  diff_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  	  diff_handle = openat (chdir_fd, file_name, open_read_flags);
     222  
     223  	  if (diff_handle < 0)
     224  	    {
     225  	      open_error (file_name);
     226  	      skip_member ();
     227  	      report_difference (&current_stat_info, NULL);
     228  	    }
     229  	  else
     230  	    {
     231  	      int status;
     232  
     233  	      if (current_stat_info.is_sparse)
     234  		sparse_diff_file (diff_handle, &current_stat_info);
     235  	      else
     236  		read_and_process (&current_stat_info, process_rawdata);
     237  
     238  	      if (atime_preserve_option == replace_atime_preserve
     239  		  && stat_data.st_size != 0)
     240  		{
     241  		  struct timespec atime = get_stat_atime (&stat_data);
     242  		  if (set_file_atime (diff_handle, chdir_fd, file_name, atime)
     243  		      != 0)
     244  		    utime_error (file_name);
     245  		}
     246  
     247  	      status = close (diff_handle);
     248  	      if (status != 0)
     249  		close_error (file_name);
     250  	    }
     251  	}
     252      }
     253  }
     254  
     255  static void
     256  diff_link (void)
     257  {
     258    struct stat file_data;
     259    struct stat link_data;
     260  
     261    if (get_stat_data (current_stat_info.file_name, &file_data)
     262        && get_stat_data (current_stat_info.link_name, &link_data)
     263        && !sys_compare_links (&file_data, &link_data))
     264      report_difference (&current_stat_info,
     265  		       _("Not linked to %s"),
     266  		       quote_n_colon (QUOTE_ARG,
     267  				      current_stat_info.link_name));
     268  }
     269  
     270  #ifdef HAVE_READLINK
     271  static void
     272  diff_symlink (void)
     273  {
     274    char buf[1024];
     275    size_t len = strlen (current_stat_info.link_name);
     276    char *linkbuf = len < sizeof buf ? buf : xmalloc (len + 1);
     277  
     278    ssize_t status = readlinkat (chdir_fd, current_stat_info.file_name,
     279  			       linkbuf, len + 1);
     280  
     281    if (status < 0)
     282      {
     283        if (errno == ENOENT)
     284  	readlink_warn (current_stat_info.file_name);
     285        else
     286  	readlink_error (current_stat_info.file_name);
     287        report_difference (&current_stat_info, NULL);
     288      }
     289    else if (status != len
     290  	   || memcmp (current_stat_info.link_name, linkbuf, len) != 0)
     291      report_difference (&current_stat_info, _("Symlink differs"));
     292  
     293    if (linkbuf != buf)
     294      free (linkbuf);
     295  }
     296  #endif
     297  
     298  static void
     299  diff_special (void)
     300  {
     301    struct stat stat_data;
     302  
     303    /* FIXME: deal with umask.  */
     304  
     305    if (!get_stat_data (current_stat_info.file_name, &stat_data))
     306      return;
     307  
     308    if (current_header->header.typeflag == CHRTYPE
     309        ? !S_ISCHR (stat_data.st_mode)
     310        : current_header->header.typeflag == BLKTYPE
     311        ? !S_ISBLK (stat_data.st_mode)
     312        : /* current_header->header.typeflag == FIFOTYPE */
     313        !S_ISFIFO (stat_data.st_mode))
     314      {
     315        report_difference (&current_stat_info, _("File type differs"));
     316        return;
     317      }
     318  
     319    if ((current_header->header.typeflag == CHRTYPE
     320         || current_header->header.typeflag == BLKTYPE)
     321        && current_stat_info.stat.st_rdev != stat_data.st_rdev)
     322      {
     323        report_difference (&current_stat_info, _("Device number differs"));
     324        return;
     325      }
     326  
     327    if ((current_stat_info.stat.st_mode & MODE_ALL) !=
     328        (stat_data.st_mode & MODE_ALL))
     329      report_difference (&current_stat_info, _("Mode differs"));
     330  }
     331  
     332  static int
     333  dumpdir_cmp (const char *a, const char *b)
     334  {
     335    size_t len;
     336  
     337    while (*a)
     338      switch (*a)
     339        {
     340        case 'Y':
     341        case 'N':
     342  	if (!strchr ("YN", *b))
     343  	  return 1;
     344  	if (strcmp(a + 1, b + 1))
     345  	  return 1;
     346  	len = strlen (a) + 1;
     347  	a += len;
     348  	b += len;
     349  	break;
     350  
     351        case 'D':
     352  	if (strcmp(a, b))
     353  	  return 1;
     354  	len = strlen (a) + 1;
     355  	a += len;
     356  	b += len;
     357  	break;
     358  
     359        case 'R':
     360        case 'T':
     361        case 'X':
     362  	return *b;
     363        }
     364    return *b;
     365  }
     366  
     367  static void
     368  diff_dumpdir (struct tar_stat_info *dir)
     369  {
     370    const char *dumpdir_buffer;
     371  
     372    if (dir->fd == 0)
     373      {
     374        void (*diag) (char const *) = NULL;
     375        int fd = subfile_open (dir->parent, dir->orig_file_name, open_read_flags);
     376        if (fd < 0)
     377  	diag = open_diag;
     378        else if (fstat (fd, &dir->stat))
     379          {
     380  	  diag = stat_diag;
     381            close (fd);
     382          }
     383        else
     384  	dir->fd = fd;
     385        if (diag)
     386  	{
     387  	  file_removed_diag (dir->orig_file_name, false, diag);
     388  	  return;
     389  	}
     390      }
     391    dumpdir_buffer = directory_contents (scan_directory (dir));
     392  
     393    if (dumpdir_buffer)
     394      {
     395        if (dumpdir_cmp (dir->dumpdir, dumpdir_buffer))
     396  	report_difference (dir, _("Contents differ"));
     397      }
     398    else
     399      read_and_process (dir, process_noop);
     400  }
     401  
     402  static void
     403  diff_multivol (void)
     404  {
     405    struct stat stat_data;
     406    int fd, status;
     407    off_t offset;
     408  
     409    if (current_stat_info.had_trailing_slash)
     410      {
     411        diff_dir ();
     412        return;
     413      }
     414  
     415    if (!get_stat_data (current_stat_info.file_name, &stat_data))
     416      return;
     417  
     418    if (!S_ISREG (stat_data.st_mode))
     419      {
     420        report_difference (&current_stat_info, _("File type differs"));
     421        skip_member ();
     422        return;
     423      }
     424  
     425    offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
     426    if (offset < 0
     427        || INT_ADD_OVERFLOW (current_stat_info.stat.st_size, offset)
     428        || stat_data.st_size != current_stat_info.stat.st_size + offset)
     429      {
     430        report_difference (&current_stat_info, _("Size differs"));
     431        skip_member ();
     432        return;
     433      }
     434  
     435  
     436    fd = openat (chdir_fd, current_stat_info.file_name, open_read_flags);
     437  
     438    if (fd < 0)
     439      {
     440        open_error (current_stat_info.file_name);
     441        report_difference (&current_stat_info, NULL);
     442        skip_member ();
     443        return;
     444      }
     445  
     446    if (lseek (fd, offset, SEEK_SET) < 0)
     447      {
     448        seek_error_details (current_stat_info.file_name, offset);
     449        report_difference (&current_stat_info, NULL);
     450      }
     451    else
     452      read_and_process (&current_stat_info, process_rawdata);
     453  
     454    status = close (fd);
     455    if (status != 0)
     456      close_error (current_stat_info.file_name);
     457  }
     458  
     459  /* Diff a file against the archive.  */
     460  void
     461  diff_archive (void)
     462  {
     463  
     464    set_next_block_after (current_header);
     465  
     466    /* Print the block from current_header and current_stat_info.  */
     467  
     468    if (verbose_option)
     469      {
     470        if (now_verifying)
     471  	fprintf (stdlis, _("Verify "));
     472        print_header (&current_stat_info, current_header, -1);
     473      }
     474  
     475    switch (current_header->header.typeflag)
     476      {
     477      default:
     478        ERROR ((0, 0, _("%s: Unknown file type '%c', diffed as normal file"),
     479  	      quotearg_colon (current_stat_info.file_name),
     480  	      current_header->header.typeflag));
     481        FALLTHROUGH;
     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 (&current_stat_info);
     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    int may_fail = 0;
     530    if (removed_prefixes_p ())
     531      {
     532        WARN((0, 0,
     533  	    _("Archive contains file names with leading prefixes removed.")));
     534        may_fail = 1;
     535      }
     536    if (transform_program_p ())
     537      {
     538        WARN((0, 0,
     539  	    _("Archive contains transformed file names.")));
     540        may_fail = 1;
     541      }
     542    if (may_fail)
     543      WARN((0, 0,
     544  	  _("Verification may fail to locate original files.")));
     545  
     546    clear_directory_table ();
     547  
     548    if (!diff_buffer)
     549      diff_init ();
     550  
     551    /* Verifying an archive is meant to check if the physical media got it
     552       correctly, so try to defeat clever in-memory buffering pertaining to
     553       this particular media.  On Linux, for example, the floppy drive would
     554       not even be accessed for the whole verification.
     555  
     556       The code was using fsync only when the ioctl is unavailable, but
     557       Marty Leisner says that the ioctl does not work when not preceded by
     558       fsync.  So, until we know better, or maybe to please Marty, let's do it
     559       the unbelievable way :-).  */
     560  
     561  #if HAVE_FSYNC
     562    fsync (archive);
     563  #endif
     564  #ifdef FDFLUSH
     565    ioctl (archive, FDFLUSH);
     566  #endif
     567  
     568    if (!mtioseek (true, -1) && rmtlseek (archive, 0, SEEK_SET) != 0)
     569      {
     570        /* Lseek failed.  Try a different method.  */
     571        seek_warn (archive_name_array[0]);
     572        return;
     573      }
     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 (&current_header,
     582                                               &current_stat_info,
     583                                               read_header_auto);
     584  
     585        if (status == HEADER_FAILURE)
     586  	{
     587  	  int counter = 0;
     588  
     589  	  do
     590  	    {
     591  	      counter++;
     592  	      set_next_block_after (current_header);
     593  	      status = read_header (&current_header, &current_stat_info,
     594  	                            read_header_auto);
     595  	    }
     596  	  while (status == HEADER_FAILURE);
     597  
     598  	  ERROR ((0, 0,
     599  		  ngettext ("VERIFY FAILURE: %d invalid header detected",
     600  			    "VERIFY FAILURE: %d invalid headers detected",
     601  			    counter), counter));
     602  	}
     603        if (status == HEADER_END_OF_FILE)
     604  	break;
     605        if (status == HEADER_ZERO_BLOCK)
     606  	{
     607  	  set_next_block_after (current_header);
     608            if (!ignore_zeros_option)
     609              {
     610  	      char buf[UINTMAX_STRSIZE_BOUND];
     611  
     612  	      status = read_header (&current_header, &current_stat_info,
     613  	                            read_header_auto);
     614  	      if (status == HEADER_ZERO_BLOCK)
     615  	        break;
     616  	      WARNOPT (WARN_ALONE_ZERO_BLOCK,
     617  		       (0, 0, _("A lone zero block at %s"),
     618  			STRINGIFY_BIGINT (current_block_ordinal (), buf)));
     619              }
     620  	  continue;
     621  	}
     622  
     623        decode_header (current_header, &current_stat_info, &current_format, 1);
     624        diff_archive ();
     625        tar_stat_destroy (&current_stat_info);
     626      }
     627  
     628    access_mode = ACCESS_WRITE;
     629    now_verifying = 0;
     630  }