1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
const std = @import("std");
const builtin = @import("builtin");
const global = @import("../global.zig");
const posix = std.posix;
const windows = @import("windows.zig");
const log = std.log.scoped(.os);
pub const rlimit = if (@hasDecl(posix.system, "rlimit")) posix.rlimit else struct {};
/// This maximizes the number of file descriptors we can have open. We
/// need to do this because each window consumes at least a handful of fds.
/// This is extracted from the Zig compiler source code.
pub fn fixMaxFiles() ?rlimit {
if (!@hasDecl(posix.system, "rlimit") or
posix.system.rlimit == void) return null;
const old = posix.getrlimit(.NOFILE) catch {
log.warn("failed to query file handle limit, may limit max windows", .{});
return null; // Oh well; we tried.
};
// If we're already at the max, we're done.
if (old.cur >= old.max) {
log.debug("file handle limit already maximized value={}", .{old.cur});
return old;
}
// Do a binary search for the limit.
var lim = old;
var min: posix.rlim_t = lim.cur;
var max: posix.rlim_t = 1 << 20;
// But if there's a defined upper bound, don't search, just set it.
if (lim.max != posix.RLIM.INFINITY) {
min = lim.max;
max = lim.max;
}
while (true) {
lim.cur = min + @divTrunc(max - min, 2); // on freebsd rlim_t is signed
if (posix.setrlimit(.NOFILE, lim)) |_| {
min = lim.cur;
} else |_| {
max = lim.cur;
}
if (min + 1 >= max) break;
}
log.debug("file handle limit raised value={}", .{lim.cur});
return old;
}
pub fn restoreMaxFiles(lim: rlimit) void {
if (!@hasDecl(posix.system, "rlimit")) return;
posix.setrlimit(.NOFILE, lim) catch {};
}
/// Return the recommended path for temporary files. Any trailing
/// path separator is stripped so callers can safely join with their
/// own separator (e.g. `"{tmp}/{name}"`).
///
/// On Windows this calls `GetTempPathW` and allocates a UTF-8 copy
/// (or duplicates a hard-fallback string if the syscall fails). On
/// POSIX this returns `$TMPDIR`/`$TMP` (or `"/tmp"` as a fallback)
/// without allocating. Always pair with `freeTmpDir` to release any
/// allocation.
pub fn allocTmpDir(allocator: std.mem.Allocator, environ: std.process.Environ) std.mem.Allocator.Error![]const u8 {
if (builtin.os.tag == .windows) {
// GetTempPathW guarantees the result fits in MAX_PATH+1.
var buf: [windows.MAX_PATH + 1:0]u16 = undefined;
const len = windows.exp.kernel32.GetTempPathW(buf.len, &buf);
if (len > 0) {
// Trim the UTF-16 string before encoding as UT8-8 so that the
// returned slice's length matches its underlying allocation.
const trimmed = std.mem.trimEnd(u16, buf[0..len], &.{std.fs.path.sep});
if (std.unicode.utf16LeToUtf8Alloc(allocator, trimmed)) |utf8| {
return utf8;
} else |e| switch (e) {
error.OutOfMemory => return error.OutOfMemory,
else => log.warn("failed to convert temp dir path from windows string: {}", .{e}),
}
}
return allocator.dupe(u8, "C:\\Windows\\Temp");
}
const tmpdir = environ.getPosix("TMPDIR") orelse environ.getPosix("TMP") orelse return "/tmp";
return std.mem.trimEnd(u8, tmpdir, &.{std.fs.path.sep});
}
/// Free a path returned by `allocTmpDir` if it allocated memory.
/// This is a no-op on POSIX.
pub fn freeTmpDir(allocator: std.mem.Allocator, dir: []const u8) void {
if (builtin.os.tag != .windows) return;
allocator.free(dir);
}
const random_basename_bytes = 16;
const b64_encoder = std.base64.url_safe_no_pad.Encoder;
pub const RandomBasenameError = error{BufferTooSmall};
/// Length of the basename produced by `randomBasename`.
pub const random_basename_len = b64_encoder.calcSize(random_basename_bytes);
/// Write a random filesystem-safe base64 basename of length
/// `random_basename_len` into `buf` and return a slice over the
/// written bytes. Returns `error.BufferTooSmall` if `buf` is too
/// short.
pub fn randomBasename(buf: []u8) RandomBasenameError![]const u8 {
if (buf.len < random_basename_len) return error.BufferTooSmall;
var rand_buf: [random_basename_bytes]u8 = undefined;
global.io().random(&rand_buf);
return b64_encoder.encode(buf[0..random_basename_len], &rand_buf);
}
/// Return a freshly-allocated path of the form `{TMPDIR}/{prefix}{random}`.
/// The caller owns the returned slice and must free it with `allocator`.
///
/// Nothing is created on disk; this only builds the path string. Useful
/// for one-shot temporary file/socket paths where a full `TempDir` is
/// overkill.
pub fn randomTmpPath(
allocator: std.mem.Allocator,
prefix: []const u8,
) std.mem.Allocator.Error![]u8 {
var name_buf: [random_basename_len]u8 = undefined;
const basename = randomBasename(&name_buf) catch unreachable;
return std.fmt.allocPrint(
allocator,
"{s}{c}{s}{s}",
.{ global.tmpDirPath(), std.fs.path.sep, prefix, basename },
);
}
test randomBasename {
const testing = std.testing;
var buf: [random_basename_len]u8 = undefined;
const name = try randomBasename(&buf);
try testing.expectEqual(random_basename_len, name.len);
for (name) |c| {
const ok = std.ascii.isAlphanumeric(c) or c == '-' or c == '_';
try testing.expect(ok);
}
var small: [random_basename_len - 1]u8 = undefined;
try testing.expectError(error.BufferTooSmall, randomBasename(&small));
}
|