Blame src/dir.c

Packit 33f14e
/* Read, sort and compare two directories.  Used for GNU DIFF.
Packit 33f14e
Packit 33f14e
   Copyright (C) 1988-1989, 1992-1995, 1998, 2001-2002, 2004, 2006-2007,
Packit 33f14e
   2009-2013, 2015-2017 Free Software Foundation, Inc.
Packit 33f14e
Packit 33f14e
   This file is part of GNU DIFF.
Packit 33f14e
Packit 33f14e
   This program is free software: you can redistribute it and/or modify
Packit 33f14e
   it under the terms of the GNU General Public License as published by
Packit 33f14e
   the Free Software Foundation, either version 3 of the License, or
Packit 33f14e
   (at your option) any later version.
Packit 33f14e
Packit 33f14e
   This program is distributed in the hope that it will be useful,
Packit 33f14e
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 33f14e
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 33f14e
   GNU General Public License for more details.
Packit 33f14e
Packit 33f14e
   You should have received a copy of the GNU General Public License
Packit 33f14e
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
Packit 33f14e
Packit 33f14e
#include "diff.h"
Packit 33f14e
#include <error.h>
Packit 33f14e
#include <exclude.h>
Packit 33f14e
#include <filenamecat.h>
Packit 33f14e
#include <setjmp.h>
Packit 33f14e
#include <xalloc.h>
Packit 33f14e
Packit 33f14e
/* Read the directory named by DIR and store into DIRDATA a sorted vector
Packit 33f14e
   of filenames for its contents.  DIR->desc == -1 means this directory is
Packit 33f14e
   known to be nonexistent, so set DIRDATA to an empty vector.
Packit 33f14e
   Return -1 (setting errno) if error, 0 otherwise.  */
Packit 33f14e
Packit 33f14e
struct dirdata
Packit 33f14e
{
Packit 33f14e
  size_t nnames;	/* Number of names.  */
Packit 33f14e
  char const **names;	/* Sorted names of files in dir, followed by 0.  */
Packit 33f14e
  char *data;	/* Allocated storage for file names.  */
Packit 33f14e
};
Packit 33f14e
Packit 33f14e
/* Whether file names in directories should be compared with
Packit 33f14e
   locale-specific sorting.  */
