]> git.ipfire.org Git - thirdparty/make.git/commitdiff
Add get_tmpfd() and allow anonymous temp files
authorPaul Smith <psmith@gnu.org>
Sat, 27 Aug 2022 23:03:40 +0000 (19:03 -0400)
committerPaul Smith <psmith@gnu.org>
Tue, 30 Aug 2022 19:39:02 +0000 (15:39 -0400)
The output sync feature wants a file descriptor not a FILE*.  We were
using tmpfile() but this returns FILE* which means we needed to dup()
the descriptor then fclose() the original, which is just unnecessary
overhead for every command we run.

Create a get_tmpfd() method that returns a file descriptor directly
by using mkstemp() if available, else do the best we can.

Also allow anonymous temp files if the filename pointer is NULL.
This causes the file to be unlinked.  On Windows this requires a
special open so add an os_anontmp() method to handle this.

* src/makeint.h: Add prototype for get_tmpfd().
* src/misc.c (get_tmpfd): If we have mkstemp() use that, else just
open(2).  If we don't want to keep the filename, unlink the file.
(get_tmpfile): Use get_tmpfd() if we have fdopen(), else use fopen().
* src/output.c (output_tmpfd): Call get_tmpfd() with NULL.
* src/os.h (os_anontmp): On Windows make this a function, else fails.
* src/w32/compat/posixcfn.c (tmpfile): Move to w32os.c:os_anontmp().
* src/w32/w32os.c (os_anontmp): Create a temp file that will be deleted
when the process exits, and return a file descriptor to it.

src/makeint.h
src/misc.c
src/os.h
src/output.c
src/w32/compat/posixfcn.c
src/w32/w32os.c

index 737f6a0eed4ccb2520f7401825d8e544bdce834b..94fee7a95aa588ef9e6c5ea927973295886df8c6 100644 (file)
@@ -547,6 +547,7 @@ void print_spaces (unsigned int);
 char *find_percent (char *);
 const char *find_percent_cached (const char **);
 char *get_tmppath ();
+int get_tmpfd (char **);
 FILE *get_tmpfile (char **);
 ssize_t writebuf (int, const void *, size_t);
 ssize_t readbuf (int, void *, size_t);
index fa0669ada7b26973fe6492b7b73aaf251a4a4a0e..4a02ae7cba108e390c7a90de4b898deae5d6490d 100644 (file)
@@ -17,6 +17,7 @@ this program.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "makeint.h"
 #include "filedef.h"
 #include "dep.h"
+#include "os.h"
 #include "debug.h"
 
 /* GNU make no longer supports pre-ANSI89 environments.  */
@@ -573,44 +574,79 @@ get_tmppath ()
   return path;
 }
 
-FILE *
-get_tmpfile (char **name)
+/* Generate a temporary file and return an fd for it.  If name is NULL then
+   the temp file is anonymous and will be deleted when the process exits.  */
+int
+get_tmpfd (char **name)
 {
-  FILE *file;
-#ifdef HAVE_FDOPEN
-  int fd;
-#endif
+  int fd = -1;
+  char *tmpnm;
+  mode_t mask;
+
+  /* If there's an os-specific way to get an anoymous temp file use it.  */
+  if (!name)
+    {
+      fd = os_anontmp ();
+      if (fd >= 0)
+        return fd;
+    }
 
   /* Preserve the current umask, and set a restrictive one for temp files.  */
-  mode_t mask = umask (0077);
+  mask = umask (0077);
 
-#if defined(HAVE_MKSTEMP) && defined(HAVE_FDOPEN)
-  *name = get_tmptemplate ();
+#if defined(HAVE_MKSTEMP)
+  tmpnm = get_tmptemplate ();
 
   /* It's safest to use mkstemp(), if we can.  */
-  EINTRLOOP (fd, mkstemp (*name));
-  if (fd == -1)
-    file = NULL;
-  else
-    file = fdopen (fd, "w");
+  EINTRLOOP (fd, mkstemp (tmpnm));
 #else
-  *name = get_tmppath ();
+  tmpnm = get_tmppath ();
 
-# ifdef HAVE_FDOPEN
   /* Can't use mkstemp(), but try to guard against a race condition.  */
-  EINTRLOOP (fd, open (*name, O_CREAT|O_EXCL|O_WRONLY, 0600));
-  if (fd == -1)
-    return 0;
-  file = fdopen (fd, "w");
-# else
-  /* Not secure, but what can we do?  */
-  file = fopen (*name, "w");
-# endif
+  EINTRLOOP (fd, open (tmpnm, O_CREAT|O_EXCL|O_RDWR, 0600));
 #endif
 
   umask (mask);
 
+  if (name)
+    *name = tmpnm;
+  else
+    {
+      unlink (tmpnm);
+      free (tmpnm);
+    }
+
+  return fd;
+}
+
+FILE *
+get_tmpfile (char **name)
+{
+#if defined(HAVE_FDOPEN)
+  int fd = get_tmpfd (name);
+
+  return fd < 0 ? NULL : fdopen (fd, "w");
+#else
+  /* Preserve the current umask, and set a restrictive one for temp files.  */
+  mode_t mask = umask (0077);
+
+  char *tmpnm = get_tmppath ();
+
+  /* Not secure, but...?  If name is NULL we could use tmpfile()...  */
+  FILE *file = fopen (tmpnm, "w");
+
+  umask (mask);
+
+  if (name)
+    *name = tmpnm;
+  else
+    {
+      unlink (tmpnm);
+      free (tmpnm);
+    }
+
   return file;
+#endif
 }
 \f
 
