Add get_tmpfd() and allow anonymous temp files

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.
This commit is contained in:
Paul Smith 2022-08-27 19:03:40 -04:00
parent 5eff618c8c
commit a2ba5ccbda
6 changed files with 164 additions and 143 deletions

View 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);

View 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
}

View File

@ -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

View 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;
}

View 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

View 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];