Imported Upstream version 4.4
[platform/upstream/make.git] / src / dir.c
index 862a18e..b47e94f 100644 (file)
--- a/src/dir.c
+++ b/src/dir.c
@@ -1,5 +1,5 @@
 /* Directory hashing for GNU Make.
-Copyright (C) 1988-2020 Free Software Foundation, Inc.
+Copyright (C) 1988-2022 Free Software Foundation, Inc.
 This file is part of GNU Make.
 
 GNU Make is free software; you can redistribute it and/or modify it under the
@@ -12,12 +12,13 @@ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License along with
-this program.  If not, see <http://www.gnu.org/licenses/>.  */
+this program.  If not, see <https://www.gnu.org/licenses/>.  */
 
 #include "makeint.h"
 #include "hash.h"
 #include "filedef.h"
 #include "dep.h"
+#include "debug.h"
 
 #ifdef  HAVE_DIRENT_H
 # include <dirent.h>
@@ -175,7 +176,7 @@ vms_hash (const char *name)
 
   while (*name)
     {
-      unsigned char uc = *name;
+      unsigned char uc = (unsigned char) *name;
       int g;
 #ifdef HAVE_CASE_INSENSITIVE_FS
       h = (h << 4) + (isupper (uc) ? tolower (uc) : uc);
@@ -232,6 +233,12 @@ vmsstat_dir (const char *name, struct stat *st)
 #endif /* _USE_STD_STAT */
 #endif /* VMS */
 \f
+/* Never have more than this many directories open at once.  */
+
+#define MAX_OPEN_DIRECTORIES 10
+
+static unsigned int open_directories = 0;
+
 /* Hash table of directories.  */
 
 #ifndef DIRECTORY_BUCKETS
@@ -262,9 +269,25 @@ struct directory_contents
 # endif
 #endif /* WINDOWS32 */
     struct hash_table dirfiles; /* Files in this directory.  */
+    unsigned long counter;      /* command_count value when last read. */
     DIR *dirstream;             /* Stream reading this directory.  */
   };
 
