On Tuesday 18 November 2025 15:50:06 Martin Storsjö wrote:
> On Wed, 29 Oct 2025, Pali Rohár wrote:
> 
> > Function tmpfile() in msvcr100 and older versions (including the system os
> > msvcrt.dll) is trying to create a temporary file in the root of the drive
> > corresponding to current working directory. In most cases is the current
> > working directory in some user profile application directory, which means
> > that the directory for temporary file used by the tmpfile() is often C:\.
> > Writing to the C:\ is not allowed by unprivileged processes on Windows
> > Vista and new systems, so tmpfile() is not working at all.
> > 
> > This tmpfile() problem was fixed in msvcr110.dll (and hence also in UCRT)
> > and tmpfile() in these new versions is creating a temporary file in the
> > temporary directory returned by WinAPI function GetTempPath() instead of
> > the root directory.
> > 
> > For affected msvcrt import libraries, replace the msvcrt version of
> > tmpfile() function by mingw-w64 emulation which attempts to create a
> > temporary file in GetTempPath() too. For generating unique filename is used
> > the tmpnam() function together with open(O_CREAT | O_EXCL) in a loop to
> > prevent race condition in case two different processes or threads generate
> > same temporary name by the tmpnam() function.
> > 
> > Seems that tmpnam() function generates same file name pattern as the
> > tmpfile() function. So with this change, the file name should be same, just
> > located in the proper temporary directory.
> > 
> > Bug: https://sourceforge.net/p/mingw-w64/bugs/921/
> 
> > diff --git a/mingw-w64-crt/stdio/tmpfile.c b/mingw-w64-crt/stdio/tmpfile.c
> > new file mode 100644
> > index 000000000000..f0c77f52a18a
> > --- /dev/null
> > +++ b/mingw-w64-crt/stdio/tmpfile.c
> > @@ -0,0 +1,71 @@
> > +/**
> > + * This file has no copyright assigned and is placed in the Public Domain.
> > + * This file is part of the mingw-w64 runtime package.
> > + * No warranty is given; refer to the file DISCLAIMER.PD within this 
> > package.
> > + */
> > +
> > +#include <stdio.h>
> > +#include <string.h>
> > +#include <errno.h>
> > +#include <sys/stat.h>
> > +#include <fcntl.h>
> > +#include <io.h>
> > +#include <windows.h>
> > +
> > +FILE *__cdecl tmpfile(void)
> > +{
> > +    /* GetTempPathA() requires buffer of size MAX_PATH+1
> > +     * and tmpnam() requires buffer of size L_tmpnam.
> > +     */
> > +    char path[MAX_PATH+1+L_tmpnam];
> > +    DWORD len;
> > +    int fd;
> > +    FILE *file;
> > +
> > +    len = GetTempPathA(MAX_PATH, path);
> > +    if (len == 0 || len > MAX_PATH) {
> > +        errno = EINVAL;
> > +        return NULL;
> > +    }
> > +
> > +    /* Function tmpnam() generates path name starting with backslash,
> > +     * so ensure that the temp directory path does not contain it.
> > +     */
> 
> https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/tempnam-wtempnam-tmpnam-wtmpnam?view=msvc-170
> says that tmpnam() returns a name in the directory GetTempPathW() - but I

This does not apply for msvcrt.dll and all versioned msvcr*.dll
libraries except the msvcr110 and msvcr120.

> presume this is new in msvcr110/UCRT or newer? As the behaviour you describe
> of tmpnam() is that it only generates a string "\<random>", right?

Yes, in my tests I saw only "\<random>" in buffer returned by tmpnam().

> As the
> old tmpnam() doesn't see the full string, only the name subset, does it do
> anything to check that the returned names are unique already? As the current
> docs say "Each of these functions returns the name of a file that doesn't
> currently exist. " - but that can't be proven if it doesn't even see the
> full path?

Seems that the tmpnam() generates fixed string pattern followed by some
number which is increased by every call.

On the learn.microsoft.com webpage is written:
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/tempnam-wtempnam-tmpnam-wtmpnam?view=msvc-170
"When a file name is prepended with a backslash and no path information,
such as \fname21, it indicates that the name is valid for the current
working directory."

But this is incorrect information. WinAPI path "\filename.ext" which
starts with backslash is relative to the current drive letter, not
relative to the current directory. That is why new temp files in most
cases are being created in root of the C:\.

So basically library could theoretically call getcwd(), extract from it
drive letter and then check if filename "X:\filename.ext" (where X is
current drive letter) exists or not.

But because tmpnam() function does not create a file (it just generates
filename), there can be still a race condition between tmpnam() and
creat() calls. For example another process could generate by its own
same filename and create file earlier.

> The patch looks ok, if old versions of tmpnam() behave in the way of just
> returning a random string and not trying to check if the file is unique (ok,
> we can't know what it does internally) without a full path, just starting
> with a backslash.

For our mingw-w64 tmpfile() implementation it does not matter if the
tmpnam() returned existing or non-existing filename in the root C:\.

We are creating a new file in GetTempPathA() directory.
We always need to create that temp file exclusively.

> It would be good to clarify this aspect. But I guess that's what you already
> try to say in
> 
> > Seems that tmpnam() function generates same file name pattern as the
> > tmpfile() function.
> 
> So in short - the current documentation of tmpnam() doesn't hold up at all
> for the old tmpnam() functions?
> 
> // Martin

I found older documentations for tmpnam() function:
msvcrt40.dll:  
https://archive.org/embed/bitsavers_microsoftv95VisualCVersion4Volume51995_30331194/page/638
msvcrt.dll(5): 
https://archive.org/embed/bitsavers_microsoftv7VisualC5.0Volume3RunTimeLibraryReferenc_41063058/page/629
msvcrt.dll(6): 
https://web.archive.org/web/20001017140858/http://msdn.microsoft.com/library/devprods/vs6/visualc/vccore/_crt__tempnam.2c_._wtempnam.2c_.tmpnam.2c_._wtmpnam.htm
msvcr70.dll:   
https://web.archive.org/web/20020406012445/http://msdn.microsoft.com/library/en-us/vclib/html/_crt__tempnam.2c_._wtempnam.2c_.tmpnam.2c_._wtmpnam.asp
msvcr71.dll:   
https://web.archive.org/web/20030608010640/http://msdn.microsoft.com/library/en-us/vclib/html/_crt__tempnam.2c_._wtempnam.2c_.tmpnam.2c_._wtmpnam.asp
msvcr80.dll:   
https://web.archive.org/web/20081210013532/http://msdn.microsoft.com/en-us/library/hs3e7355(VS.80).aspx
msvcr90.dll:   
https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2008/hs3e7355(v=vs.90)
msvcr100.dll:  
https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/hs3e7355(v=vs.100)
msvcr110.dll:  
https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2012/hs3e7355(v=vs.110)
msvcr120.dll:  
https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2013/hs3e7355(v=vs.120)

msvcrt40.dll and msvcrt.dll do not say anything about directory for tmpnam().

msvcr70-msvcr120 says: "tmpnam returns a name unique in the current
working directory". This is also wrong information as msvcr70-msvcr100
creates in the root drive and msvcr110-msvcr120 creates in the
GetTempPath() directory. None of those libraries creates in current
working directory.

So basically neither current, nor the old documentations describe
the real tmpnam() behavior.


_______________________________________________
Mingw-w64-public mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public

Reply via email to