Fix tmpfile on MS-Windows.
authorEli Zaretskii <eliz@gnu.org>
Wed, 2 Oct 2013 16:48:21 +0000 (19:48 +0300)
committerEli Zaretskii <eliz@gnu.org>
Wed, 2 Oct 2013 16:48:21 +0000 (19:48 +0300)
 w32/compat/posixfcn.c (tmpfile): New function, a replacement for
 the Windows libc version.

ChangeLog
w32/compat/posixfcn.c

index 5f7f7ae33f39937f6906bf4c32e6c510d4996fcb..f9b6e349bfa6056dd2a72a8b3260a37ae5fec29b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,8 @@
 2013-10-02  Eli Zaretskii  <eliz@gnu.org>
 
+       * w32/compat/posixfcn.c (tmpfile): New function, a replacement for
+       the Windows libc version.
+
        Fix $abspath on Cygwin when HAVE_DOS_PATHS is in effect.
        * function.c (IS_ABSOLUTE) [__CYGWIN__]: Special definition for
        Cygwin.
index cde81ce04dc1365d47e0c9643b4d265e8a5d1f94..0ebf9a61ef5f6f3d299ee956b05278eb356df1df 100644 (file)
@@ -257,6 +257,106 @@ 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))
+    {
+      sprintf (temp_path + path_size,
+              "%s%s%u-%x.tmp",
+              temp_path[path_size - 1] == '\\' ? "" : "\\",
+              base, pid, uniq);
+      HANDLE 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