Packit 33f14e
static bool locale_specific_sorting;
Packit 33f14e
Packit 33f14e
/* Where to go if locale-specific sorting fails.  */
Packit 33f14e
static jmp_buf failed_locale_specific_sorting;
Packit 33f14e
Packit 33f14e
static bool dir_loop (struct comparison const *, int);
Packit 33f14e
Packit 33f14e
Packit 33f14e
/* Read a directory and get its vector of names.  */
Packit 33f14e
Packit 33f14e
static bool
Packit 33f14e
dir_read (struct file_data const *dir, struct dirdata *dirdata)
Packit 33f14e
{
Packit 33f14e
  register struct dirent *next;
Packit 33f14e
  register size_t i;
Packit 33f14e
Packit 33f14e
  /* Address of block containing the files that are described.  */
Packit 33f14e
  char const **names;
Packit 33f14e
Packit 33f14e
  /* Number of files in directory.  */
Packit 33f14e
  size_t nnames;
Packit 33f14e
Packit 33f14e
  /* Allocated and used storage for file name data.  */
Packit 33f14e
  char *data;
Packit 33f14e
  size_t data_alloc, data_used;
Packit 33f14e
Packit 33f14e
  dirdata->names = 0;
Packit 33f14e
  dirdata->data = 0;
Packit 33f14e
  nnames = 0;
Packit 33f14e
  data = 0;
Packit 33f14e
Packit 33f14e
  if (dir->desc != -1)
Packit 33f14e
    {
Packit 33f14e
      /* Open the directory and check for errors.  */
Packit 33f14e
      register DIR *reading = opendir (dir->name);
Packit 33f14e
      if (!reading)
Packit 33f14e
	return false;
Packit 33f14e
Packit 33f14e
      /* Initialize the table of filenames.  */
Packit 33f14e
Packit 33f14e
      data_alloc = 512;
Packit 33f14e
      data_used = 0;
Packit 33f14e
      dirdata->data = data = xmalloc (data_alloc);
Packit 33f14e
Packit 33f14e
      /* Read the directory entries, and insert the subfiles
Packit 33f14e
	 into the 'data' table.  */
Packit 33f14e
Packit 33f14e
      while ((errno = 0, (next = readdir (reading)) != 0))
Packit 33f14e
	{
Packit 33f14e
	  char *d_name = next->d_name;
Packit 33f14e
	  size_t d_size = _D_EXACT_NAMLEN (next) + 1;
Packit 33f14e
Packit 33f14e
	  /* Ignore "." and "..".  */
Packit 33f14e
	  if (d_name[0] == '.'
Packit 33f14e
	      && (d_name[1] == 0 || (d_name[1] == '.' && d_name[2] == 0)))
Packit 33f14e
	    continue;
Packit 33f14e
Packit 33f14e
	  if (excluded_file_name (excluded, d_name))
Packit 33f14e
	    continue;
Packit 33f14e
Packit 33f14e
	  while (data_alloc < data_used + d_size)
Packit 33f14e
	    {
Packit 33f14e
	      if (PTRDIFF_MAX / 2 <= data_alloc)
Packit 33f14e
		xalloc_die ();
Packit 33f14e
	      dirdata->data = data = xrealloc (data, data_alloc *= 2);
Packit 33f14e
	    }
Packit 33f14e
Packit 33f14e
	  memcpy (data + data_used, d_name, d_size);
Packit 33f14e
	  data_used += d_size;
Packit 33f14e
	  nnames++;
Packit 33f14e
	}
Packit 33f14e
      if (errno)
Packit 33f14e
	{
Packit 33f14e
	  int e = errno;
Packit 33f14e
	  closedir (reading);
Packit 33f14e
	  errno = e;
Packit 33f14e
	  return false;
Packit 33f14e
	}
Packit 33f14e
#if CLOSEDIR_VOID
Packit 33f14e
      closedir (reading);
Packit 33f14e
#else
Packit 33f14e
      if (closedir (reading) != 0)
Packit 33f14e
	return false;
Packit 33f14e
#endif
Packit 33f14e
    }
Packit 33f14e
Packit 33f14e
  /* Create the 'names' table from the 'data' table.  */
Packit 33f14e
  if (PTRDIFF_MAX / sizeof *names - 1 <= nnames)
Packit 33f14e
    xalloc_die ();
Packit 33f14e
  dirdata->names = names = xmalloc ((nnames + 1) * sizeof *names);
Packit 33f14e
  dirdata->nnames = nnames;
Packit 33f14e
  for (i = 0;  i < nnames;  i++)
Packit 33f14e
    {
Packit 33f14e
      names[i] = data;
Packit 33f14e
      data += strlen (data) + 1;
Packit 33f14e
    }
Packit 33f14e
  names[nnames] = 0;
Packit 33f14e
  return true;
Packit 33f14e
}
Packit 33f14e
Packit 33f14e
/* Compare strings in a locale-specific way, returning a value
Packit 33f14e
   compatible with strcmp.  */