+static struct directory_contents *
+clear_directory_contents (struct directory_contents *dc)
+{
+  dc->counter = 0;
+  if (dc->dirstream)
+    {
+      --open_directories;
+      closedir (dc->dirstream);
+      dc->dirstream = 0;
+    }
+  hash_free (&dc->dirfiles, 1);
+
+  return NULL;
+}
+
 static unsigned long
 directory_contents_hash_1 (const void *key_0)
 {
@@ -363,7 +386,9 @@ static struct hash_table directory_contents;
 
 struct directory
   {
-    const char *name;                   /* Name of the directory.  */
+    const char *name;           /* Name of the directory.  */
+    unsigned long counter;      /* command_count value when last read.
+                                   Used for non-existent directories.  */
 
     /* The directory's contents.  This data may be shared by several
        entries in the hash table, which refer to the same directory
@@ -393,12 +418,6 @@ directory_hash_cmp (const void *x, const void *y)
 /* Table of directories hashed by name.  */
 static struct hash_table directories;
 
-/* Never have more than this many directories open at once.  */
-
-#define MAX_OPEN_DIRECTORIES 10
-
-static unsigned int open_directories = 0;
-
 
 /* Hash table of files in each directory.  */
 
@@ -449,150 +468,162 @@ find_directory (const char *name)
   struct directory *dir;
   struct directory **dir_slot;
   struct directory dir_key;
+  struct directory_contents *dc;
+  struct directory_contents **dc_slot;
+  struct directory_contents dc_key;
+
+  struct stat st;
+  int r;
+#ifdef WINDOWS32
+  char *w32_path;
+#endif
 
   dir_key.name = name;
   dir_slot = (struct directory **) hash_find_slot (&directories, &dir_key);
   dir = *dir_slot;
 
-  if (HASH_VACANT (dir))
+  if (!HASH_VACANT (dir))
+    {
+      unsigned long ctr = dir->contents ? dir->contents->counter : dir->counter;
+
+      /* No commands have run since we parsed this directory so it's good.  */
+      if (ctr == command_count)
+        return dir;
+
+      DB (DB_VERBOSE, ("Directory %s cache invalidated (count %lu != command %lu)\n",
+                       name, ctr, command_count));
+
+      if (dir->contents)
+        clear_directory_contents (dir->contents);
+    }
+  else
     {
       /* The directory was not found.  Create a new entry for it.  */
-      const char *p = name + strlen (name);
-      struct stat st;
-      int r;
+      size_t len = strlen (name);
 
       dir = xmalloc (sizeof (struct directory));
 #if defined(HAVE_CASE_INSENSITIVE_FS) && defined(VMS)
       /* Todo: Why is this only needed on VMS? */
       {
         char *lname = downcase_inplace (xstrdup (name));
-        dir->name = strcache_add_len (lname, p - name);
+        dir->name = strcache_add_len (lname, len);
         free (lname);
       }
 #else
-      dir->name = strcache_add_len (name, p - name);
+      dir->name = strcache_add_len (name, len);
 #endif
       hash_insert_at (&directories, dir, dir_slot);
-      /* The directory is not in the name hash table.
-         Find its device and inode numbers, and look it up by them.  */
+    }
 
+  dir->contents = NULL;
+  dir->counter = command_count;
+
+  /* See if the directory exists.  */
 #if defined(WINDOWS32)
-      {
-        char tem[MAXPATHLEN], *tstart, *tend;
-
-        /* Remove any trailing slashes.  Windows32 stat fails even on
-           valid directories if they end in a slash. */
-        memcpy (tem, name, p - name + 1);
-        tstart = tem;
-        if (tstart[1] == ':')
-          tstart += 2;
-        for (tend = tem + (p - name - 1);
-             tend > tstart && (*tend == '/' || *tend == '\\');
-             tend--)
-          *tend = '\0';
-
-        r = stat (tem, &st);
-      }
+  {
+    char tem[MAX_PATH+1], *tstart, *tend;
+    size_t len = strlen (name);
+
+    /* Remove any trailing slashes.  Windows32 stat fails even on
+       valid directories if they end in a slash. */
+    memcpy (tem, name, len + 1);
+    tstart = tem;
+    if (tstart[1] == ':')
+      tstart += 2;
+    for (tend = tem + (len - 1); tend > tstart && ISDIRSEP (*tend); tend--)
+      *tend = '\0';
+
+    r = stat (tem, &st);
+  }
 #else
-      EINTRLOOP (r, stat (name, &st));
+  EINTRLOOP (r, stat (name, &st));
 #endif
 
-      if (r < 0)
-        {
-        /* Couldn't stat the directory.  Mark this by
-           setting the 'contents' member to a nil pointer.  */
-          dir->contents = 0;
-        }
-      else
-        {
-          /* Search the contents hash table; device and inode are the key.  */
+  if (r < 0)
+    /* Couldn't stat the directory; nothing else to do.  */
+    return dir;
 
-#ifdef WINDOWS32
-          char *w32_path;
-#endif
-          struct directory_contents *dc;
-          struct directory_contents **dc_slot;
-          struct directory_contents dc_key;
+  /* Search the contents hash table; device and inode are the key.  */
 
-          dc_key.dev = st.st_dev;
+  memset (&dc_key, '\0', sizeof (dc_key));
+  dc_key.dev = st.st_dev;
 #ifdef WINDOWS32
-          dc_key.path_key = w32_path = w32ify (name, 1);
-          dc_key.ctime = st.st_ctime;
+  dc_key.path_key = w32_path = w32ify (name, 1);
+  dc_key.ctime = st.st_ctime;
 #else
 # ifdef VMS_INO_T
-          dc_key.ino[0] = st.st_ino[0];
-          dc_key.ino[1] = st.st_ino[1];
-          dc_key.ino[2] = st.st_ino[2];
+  dc_key.ino[0] = st.st_ino[0];
+  dc_key.ino[1] = st.st_ino[1];
+  dc_key.ino[2] = st.st_ino[2];
 # else
-          dc_key.ino = st.st_ino;
+  dc_key.ino = st.st_ino;
 # endif
 #endif
-          dc_slot = (struct directory_contents **) hash_find_slot (&directory_contents, &dc_key);
-          dc = *dc_slot;
+  dc_slot = (struct directory_contents **) hash_find_slot (&directory_contents, &dc_key);
+  dc = *dc_slot;
 
-          if (HASH_VACANT (dc))
-            {
-              /* Nope; this really is a directory we haven't seen before.  */
+  if (HASH_VACANT (dc))
+    {
+      /* Nope; this really is a directory we haven't seen before.  */
 #ifdef WINDOWS32
-              char  fs_label[BUFSIZ];
-              char  fs_type[BUFSIZ];
-              unsigned long  fs_serno;
-              unsigned long  fs_flags;
-              unsigned long  fs_len;
+      char  fs_label[BUFSIZ];
+      char  fs_type[BUFSIZ];
+      unsigned long  fs_serno;
+      unsigned long  fs_flags;
+      unsigned long  fs_len;
 #endif
-              dc = (struct directory_contents *)
-                xmalloc (sizeof (struct directory_contents));
+      /* Enter it in the contents hash table.  */
+      dc = xcalloc (sizeof (struct directory_contents));
+      *dc = dc_key;
 
-              /* Enter it in the contents hash table.  */
-              dc->dev = st.st_dev;
 #ifdef WINDOWS32
-              dc->path_key = xstrdup (w32_path);
-              dc->ctime = st.st_ctime;
-              dc->mtime = st.st_mtime;
-
-              /* NTFS is the only WINDOWS32 filesystem that bumps mtime on a
-                 directory when files are added/deleted from a directory.  */
-              w32_path[3] = '\0';
-              if (GetVolumeInformation (w32_path, fs_label, sizeof (fs_label),
-                                        &fs_serno, &fs_len, &fs_flags, fs_type,
-                                        sizeof (fs_type)) == FALSE)
-                dc->fs_flags = FS_UNKNOWN;
-              else if (!strcmp (fs_type, "FAT"))
-                dc->fs_flags = FS_FAT;
-              else if (!strcmp (fs_type, "NTFS"))
-                dc->fs_flags = FS_NTFS;
-              else
-                dc->fs_flags = FS_UNKNOWN;
-#else
-# ifdef VMS_INO_T
-              dc->ino[0] = st.st_ino[0];
-              dc->ino[1] = st.st_ino[1];
-              dc->ino[2] = st.st_ino[2];
-# else
-              dc->ino = st.st_ino;
-# endif
+      dc->path_key = xstrdup (w32_path);
+      dc->mtime = st.st_mtime;
+
+      /* NTFS is the only WINDOWS32 filesystem that bumps mtime on a
+         directory when files are added/deleted from a directory.  */
+      w32_path[3] = '\0';
+      if (GetVolumeInformation (w32_path, fs_label, sizeof (fs_label),
+                                &fs_serno, &fs_len, &fs_flags, fs_type,
+                                sizeof (fs_type)) == FALSE)
+        dc->fs_flags = FS_UNKNOWN;
+      else if (!strcmp (fs_type, "FAT"))
+        dc->fs_flags = FS_FAT;
+      else if (!strcmp (fs_type, "NTFS"))
+        dc->fs_flags = FS_NTFS;
+      else
+        dc->fs_flags = FS_UNKNOWN;
 #endif /* WINDOWS32 */
-              hash_insert_at (&directory_contents, dc, dc_slot);
-              ENULLLOOP (dc->dirstream, opendir (name));
-              if (dc->dirstream == 0)
-                /* Couldn't open the directory.  Mark this by setting the
-                   'files' member to a nil pointer.  */
-                dc->dirfiles.ht_vec = 0;
-              else
-                {
-                  hash_init (&dc->dirfiles, DIRFILE_BUCKETS,
-                             dirfile_hash_1, dirfile_hash_2, dirfile_hash_cmp);
-                  /* Keep track of how many directories are open.  */
-                  ++open_directories;
-                  if (open_directories == MAX_OPEN_DIRECTORIES)
-                    /* We have too many directories open already.
-                       Read the entire directory and then close it.  */
-                    dir_contents_file_exists_p (dc, 0);
-                }
-            }
 
-          /* Point the name-hashed entry for DIR at its contents data.  */
-          dir->contents = dc;
+      hash_insert_at (&directory_contents, dc, dc_slot);
+    }
+
+  /* Point the name-hashed entry for DIR at its contents data.  */
+  dir->contents = dc;
+
+  /* If the contents have changed, we need to reseed.  */
+  if (dc->counter != command_count)
+    {
+      if (dc->counter)
+        clear_directory_contents (dc);
+
+      dc->counter = command_count;
+
+      ENULLLOOP (dc->dirstream, opendir (name));
+      if (dc->dirstream == 0)
+        /* Couldn't open the directory.  Mark this by setting the
+           'files' member to a nil pointer.  */
+        dc->dirfiles.ht_vec = 0;
+      else
+        {
+          hash_init (&dc->dirfiles, DIRFILE_BUCKETS,
+                     dirfile_hash_1, dirfile_hash_2, dirfile_hash_cmp);
+          /* Keep track of how many directories are open.  */
+          ++open_directories;
+          if (open_directories == MAX_OPEN_DIRECTORIES)
+            /* We have too many directories open already.
+               Read the entire directory and then close it.  */
+            dir_contents_file_exists_p (dc, 0);
         }
     }
 
@@ -834,7 +865,7 @@ file_exists_p (const char *name)
 #ifdef HAVE_DOS_PATHS
   /* d:/ and d: are *very* different...  */
       if (dirend < name + 3 && name[1] == ':' &&
-          (*dirend == '/' || *dirend == '\\' || *dirend == ':'))
+          (ISDIRSEP (*dirend) || *dirend == ':'))
         dirend++;
 #endif
       p = alloca (dirend - name + 1);
@@ -910,7 +941,7 @@ file_impossible (const char *filename)
 #ifdef HAVE_DOS_PATHS
           /* d:/ and d: are *very* different...  */
           if (dirend < p + 3 && p[1] == ':' &&
-              (*dirend == '/' || *dirend == '\\' || *dirend == ':'))
+              (ISDIRSEP (*dirend) || *dirend == ':'))
             dirend++;
 #endif
           cp = alloca (dirend - p + 1);
@@ -1008,7 +1039,7 @@ file_impossible_p (const char *filename)
 #ifdef HAVE_DOS_PATHS
           /* d:/ and d: are *very* different...  */
           if (dirend < filename + 3 && filename[1] == ':' &&
-              (*dirend == '/' || *dirend == '\\' || *dirend == ':'))
+              (ISDIRSEP (*dirend) || *dirend == ':'))
             dirend++;
 #endif
           cp = alloca (dirend - filename + 1);
