diff --git a/src/os/TempDir.zig b/src/os/TempDir.zig index 2ddf18da3..c09e2f48d 100644 --- a/src/os/TempDir.zig +++ b/src/os/TempDir.zig @@ -3,10 +3,8 @@ const TempDir = @This(); const std = @import("std"); -const testing = std.testing; const Dir = std.fs.Dir; -const allocTmpDir = @import("file.zig").allocTmpDir; -const freeTmpDir = @import("file.zig").freeTmpDir; +const file = @import("file.zig"); const log = std.log.scoped(.tempdir); @@ -18,28 +16,26 @@ parent: Dir, /// Name buffer that name points into. Generally do not use. To get the /// name call the name() function. -name_buf: [TMP_PATH_LEN:0]u8, +name_buf: [file.RANDOM_BASENAME_LEN:0]u8, /// Create the temporary directory. pub fn init() !TempDir { // Note: the tmp_path_buf sentinel is important because it ensures - // we actually always have TMP_PATH_LEN+1 bytes of available space. We - // need that so we can set the sentinel in the case we use all the - // possible length. - var tmp_path_buf: [TMP_PATH_LEN:0]u8 = undefined; - var rand_buf: [RANDOM_BYTES]u8 = undefined; + // we actually always have RANDOM_BASENAME_LEN+1 bytes of available + // space. We need that so we can set the sentinel in the case we use + // all the possible length. + var tmp_path_buf: [file.RANDOM_BASENAME_LEN:0]u8 = undefined; const dir = dir: { const cwd = std.fs.cwd(); - const tmp_dir = allocTmpDir(std.heap.page_allocator) orelse break :dir cwd; - defer freeTmpDir(std.heap.page_allocator, tmp_dir); + const tmp_dir = file.allocTmpDir(std.heap.page_allocator) orelse break :dir cwd; + defer file.freeTmpDir(std.heap.page_allocator, tmp_dir); break :dir try cwd.openDir(tmp_dir, .{}); }; // We now loop forever until we can find a directory that we can create. while (true) { - std.crypto.random.bytes(rand_buf[0..]); - const tmp_path = b64_encoder.encode(&tmp_path_buf, &rand_buf); + const tmp_path = try file.randomBasename(&tmp_path_buf); tmp_path_buf[tmp_path.len] = 0; dir.makeDir(tmp_path) catch |err| switch (err) { @@ -69,16 +65,9 @@ pub fn deinit(self: *TempDir) void { log.err("error deleting temp dir err={}", .{err}); } -// The amount of random bytes to get to determine our filename. -const RANDOM_BYTES = 16; -const TMP_PATH_LEN = b64_encoder.calcSize(RANDOM_BYTES); - -// Base64 encoder, replacing the standard `+/` with `-_` so that it can -// be used in a file name on any filesystem. -const b64_encoder = std.base64.Base64Encoder.init(b64_alphabet, null); -const b64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*; - test { + const testing = std.testing; + var td = try init(); errdefer td.deinit(); diff --git a/src/os/file.zig b/src/os/file.zig index dc2d9f857..eedc20841 100644 --- a/src/os/file.zig +++ b/src/os/file.zig @@ -79,3 +79,58 @@ pub fn freeTmpDir(allocator: std.mem.Allocator, dir: []const u8) void { 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; + std.crypto.random.bytes(&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 { + const tmp_dir = allocTmpDir(allocator) orelse "/tmp"; + defer freeTmpDir(allocator, tmp_dir); + var name_buf: [RANDOM_BASENAME_LEN]u8 = undefined; + const basename = randomBasename(&name_buf) catch unreachable; + return std.fmt.allocPrint( + allocator, + "{s}{c}{s}{s}", + .{ tmp_dir, 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)); +} diff --git a/src/os/main.zig b/src/os/main.zig index 2aadabac5..b30182740 100644 --- a/src/os/main.zig +++ b/src/os/main.zig @@ -52,6 +52,7 @@ pub const fixMaxFiles = file.fixMaxFiles; pub const restoreMaxFiles = file.restoreMaxFiles; pub const allocTmpDir = file.allocTmpDir; pub const freeTmpDir = file.freeTmpDir; +pub const randomTmpPath = file.randomTmpPath; pub const isFlatpak = flatpak.isFlatpak; pub const FlatpakHostCommand = flatpak.FlatpakHostCommand; pub const home = homedir.home; @@ -67,6 +68,7 @@ pub const ShellEscapeWriter = shell.ShellEscapeWriter; pub const getKernelInfo = kernel_info.getKernelInfo; test { + _ = file; _ = i18n; _ = path; _ = uri;