1170754Sdelphij/* Read, sort and compare two directories.  Used for GNU DIFF.
2170754Sdelphij
3170754Sdelphij   Copyright (C) 1988, 1989, 1992, 1993, 1994, 1995, 1998, 2001, 2002,
4170754Sdelphij   2004 Free Software Foundation, Inc.
5170754Sdelphij
6170754Sdelphij   This file is part of GNU DIFF.
7170754Sdelphij
8170754Sdelphij   GNU DIFF is free software; you can redistribute it and/or modify
9170754Sdelphij   it under the terms of the GNU General Public License as published by
10170754Sdelphij   the Free Software Foundation; either version 2, or (at your option)
11170754Sdelphij   any later version.
12170754Sdelphij
13170754Sdelphij   GNU DIFF is distributed in the hope that it will be useful,
14170754Sdelphij   but WITHOUT ANY WARRANTY; without even the implied warranty of
15170754Sdelphij   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16170754Sdelphij   GNU General Public License for more details.
17170754Sdelphij
18170754Sdelphij   You should have received a copy of the GNU General Public License
19170754Sdelphij   along with this program; see the file COPYING.
20170754Sdelphij   If not, write to the Free Software Foundation,
21170754Sdelphij   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
22170754Sdelphij
23170754Sdelphij#include "diff.h"
24170754Sdelphij#include <error.h>
25170754Sdelphij#include <exclude.h>
26170754Sdelphij#include <setjmp.h>
27170754Sdelphij#include <strcase.h>
28170754Sdelphij#include <xalloc.h>
29170754Sdelphij
30170754Sdelphij/* Read the directory named by DIR and store into DIRDATA a sorted vector
31170754Sdelphij   of filenames for its contents.  DIR->desc == -1 means this directory is
32170754Sdelphij   known to be nonexistent, so set DIRDATA to an empty vector.
33170754Sdelphij   Return -1 (setting errno) if error, 0 otherwise.  */
34170754Sdelphij
35170754Sdelphijstruct dirdata
36170754Sdelphij{
37170754Sdelphij  size_t nnames;	/* Number of names.  */
38170754Sdelphij  char const **names;	/* Sorted names of files in dir, followed by 0.  */
39170754Sdelphij  char *data;	/* Allocated storage for file names.  */
40170754Sdelphij};
41170754Sdelphij
42170754Sdelphij/* Whether file names in directories should be compared with
43170754Sdelphij   locale-specific sorting.  */
44170754Sdelphijstatic bool locale_specific_sorting;
45170754Sdelphij
46170754Sdelphij/* Where to go if locale-specific sorting fails.  */
47170754Sdelphijstatic jmp_buf failed_locale_specific_sorting;
48170754Sdelphij
49170754Sdelphijstatic bool dir_loop (struct comparison const *, int);
50170754Sdelphijstatic int compare_names_for_qsort (void const *, void const *);
51170754Sdelphij
52170754Sdelphij
53170754Sdelphij/* Read a directory and get its vector of names.  */
54170754Sdelphij
55170754Sdelphijstatic bool
56170754Sdelphijdir_read (struct file_data const *dir, struct dirdata *dirdata)
57170754Sdelphij{
58170754Sdelphij  register struct dirent *next;
59170754Sdelphij  register size_t i;
60170754Sdelphij
61170754Sdelphij  /* Address of block containing the files that are described.  */
62170754Sdelphij  char const **names;
63170754Sdelphij
64170754Sdelphij  /* Number of files in directory.  */
65170754Sdelphij  size_t nnames;
66170754Sdelphij
67170754Sdelphij  /* Allocated and used storage for file name data.  */
68170754Sdelphij  char *data;
69170754Sdelphij  size_t data_alloc, data_used;
70170754Sdelphij
71170754Sdelphij  dirdata->names = 0;
72170754Sdelphij  dirdata->data = 0;
73170754Sdelphij  nnames = 0;
74170754Sdelphij  data = 0;
75170754Sdelphij
76170754Sdelphij  if (dir->desc != -1)
77170754Sdelphij    {
78170754Sdelphij      /* Open the directory and check for errors.  */
79170754Sdelphij      register DIR *reading = opendir (dir->name);
80170754Sdelphij      if (!reading)
81170754Sdelphij	return false;
82170754Sdelphij
83170754Sdelphij      /* Initialize the table of filenames.  */
84170754Sdelphij
85170754Sdelphij      data_alloc = 512;
86170754Sdelphij      data_used = 0;
87170754Sdelphij      dirdata->data = data = xmalloc (data_alloc);
88170754Sdelphij
89170754Sdelphij      /* Read the directory entries, and insert the subfiles
90170754Sdelphij	 into the `data' table.  */
91170754Sdelphij
92170754Sdelphij      while ((errno = 0, (next = readdir (reading)) != 0))
93170754Sdelphij	{
94170754Sdelphij	  char *d_name = next->d_name;
95170754Sdelphij	  size_t d_size = NAMLEN (next) + 1;
96170754Sdelphij
97170754Sdelphij	  /* Ignore "." and "..".  */
98170754Sdelphij	  if (d_name[0] == '.'
99170754Sdelphij	      && (d_name[1] == 0 || (d_name[1] == '.' && d_name[2] == 0)))
100170754Sdelphij	    continue;
101170754Sdelphij
102170754Sdelphij	  if (excluded_filename (excluded, d_name))
103170754Sdelphij	    continue;
104170754Sdelphij
105170754Sdelphij	  while (data_alloc < data_used + d_size)
106170754Sdelphij	    {
107170754Sdelphij	      if (PTRDIFF_MAX / 2 <= data_alloc)
108170754Sdelphij		xalloc_die ();
109170754Sdelphij	      dirdata->data = data = xrealloc (data, data_alloc *= 2);
110170754Sdelphij	    }
111170754Sdelphij
112170754Sdelphij	  memcpy (data + data_used, d_name, d_size);
113170754Sdelphij	  data_used += d_size;
114170754Sdelphij	  nnames++;
115170754Sdelphij	}
116170754Sdelphij      if (errno)
117170754Sdelphij	{
118170754Sdelphij	  int e = errno;
119170754Sdelphij	  closedir (reading);
120170754Sdelphij	  errno = e;
121170754Sdelphij	  return false;
122170754Sdelphij	}
123170754Sdelphij#if CLOSEDIR_VOID
124170754Sdelphij      closedir (reading);
125170754Sdelphij#else
126170754Sdelphij      if (closedir (reading) != 0)
127170754Sdelphij	return false;
128170754Sdelphij#endif
129170754Sdelphij    }
130170754Sdelphij
131170754Sdelphij  /* Create the `names' table from the `data' table.  */
132170754Sdelphij  if (PTRDIFF_MAX / sizeof *names - 1 <= nnames)
133170754Sdelphij    xalloc_die ();
134170754Sdelphij  dirdata->names = names = xmalloc ((nnames + 1) * sizeof *names);
135170754Sdelphij  dirdata->nnames = nnames;
136170754Sdelphij  for (i = 0;  i < nnames;  i++)
137170754Sdelphij    {
138170754Sdelphij      names[i] = data;
139170754Sdelphij      data += strlen (data) + 1;
140170754Sdelphij    }
141170754Sdelphij  names[nnames] = 0;
142170754Sdelphij  return true;
143170754Sdelphij}
144170754Sdelphij
145170754Sdelphij/* Compare file names, returning a value compatible with strcmp.  */
146170754Sdelphij
147170754Sdelphijstatic int
148170754Sdelphijcompare_names (char const *name1, char const *name2)
149170754Sdelphij{
150170754Sdelphij  if (locale_specific_sorting)
151170754Sdelphij    {
152170754Sdelphij      int r;
153170754Sdelphij      errno = 0;
154170754Sdelphij      if (ignore_file_name_case)
155170754Sdelphij	r = strcasecoll (name1, name2);
156170754Sdelphij      else
157170754Sdelphij	r = strcoll (name1, name2);
158170754Sdelphij      if (errno)
159170754Sdelphij	{
160170754Sdelphij	  error (0, errno, _("cannot compare file names `%s' and `%s'"),
161170754Sdelphij		 name1, name2);
162170754Sdelphij	  longjmp (failed_locale_specific_sorting, 1);
163170754Sdelphij	}
164170754Sdelphij      return r;
165170754Sdelphij    }
166170754Sdelphij
167170754Sdelphij  return (ignore_file_name_case
168170754Sdelphij	  ? strcasecmp (name1, name2)
169170754Sdelphij	  : file_name_cmp (name1, name2));
170170754Sdelphij}
171170754Sdelphij
172170754Sdelphij/* A wrapper for compare_names suitable as an argument for qsort.  */
173170754Sdelphij
174170754Sdelphijstatic int
175170754Sdelphijcompare_names_for_qsort (void const *file1, void const *file2)
176170754Sdelphij{
177170754Sdelphij  char const *const *f1 = file1;
178170754Sdelphij  char const *const *f2 = file2;
179170754Sdelphij  return compare_names (*f1, *f2);
180170754Sdelphij}
181170754Sdelphij
182170754Sdelphij/* Compare the contents of two directories named in CMP.
183170754Sdelphij   This is a top-level routine; it does everything necessary for diff
184170754Sdelphij   on two directories.
185170754Sdelphij
186170754Sdelphij   CMP->file[0].desc == -1 says directory CMP->file[0] doesn't exist,
187170754Sdelphij   but pretend it is empty.  Likewise for CMP->file[1].
188170754Sdelphij
189170754Sdelphij   HANDLE_FILE is a caller-provided subroutine called to handle each file.
190170754Sdelphij   It gets three operands: CMP, name of file in dir 0, name of file in dir 1.
191170754Sdelphij   These names are relative to the original working directory.
192170754Sdelphij
193170754Sdelphij   For a file that appears in only one of the dirs, one of the name-args
194170754Sdelphij   to HANDLE_FILE is zero.
195170754Sdelphij
196170754Sdelphij   Returns the maximum of all the values returned by HANDLE_FILE,
197170754Sdelphij   or EXIT_TROUBLE if trouble is encountered in opening files.  */
198170754Sdelphij
199170754Sdelphijint
200170754Sdelphijdiff_dirs (struct comparison const *cmp,
201170754Sdelphij	   int (*handle_file) (struct comparison const *,
202170754Sdelphij			       char const *, char const *))
203170754Sdelphij{
204170754Sdelphij  struct dirdata dirdata[2];
205170754Sdelphij  int volatile val = EXIT_SUCCESS;
206170754Sdelphij  int i;
207170754Sdelphij
208170754Sdelphij  if ((cmp->file[0].desc == -1 || dir_loop (cmp, 0))
209170754Sdelphij      && (cmp->file[1].desc == -1 || dir_loop (cmp, 1)))
210170754Sdelphij    {
211170754Sdelphij      error (0, 0, "%s: recursive directory loop",
212170754Sdelphij	     cmp->file[cmp->file[0].desc == -1].name);
213170754Sdelphij      return EXIT_TROUBLE;
214170754Sdelphij    }
215170754Sdelphij
216170754Sdelphij  /* Get contents of both dirs.  */
217170754Sdelphij  for (i = 0; i < 2; i++)
218170754Sdelphij    if (! dir_read (&cmp->file[i], &dirdata[i]))
219170754Sdelphij      {
220170754Sdelphij	perror_with_name (cmp->file[i].name);
221170754Sdelphij	val = EXIT_TROUBLE;
222170754Sdelphij      }
223170754Sdelphij
224170754Sdelphij  if (val == EXIT_SUCCESS)
225170754Sdelphij    {
226170754Sdelphij      char const **volatile names[2];
227170754Sdelphij      names[0] = dirdata[0].names;
228170754Sdelphij      names[1] = dirdata[1].names;
229170754Sdelphij
230170754Sdelphij      /* Use locale-specific sorting if possible, else native byte order.  */
231170754Sdelphij      locale_specific_sorting = true;
232170754Sdelphij      if (setjmp (failed_locale_specific_sorting))
233170754Sdelphij	locale_specific_sorting = false;
234170754Sdelphij
235170754Sdelphij      /* Sort the directories.  */
236170754Sdelphij      for (i = 0; i < 2; i++)
237170754Sdelphij	qsort (names[i], dirdata[i].nnames, sizeof *dirdata[i].names,
238170754Sdelphij	       compare_names_for_qsort);
239170754Sdelphij
240170754Sdelphij      /* If `-S name' was given, and this is the topmost level of comparison,
241170754Sdelphij	 ignore all file names less than the specified starting name.  */
242170754Sdelphij
243170754Sdelphij      if (starting_file && ! cmp->parent)
244170754Sdelphij	{
245170754Sdelphij	  while (*names[0] && compare_names (*names[0], starting_file) < 0)
246170754Sdelphij	    names[0]++;
247170754Sdelphij	  while (*names[1] && compare_names (*names[1], starting_file) < 0)
248170754Sdelphij	    names[1]++;
249170754Sdelphij	}
250170754Sdelphij
251170754Sdelphij      /* Loop while files remain in one or both dirs.  */
252170754Sdelphij      while (*names[0] || *names[1])
253170754Sdelphij	{
254170754Sdelphij	  /* Compare next name in dir 0 with next name in dir 1.
255170754Sdelphij	     At the end of a dir,
256170754Sdelphij	     pretend the "next name" in that dir is very large.  */
257170754Sdelphij	  int nameorder = (!*names[0] ? 1 : !*names[1] ? -1
258170754Sdelphij			   : compare_names (*names[0], *names[1]));
259170754Sdelphij	  int v1 = (*handle_file) (cmp,
260170754Sdelphij				   0 < nameorder ? 0 : *names[0]++,
261170754Sdelphij				   nameorder < 0 ? 0 : *names[1]++);
262170754Sdelphij	  if (val < v1)
263170754Sdelphij	    val = v1;
264170754Sdelphij	}
265170754Sdelphij    }
266170754Sdelphij
267170754Sdelphij  for (i = 0; i < 2; i++)
268170754Sdelphij    {
269170754Sdelphij      if (dirdata[i].names)
270170754Sdelphij	free (dirdata[i].names);
271170754Sdelphij      if (dirdata[i].data)
272170754Sdelphij	free (dirdata[i].data);
273170754Sdelphij    }
274170754Sdelphij
275170754Sdelphij  return val;
276170754Sdelphij}
277170754Sdelphij
278170754Sdelphij/* Return nonzero if CMP is looping recursively in argument I.  */
279170754Sdelphij
280170754Sdelphijstatic bool
281170754Sdelphijdir_loop (struct comparison const *cmp, int i)
282170754Sdelphij{
283170754Sdelphij  struct comparison const *p = cmp;
284170754Sdelphij  while ((p = p->parent))
285170754Sdelphij    if (0 < same_file (&p->file[i].stat, &cmp->file[i].stat))
286170754Sdelphij      return true;
287170754Sdelphij  return false;
288170754Sdelphij}
289