Packit 33f14e
Packit 33f14e
static int
Packit 33f14e
compare_collated (char const *name1, char const *name2)
Packit 33f14e
{
Packit 33f14e
  int r;
Packit 33f14e
  errno = 0;
Packit 33f14e
  if (ignore_file_name_case)
Packit 33f14e
    r = strcasecoll (name1, name2);
Packit 33f14e
  else
Packit 33f14e
    r = strcoll (name1, name2);
Packit 33f14e
  if (errno)
Packit 33f14e
    {
Packit 33f14e
      error (0, errno, _("cannot compare file names '%s' and '%s'"),
Packit 33f14e
	     name1, name2);
Packit 33f14e
      longjmp (failed_locale_specific_sorting, 1);
Packit 33f14e
    }
Packit 33f14e
  return r;
Packit 33f14e
}
Packit 33f14e
Packit 33f14e
/* Compare file names, returning a value compatible with strcmp.  */
Packit 33f14e
Packit 33f14e
static int
Packit 33f14e
compare_names (char const *name1, char const *name2)
Packit 33f14e
{
Packit 33f14e
  if (locale_specific_sorting)
Packit 33f14e
    {
Packit 33f14e
      int diff = compare_collated (name1, name2);
Packit 33f14e
      if (diff || ignore_file_name_case)
Packit 33f14e
	return diff;
Packit 33f14e
    }
Packit 33f14e
  return file_name_cmp (name1, name2);
Packit 33f14e
}
Packit 33f14e
Packit 33f14e
/* Compare names FILE1 and FILE2 when sorting a directory.
Packit 33f14e
   Prefer filtered comparison, breaking ties with file_name_cmp.  */
Packit 33f14e
Packit 33f14e
static int
Packit 33f14e
compare_names_for_qsort (void const *file1, void const *file2)
Packit 33f14e
{
Packit 33f14e
  char const *const *f1 = file1;
Packit 33f14e
  char const *const *f2 = file2;
Packit 33f14e
  char const *name1 = *f1;
Packit 33f14e
  char const *name2 = *f2;
Packit 33f14e
  if (locale_specific_sorting)
Packit 33f14e
    {
Packit 33f14e
      int diff = compare_collated (name1, name2);
Packit 33f14e
      if (diff)
Packit 33f14e
	return diff;
Packit 33f14e
    }
Packit 33f14e
  return file_name_cmp (name1, name2);
Packit 33f14e
}
Packit 33f14e

Packit 33f14e
/* Compare the contents of two directories named in CMP.
Packit 33f14e
   This is a top-level routine; it does everything necessary for diff
Packit 33f14e
   on two directories.
Packit 33f14e
Packit 33f14e
   CMP->file[0].desc == -1 says directory CMP->file[0] doesn't exist,
Packit 33f14e
   but pretend it is empty.  Likewise for CMP->file[1].
Packit 33f14e
Packit 33f14e
   HANDLE_FILE is a caller-provided subroutine called to handle each file.
Packit 33f14e
   It gets three operands: CMP, name of file in dir 0, name of file in dir 1.
Packit 33f14e
   These names are relative to the original working directory.
Packit 33f14e
Packit 33f14e
   For a file that appears in only one of the dirs, one of the name-args
Packit 33f14e
   to HANDLE_FILE is zero.
Packit 33f14e
Packit 33f14e
   Returns the maximum of all the values returned by HANDLE_FILE,
Packit 33f14e
   or EXIT_TROUBLE if trouble is encountered in opening files.  */
Packit 33f14e
Packit 33f14e
int
Packit 33f14e
diff_dirs (struct comparison const *cmp,
Packit 33f14e
	   int (*handle_file) (struct comparison const *,
Packit 33f14e
			       char const *, char const *))