@@ -1069,6 +1100,9 @@ print_dir_data_base (void)
   unsigned int impossible;
   struct directory **dir_slot;
   struct directory **dir_end;
+#ifdef WINDOWS32
+  char buf[INTSTR_LENGTH + 1];
+#endif
 
   puts (_("\n# Directories\n"));
 
@@ -1084,24 +1118,19 @@ print_dir_data_base (void)
           if (dir->contents == 0)
             printf (_("# %s: could not be stat'd.\n"), dir->name);
           else if (dir->contents->dirfiles.ht_vec == 0)
-            {
 #ifdef WINDOWS32
-              printf (_("# %s (key %s, mtime %I64u): could not be opened.\n"),
-                      dir->name, dir->contents->path_key,
-                      (unsigned long long)dir->contents->mtime);
-#else  /* WINDOWS32 */
-#ifdef VMS_INO_T
-              printf (_("# %s (device %d, inode [%d,%d,%d]): could not be opened.\n"),
-                      dir->name, dir->contents->dev,
-                      dir->contents->ino[0], dir->contents->ino[1],
-                      dir->contents->ino[2]);
+            printf (_("# %s (key %s, mtime %s): could not be opened.\n"),
+                    dir->name, dir->contents->path_key,
+                    make_ulltoa ((unsigned long long)dir->contents->mtime, buf));
+#elif defined(VMS_INO_T)
+            printf (_("# %s (device %d, inode [%d,%d,%d]): could not be opened.\n"),
+                    dir->name, dir->contents->dev,
+                    dir->contents->ino[0], dir->contents->ino[1],
+                    dir->contents->ino[2]);
 #else
-              printf (_("# %s (device %ld, inode %ld): could not be opened.\n"),
-                      dir->name, (long int) dir->contents->dev,
-                      (long int) dir->contents->ino);
+            printf (_("# %s (device %ld, inode %ld): could not be opened.\n"),
+                    dir->name, (long) dir->contents->dev, (long) dir->contents->ino);
 #endif
-#endif /* WINDOWS32 */
-            }
           else
             {
               unsigned int f = 0;
@@ -1123,21 +1152,18 @@ print_dir_data_base (void)
                     }
                 }
 #ifdef WINDOWS32
