summaryrefslogtreecommitdiff
path: root/src/temp_file.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-02 18:35:29 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit45f152b2de946aa7c7ed5ffe37d07b0930dfc38b (patch)
tree0b18f8484163596414b20ee75a13fb508babc4ec /src/temp_file.zig
parent35cecf26b06cf50d9827056da99d883ca975c39b (diff)
downloadpardes-45f152b2de946aa7c7ed5ffe37d07b0930dfc38b.tar.gz
pardes-45f152b2de946aa7c7ed5ffe37d07b0930dfc38b.zip
add New builtin for an empty temporary file in the calling column
Diffstat (limited to 'src/temp_file.zig')
-rw-r--r--src/temp_file.zig84
1 files changed, 84 insertions, 0 deletions
diff --git a/src/temp_file.zig b/src/temp_file.zig
new file mode 100644
index 00000000..a6174efd
--- /dev/null
+++ b/src/temp_file.zig
@@ -0,0 +1,84 @@
+//! Native-shell ownership of `New`'s one filesystem operation.
+//!
+//! The core asks for a temporary file by effect and receives only its path.
+//! `mkstemp` creates and opens the name atomically with mode 0600, so there is
+//! no name-then-open race and repeated requests cannot collide. A file the
+//! core declines is unlinked here immediately; an adopted file becomes an
+//! ordinary Pardes document and is deliberately left on disk when its pane is
+//! closed, just like every other document (and so a dump remains restorable).
+const std = @import("std");
+const libc = std.c;
+
+extern "c" fn mkstemp(template: [*:0]u8) c_int;
+extern "c" fn lseek(fd: c_int, offset: libc.off_t, whence: c_int) libc.off_t;
+
+pub const Created = struct {
+ fd: c_int,
+ path: [:0]u8,
+
+ /// The core copied the path and now owns the document. Close our creation
+ /// handle; Save and watching reopen/use the pathname through their normal
+ /// seams.
+ pub fn adopt(f: Created) void {
+ _ = libc.close(f.fd);
+ }
+
+ /// Creation succeeded but the request became stale or the core could not
+ /// allocate a pane. Nothing user-visible owns this name, so remove it.
+ pub fn discard(f: Created) void {
+ _ = libc.close(f.fd);
+ _ = libc.unlink(f.path);
+ }
+};
+
+/// Create in the platform's conventional temporary directory. TMPDIR is a
+/// shell concern (environment + filesystem), intentionally outside the core.
+pub fn create(buf: *[4096:0]u8) ?Created {
+ const env = libc.getenv("TMPDIR");
+ const dir = if (env) |p| std.mem.span(p) else "/tmp";
+ return createIn(buf, if (dir.len > 0) dir else "/tmp");
+}
+
+/// Split out for a hermetic failure test and to keep template construction
+/// independently checkable. The six Xs are consumed by mkstemp itself.
+pub fn createIn(buf: *[4096:0]u8, dir_arg: []const u8) ?Created {
+ const dir = std.mem.trimEnd(u8, dir_arg, "/");
+ const path = std.fmt.bufPrintSentinel(
+ buf,
+ "{s}{s}pardes-XXXXXX",
+ .{ if (dir.len == 0) "/" else dir, if (dir.len == 0) "" else "/" },
+ 0,
+ ) catch return null;
+ const fd = mkstemp(path.ptr);
+ if (fd < 0) return null;
+ return .{ .fd = fd, .path = path };
+}
+
+test "mkstemp creates distinct empty files and rejected files are removable" {
+ var abuf: [4096:0]u8 = undefined;
+ var bbuf: [4096:0]u8 = undefined;
+ const a = createIn(&abuf, "/tmp") orelse return error.TempCreateFailed;
+ const b = createIn(&bbuf, "/tmp") orelse return error.TempCreateFailed;
+ defer b.discard();
+
+ try std.testing.expect(!std.mem.eql(u8, a.path, b.path));
+ try std.testing.expectEqual(@as(libc.off_t, 0), lseek(a.fd, 0, 2)); // SEEK_END
+ try std.testing.expectEqual(@as(libc.off_t, 0), lseek(b.fd, 0, 2));
+
+ a.discard();
+ try std.testing.expect(libc.unlink(a.path) < 0); // already removed
+}
+
+test "an adopted tempfile remains named for the document" {
+ var buf: [4096:0]u8 = undefined;
+ const made = createIn(&buf, "/tmp") orelse return error.TempCreateFailed;
+ made.adopt();
+ // Adoption closes only the creation handle. The ordinary file document
+ // keeps this path for Save, watch, Del and dump/restore.
+ try std.testing.expectEqual(@as(c_int, 0), libc.unlink(made.path));
+}
+
+test "mkstemp failure creates no candidate file" {
+ var buf: [4096:0]u8 = undefined;
+ try std.testing.expect(createIn(&buf, "/definitely/not/a/pardes/temp/directory") == null);
+}