index 06ac79c8571b909c8383fad2c4178ac1903f2b5e..a92d5e2202f8bd47659c686291c641257451e298 100644 (file)
--- a/src/os.h
+++ b/src/os.h
@@ -15,6 +15,13 @@ You should have received a copy of the GNU General Public License along with
 this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 
+/* Return a file descriptor for a new anonymous temp file, or -1.  */
+#if defined(WINDOWS32)
+int os_anontmp ();
+#else
+# define os_anontmp() (-1)
+#endif
+
 /* This section provides OS-specific functions to support the jobserver.  */
 
 #ifdef MAKE_JOBSERVER
index dccbff26d66d475eb82d2a5ba2d532bd6bcc500c..fda783bb3144ce33e1680d40afa3f260d582e347 100644 (file)
@@ -285,24 +285,8 @@ release_semaphore (void *sem)
 int
 output_tmpfd (void)
 {
-  mode_t mask = umask (0077);
-  int fd = -1;
-  FILE *tfile = tmpfile ();
-
-  if (! tfile)
-    pfatal_with_name ("tmpfile");
-
-  /* Create a duplicate so we can close the stream.  */
-  fd = dup (fileno (tfile));
-  if (fd < 0)
-    pfatal_with_name ("dup");
-
-  fclose (tfile);
-
+  int fd = get_tmpfd (NULL);
   set_append_mode (fd);
-
-  umask (mask);
-
   return fd;
 }
 
index 8276939c5a632364d67ea6dd9d4381eae61921a0..acdfe289dc3f0c021cafbbe567ac143f815d69e9 100644 (file)
@@ -260,108 +260,6 @@ same_stream (FILE *f1, FILE *f2)
   return 0;
 }
 
-/* A replacement for tmpfile, since the MSVCRT implementation creates
-   the file in the root directory of the current drive, which might
-   not be writable by our user.  Most of the code borrowed from
-   create_batch_file, see job.c.  */
-FILE *
-tmpfile (void)
-{
-  char temp_path[MAXPATHLEN];
-  unsigned path_size = GetTempPath (sizeof temp_path, temp_path);
-  int path_is_dot = 0;
-  /* The following variable is static so we won't try to reuse a name
-     that was generated a little while ago, because that file might
-     not be on disk yet, since we use FILE_ATTRIBUTE_TEMPORARY below,
-     which tells the OS it doesn't need to flush the cache to disk.
-     If the file is not yet on disk, we might think the name is
-     available, while it really isn't.  This happens in parallel
-     builds, where Make doesn't wait for one job to finish before it
-     launches the next one.  */
-  static unsigned uniq = 0;
-  static int second_loop = 0;
-  const char base[] = "gmake_tmpf";
-  const unsigned sizemax = sizeof base - 1 + 4 + 10 + 10;
-  unsigned pid = GetCurrentProcessId ();
-
-  if (path_size == 0)
-    {
-      path_size = GetCurrentDirectory (sizeof temp_path, temp_path);
-      path_is_dot = 1;
-    }
-
-  ++uniq;
-  if (uniq >= 0x10000 && !second_loop)
-    {
-      /* If we already had 64K batch files in this
-         process, make a second loop through the numbers,
-         looking for free slots, i.e. files that were
-         deleted in the meantime.  */
-      second_loop = 1;
-      uniq = 1;
-    }
-  while (path_size > 0 &&
-         path_size + sizemax < sizeof temp_path &&
-         !(uniq >= 0x10000 && second_loop))
-    {
-      HANDLE h;
-
-      sprintf (temp_path + path_size,
-               "%s%s%u-%x.tmp",
-               temp_path[path_size - 1] == '\\' ? "" : "\\",
-               base, pid, uniq);
-      h = CreateFile (temp_path,  /* file name */
-                      GENERIC_READ | GENERIC_WRITE | DELETE, /* desired access */
-                      FILE_SHARE_READ | FILE_SHARE_WRITE,    /* share mode */
-                      NULL,                                  /* default security attributes */
-                      CREATE_NEW,                            /* creation disposition */
-                      FILE_ATTRIBUTE_NORMAL |                /* flags and attributes */
-                      FILE_ATTRIBUTE_TEMPORARY |
-                      FILE_FLAG_DELETE_ON_CLOSE,
-                      NULL);                                 /* no template file */
-
-      if (h == INVALID_HANDLE_VALUE)
-        {
-          const DWORD er = GetLastError ();
-
-          if (er == ERROR_FILE_EXISTS || er == ERROR_ALREADY_EXISTS)
-            {
-              ++uniq;
-              if (uniq == 0x10000 && !second_loop)
-                {
-                  second_loop = 1;
-                  uniq = 1;
-                }
-            }
-
-          /* The temporary path is not guaranteed to exist, or might
-             not be writable by user.  Use the current directory as
-             fallback.  */
-          else if (path_is_dot == 0)
-            {
-              path_size = GetCurrentDirectory (sizeof temp_path, temp_path);
-              path_is_dot = 1;
-            }
-
-          else
-            {
-              errno = EACCES;
-              break;
-            }
-        }
-      else
-        {
-          int fd = _open_osfhandle ((intptr_t)h, 0);
-
-          return _fdopen (fd, "w+b");
-        }
-    }
-
-  if (uniq >= 0x10000)
-    errno = EEXIST;
-  return NULL;
-}
-
 #endif  /* !NO_OUTPUT_SYNC */
 
 #if MAKE_LOAD
