1128456Sdes/* Read, sort and compare two directories.  Used for GNU DIFF.
2124208Sdes
398937Sdes   Copyright (C) 1988, 1989, 1992, 1993, 1994, 1995, 1998, 2001, 2002,
4126274Sdes   2004 Free Software Foundation, Inc.
598937Sdes
6126274Sdes   This file is part of GNU DIFF.
7126274Sdes
8126274Sdes   GNU DIFF is free software; you can redistribute it and/or modify
998937Sdes   it under the terms of the GNU General Public License as published by
10126274Sdes   the Free Software Foundation; either version 2, or (at your option)
11126274Sdes   any later version.
12126274Sdes
13126274Sdes   GNU DIFF is distributed in the hope that it will be useful,
14126274Sdes   but WITHOUT ANY WARRANTY; without even the implied warranty of
15126274Sdes   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16126274Sdes   GNU General Public License for more details.
1798937Sdes
1898937Sdes   You should have received a copy of the GNU General Public License
1998937Sdes   along with this program; see the file COPYING.
2098937Sdes   If not, write to the Free Software Foundation,
2198937Sdes   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
22124208Sdes
2398937Sdes#include "diff.h"
24124208Sdes#include <error.h>
2598937Sdes#include <exclude.h>
2698937Sdes#include <setjmp.h>
2798937Sdes#include <strcase.h>
2898937Sdes#include <xalloc.h>
2998937Sdes
3098937Sdes/* Read the directory named by DIR and store into DIRDATA a sorted vector
31124208Sdes   of filenames for its contents.  DIR->desc == -1 means this directory is
3298937Sdes   known to be nonexistent, so set DIRDATA to an empty vector.
3398937Sdes   Return -1 (setting errno) if error, 0 otherwise.  */
3498937Sdes
35124208Sdesstruct dirdata
3698937Sdes{
3798937Sdes  size_t nnames;	/* Number of names.  */
3898937Sdes  char const **names;	/* Sorted names of files in dir, followed by 0.  */
39124208Sdes  char *data;	/* Allocated storage for file names.  */
4098937Sdes};
4198937Sdes
4298937Sdes/* Whether file names in directories should be compared with
43124208Sdes   locale-specific sorting.  */
4498937Sdesstatic bool locale_specific_sorting;
4598937Sdes
4698937Sdes/* Where to go if locale-specific sorting fails.  */
47124208Sdesstatic jmp_buf failed_locale_specific_sorting;
4898937Sdes
4998937Sdesstatic bool dir_loop (struct comparison const *, int);
5098937Sdesstatic int compare_names_for_qsort (void const *, void const *);
51124208Sdes
5298937Sdes
5398937Sdes/* Read a directory and get its vector of names.  */
5498937Sdes
5598937Sdesstatic bool
5698937Sdesdir_read (struct file_data const *dir, struct dirdata *dirdata)
5798937Sdes{
5898937Sdes  register struct dirent *next;
5998937Sdes  register size_t i;
6098937Sdes
6198937Sdes  /* Address of block containing the files that are described.  */
6298937Sdes  char const **names;
63124208Sdes
6498937Sdes  /* Number of files in directory.  */
6598937Sdes  size_t nnames;
6698937Sdes
67124208Sdes  /* Allocated and used storage for file name data.  */
6898937Sdes  char *data;
6998937Sdes  size_t data_alloc, data_used;
7098937Sdes
71124208Sdes  dirdata->names = 0;
7298937Sdes  dirdata->data = 0;
7398937Sdes  nnames = 0;
74113908Sdes  data = 0;
75113908Sdes
76113908Sdes  if (dir->desc != -1)
77113908Sdes    {
78113908Sdes      /* Open the directory and check for errors.  */
79113908Sdes      register DIR *reading = opendir (dir->name);
80113908Sdes      if (!reading)
81124208Sdes	return false;
82113908Sdes
8398937Sdes      /* Initialize the table of filenames.  */
84124208Sdes
85124208Sdes      data_alloc = 512;
86124208Sdes      data_used = 0;
87124208Sdes      dirdata->data = data = xmalloc (data_alloc);
88124208Sdes
89124208Sdes      /* Read the directory entries, and insert the subfiles
90124208Sdes	 into the `data' table.  */
91124208Sdes
92128456Sdes      while ((errno = 0, (next = readdir (reading)) != 0))
93128456Sdes	{
94128456Sdes	  char *d_name = next->d_name;
95128456Sdes	  size_t d_size = NAMLEN (next) + 1;
96124208Sdes
97124208Sdes	  /* Ignore "." and "..".  */
98124208Sdes	  if (d_name[0] == '.'
99124208Sdes	      && (d_name[1] == 0 || (d_name[1] == '.' && d_name[2] == 0)))
100124208Sdes	    continue;
101124208Sdes
10298937Sdes	  if (excluded_filename (excluded, d_name))
103	    continue;
104
105	  while (data_alloc < data_used + d_size)
106	    {
107	      if (PTRDIFF_MAX / 2 <= data_alloc)
108		xalloc_die ();
109	      dirdata->data = data = xrealloc (data, data_alloc *= 2);
110	    }
111
112	  memcpy (data + data_used, d_name, d_size);
113	  data_used += d_size;
114	  nnames++;
115	}
116      if (errno)
117	{
118	  int e = errno;
119	  closedir (reading);
120	  errno = e;
121	  return false;
122	}
123#if CLOSEDIR_VOID
124      closedir (reading);
125#else
126      if (closedir (reading) != 0)
127	return false;
128#endif
129    }
130
131  /* Create the `names' table from the `data' table.  */
132  if (PTRDIFF_MAX / sizeof *names - 1 <= nnames)
133    xalloc_die ();
134  dirdata->names = names = xmalloc ((nnames + 1) * sizeof *names);
135  dirdata->nnames = nnames;
136  for (i = 0;  i < nnames;  i++)
137    {
138      names[i] = data;
139      data += strlen (data) + 1;
140    }
141  names[nnames] = 0;
142  return true;
143}
144
145/* Compare file names, returning a value compatible with strcmp.  */
146
147static int
148compare_names (char const *name1, char const *name2)
149{
150  if (locale_specific_sorting)
151    {
152      int r;
153      errno = 0;
154      if (ignore_file_name_case)
155	r = strcasecoll (name1, name2);
156      else
157	r = strcoll (name1, name2);
158      if (errno)
159	{
160	  error (0, errno, _("cannot compare file names `%s' and `%s'"),
161		 name1, name2);
162	  longjmp (failed_locale_specific_sorting, 1);
163	}
164      return r;
165    }
166
167  return (ignore_file_name_case
168	  ? strcasecmp (name1, name2)
169	  : file_name_cmp (name1, name2));
170}
171
172/* A wrapper for compare_names suitable as an argument for qsort.  */
173
174static int
175compare_names_for_qsort (void const *file1, void const *file2)
176{
177  char const *const *f1 = file1;
178  char const *const *f2 = file2;
179  return compare_names (*f1, *f2);
180}
181
182/* Compare the contents of two directories named in CMP.
183   This is a top-level routine; it does everything necessary for diff
184   on two directories.
185
186   CMP->file[0].desc == -1 says directory CMP->file[0] doesn't exist,
187   but pretend it is empty.  Likewise for CMP->file[1].
188
189   HANDLE_FILE is a caller-provided subroutine called to handle each file.
190   It gets three operands: CMP, name of file in dir 0, name of file in dir 1.
191   These names are relative to the original working directory.
192
193   For a file that appears in only one of the dirs, one of the name-args
194   to HANDLE_FILE is zero.
195
196   Returns the maximum of all the values returned by HANDLE_FILE,
197   or EXIT_TROUBLE if trouble is encountered in opening files.  */
198
199int
200diff_dirs (struct comparison const *cmp,
201	   int (*handle_file) (struct comparison const *,
202			       char const *, char const *))
203{
204  struct dirdata dirdata[2];
205  int volatile val = EXIT_SUCCESS;
206  int i;
207
208  if ((cmp->file[0].desc == -1 || dir_loop (cmp, 0))
209      && (cmp->file[1].desc == -1 || dir_loop (cmp, 1)))
210    {
211      error (0, 0, "%s: recursive directory loop",
212	     cmp->file[cmp->file[0].desc == -1].name);
213      return EXIT_TROUBLE;
214    }
215
216  /* Get contents of both dirs.  */
217  for (i = 0; i < 2; i++)
218    if (! dir_read (&cmp->file[i], &dirdata[i]))
219      {
220	perror_with_name (cmp->file[i].name);
221	val = EXIT_TROUBLE;
222      }
223
224  if (val == EXIT_SUCCESS)
225    {
226      char const **volatile names[2];
227      names[0] = dirdata[0].names;
228      names[1] = dirdata[1].names;
229
230      /* Use locale-specific sorting if possible, else native byte order.  */
231      locale_specific_sorting = true;
232      if (setjmp (failed_locale_specific_sorting))
233	locale_specific_sorting = false;
234
235      /* Sort the directories.  */
236      for (i = 0; i < 2; i++)
237	qsort (names[i], dirdata[i].nnames, sizeof *dirdata[i].names,
238	       compare_names_for_qsort);
239
240      /* If `-S name' was given, and this is the topmost level of comparison,
241	 ignore all file names less than the specified starting name.  */
242
243      if (starting_file && ! cmp->parent)
244	{
245	  while (*names[0] && compare_names (*names[0], starting_file) < 0)
246	    names[0]++;
247	  while (*names[1] && compare_names (*names[1], starting_file) < 0)
248	    names[1]++;
249	}
250
251      /* Loop while files remain in one or both dirs.  */
252      while (*names[0] || *names[1])
253	{
254	  /* Compare next name in dir 0 with next name in dir 1.
255	     At the end of a dir,
256	     pretend the "next name" in that dir is very large.  */
257	  int nameorder = (!*names[0] ? 1 : !*names[1] ? -1
258			   : compare_names (*names[0], *names[1]));
259	  int v1 = (*handle_file) (cmp,
260				   0 < nameorder ? 0 : *names[0]++,
261				   nameorder < 0 ? 0 : *names[1]++);
262	  if (val < v1)
263	    val = v1;
264	}
265    }
266
267  for (i = 0; i < 2; i++)
268    {
269      if (dirdata[i].names)
270	free (dirdata[i].names);
271      if (dirdata[i].data)
272	free (dirdata[i].data);
273    }
274
275  return val;
276}
277
278/* Return nonzero if CMP is looping recursively in argument I.  */
279
280static bool
281dir_loop (struct comparison const *cmp, int i)
282{
283  struct comparison const *p = cmp;
284  while ((p = p->parent))
285    if (0 < same_file (&p->file[i].stat, &cmp->file[i].stat))
286      return true;
287  return false;
288}
289