/* Read, sort and compare two directories. Used for GNU DIFF.12Copyright (C) 1988, 1989, 1992, 1993, 1994, 1995, 1998, 2001, 2002,32004 Free Software Foundation, Inc.45This file is part of GNU DIFF.67GNU DIFF is free software; you can redistribute it and/or modify8it under the terms of the GNU General Public License as published by9the Free Software Foundation; either version 2, or (at your option)10any later version.1112GNU DIFF is distributed in the hope that it will be useful,13but WITHOUT ANY WARRANTY; without even the implied warranty of14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the15GNU General Public License for more details.1617You should have received a copy of the GNU General Public License18along with this program; see the file COPYING.19If not, write to the Free Software Foundation,2059 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */2122#include "diff.h"23#include <error.h>24#include <exclude.h>25#include <setjmp.h>26#include <strcase.h>27#include <xalloc.h>2829/* Read the directory named by DIR and store into DIRDATA a sorted vector30of filenames for its contents. DIR->desc == -1 means this directory is31known to be nonexistent, so set DIRDATA to an empty vector.32Return -1 (setting errno) if error, 0 otherwise. */3334struct dirdata35{36size_t nnames; /* Number of names. */37char const **names; /* Sorted names of files in dir, followed by 0. */38char *data; /* Allocated storage for file names. */39};4041/* Whether file names in directories should be compared with42locale-specific sorting. */43static bool locale_specific_sorting;4445/* Where to go if locale-specific sorting fails. */46static jmp_buf failed_locale_specific_sorting;4748static bool dir_loop (struct comparison const *, int);49static int compare_names_for_qsort (void const *, void const *);505152/* Read a directory and get its vector of names. */5354static bool55dir_read (struct file_data const *dir, struct dirdata *dirdata)56{57register struct dirent *next;58register size_t i;5960/* Address of block containing the files that are described. */61char const **names;6263/* Number of files in directory. */64size_t nnames;6566/* Allocated and used storage for file name data. */67char *data;68size_t data_alloc, data_used;6970dirdata->names = 0;71dirdata->data = 0;72nnames = 0;73data = 0;7475if (dir->desc != -1)76{77/* Open the directory and check for errors. */78register DIR *reading = opendir (dir->name);79if (!reading)80return false;8182/* Initialize the table of filenames. */8384data_alloc = 512;85data_used = 0;86dirdata->data = data = xmalloc (data_alloc);8788/* Read the directory entries, and insert the subfiles89into the `data' table. */9091while ((errno = 0, (next = readdir (reading)) != 0))92{93char *d_name = next->d_name;94size_t d_size = NAMLEN (next) + 1;9596/* Ignore "." and "..". */97if (d_name[0] == '.'98&& (d_name[1] == 0 || (d_name[1] == '.' && d_name[2] == 0)))99continue;100101if (excluded_filename (excluded, d_name))102continue;103104while (data_alloc < data_used + d_size)105{106if (PTRDIFF_MAX / 2 <= data_alloc)107xalloc_die ();108dirdata->data = data = xrealloc (data, data_alloc *= 2);109}110111memcpy (data + data_used, d_name, d_size);112data_used += d_size;113nnames++;114}115if (errno)116{117int e = errno;118closedir (reading);119errno = e;120return false;121}122#if CLOSEDIR_VOID123closedir (reading);124#else125if (closedir (reading) != 0)126return false;127#endif128}129130/* Create the `names' table from the `data' table. */131if (PTRDIFF_MAX / sizeof *names - 1 <= nnames)132xalloc_die ();133dirdata->names = names = xmalloc ((nnames + 1) * sizeof *names);134dirdata->nnames = nnames;135for (i = 0; i < nnames; i++)136{137names[i] = data;138data += strlen (data) + 1;139}140names[nnames] = 0;141return true;142}143144/* Compare file names, returning a value compatible with strcmp. */145146static int147compare_names (char const *name1, char const *name2)148{149if (locale_specific_sorting)150{151int r;152errno = 0;153if (ignore_file_name_case)154r = strcasecoll (name1, name2);155else156r = strcoll (name1, name2);157if (errno)158{159error (0, errno, _("cannot compare file names `%s' and `%s'"),160name1, name2);161longjmp (failed_locale_specific_sorting, 1);162}163return r;164}165166return (ignore_file_name_case167? strcasecmp (name1, name2)168: file_name_cmp (name1, name2));169}170171/* A wrapper for compare_names suitable as an argument for qsort. */172173static int174compare_names_for_qsort (void const *file1, void const *file2)175{176char const *const *f1 = file1;177char const *const *f2 = file2;178return compare_names (*f1, *f2);179}180181/* Compare the contents of two directories named in CMP.182This is a top-level routine; it does everything necessary for diff183on two directories.184185CMP->file[0].desc == -1 says directory CMP->file[0] doesn't exist,186but pretend it is empty. Likewise for CMP->file[1].187188HANDLE_FILE is a caller-provided subroutine called to handle each file.189It gets three operands: CMP, name of file in dir 0, name of file in dir 1.190These names are relative to the original working directory.191192For a file that appears in only one of the dirs, one of the name-args193to HANDLE_FILE is zero.194195Returns the maximum of all the values returned by HANDLE_FILE,196or EXIT_TROUBLE if trouble is encountered in opening files. */197198int199diff_dirs (struct comparison const *cmp,200int (*handle_file) (struct comparison const *,201char const *, char const *))202{203struct dirdata dirdata[2];204int volatile val = EXIT_SUCCESS;205int i;206207if ((cmp->file[0].desc == -1 || dir_loop (cmp, 0))208&& (cmp->file[1].desc == -1 || dir_loop (cmp, 1)))209{210error (0, 0, "%s: recursive directory loop",211cmp->file[cmp->file[0].desc == -1].name);212return EXIT_TROUBLE;213}214215/* Get contents of both dirs. */216for (i = 0; i < 2; i++)217if (! dir_read (&cmp->file[i], &dirdata[i]))218{219perror_with_name (cmp->file[i].name);220val = EXIT_TROUBLE;221}222223if (val == EXIT_SUCCESS)224{225char const **volatile names[2];226names[0] = dirdata[0].names;227names[1] = dirdata[1].names;228229/* Use locale-specific sorting if possible, else native byte order. */230locale_specific_sorting = true;231if (setjmp (failed_locale_specific_sorting))232locale_specific_sorting = false;233234/* Sort the directories. */235for (i = 0; i < 2; i++)236qsort (names[i], dirdata[i].nnames, sizeof *dirdata[i].names,237compare_names_for_qsort);238239/* If `-S name' was given, and this is the topmost level of comparison,240ignore all file names less than the specified starting name. */241242if (starting_file && ! cmp->parent)243{244while (*names[0] && compare_names (*names[0], starting_file) < 0)245names[0]++;246while (*names[1] && compare_names (*names[1], starting_file) < 0)247names[1]++;248}249250/* Loop while files remain in one or both dirs. */251while (*names[0] || *names[1])252{253/* Compare next name in dir 0 with next name in dir 1.254At the end of a dir,255pretend the "next name" in that dir is very large. */256int nameorder = (!*names[0] ? 1 : !*names[1] ? -1257: compare_names (*names[0], *names[1]));258int v1 = (*handle_file) (cmp,2590 < nameorder ? 0 : *names[0]++,260nameorder < 0 ? 0 : *names[1]++);261if (val < v1)262val = v1;263}264}265266for (i = 0; i < 2; i++)267{268if (dirdata[i].names)269free (dirdata[i].names);270if (dirdata[i].data)271free (dirdata[i].data);272}273274return val;275}276277/* Return nonzero if CMP is looping recursively in argument I. */278279static bool280dir_loop (struct comparison const *cmp, int i)281{282struct comparison const *p = cmp;283while ((p = p->parent))284if (0 < same_file (&p->file[i].stat, &cmp->file[i].stat))285return true;286return false;287}288289290