diff options
Diffstat (limited to 'src/temp_file.zig')
| -rw-r--r-- | src/temp_file.zig | 84 |
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); +} |
