diff --git a/src/makeint.h b/src/makeint.h index 737f6a0e..94fee7a9 100644 --- a/src/makeint.h +++ b/src/makeint.h @@ -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); diff --git a/src/misc.c b/src/misc.c index fa0669ad..4a02ae7c 100644 --- a/src/misc.c +++ b/src/misc.c @@ -17,6 +17,7 @@ this program. If not, see . */ #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 } diff --git a/src/os.h b/src/os.h index 06ac79c8..a92d5e22 100644 --- 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 . */ +/* 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 diff --git a/src/output.c b/src/output.c index dccbff26..fda783bb 100644 --- a/src/output.c +++ b/src/output.c @@ -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; } diff --git a/src/w32/compat/posixfcn.c b/src/w32/compat/posixfcn.c index 8276939c..acdfe289 100644 --- a/src/w32/compat/posixfcn.c +++ b/src/w32/compat/posixfcn.c @@ -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 diff --git a/src/w32/w32os.c b/src/w32/w32os.c index e1c6a359..ef8116a4 100644 --- a/src/w32/w32os.c +++ b/src/w32/w32os.c @@ -28,6 +28,101 @@ this program. If not, see . */ #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];