index e1c6a3595c5064c0cd0d26756d285af16aa66c9f..ef8116a45a892f1edc89472dd306e76542b99fdd 100644 (file)
@@ -28,6 +28,101 @@ this program.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "os.h"
 #include "debug.h"
 
+/* A replacement for tmpfile, since the MSVCRT implementation creates
+   the file in the root directory of the current drive, which might
+   not be writable by our user, and also it returns a FILE* and we want a file
+   descriptor.  Mostly borrowed from create_batch_file, see job.c.  */
+int
+os_anontmp ()
+{
+  char temp_path[MAXPATHLEN];
+  unsigned path_size = GetTempPath (sizeof (temp_path), temp_path);
+  int using_cwd = 0;
+
+  /* These variables are static so we won't try to reuse a name that was
+     generated a little while ago, because that file might not be on disk yet,
+     since we use FILE_ATTRIBUTE_TEMPORARY below, which tells the OS it
+     doesn't need to flush the cache to disk.  If the file is not yet on disk,
+     we might think the name is available, while it really isn't.  This
+     happens in parallel builds.  */
+  static unsigned uniq = 0;
+  static int second_loop = 0;
+
+  const char base[] = "gmake_tmpf";
+  const unsigned sizemax = sizeof (base) - 1 + 4 + 10 + 10;
+  unsigned pid = GetCurrentProcessId ();
+
+  if (path_size == 0)
+    {
+      path_size = GetCurrentDirectory (sizeof (temp_path), temp_path);
+      using_cwd = 1;
+    }
+
+  ++uniq;
+  if (uniq >= 0x10000 && !second_loop)
+    {
+      /* If we already had 64K batch files in this
+         process, make a second loop through the numbers,
+         looking for free slots, i.e. files that were
+         deleted in the meantime.  */
+      second_loop = 1;
+      uniq = 1;
+    }
+
+  while (path_size > 0 && path_size + sizemax < sizeof (temp_path)
+         && (uniq < 0x10000 || !second_loop))
+    {
+      HANDLE h;
+
+      sprintf (temp_path + path_size,
+               "%s%s%u-%x.tmp",
+               temp_path[path_size - 1] == '\\' ? "" : "\\",
+               base, pid, uniq);
+      h = CreateFile (temp_path,  /* file name */
+                      GENERIC_READ | GENERIC_WRITE | DELETE, /* desired access */
+                      FILE_SHARE_READ | FILE_SHARE_WRITE,    /* share mode */
+                      NULL,                                  /* default security attributes */
+                      CREATE_NEW,                            /* creation disposition */
+                      FILE_ATTRIBUTE_NORMAL |                /* flags and attributes */
+                      FILE_ATTRIBUTE_TEMPORARY |
+                      FILE_FLAG_DELETE_ON_CLOSE,
+                      NULL);                                 /* no template file */
+
+      if (h != INVALID_HANDLE_VALUE)
+        return _open_osfhandle ((intptr_t)h, 0);
+
+      {
+        const DWORD er = GetLastError ();
+
+        if (er == ERROR_FILE_EXISTS || er == ERROR_ALREADY_EXISTS)
+          {
+            ++uniq;
+            if (uniq == 0x10000 && !second_loop)
+              {
+                second_loop = 1;
+                uniq = 1;
+              }
+          }
+        /* The temporary path is not guaranteed to exist, or might not be
+           writable by user.  Use the current directory as fallback.  */
+        else if (!using_cwd)
+          {
+            path_size = GetCurrentDirectory (sizeof (temp_path), temp_path);
+            using_cwd = 1;
+          }
+        else
+          {
+            errno = EACCES;
+            return -1;
+          }
+      }
+    }
+
+  if (uniq >= 0x10000)
+    errno = EEXIST;
+  return -1;
+}
+
 /* This section provides OS-specific functions to support the jobserver.  */
 
 static char jobserver_semaphore_name[MAX_PATH + 1];