(root)/
diffutils-3.10/
src/
dir.c
       1  /* Read, sort and compare two directories.  Used for GNU DIFF.
       2  
       3     Copyright (C) 1988-1989, 1992-1995, 1998, 2001-2002, 2004, 2006-2007,
       4     2009-2013, 2015-2023 Free Software Foundation, Inc.
       5  
       6     This file is part of GNU DIFF.
       7  
       8     This program is free software: you can redistribute it and/or modify
       9     it under the terms of the GNU General Public License as published by
      10     the Free Software Foundation, either version 3 of the License, or
      11     (at your option) any later version.
      12  
      13     This program is distributed in the hope that it will be useful,
      14     but WITHOUT ANY WARRANTY; without even the implied warranty of
      15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16     GNU General Public License for more details.
      17  
      18     You should have received a copy of the GNU General Public License
      19     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
      20  
      21  #include "diff.h"
      22  #include <error.h>
      23  #include <exclude.h>
      24  #include <filenamecat.h>
      25  #include <setjmp.h>
      26  #include <xalloc.h>
      27  
      28  /* Read the directory named by DIR and store into DIRDATA a sorted vector
      29     of filenames for its contents.  DIR->desc == -1 means this directory is
      30     known to be nonexistent, so set DIRDATA to an empty vector.
      31     Return -1 (setting errno) if error, 0 otherwise.  */
      32  
      33  struct dirdata
      34  {
      35    size_t nnames;	/* Number of names.  */
      36    char const **names;	/* Sorted names of files in dir, followed by 0.  */
      37    char *data;	/* Allocated storage for file names.  */
      38  };
      39  
      40  /* Whether file names in directories should be compared with
      41     locale-specific sorting.  */
      42  static bool locale_specific_sorting;
      43  
      44  /* Where to go if locale-specific sorting fails.  */
      45  static jmp_buf failed_locale_specific_sorting;
      46  
      47  static bool dir_loop (struct comparison const *, int);
      48  
      49  
      50  /* Read a directory and get its vector of names.  */
      51  
      52  static bool
      53  dir_read (struct file_data const *dir, struct dirdata *dirdata)
      54  {
      55    register struct dirent *next;
      56    register size_t i;
      57  
      58    /* Address of block containing the files that are described.  */
      59    char const **names;
      60  
      61    /* Number of files in directory.  */
      62    size_t nnames;
      63  
      64    /* Allocated and used storage for file name data.  */
      65    char *data;
      66    size_t data_alloc, data_used;
      67  
      68    dirdata->names = 0;
      69    dirdata->data = 0;
      70    nnames = 0;
      71    data = 0;
      72  
      73    if (dir->desc != -1)
      74      {
      75        /* Open the directory and check for errors.  */
      76        register DIR *reading = opendir (dir->name);
      77        if (!reading)
      78          return false;
      79  
      80        /* Initialize the table of filenames.  */
      81  
      82        data_alloc = 512;
      83        data_used = 0;
      84        dirdata->data = data = xmalloc (data_alloc);
      85  
      86        /* Read the directory entries, and insert the subfiles
      87           into the 'data' table.  */
      88  
      89        while ((errno = 0, (next = readdir (reading)) != 0))
      90          {
      91            char *d_name = next->d_name;
      92            size_t d_size = _D_EXACT_NAMLEN (next) + 1;
      93  
      94            /* Ignore "." and "..".  */
      95            if (d_name[0] == '.'
      96                && (d_name[1] == 0 || (d_name[1] == '.' && d_name[2] == 0)))
      97              continue;
      98  
      99            if (excluded_file_name (excluded, d_name))
     100              continue;
     101  
     102            while (data_alloc < data_used + d_size)
     103              {
     104                if (PTRDIFF_MAX / 2 <= data_alloc)
     105                  xalloc_die ();
     106                dirdata->data = data = xrealloc (data, data_alloc *= 2);
     107              }
     108  
     109            memcpy (data + data_used, d_name, d_size);
     110            data_used += d_size;
     111            nnames++;
     112          }
     113        if (errno)
     114          {
     115            int e = errno;
     116            closedir (reading);
     117            errno = e;
     118            return false;
     119          }
     120  #if CLOSEDIR_VOID
     121        closedir (reading);
     122  #else
     123        if (closedir (reading) != 0)
     124          return false;
     125  #endif
     126      }
     127  
     128    /* Create the 'names' table from the 'data' table.  */
     129    dirdata->names = names = xnmalloc (nnames + 1, sizeof *names);
     130    dirdata->nnames = nnames;
     131    for (i = 0;  i < nnames;  i++)
     132      {
     133        names[i] = data;
     134        data += strlen (data) + 1;
     135      }
     136    names[nnames] = 0;
     137    return true;
     138  }
     139  
     140  /* Compare strings in a locale-specific way, returning a value
     141     compatible with strcmp.  */
     142  
     143  static int
     144  compare_collated (char const *name1, char const *name2)
     145  {
     146    int r;
     147    errno = 0;
     148    if (ignore_file_name_case)
     149      r = strcasecoll (name1, name2);
     150    else
     151      r = strcoll (name1, name2);
     152    if (errno)
     153      {
     154        error (0, errno, _("cannot compare file names '%s' and '%s'"),
     155               name1, name2);
     156        longjmp (failed_locale_specific_sorting, 1);
     157      }
     158    return r;
     159  }
     160  
     161  /* Compare file names, returning a value compatible with strcmp.  */
     162  
     163  static int
     164  compare_names (char const *name1, char const *name2)
     165  {
     166    if (locale_specific_sorting)
     167      {
     168        int diff = compare_collated (name1, name2);
     169        if (diff || ignore_file_name_case)
     170          return diff;
     171      }
     172    return file_name_cmp (name1, name2);
     173  }
     174  
     175  /* Compare names FILE1 and FILE2 when sorting a directory.
     176     Prefer filtered comparison, breaking ties with file_name_cmp.  */
     177  
     178  static int
     179  compare_names_for_qsort (void const *file1, void const *file2)
     180  {
     181    char const *const *f1 = file1;
     182    char const *const *f2 = file2;
     183    char const *name1 = *f1;
     184    char const *name2 = *f2;
     185    if (locale_specific_sorting)
     186      {
     187        int diff = compare_collated (name1, name2);
     188        if (diff)
     189          return diff;
     190      }
     191    return file_name_cmp (name1, name2);
     192  }
     193  
     194  /* Compare the contents of two directories named in CMP.
     195     This is a top-level routine; it does everything necessary for diff
     196     on two directories.
     197  
     198     CMP->file[0].desc == -1 says directory CMP->file[0] doesn't exist,
     199     but pretend it is empty.  Likewise for CMP->file[1].
     200  
     201     HANDLE_FILE is a caller-provided subroutine called to handle each file.
     202     It gets three operands: CMP, name of file in dir 0, name of file in dir 1.
     203     These names are relative to the original working directory.
     204  
     205     For a file that appears in only one of the dirs, one of the name-args
     206     to HANDLE_FILE is zero.
     207  
     208     Returns the maximum of all the values returned by HANDLE_FILE,
     209     or EXIT_TROUBLE if trouble is encountered in opening files.  */
     210  
     211  int
     212  diff_dirs (struct comparison const *cmp,
     213             int (*handle_file) (struct comparison const *,
     214                                 char const *, char const *))
     215  {
     216    struct dirdata dirdata[2];
     217    int volatile val = EXIT_SUCCESS;
     218    int i;
     219  
     220    if ((cmp->file[0].desc == -1 || dir_loop (cmp, 0))
     221        && (cmp->file[1].desc == -1 || dir_loop (cmp, 1)))
     222      {
     223        error (0, 0, _("%s: recursive directory loop"),
     224               cmp->file[cmp->file[0].desc == -1].name);
     225        return EXIT_TROUBLE;
     226      }
     227  
     228    /* Get contents of both dirs.  */
     229    for (i = 0; i < 2; i++)
     230      if (! dir_read (&cmp->file[i], &dirdata[i]))
     231        {
     232          perror_with_name (cmp->file[i].name);
     233          val = EXIT_TROUBLE;
     234        }
     235  
     236    if (val == EXIT_SUCCESS)
     237      {
     238        char const **volatile names[2];
     239        names[0] = dirdata[0].names;
     240        names[1] = dirdata[1].names;
     241  
     242        /* Use locale-specific sorting if possible, else native byte order.  */
     243        locale_specific_sorting = true;
     244        if (setjmp (failed_locale_specific_sorting))
     245          locale_specific_sorting = false;
     246  
     247        /* Sort the directories.  */
     248        for (i = 0; i < 2; i++)
     249          qsort (names[i], dirdata[i].nnames, sizeof *dirdata[i].names,
     250                 compare_names_for_qsort);
     251  
     252        /* If '-S name' was given, and this is the topmost level of comparison,
     253           ignore all file names less than the specified starting name.  */
     254  
     255        if (starting_file && ! cmp->parent)
     256          {
     257            while (*names[0] && compare_names (*names[0], starting_file) < 0)
     258              names[0]++;
     259            while (*names[1] && compare_names (*names[1], starting_file) < 0)
     260              names[1]++;
     261          }
     262  
     263        /* Loop while files remain in one or both dirs.  */
     264        while (*names[0] || *names[1])
     265          {
     266            /* Compare next name in dir 0 with next name in dir 1.
     267               At the end of a dir,
     268               pretend the "next name" in that dir is very large.  */
     269            int nameorder = (!*names[0] ? 1 : !*names[1] ? -1
     270                             : compare_names (*names[0], *names[1]));
     271  
     272            /* Prefer a file_name_cmp match if available.  This algorithm is
     273               O(N**2), where N is the number of names in a directory
     274               that compare_names says are all equal, but in practice N
     275               is so small it's not worth tuning.  */
     276            if (nameorder == 0 && ignore_file_name_case)
     277              {
     278                int raw_order = file_name_cmp (*names[0], *names[1]);
     279                if (raw_order != 0)
     280                  {
     281                    int greater_side = raw_order < 0;
     282                    int lesser_side = 1 - greater_side;
     283                    char const **lesser = names[lesser_side];
     284                    char const *greater_name = *names[greater_side];
     285                    char const **p;
     286  
     287                    for (p = lesser + 1;
     288                         *p && compare_names (*p, greater_name) == 0;
     289                         p++)
     290                      {
     291                        int c = file_name_cmp (*p, greater_name);
     292                        if (0 <= c)
     293                          {
     294                            if (c == 0)
     295                              {
     296                                memmove (lesser + 1, lesser,
     297                                         (char *) p - (char *) lesser);
     298                                *lesser = greater_name;
     299                              }
     300                            break;
     301                          }
     302                      }
     303                  }
     304              }
     305  
     306            int v1 = (*handle_file) (cmp,
     307                                     0 < nameorder ? 0 : *names[0]++,
     308                                     nameorder < 0 ? 0 : *names[1]++);
     309            if (val < v1)
     310              val = v1;
     311          }
     312      }
     313  
     314    for (i = 0; i < 2; i++)
     315      {
     316        free (dirdata[i].names);
     317        free (dirdata[i].data);
     318      }
     319  
     320    return val;
     321  }
     322  
     323  /* Return nonzero if CMP is looping recursively in argument I.  */
     324  
     325  static bool ATTRIBUTE_PURE
     326  dir_loop (struct comparison const *cmp, int i)
     327  {
     328    struct comparison const *p = cmp;
     329    while ((p = p->parent))
     330      if (0 < same_file (&p->file[i].stat, &cmp->file[i].stat))
     331        return true;
     332    return false;
     333  }
     334  
     335  /* Find a matching filename in a directory.  */
     336  
     337  char *
     338  find_dir_file_pathname (char const *dir, char const *file)
     339  {
     340    /* IF_LINT due to GCC bug 21161.  */
     341    char const * IF_LINT (volatile) match = file;
     342  
     343    char *val;
     344    struct dirdata dirdata;
     345    dirdata.names = nullptr;
     346    dirdata.data = nullptr;
     347  
     348    if (ignore_file_name_case)
     349      {
     350        struct file_data filedata;
     351        filedata.name = dir;
     352        filedata.desc = 0;
     353  
     354        if (dir_read (&filedata, &dirdata))
     355          {
     356            locale_specific_sorting = true;
     357            if (setjmp (failed_locale_specific_sorting))
     358              match = file; /* longjmp may mess up MATCH.  */
     359            else
     360              {
     361                for (char const **p = dirdata.names; *p; p++)
     362                  if (compare_names (*p, file) == 0)
     363                    {
     364                      if (file_name_cmp (*p, file) == 0)
     365                        {
     366                          match = *p;
     367                          break;
     368                        }
     369                      if (match == file)
     370                        match = *p;
     371                    }
     372              }
     373          }
     374      }
     375  
     376    val = file_name_concat (dir, match, nullptr);
     377    free (dirdata.names);
     378    free (dirdata.data);
     379    return val;
     380  }