Skip to content

Commit 1f22f44

Browse files
Align temp directory resolution with os.tmpdir() (#25878)
## Summary - Aligns Bun's temp directory resolution with Node.js's `os.tmpdir()` behavior - Checks `TMPDIR`, `TMP`, and `TEMP` environment variables in order (matching Node.js) - Uses `bun.once` for lazy initialization instead of mutable static state - Removes `setTempdir` function and simplifies the API to use `RealFS.tmpdirPath()` directly ## Test plan - [ ] Verify temp directory resolution matches Node.js behavior - [ ] Test with various environment variable configurations - [ ] Ensure existing tests pass with `bun bd test` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <[email protected]>
1 parent ff590e9 commit 1f22f44

File tree

7 files changed

+62
-56
lines changed

7 files changed

+62
-56
lines changed

scripts/runner.node.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,7 @@ async function spawnBun(execPath, { args, cwd, timeout, env, stdout, stderr }) {
11281128
...process.env,
11291129
PATH: path,
11301130
TMPDIR: tmpdirPath,
1131+
BUN_TMPDIR: tmpdirPath,
11311132
USER: username,
11321133
HOME: homedir,
11331134
SHELL: shellPath,

src/OutputFile.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ pub const FileOperation = struct {
7474

7575
pub fn getPathname(file: *const FileOperation) string {
7676
if (file.is_tmpdir) {
77-
return resolve_path.joinAbs(@TypeOf(Fs.FileSystem.instance.fs).tmpdir_path, .auto, file.pathname);
77+
return resolve_path.joinAbs(Fs.FileSystem.RealFS.tmpdirPath(), .auto, file.pathname);
7878
} else {
7979
return file.pathname;
8080
}

src/StandaloneModuleGraph.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ pub const StandaloneModuleGraph = struct {
670670
if (!tried_changing_abs_dir) {
671671
tried_changing_abs_dir = true;
672672
const zname_z = bun.strings.concat(bun.default_allocator, &.{
673-
bun.fs.FileSystem.instance.fs.tmpdirPath(),
673+
bun.fs.FileSystem.RealFS.tmpdirPath(),
674674
std.fs.path.sep_str,
675675
zname,
676676
&.{0},

src/bun.js/ModuleLoader.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ pub fn resolveEmbeddedFile(vm: *VirtualMachine, path_buf: *bun.PathBuffer, linux
9292
},
9393
else => {},
9494
}
95-
return bun.path.joinAbsStringBuf(bun.fs.FileSystem.instance.fs.tmpdirPath(), path_buf, &[_]string{tmpfilename}, .auto);
95+
return bun.path.joinAbsStringBuf(bun.fs.FileSystem.RealFS.tmpdirPath(), path_buf, &[_]string{tmpfilename}, .auto);
9696
}
9797

9898
pub export fn Bun__getDefaultLoader(global: *JSGlobalObject, str: *const bun.String) api.Loader {

src/bun.js/RuntimeTranspilerCache.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ pub const RuntimeTranspilerCache = struct {
423423
}
424424

425425
{
426-
const parts = &[_][]const u8{ bun.fs.FileSystem.instance.fs.tmpdirPath(), "bun", "@t@" };
426+
const parts = &[_][]const u8{ bun.fs.FileSystem.RealFS.tmpdirPath(), "bun", "@t@" };
427427
return bun.fs.FileSystem.instance.absBufZ(parts, buf);
428428
}
429429
}

src/env_var.zig

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,11 @@ pub const SHELL = PlatformSpecificNew(kind.string, "SHELL", null, .{});
109109
/// C:\Windows, for example.
110110
/// Note: Do not use this variable directly -- use os.zig's implementation instead.
111111
pub const SYSTEMROOT = PlatformSpecificNew(kind.string, null, "SYSTEMROOT", .{});
112-
pub const TEMP = PlatformSpecificNew(kind.string, null, "TEMP", .{});
112+
pub const TEMP = PlatformSpecificNew(kind.string, "TEMP", "TEMP", .{});
113113
pub const TERM = New(kind.string, "TERM", .{});
114114
pub const TERM_PROGRAM = New(kind.string, "TERM_PROGRAM", .{});
115-
pub const TMP = PlatformSpecificNew(kind.string, null, "TMP", .{});
116-
pub const TMPDIR = PlatformSpecificNew(kind.string, "TMPDIR", null, .{});
115+
pub const TMP = PlatformSpecificNew(kind.string, "TMP", "TMP", .{});
116+
pub const TMPDIR = PlatformSpecificNew(kind.string, "TMPDIR", "TMPDIR", .{});
117117
pub const TMUX = New(kind.string, "TMUX", .{});
118118
pub const TODIUM = New(kind.string, "TODIUM", .{});
119119
pub const USER = PlatformSpecificNew(kind.string, "USER", "USERNAME", .{});
@@ -601,6 +601,16 @@ fn PlatformSpecificNew(
601601
return null;
602602
}
603603

604+
pub fn getNotEmpty() ReturnType {
605+
if (Self.get()) |v| {
606+
if (v.len == 0) {
607+
return null;
608+
}
609+
return v;
610+
}
611+
return null;
612+
}
613+
604614
/// Retrieve the value of the environment variable, loading it if necessary.
605615
/// Fails if the current platform is unsupported.
606616
pub fn get() ReturnType {

src/fs.zig

Lines changed: 44 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -530,77 +530,77 @@ pub const FileSystem = struct {
530530
file_limit: usize = 32,
531531
file_quota: usize = 32,
532532

533-
pub var win_tempdir_cache: ?[]const u8 = undefined;
533+
fn #platformTempDir() []const u8 {
534+
// Try TMPDIR, TMP, and TEMP in that order, matching Node.js.
535+
// https://github.com/nodejs/node/blob/e172be269890702bf2ad06252f2f152e7604d76c/src/node_credentials.cc#L132
536+
if (bun.env_var.TMPDIR.getNotEmpty() orelse
537+
bun.env_var.TMP.getNotEmpty() orelse
538+
bun.env_var.TEMP.getNotEmpty()) |dir|
539+
{
540+
if (dir.len > 1 and dir[dir.len - 1] == std.fs.path.sep) {
541+
return dir[0 .. dir.len - 1];
542+
}
543+
544+
return dir;
545+
}
534546

535-
pub fn platformTempDir() []const u8 {
536547
return switch (Environment.os) {
537548
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw#remarks
538-
.windows => win_tempdir_cache orelse {
539-
const value = bun.env_var.TEMP.get() orelse bun.env_var.TMP.get() orelse brk: {
540-
if (bun.env_var.SYSTEMROOT.get() orelse bun.env_var.WINDIR.get()) |windir| {
541-
break :brk std.fmt.allocPrint(
542-
bun.default_allocator,
543-
"{s}\\Temp",
544-
.{strings.withoutTrailingSlash(windir)},
545-
) catch |err| bun.handleOom(err);
546-
}
547-
548-
if (bun.env_var.HOME.get()) |profile| {
549-
var buf: bun.PathBuffer = undefined;
550-
var parts = [_]string{"AppData\\Local\\Temp"};
551-
const out = bun.path.joinAbsStringBuf(profile, &buf, &parts, .loose);
552-
break :brk bun.handleOom(bun.default_allocator.dupe(u8, out));
553-
}
554-
555-
var tmp_buf: bun.PathBuffer = undefined;
556-
const cwd = std.posix.getcwd(&tmp_buf) catch @panic("Failed to get cwd for platformTempDir");
557-
const root = bun.path.windowsFilesystemRoot(cwd);
558-
break :brk std.fmt.allocPrint(
549+
.windows => {
550+
if (bun.env_var.SYSTEMROOT.get() orelse bun.env_var.WINDIR.get()) |windir| {
551+
return std.fmt.allocPrint(
559552
bun.default_allocator,
560-
"{s}\\Windows\\Temp",
561-
.{strings.withoutTrailingSlash(root)},
553+
"{s}\\Temp",
554+
.{strings.withoutTrailingSlash(windir)},
562555
) catch |err| bun.handleOom(err);
563-
};
564-
win_tempdir_cache = value;
565-
return value;
556+
}
557+
558+
if (bun.env_var.HOME.get()) |profile| {
559+
var buf: bun.PathBuffer = undefined;
560+
var parts = [_]string{"AppData\\Local\\Temp"};
561+
const out = bun.path.joinAbsStringBuf(profile, &buf, &parts, .loose);
562+
return bun.handleOom(bun.default_allocator.dupe(u8, out));
563+
}
564+
565+
var tmp_buf: bun.PathBuffer = undefined;
566+
const cwd = std.posix.getcwd(&tmp_buf) catch @panic("Failed to get cwd for platformTempDir");
567+
const root = bun.path.windowsFilesystemRoot(cwd);
568+
return std.fmt.allocPrint(
569+
bun.default_allocator,
570+
"{s}\\Windows\\Temp",
571+
.{strings.withoutTrailingSlash(root)},
572+
) catch |err| bun.handleOom(err);
566573
},
567574
.mac => "/private/tmp",
568575
else => "/tmp",
569576
};
570577
}
571578

579+
var get_platform_tempdir = bun.once(#platformTempDir);
580+
pub fn platformTempDir() []const u8 {
581+
return get_platform_tempdir.call(.{});
582+
}
583+
572584
pub const Tmpfile = switch (Environment.os) {
573585
.windows => TmpfileWindows,
574586
else => TmpfilePosix,
575587
};
576588

577-
pub var tmpdir_path: []const u8 = undefined;
578-
pub var tmpdir_path_set = false;
579-
pub fn tmpdirPath(_: *const @This()) []const u8 {
580-
if (!tmpdir_path_set) {
581-
tmpdir_path = bun.env_var.BUN_TMPDIR.get() orelse platformTempDir();
582-
tmpdir_path_set = true;
583-
}
584-
585-
return tmpdir_path;
589+
pub fn tmpdirPath() []const u8 {
590+
return bun.env_var.BUN_TMPDIR.getNotEmpty() orelse platformTempDir();
586591
}
587592

588593
pub fn openTmpDir(_: *const RealFS) !std.fs.Dir {
589-
if (!tmpdir_path_set) {
590-
tmpdir_path = bun.env_var.BUN_TMPDIR.get() orelse platformTempDir();
591-
tmpdir_path_set = true;
592-
}
593-
594594
if (comptime Environment.isWindows) {
595-
return (try bun.sys.openDirAtWindowsA(bun.invalid_fd, tmpdir_path, .{
595+
return (try bun.sys.openDirAtWindowsA(bun.invalid_fd, tmpdirPath(), .{
596596
.iterable = true,
597597
// we will not delete the temp directory
598598
.can_rename_or_delete = false,
599599
.read_only = true,
600600
}).unwrap()).stdDir();
601601
}
602602

603-
return try bun.openDirAbsolute(tmpdir_path);
603+
return try bun.openDirAbsolute(tmpdirPath());
604604
}
605605

606606
pub fn entriesAt(this: *RealFS, index: allocators.IndexType, generation: bun.Generation) ?*EntriesOption {
@@ -639,11 +639,6 @@ pub const FileSystem = struct {
639639
return bun.env_var.BUN_TMPDIR.get() orelse platformTempDir();
640640
}
641641

642-
pub fn setTempdir(path: ?string) void {
643-
tmpdir_path = path orelse getDefaultTempDir();
644-
tmpdir_path_set = true;
645-
}
646-
647642
pub const TmpfilePosix = struct {
648643
fd: bun.FileDescriptor = bun.invalid_fd,
649644
dir_fd: bun.FileDescriptor = bun.invalid_fd,

0 commit comments

Comments
 (0)