-              printf (_("# %s (key %s, mtime %I64u): "),
+              printf (_("# %s (key %s, mtime %s): "),
                       dir->name, dir->contents->path_key,
-                      (unsigned long long)dir->contents->mtime);
-#else  /* WINDOWS32 */
-#ifdef VMS_INO_T
+                      make_ulltoa ((unsigned long long)dir->contents->mtime, buf));
+#elif defined(VMS_INO_T)
               printf (_("# %s (device %d, inode [%d,%d,%d]): "),
                       dir->name, dir->contents->dev,
                       dir->contents->ino[0], dir->contents->ino[1],
                       dir->contents->ino[2]);
 #else
-              printf (_("# %s (device %ld, inode %ld): "),
-                      dir->name,
+              printf (_("# %s (device %ld, inode %ld): "), dir->name,
                       (long)dir->contents->dev, (long)dir->contents->ino);
 #endif
-#endif /* WINDOWS32 */
               if (f == 0)
                 fputs (_("No"), stdout);
               else
@@ -1286,13 +1312,12 @@ local_stat (const char *path, struct stat *buf)
   /* Make sure the parent of "." exists and is a directory, not a
      file.  This is because 'stat' on Windows normalizes the argument
      foo/. => foo without checking first that foo is a directory.  */
-  if (plen > 1 && path[plen - 1] == '.'
-      && (path[plen - 2] == '/' || path[plen - 2] == '\\'))
+  if (plen > 2 && path[plen - 1] == '.' && ISDIRSEP (path[plen - 2]))
     {
-      char parent[MAXPATHLEN];
+      char parent[MAX_PATH+1];
 
-      strncpy (parent, path, plen - 2);
-      parent[plen - 2] = '\0';
+      strncpy (parent, path, MAX_PATH);
+      parent[MIN(plen - 2, MAX_PATH)] = '\0';
       if (stat (parent, buf) < 0 || !_S_ISDIR (buf->st_mode))
         return -1;
     }