mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-24 05:40:15 +00:00
os: add randomTmpPath for allocating temp paths
Factor TempDir's name generation into a reusable `randomBasename` (16
random bytes, url-safe base64) and add `randomTmpPath` on top, which
composes `allocTmpDir` + `randomBasename` into a single allocated path
in the form `{TMPDIR}/{prefix}{random}` (mktemp(1)-ish).
This is convenient for callers who want a unique path under TMPDIR (for
a temporary file, socket, etc.) without having to think about basename
buffer sizing or path joining.
Also, use `std.base64.url_safe_no_pad.Encoder` instead of the custom
base64 alphabet, which is exactly equivalent.
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user