Packit 33f14e
{
Packit 33f14e
  struct dirdata dirdata[2];
Packit 33f14e
  int volatile val = EXIT_SUCCESS;
Packit 33f14e
  int i;
Packit 33f14e
Packit 33f14e
  if ((cmp->file[0].desc == -1 || dir_loop (cmp, 0))
Packit 33f14e
      && (cmp->file[1].desc == -1 || dir_loop (cmp, 1)))
Packit 33f14e
    {
Packit 33f14e
      error (0, 0, _("%s: recursive directory loop"),
Packit 33f14e
	     cmp->file[cmp->file[0].desc == -1].name);
Packit 33f14e
      return EXIT_TROUBLE;
Packit 33f14e
    }
Packit 33f14e
Packit 33f14e
  /* Get contents of both dirs.  */
Packit 33f14e
  for (i = 0; i < 2; i++)
Packit 33f14e
    if (! dir_read (&cmp->file[i], &dirdata[i]))
Packit 33f14e
      {
Packit 33f14e
	perror_with_name (cmp->file[i].name);
Packit 33f14e
	val = EXIT_TROUBLE;
Packit 33f14e
      }
Packit 33f14e
Packit 33f14e
  if (val == EXIT_SUCCESS)
Packit 33f14e
    {
Packit 33f14e
      char const **volatile names[2];
Packit 33f14e
      names[0] = dirdata[0].names;
Packit 33f14e
      names[1] = dirdata[1].names;
Packit 33f14e
Packit 33f14e
      /* Use locale-specific sorting if possible, else native byte order.  */
Packit 33f14e
      locale_specific_sorting = true;
Packit 33f14e
      if (setjmp (failed_locale_specific_sorting))
Packit 33f14e
	locale_specific_sorting = false;
Packit 33f14e
Packit 33f14e
      /* Sort the directories.  */
Packit 33f14e
      for (i = 0; i < 2; i++)
Packit 33f14e
	qsort (names[i], dirdata[i].nnames, sizeof *dirdata[i].names,
Packit 33f14e
	       compare_names_for_qsort);
Packit 33f14e
Packit 33f14e
      /* If '-S name' was given, and this is the topmost level of comparison,
Packit 33f14e
	 ignore all file names less than the specified starting name.  */
Packit 33f14e
Packit 33f14e
      if (starting_file && ! cmp->parent)
Packit 33f14e
	{
Packit 33f14e
	  while (*names[0] && compare_names (*names[0], starting_file) < 0)
Packit 33f14e
	    names[0]++;
Packit 33f14e
	  while (*names[1] && compare_names (*names[1], starting_file) < 0)
Packit 33f14e
	    names[1]++;
Packit 33f14e
	}
Packit 33f14e
Packit 33f14e
      /* Loop while files remain in one or both dirs.  */
Packit 33f14e
      while (*names[0] || *names[1])
Packit 33f14e
	{
Packit 33f14e
	  /* Compare next name in dir 0 with next name in dir 1.
Packit 33f14e
	     At the end of a dir,
Packit 33f14e
	     pretend the "next name" in that dir is very large.  */
Packit 33f14e
	  int nameorder = (!*names[0] ? 1 : !*names[1] ? -1
Packit 33f14e
			   : compare_names (*names[0], *names[1]));
Packit 33f14e
Packit 33f14e
	  /* Prefer a file_name_cmp match if available.  This algorithm is
Packit 33f14e
	     O(N**2), where N is the number of names in a directory
Packit 33f14e
	     that compare_names says are all equal, but in practice N
Packit 33f14e
	     is so small it's not worth tuning.  */
Packit 33f14e
	  if (nameorder == 0 && ignore_file_name_case)
Packit 33f14e
	    {
Packit 33f14e
	      int raw_order = file_name_cmp (*names[0], *names[1]);
Packit 33f14e
	      if (raw_order != 0)
Packit 33f14e
		{
Packit 33f14e
		  int greater_side = raw_order < 0;
Packit 33f14e
		  int lesser_side = 1 - greater_side;
Packit 33f14e
		  char const **lesser = names[lesser_side];
Packit 33f14e
		  char const *greater_name = *names[greater_side];
Packit 33f14e
		  char const **p;
Packit 33f14e
Packit 33f14e
		  for (p = lesser + 1;
Packit 33f14e
		       *p && compare_names (*p, greater_name) == 0;
Packit 33f14e
		       p++)
Packit 33f14e
		    {
Packit 33f14e
		      int c = file_name_cmp (*p, greater_name);
Packit 33f14e
		      if (0 <= c)
Packit 33f14e
			{
Packit 33f14e
			  if (c == 0)
Packit 33f14e
			    {
Packit 33f14e
			      memmove (lesser + 1, lesser,
Packit 33f14e
				       (char *) p - (char *) lesser);
Packit 33f14e
			      *lesser = greater_name;
Packit 33f14e
			    }
Packit 33f14e
			  break;
Packit 33f14e
			}
Packit 33f14e
		    }
Packit 33f14e
		}
Packit 33f14e
	    }
Packit 33f14e
Packit 33f14e
	  int v1 = (*handle_file) (cmp,
Packit 33f14e
				   0 < nameorder ? 0 : *names[0]++,
Packit 33f14e
				   nameorder < 0 ? 0 : *names[1]++);
Packit 33f14e
	  if (val < v1)
Packit 33f14e
	    val = v1;
Packit 33f14e
	}
Packit 33f14e
    }
Packit 33f14e
Packit 33f14e
  for (i = 0; i < 2; i++)
Packit 33f14e
    {
Packit 33f14e
      free (dirdata[i].names);
Packit 33f14e
      free (dirdata[i].data);
Packit 33f14e
    }
Packit 33f14e
Packit 33f14e
  return val;
Packit 33f14e
}
Packit 33f14e
Packit 33f14e
/* Return nonzero if CMP is looping recursively in argument I.  */
Packit 33f14e
Packit 33f14e
static bool _GL_ATTRIBUTE_PURE
Packit 33f14e
dir_loop (struct comparison const *cmp, int i)
Packit 33f14e
{
Packit 33f14e
  struct comparison const *p = cmp;
Packit 33f14e
  while ((p = p->parent))
Packit 33f14e
    if (0 < same_file (&p->file[i].stat, &cmp->file[i].stat))
Packit 33f14e
      return true;
Packit 33f14e
  return false;
Packit 33f14e
}
Packit 33f14e
Packit 33f14e
/* Find a matching filename in a directory.  */
Packit 33f14e
Packit 33f14e
char *
Packit 33f14e
find_dir_file_pathname (char const *dir, char const *file)
Packit 33f14e
{
Packit 33f14e
  /* The 'IF_LINT (volatile)' works around what appears to be a bug in
Packit 33f14e
     gcc 4.8.0 20120825; see
Packit 33f14e
     <http://lists.gnu.org/archive/html/bug-diffutils/2012-08/msg00007.html>.
Packit 33f14e
     */
Packit 33f14e
  char const * IF_LINT (volatile) match = file;
Packit 33f14e
Packit 33f14e
  char *val;
Packit 33f14e
  struct dirdata dirdata;
Packit 33f14e
  dirdata.names = NULL;
Packit 33f14e
  dirdata.data = NULL;
Packit 33f14e
Packit 33f14e
  if (ignore_file_name_case)
Packit 33f14e
    {
Packit 33f14e
      struct file_data filedata;
Packit 33f14e
      filedata.name = dir;
Packit 33f14e
      filedata.desc = 0;
Packit 33f14e
Packit 33f14e
      if (dir_read (&filedata, &dirdata))
Packit 33f14e
	{
Packit 33f14e
	  locale_specific_sorting = true;
Packit 33f14e
	  if (setjmp (failed_locale_specific_sorting))
Packit 33f14e
	    match = file; /* longjmp may mess up MATCH.  */
Packit 33f14e
	  else
Packit 33f14e
	    {
Packit 33f14e
	      for (char const **p = dirdata.names; *p; p++)
Packit 33f14e
		if (compare_names (*p, file) == 0)
Packit 33f14e
		  {
Packit 33f14e
		    if (file_name_cmp (*p, file) == 0)
Packit 33f14e
		      {
Packit 33f14e
			match = *p;
Packit 33f14e
			break;
Packit 33f14e
		      }
Packit 33f14e
		    if (match == file)
Packit 33f14e
		      match = *p;
Packit 33f14e
		  }
Packit 33f14e
	    }
Packit 33f14e
	}
Packit 33f14e
    }
Packit 33f14e
Packit 33f14e
  val = file_name_concat (dir, match, NULL);
Packit 33f14e
  free (dirdata.names);
Packit 33f14e
  free (dirdata.data);
Packit 33f14e
  return val;
Packit 33f14e
}