From e2ae19e763ebdbf128a700defa8a7430c8e2a8f8 Mon Sep 17 00:00:00 2001 From: AnHeuermann <38031952+AnHeuermann@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:05:45 +0200 Subject: [PATCH] Replace tmpnam with mkstemp/GetTempFileName in temporaryFileName tmpnam is deprecated and has a dangerous race condition: * On POSIX, atomically create the file itself via `mkstemp` * Respect environment variable `TMPDIR`, fall back to `P_tmpdir` or `/tmp`. * On Windows, use `GetTempPathA`/`GetTempFileNameA`, which also atomically create the file. * Update Files.mo documention * Function returns the path of a freshly created empty file rather than a name that does not yet exist. * Drop the now-obsolete race-condition warning --- .../Resources/C-Sources/ModelicaInternal.c | 54 ++++++++++++++++++- Modelica/Utilities/Files.mo | 21 +++----- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/Modelica/Resources/C-Sources/ModelicaInternal.c b/Modelica/Resources/C-Sources/ModelicaInternal.c index bd2d2ae68e..ad3468d395 100644 --- a/Modelica/Resources/C-Sources/ModelicaInternal.c +++ b/Modelica/Resources/C-Sources/ModelicaInternal.c @@ -303,6 +303,10 @@ void ModelicaInternal_setenv(_In_z_ const char* name, #include #include #include + #if !defined(WIN32_LEAN_AND_MEAN) + #define WIN32_LEAN_AND_MEAN + #endif + #include #if defined(__MINGW32__) || defined(__CYGWIN__) /* MinGW and Cygwin have dirent.h */ #include @@ -754,9 +758,54 @@ _Ret_z_ const char* ModelicaInternal_fullPathName(_In_z_ const char* name) { } _Ret_z_ const char* ModelicaInternal_temporaryFileName(void) { - /* Get full path name of a temporary file name which does not exist */ + /* Atomically create an empty temporary file and return its full path name */ char* fullName; - +#if defined(_WIN32) + char tempPath[BUFFER_LENGTH]; + char tempFile[BUFFER_LENGTH]; + DWORD pathLen = GetTempPathA((DWORD)sizeof(tempPath), tempPath); + if (pathLen == 0 || pathLen >= (DWORD)sizeof(tempPath)) { + ModelicaFormatError("Not possible to get path of temporary directory\nError code: %lu", + (unsigned long)GetLastError()); + return ""; + } + if (0 == GetTempFileNameA(tempPath, "tmp", 0, tempFile)) { + ModelicaFormatError("Not possible to get temporary filename\nError code: %lu", + (unsigned long)GetLastError()); + return ""; + } + /* GetTempFileNameA already atomically created an empty file with this unique name */ + fullName = ModelicaDuplicateString(tempFile); + ModelicaConvertToUnixDirectorySeparator(fullName); +#elif defined(_POSIX_) || defined(__GNUC__) + /* Use mkstemp to atomically create the temporary file itself (avoiding + the race condition of tmpnam) */ + char fileTemplate[BUFFER_LENGTH]; + static const char fileSuffix[] = "/modelicaXXXXXX"; + const char* tmpDir = getenv("TMPDIR"); + int fileDescriptor; + if (tmpDir == NULL || tmpDir[0] == '\0') { +#if defined(P_tmpdir) + tmpDir = P_tmpdir; +#else + tmpDir = "/tmp"; +#endif + } + if (strlen(tmpDir) + sizeof(fileSuffix) > sizeof(fileTemplate)) { + ModelicaFormatError("Path of temporary directory is too long\n\"%s\"", tmpDir); + return ""; + } + strcpy(fileTemplate, tmpDir); + strcat(fileTemplate, fileSuffix); + fileDescriptor = mkstemp(fileTemplate); + if (fileDescriptor == -1) { + ModelicaFormatError("Not possible to create temporary file\n%s", strerror(errno)); + return ""; + } + close(fileDescriptor); + fullName = ModelicaDuplicateString(fileTemplate); + ModelicaConvertToUnixDirectorySeparator(fullName); +#else char* tempName = tmpnam(NULL); if (tempName == NULL) { ModelicaFormatError("Not possible to get temporary filename\n%s", strerror(errno)); @@ -764,6 +813,7 @@ _Ret_z_ const char* ModelicaInternal_temporaryFileName(void) { } fullName = ModelicaDuplicateString(tempName); ModelicaConvertToUnixDirectorySeparator(fullName); +#endif return fullName; } diff --git a/Modelica/Utilities/Files.mo b/Modelica/Utilities/Files.mo index 6a13c276dc..3ea675d2a7 100644 --- a/Modelica/Utilities/Files.mo +++ b/Modelica/Utilities/Files.mo @@ -655,7 +655,7 @@ Function splitPathName(..) splits a path name into its parts. end splitPathName; impure function temporaryFileName - "Return arbitrary name of a file that does not exist and is in a directory where access rights allow to write to this file (useful for temporary output of files)" + "Return arbitrary name of an empty file freshly created in a directory where access rights allow to write to this file (useful for temporary output of files)" extends Modelica.Icons.Function; output String fileName "Full path name of temporary file"; external "C" fileName=ModelicaInternal_temporaryFileName() annotation(IncludeDirectory="modelica://Modelica/Resources/C-Sources", Include="#include \"ModelicaInternal.h\"", Library="ModelicaExternalC"); @@ -666,24 +666,19 @@ fileName = Files.temporaryFileName();

Description

-Return arbitrary name of a file that does not exist -and is in a directory where access rights allow to -write to this file (useful for temporary output of files). +Atomically create an empty file with an arbitrary name in a directory +where access rights allow to write to this file (useful for temporary +output of files), and return its full path name.

The created temporary file is not automatically deleted when closed, but needs to be explicitly deleted, e.g. by removeFile(fileName).

-

-Warning: -The underlying C implementation of ModelicaInternal_temporaryFileName calls the standard C function tmpnam, which has a race condition security problem in the case another process creates a file with the same fileName just after tmpnam generated the full path name. -

Example

 fileName = Files.temporaryFileName();
-   -> fileName is the absolute path name of the temporary file
+   -> fileName is the absolute path name of the (empty) temporary file
 Streams.print(String(System.getPid()), fileName);
-   -> Create the temporary file
-      Warning: Possible race condition on file access
+   -> Write to the temporary file
 Files.removeFile(fileName);
    -> Explicitly delete the temporary file (after use)
 
@@ -782,8 +777,8 @@ In the table below an example call to every function is given: Split path name in directory, file name kernel, file name extension. fileName = temporaryFileName() - Return arbitrary name of a file that does not exist
- and is in a directory where access rights allow to
+ Return arbitrary name of an empty file freshly created
+ in a directory where access rights allow to
write to this file (useful for temporary output of files). fileReference = loadResource(uri)