Blame src/dir.c

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