[flang-commits] [flang] [llvm] [flang-rt] Honor TMPDIR/TMP/TEMP/TEMPDIR for OPEN(STATUS='SCRATCH') (PR #199517)
Eugene Epshteyn via flang-commits
flang-commits at lists.llvm.org
Mon May 25 05:03:39 PDT 2026
https://github.com/eugeneepshteyn updated https://github.com/llvm/llvm-project/pull/199517
>From 0a589aa0e86d4e073cbd8570ad4eaa6398d001c8 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 24 May 2026 22:58:18 -0400
Subject: [PATCH 1/2] [flang-rt] Honor TMPDIR/TMP/TEMP/TEMPDIR for
OPEN(STATUS='SCRATCH')
The POSIX branch of openfile_mkstemp() hardcoded the scratch directory
to /tmp, ignoring user-specified temp locations. This breaks deployments
where /tmp is small, noexec, missing (some containers/chroots), or on a
different filesystem than the user's chosen scratch space, and it
diverges from gfortran and the rest of the LLVM toolchain.
Add a small file-local GetSystemTempDir() helper that mirrors the lookup
chain used by llvm::sys::path::system_temp_directory on Unix:
TMPDIR -> TMP -> TEMP -> TEMPDIR -> P_tmpdir -> /tmp
Windows behavior is unchanged - GetTempPathA already consults the
relevant environment variables.
Document the new lookup chain in flang/docs/RuntimeEnvironment.md
alongside the other variables consulted by the runtime.
Assisted-by: AI
---
flang-rt/lib/runtime/file.cpp | 44 ++++-
flang-rt/unittests/Runtime/CMakeLists.txt | 1 +
flang-rt/unittests/Runtime/ScratchTempDir.cpp | 165 ++++++++++++++++++
flang/docs/RuntimeEnvironment.md | 17 ++
4 files changed, 223 insertions(+), 4 deletions(-)
create mode 100644 flang-rt/unittests/Runtime/ScratchTempDir.cpp
diff --git a/flang-rt/lib/runtime/file.cpp b/flang-rt/lib/runtime/file.cpp
index 22cd57662b8ce..e050edd98bf9e 100644
--- a/flang-rt/lib/runtime/file.cpp
+++ b/flang-rt/lib/runtime/file.cpp
@@ -12,9 +12,10 @@
#include "flang/Runtime/magic-numbers.h"
#include <algorithm>
#include <cerrno>
+#include <cstdio>
+#include <cstdlib>
#include <cstring>
#include <fcntl.h>
-#include <stdlib.h>
#include <sys/stat.h>
#ifdef _WIN32
#include "flang/Common/windows-include.h"
@@ -30,6 +31,29 @@ void OpenFile::set_path(OwningPtr<char> &&path, std::size_t bytes) {
pathLength_ = bytes;
}
+#ifndef _WIN32
+// Mirrors the lookup chain used by llvm::sys::path::system_temp_directory on
+// Unix: first the conventional environment variables, then P_tmpdir if the
+// platform defines one, then /tmp as the final fallback. The returned pointer
+// is owned by the environment or points to a string literal; the caller must
+// copy before mutating.
+static const char *GetSystemTempDir() {
+ static const char *const envVars[]{"TMPDIR", "TMP", "TEMP", "TEMPDIR"};
+ for (const char *name : envVars) {
+ const char *value{std::getenv(name)};
+ if (value && *value) {
+ return value;
+ }
+ }
+#ifdef P_tmpdir
+ if (P_tmpdir && *P_tmpdir) {
+ return P_tmpdir;
+ }
+#endif
+ return "/tmp";
+}
+#endif
+
static int openfile_mkstemp(IoErrorHandler &handler) {
#ifdef _WIN32
const unsigned int uUnique{0};
@@ -48,14 +72,26 @@ static int openfile_mkstemp(IoErrorHandler &handler) {
int fd{::_open(tempFileName, _O_CREAT | _O_BINARY | _O_TEMPORARY | _O_RDWR,
_S_IREAD | _S_IWRITE)};
#else
- char path[]{"/tmp/Fortran-Scratch-XXXXXX"};
- int fd{::mkstemp(path)};
+ static constexpr char suffix[]{"/Fortran-Scratch-XXXXXX"};
+ const char *dir{GetSystemTempDir()};
+ std::size_t dirLen{std::strlen(dir)};
+ // Drop a single trailing slash so we never produce a "//" in the path.
+ if (dirLen > 1 && dir[dirLen - 1] == '/') {
+ --dirLen;
+ }
+ OwningPtr<char> path{reinterpret_cast<char *>(
+ AllocateMemoryOrCrash(handler, dirLen + sizeof(suffix)))};
+ runtime::memcpy(path.get(), dir, dirLen);
+ runtime::memcpy(path.get() + dirLen, suffix, sizeof(suffix));
+ int fd{::mkstemp(path.get())};
#endif
if (fd < 0) {
handler.SignalErrno();
}
#ifndef _WIN32
- ::unlink(path);
+ if (fd >= 0) {
+ ::unlink(path.get());
+ }
#endif
return fd;
}
diff --git a/flang-rt/unittests/Runtime/CMakeLists.txt b/flang-rt/unittests/Runtime/CMakeLists.txt
index ddabe346b54d1..09d6dfcef4771 100644
--- a/flang-rt/unittests/Runtime/CMakeLists.txt
+++ b/flang-rt/unittests/Runtime/CMakeLists.txt
@@ -37,6 +37,7 @@ add_flangrt_unittest(RuntimeTests
Random.cpp
Reduction.cpp
RuntimeCrashTest.cpp
+ ScratchTempDir.cpp
Stop.cpp
Support.cpp
Time.cpp
diff --git a/flang-rt/unittests/Runtime/ScratchTempDir.cpp b/flang-rt/unittests/Runtime/ScratchTempDir.cpp
new file mode 100644
index 0000000000000..fc2a5c4539187
--- /dev/null
+++ b/flang-rt/unittests/Runtime/ScratchTempDir.cpp
@@ -0,0 +1,165 @@
+//===-- unittests/Runtime/ScratchTempDir.cpp --------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Verifies that OPEN(STATUS='SCRATCH') honors the TMPDIR/TMP/TEMP/TEMPDIR
+// environment variables on POSIX, matching llvm::sys::path::system_temp_directory.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _WIN32
+
+#include "CrashHandlerFixture.h"
+#include "gtest/gtest.h"
+#include "flang/Runtime/io-api.h"
+#include "flang/Runtime/iostat-consts.h"
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <optional>
+#include <string>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <utility>
+#include <vector>
+
+using namespace Fortran::runtime;
+using namespace Fortran::runtime::io;
+
+namespace {
+
+// Saves the values of TMPDIR/TMP/TEMP/TEMPDIR on construction and restores
+// them on destruction so a test that mutates them does not leak state to
+// later tests in the same process.
+class TempDirEnvGuard {
+public:
+ TempDirEnvGuard() {
+ for (const char *name : kVars) {
+ if (const char *value{std::getenv(name)}) {
+ saved_.emplace_back(name, std::string{value});
+ } else {
+ saved_.emplace_back(name, std::nullopt);
+ }
+ ::unsetenv(name);
+ }
+ }
+ ~TempDirEnvGuard() {
+ for (const auto &entry : saved_) {
+ if (entry.second) {
+ ::setenv(entry.first, entry.second->c_str(), /*overwrite=*/1);
+ } else {
+ ::unsetenv(entry.first);
+ }
+ }
+ }
+
+private:
+ static constexpr const char *kVars[]{"TMPDIR", "TMP", "TEMP", "TEMPDIR"};
+ std::vector<std::pair<const char *, std::optional<std::string>>> saved_;
+};
+
+// Opens a SCRATCH unit and returns the iostat value from EndIoStatement
+// (IostatOk on success, non-zero on error). EnableHandlers makes the
+// runtime defer I/O errors to EndIoStatement rather than crashing the
+// process.
+int OpenScratchUnit() {
+ Cookie io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
+ IONAME(EnableHandlers)(io, /*hasIoStat=*/true);
+ IONAME(SetStatus)(io, "SCRATCH", 7);
+ IONAME(SetAction)(io, "READWRITE", 9);
+ int unit{-1};
+ IONAME(GetNewUnit)(io, unit);
+ int iostat{IONAME(EndIoStatement)(io)};
+ if (iostat == IostatOk && unit >= 0) {
+ Cookie close{IONAME(BeginClose)(unit, __FILE__, __LINE__)};
+ IONAME(EndIoStatement)(close);
+ }
+ return iostat;
+}
+
+struct ScratchTempDirTests : CrashHandlerFixture {};
+
+// TMPDIR pointing at a non-existent directory must cause OPEN to fail —
+// this proves the env var is actually consulted by the runtime rather
+// than the hardcoded /tmp being used.
+TEST_F(ScratchTempDirTests, TmpdirHonored) {
+ TempDirEnvGuard guard;
+ ::setenv("TMPDIR", "/this/path/does/not/exist/flang-rt-test", 1);
+ EXPECT_NE(OpenScratchUnit(), IostatOk)
+ << "OPEN(STATUS='SCRATCH') should fail when TMPDIR points to a "
+ "non-existent directory";
+}
+
+// With no env vars set, OPEN must succeed via the /tmp fallback. This
+// confirms backward compatibility for users who do not set TMPDIR.
+TEST_F(ScratchTempDirTests, FallbackToTmp) {
+ TempDirEnvGuard guard;
+ EXPECT_EQ(OpenScratchUnit(), IostatOk)
+ << "OPEN(STATUS='SCRATCH') should succeed via /tmp fallback when no "
+ "temp env vars are set";
+}
+
+// TMP must be consulted when TMPDIR is unset. Set TMP to a non-existent
+// path and expect failure.
+TEST_F(ScratchTempDirTests, TmpConsulted) {
+ TempDirEnvGuard guard;
+ ::setenv("TMP", "/this/path/does/not/exist/flang-rt-test", 1);
+ EXPECT_NE(OpenScratchUnit(), IostatOk)
+ << "OPEN(STATUS='SCRATCH') should fail when TMP points to a "
+ "non-existent directory and TMPDIR is unset";
+}
+
+// An empty TMPDIR string must be skipped, so the next env var (TMP) wins.
+// Here TMP is a real directory, so OPEN should succeed.
+TEST_F(ScratchTempDirTests, EmptyTmpdirSkipped) {
+ TempDirEnvGuard guard;
+ char tmpDir[]{"/tmp/flang-rt-scratch-test-XXXXXX"};
+ ASSERT_NE(::mkdtemp(tmpDir), nullptr) << "mkdtemp failed: " << strerror(errno);
+ ::setenv("TMPDIR", "", 1);
+ ::setenv("TMP", tmpDir, 1);
+ EXPECT_EQ(OpenScratchUnit(), IostatOk)
+ << "Empty TMPDIR should be skipped in favor of TMP";
+ ::rmdir(tmpDir);
+}
+
+// SCRATCH file should actually be created inside the user-specified temp
+// directory. We can't observe the file by name (it is unlink'd immediately
+// after creation), but we can observe that the inode count of the directory
+// transiently increases while a SCRATCH unit is open.
+TEST_F(ScratchTempDirTests, FileCreatedInTmpdir) {
+ TempDirEnvGuard guard;
+ char tmpDir[]{"/tmp/flang-rt-scratch-test-XXXXXX"};
+ ASSERT_NE(::mkdtemp(tmpDir), nullptr) << "mkdtemp failed: " << strerror(errno);
+ ::setenv("TMPDIR", tmpDir, 1);
+
+ struct stat before{};
+ ASSERT_EQ(::stat(tmpDir, &before), 0);
+
+ Cookie io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
+ IONAME(EnableHandlers)(io, /*hasIoStat=*/true);
+ ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7));
+ ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9));
+ int unit{-1};
+ ASSERT_TRUE(IONAME(GetNewUnit)(io, unit));
+ ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk);
+
+ struct stat during{};
+ ASSERT_EQ(::stat(tmpDir, &during), 0);
+ // mkstemp creates the file then we immediately unlink it, so st_nlink
+ // returns to the original count, but the directory's mtime/ctime should
+ // have been bumped by the create+unlink pair.
+ EXPECT_GE(during.st_mtime, before.st_mtime)
+ << "TMPDIR should have been touched by SCRATCH file creation";
+
+ Cookie close{IONAME(BeginClose)(unit, __FILE__, __LINE__)};
+ EXPECT_EQ(IONAME(EndIoStatement)(close), IostatOk);
+ ::rmdir(tmpDir);
+}
+
+} // namespace
+
+#endif // !_WIN32
diff --git a/flang/docs/RuntimeEnvironment.md b/flang/docs/RuntimeEnvironment.md
index 0414ea1e0d59c..af50e567e132d 100644
--- a/flang/docs/RuntimeEnvironment.md
+++ b/flang/docs/RuntimeEnvironment.md
@@ -84,3 +84,20 @@ the pool can hold, the runtime terminates with a diagnostic message that
includes the current pool capacity.
Example: `export FLANG_TRAMPOLINE_POOL_SIZE=4096`
+
+## `TMPDIR`, `TMP`, `TEMP`, `TEMPDIR`
+
+These conventional POSIX environment variables select the directory in
+which the runtime creates scratch files for `OPEN(STATUS='SCRATCH')`.
+The first one that is set to a non-empty value wins, with the search
+order being `TMPDIR`, then `TMP`, then `TEMP`, then `TEMPDIR`. If none
+are set, the runtime falls back to `P_tmpdir` (typically `/tmp` on
+glibc) and finally to `/tmp`. This lookup order matches
+`llvm::sys::path::system_temp_directory`, so flang agrees with the rest
+of the LLVM toolchain on where temporary files go.
+
+On Windows these variables are not consulted directly; scratch files
+are placed in the directory returned by `GetTempPathA`, which itself
+consults `TMP`, `TEMP`, and `USERPROFILE`.
+
+Example: `export TMPDIR=/var/local/scratch`
>From b7ac0ed7d10aeaf4b7a0d1d9071ce7faf38db873 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Mon, 25 May 2026 08:03:23 -0400
Subject: [PATCH 2/2] clang-format
---
flang-rt/unittests/Runtime/ScratchTempDir.cpp | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/flang-rt/unittests/Runtime/ScratchTempDir.cpp b/flang-rt/unittests/Runtime/ScratchTempDir.cpp
index fc2a5c4539187..fbff7c348a58f 100644
--- a/flang-rt/unittests/Runtime/ScratchTempDir.cpp
+++ b/flang-rt/unittests/Runtime/ScratchTempDir.cpp
@@ -7,7 +7,8 @@
//===----------------------------------------------------------------------===//
//
// Verifies that OPEN(STATUS='SCRATCH') honors the TMPDIR/TMP/TEMP/TEMPDIR
-// environment variables on POSIX, matching llvm::sys::path::system_temp_directory.
+// environment variables on POSIX, matching
+// llvm::sys::path::system_temp_directory.
//
//===----------------------------------------------------------------------===//
@@ -118,7 +119,8 @@ TEST_F(ScratchTempDirTests, TmpConsulted) {
TEST_F(ScratchTempDirTests, EmptyTmpdirSkipped) {
TempDirEnvGuard guard;
char tmpDir[]{"/tmp/flang-rt-scratch-test-XXXXXX"};
- ASSERT_NE(::mkdtemp(tmpDir), nullptr) << "mkdtemp failed: " << strerror(errno);
+ ASSERT_NE(::mkdtemp(tmpDir), nullptr)
+ << "mkdtemp failed: " << strerror(errno);
::setenv("TMPDIR", "", 1);
::setenv("TMP", tmpDir, 1);
EXPECT_EQ(OpenScratchUnit(), IostatOk)
@@ -133,7 +135,8 @@ TEST_F(ScratchTempDirTests, EmptyTmpdirSkipped) {
TEST_F(ScratchTempDirTests, FileCreatedInTmpdir) {
TempDirEnvGuard guard;
char tmpDir[]{"/tmp/flang-rt-scratch-test-XXXXXX"};
- ASSERT_NE(::mkdtemp(tmpDir), nullptr) << "mkdtemp failed: " << strerror(errno);
+ ASSERT_NE(::mkdtemp(tmpDir), nullptr)
+ << "mkdtemp failed: " << strerror(errno);
::setenv("TMPDIR", tmpDir, 1);
struct stat before{};
More information about the flang-commits
mailing list