summaryrefslogtreecommitdiff
path: root/src/temp_file.zig
blob: a6174efd8e6660d82a118c39a7e577dfea28bb54 (plain) (blame)
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
//! 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);
}