diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-09 10:41:33 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-10 09:17:07 -0300 |
| commit | 9085cb5bfdd0b78ff3a62c0c71fc231dd7b5052a (patch) | |
| tree | 91fce278abc85e4f7491de8b5f02184c8a2d5a56 /test/snapshot.zig | |
| parent | 2b1fac6fecbf7d7cc08f9dad24687b05e9968c9a (diff) | |
| download | pardes-9085cb5bfdd0b78ff3a62c0c71fc231dd7b5052a.tar.gz pardes-9085cb5bfdd0b78ff3a62c0c71fc231dd7b5052a.zip | |
replace ArrayLists with bounded storage
Diffstat (limited to 'test/snapshot.zig')
| -rw-r--r-- | test/snapshot.zig | 372 |
1 files changed, 279 insertions, 93 deletions
diff --git a/test/snapshot.zig b/test/snapshot.zig index 36a5b20b..c3611291 100644 --- a/test/snapshot.zig +++ b/test/snapshot.zig @@ -91,7 +91,7 @@ pub fn main(init: std.process.Init) !void { // pass costs more than the extra parallelism saves. const cpus = std.Thread.getCpuCount() catch 4; var jobs: usize = cpus + cpus / 4; - var scripts: std.ArrayList([]const u8) = .empty; + var script_count: usize = 0; for (args[1..]) |a| { if (std.mem.eql(u8, a, "--update")) { update = true; @@ -102,28 +102,42 @@ pub fn main(init: std.process.Init) !void { } else if (exe == null) { exe = try absPath(arena, orig_cwd, a); } else { - try scripts.append(arena, try absPath(arena, orig_cwd, a)); + script_count += 1; } } + var scripts = try arena.alloc([]const u8, script_count); + var script_i: usize = 0; + var saw_exe = false; + for (args[1..]) |a| { + if (std.mem.eql(u8, a, "--update") or + std.mem.eql(u8, a, "--trace-stable") or + std.mem.startsWith(u8, a, "--jobs=")) continue; + if (!saw_exe) { + saw_exe = true; + continue; + } + scripts[script_i] = try absPath(arena, orig_cwd, a); + script_i += 1; + } if (exe == null) fatal("usage: pardes-snap <exe> [scripts...] [--update] [--jobs=N]", .{}); const exe_z = try arena.dupeZ(u8, exe.?); - if (scripts.items.len == 0) { - try listSnaps(arena, try absPath(arena, orig_cwd, "test/snapshots"), &scripts); - std.mem.sort([]const u8, scripts.items, {}, struct { + if (scripts.len == 0) { + scripts = try listSnaps(arena, try absPath(arena, orig_cwd, "test/snapshots")); + std.mem.sort([]const u8, scripts, {}, struct { fn lt(_: void, x: []const u8, y: []const u8) bool { return std.mem.lessThan(u8, x, y); } }.lt); } - if (scripts.items.len == 0) fatal("no .snap scripts found", .{}); + if (scripts.len == 0) fatal("no .snap scripts found", .{}); try mkdir(arena, SNAP_BASE, true); // one run at a time (this is the flock that used to be typed by hand) _ = lockOrWait(SNAP_BASE ++ "/.run.lock", "another pardes-snap run"); - const codes = try arena.alloc(u8, scripts.items.len); - try runBatch(arena, exe_z, toBatch(arena, scripts.items), update, jobs, codes); + const codes = try arena.alloc(u8, scripts.len); + try runBatch(arena, exe_z, toBatch(arena, scripts), update, jobs, codes); // A failure in the parallel pass is more often contention than regression: // the app can lose a race with a kworker while 20 siblings fight for the @@ -135,11 +149,21 @@ pub fn main(init: std.process.Init) !void { if (jobs > 1) { var round: usize = 0; while (round < 2) : (round += 1) { - var retry: std.ArrayList(usize) = .empty; - for (codes, 0..) |c, i| if (c != 0) try retry.append(arena, i); - if (retry.items.len == 0 or retry.items.len > 10) break; - const paths = try arena.alloc(Idx, retry.items.len); - for (retry.items, paths, 0..) |i, *p, n| p.* = .{ .path = scripts.items[i], .code_idx = n }; + var retry_count: usize = 0; + for (codes) |c| { + if (c != 0) retry_count += 1; + } + if (retry_count == 0 or retry_count > 10) break; + const retry = try arena.alloc(usize, retry_count); + var retry_i: usize = 0; + for (codes, 0..) |c, i| { + if (c != 0) { + retry[retry_i] = i; + retry_i += 1; + } + } + const paths = try arena.alloc(Idx, retry.len); + for (retry, paths, 0..) |i, *p, n| p.* = .{ .path = scripts[i], .code_idx = n }; std.debug.print("re-running {d} failed script(s) serially{s}:", .{ paths.len, if (round == 0) "" else " with wider settle probes", @@ -149,12 +173,12 @@ pub fn main(init: std.process.Init) !void { careful = round > 0; const recodes = try arena.alloc(u8, paths.len); try runBatch(arena, exe_z, paths, update, 1, recodes); - for (retry.items, recodes) |i, c| codes[i] = c; + for (retry, recodes) |i, c| codes[i] = c; } } var failed: usize = 0; - for (scripts.items, codes) |script_path, code| { + for (scripts, codes) |script_path, code| { const report = readFileAlloc(arena, reportPath(arena, script_path) catch "") catch ""; std.debug.print("{s}", .{report}); if (code != 0) { @@ -164,10 +188,10 @@ pub fn main(init: std.process.Init) !void { } } if (failed > 0) { - std.debug.print("{d}/{d} snapshot scripts FAILED\n", .{ failed, scripts.items.len }); + std.debug.print("{d}/{d} snapshot scripts FAILED\n", .{ failed, scripts.len }); std.process.exit(1); } - std.debug.print("all {d} snapshot scripts ok\n", .{scripts.items.len}); + std.debug.print("all {d} snapshot scripts ok\n", .{scripts.len}); } /// Run `scripts` `jobs` at a time, one forked child each, filling `codes` with @@ -198,24 +222,26 @@ fn runBatch( codes: []u8, ) !void { const Job = struct { pid: libc.pid_t, idx: usize }; - var running: std.ArrayList(Job) = .empty; - defer running.deinit(arena); + const running = try arena.alloc(Job, @min(jobs, scripts.len)); + var running_len: usize = 0; var next: usize = 0; - while (next < scripts.len or running.items.len > 0) { - while (next < scripts.len and running.items.len < jobs) : (next += 1) { + while (next < scripts.len or running_len > 0) { + while (next < scripts.len and running_len < running.len) : (next += 1) { const idx = next; const pid = libc.fork(); if (pid == 0) std.process.exit(runOne(arena, exe_z, scripts[idx].path, update)); if (pid < 0) fatal("fork failed", .{}); - try running.append(arena, .{ .pid = pid, .idx = scripts[idx].code_idx }); + running[running_len] = .{ .pid = pid, .idx = scripts[idx].code_idx }; + running_len += 1; } var status: c_int = 0; const pid = libc.waitpid(-1, &status, 0); - for (running.items, 0..) |j, i| { + for (running[0..running_len], 0..) |j, i| { if (j.pid != pid) continue; // a killed child (crash in the harness itself) counts as a failure codes[j.idx] = if (status == 0) 0 else 1; - _ = running.swapRemove(i); + running_len -= 1; + running[i] = running[running_len]; break; } } @@ -337,7 +363,8 @@ fn runScript(arena: std.mem.Allocator, exe_z: [:0]const u8, script_path: []const _ = setenv("PARDES_DUMP", try arena.dupeZ(u8, dump_path), 1); if (chdirRc(try arena.dupeZ(u8, work)) < 0) return error.ChdirFailed; - var out: std.ArrayList(u8) = .empty; + const chunks = try arena.alloc([]const u8, countCaptures(src)); + var chunk_len: usize = 0; var h: ?eh.Harness = null; defer if (h) |*hp| hp.deinit(); @@ -368,9 +395,8 @@ fn runScript(arena: std.mem.Allocator, exe_z: [:0]const u8, script_path: []const // one) — a file that is long AND wide, which is what a horizontal // scroll test needs and `file` can only give as one huge script line const tail = tok.rest(); - var buf: std.ArrayList(u8) = .empty; - for (1..n + 1) |i| try buf.appendSlice(arena, try std.fmt.allocPrint(arena, "line {d}{s}\n", .{ i, tail })); - try eh.writeFile(try arena.dupeZ(u8, name), buf.items); + const buf = try numberedLines(arena, n, tail); + try eh.writeFile(try arena.dupeZ(u8, name), buf); } else if (std.mem.eql(u8, cmd, "ppmdiag")) { // The diagonal-split test PPM from the old e2e (white over blue), // grown to 512x512 with the white half filled by a fixed LCG @@ -381,19 +407,8 @@ fn runScript(arena: std.mem.Allocator, exe_z: [:0]const u8, script_path: []const // the art, which a 64x64 image cannot show (its cells average // down to flat color and a block always wins). const name = tok.next() orelse return error.BadScript; - var ppm: std.ArrayList(u8) = .empty; - try ppm.appendSlice(arena, "P6\n512 512\n255\n"); - var seed: u64 = 12345; - var py: usize = 0; - while (py < 512) : (py += 1) { - var px: usize = 0; - while (px < 512) : (px += 1) { - seed = seed *% 1103515245 +% 12345; - const v: u8 = @truncate(seed >> 16); - try ppm.appendSlice(arena, if (px + py < 512) &[_]u8{ v, v, v } else &[_]u8{ 0, 0, 255 }); - } - } - try eh.writeFile(try arena.dupeZ(u8, name), ppm.items); + const ppm = try ppmDiag(arena); + try eh.writeFile(try arena.dupeZ(u8, name), ppm); } else if (std.mem.eql(u8, cmd, "dirmk")) { try mkdir(arena, tok.next() orelse return error.BadScript, false); } else if (std.mem.eql(u8, cmd, "run")) { @@ -418,22 +433,10 @@ fn runScript(arena: std.mem.Allocator, exe_z: [:0]const u8, script_path: []const } else if (std.mem.eql(u8, cmd, "snapfile")) { const label = tok.next() orelse return error.BadScript; const contents = try readFileAlloc(arena, tok.rest()); - try out.appendSlice(arena, try std.fmt.allocPrint(arena, "== file {s}\n", .{label})); - // elide base64 payloads: they encode raw pty byte HISTORY, whose + // Elide base64 payloads: they encode raw pty byte HISTORY, whose // micro-timing (prompt redraw cycles) legitimately differs between // implementations; the cleaned text fields are the contract. - var flines = std.mem.splitScalar(u8, contents, '\n'); - while (flines.next()) |fl| { - if (std.mem.indexOf(u8, fl, "_b64 = \"") != null) { - const eq = std.mem.indexOf(u8, fl, "=").?; - try out.appendSlice(arena, fl[0 .. eq + 1]); - try out.appendSlice(arena, " <elided>\n"); - } else { - try out.appendSlice(arena, fl); - try out.append(arena, '\n'); - } - } - try out.appendSlice(arena, "== end file\n"); + try appendChunk(chunks, &chunk_len, try snapFile(arena, label, contents)); } else if (std.mem.eql(u8, cmd, "wait")) { const ms = try std.fmt.parseInt(i64, tok.next() orelse return error.BadScript, 10); try live(&h).expectWaitFor(tok.rest(), ms, "snapshot wait"); @@ -471,15 +474,119 @@ fn runScript(arena: std.mem.Allocator, exe_z: [:0]const u8, script_path: []const const cols = try std.fmt.parseInt(u16, tok.next() orelse return error.BadScript, 10); try live(&h).resize(rows, cols); } else if (std.mem.eql(u8, cmd, "snap")) { - try snapText(arena, &out, live(&h), tok.rest()); + try appendChunk(chunks, &chunk_len, try snapText(arena, live(&h), tok.rest())); } else if (std.mem.eql(u8, cmd, "snapstyle")) { - try snapStyle(arena, &out, live(&h), tok.rest()); + try appendChunk(chunks, &chunk_len, try snapStyle(arena, live(&h), tok.rest())); } else { std.debug.print("unknown command {s}\n", .{cmd}); return error.BadScript; } } - return out.items; + return concatChunks(arena, chunks[0..chunk_len]); +} + +fn countCaptures(src: []const u8) usize { + var count: usize = 0; + var lines = std.mem.splitScalar(u8, src, '\n'); + while (lines.next()) |raw_line| { + const line = std.mem.trim(u8, raw_line, " \t\r"); + if (line.len == 0 or line[0] == '#') continue; + var tok = std.mem.tokenizeScalar(u8, line, ' '); + const cmd = tok.next().?; + if (std.mem.eql(u8, cmd, "snap") or + std.mem.eql(u8, cmd, "snapstyle") or + std.mem.eql(u8, cmd, "snapfile")) count += 1; + } + return count; +} + +fn appendChunk(chunks: [][]const u8, len: *usize, chunk: []const u8) !void { + if (len.* == chunks.len) return error.BadScript; + chunks[len.*] = chunk; + len.* += 1; +} + +fn concatChunks(arena: std.mem.Allocator, chunks: []const []const u8) ![]u8 { + var total: usize = 0; + for (chunks) |chunk| total += chunk.len; + const out = try arena.alloc(u8, total); + var pos: usize = 0; + for (chunks) |chunk| copyBytes(out, &pos, chunk); + return out; +} + +fn copyBytes(out: []u8, pos: *usize, bytes: []const u8) void { + @memcpy(out[pos.*..][0..bytes.len], bytes); + pos.* += bytes.len; +} + +fn decimalDigits(value: usize) usize { + var v = value; + var digits: usize = 1; + while (v >= 10) : (digits += 1) v /= 10; + return digits; +} + +fn numberedLines(arena: std.mem.Allocator, n: usize, tail: []const u8) ![]u8 { + var total: usize = 0; + for (1..n + 1) |i| total += "line ".len + decimalDigits(i) + tail.len + 1; + const out = try arena.alloc(u8, total); + var pos: usize = 0; + for (1..n + 1) |i| { + const line = try std.fmt.bufPrint(out[pos..], "line {d}{s}\n", .{ i, tail }); + pos += line.len; + } + return out; +} + +fn ppmDiag(arena: std.mem.Allocator) ![]u8 { + const header = "P6\n512 512\n255\n"; + const out = try arena.alloc(u8, header.len + 512 * 512 * 3); + @memcpy(out[0..header.len], header); + var pos = header.len; + var seed: u64 = 12345; + var py: usize = 0; + while (py < 512) : (py += 1) { + var px: usize = 0; + while (px < 512) : (px += 1) { + seed = seed *% 1103515245 +% 12345; + const v: u8 = @truncate(seed >> 16); + const pixel: [3]u8 = if (px + py < 512) .{ v, v, v } else .{ 0, 0, 255 }; + @memcpy(out[pos..][0..pixel.len], &pixel); + pos += pixel.len; + } + } + return out; +} + +fn snapFile(arena: std.mem.Allocator, label: []const u8, contents: []const u8) ![]u8 { + const header = try std.fmt.allocPrint(arena, "== file {s}\n", .{label}); + var total = header.len + "== end file\n".len; + var lines = std.mem.splitScalar(u8, contents, '\n'); + while (lines.next()) |line| { + if (std.mem.indexOf(u8, line, "_b64 = \"") != null) { + const eq = std.mem.indexOfScalar(u8, line, '=').?; + total += eq + 1 + " <elided>\n".len; + } else { + total += line.len + 1; + } + } + const out = try arena.alloc(u8, total); + var pos: usize = 0; + copyBytes(out, &pos, header); + lines = std.mem.splitScalar(u8, contents, '\n'); + while (lines.next()) |line| { + if (std.mem.indexOf(u8, line, "_b64 = \"") != null) { + const eq = std.mem.indexOfScalar(u8, line, '=').?; + copyBytes(out, &pos, line[0 .. eq + 1]); + copyBytes(out, &pos, " <elided>\n"); + } else { + copyBytes(out, &pos, line); + copyBytes(out, &pos, "\n"); + } + } + copyBytes(out, &pos, "== end file\n"); + return out; } fn live(h: *?eh.Harness) *eh.Harness { @@ -649,48 +756,78 @@ fn stateKey(h: *eh.Harness) ![]u8 { return std.fmt.allocPrint(h.gpa, "{d},{d},{x}\n{s}", .{ c.x, c.y, styles.final(), text }); } -fn snapText(arena: std.mem.Allocator, out: *std.ArrayList(u8), h: *eh.Harness, label: []const u8) !void { +fn snapText(arena: std.mem.Allocator, h: *eh.Harness, label: []const u8) ![]u8 { const text = try h.screenText(); defer h.gpa.free(text); const c = h.cursor(); - try out.appendSlice(arena, try std.fmt.allocPrint(arena, "== snap {s} grid={d}x{d} cursor={d},{d}\n", .{ label, h.cols, h.rows, c.x, c.y })); + const header = try std.fmt.allocPrint(arena, "== snap {s} grid={d}x{d} cursor={d},{d}\n", .{ label, h.cols, h.rows, c.x, c.y }); + var total = header.len; var it = std.mem.splitScalar(u8, text, '\n'); var row: usize = 0; while (row < h.rows) : (row += 1) { const line = it.next() orelse ""; - try out.append(arena, '|'); - try out.appendSlice(arena, std.mem.trimEnd(u8, line, " ")); - try out.append(arena, '\n'); + total += 1 + std.mem.trimEnd(u8, line, " ").len + 1; } + const out = try arena.alloc(u8, total); + var pos: usize = 0; + copyBytes(out, &pos, header); + it = std.mem.splitScalar(u8, text, '\n'); + row = 0; + while (row < h.rows) : (row += 1) { + const line = it.next() orelse ""; + copyBytes(out, &pos, "|"); + copyBytes(out, &pos, std.mem.trimEnd(u8, line, " ")); + copyBytes(out, &pos, "\n"); + } + return out; } /// Per-row style runs from the harness emulator: `|y: x0-x1 fg,bg,attrs ...`. /// fg/bg: d (default) | pN (palette) | #rrggbb. attrs: b f i k r v x + u U ~ . - /// for underline single/double/curly/dotted/dashed. -fn snapStyle(arena: std.mem.Allocator, out: *std.ArrayList(u8), h: *eh.Harness, label: []const u8) !void { - try out.appendSlice(arena, try std.fmt.allocPrint(arena, "== style {s}\n", .{label})); +fn snapStyle(arena: std.mem.Allocator, h: *eh.Harness, label: []const u8) ![]u8 { + const header = try std.fmt.allocPrint(arena, "== style {s}\n", .{label}); + const rows_len = styleRows(h, null); + const out = try arena.alloc(u8, header.len + rows_len); + var pos: usize = 0; + copyBytes(out, &pos, header); + _ = styleRows(h, out[pos..]); + return out; +} + +fn styleRows(h: *eh.Harness, dst: ?[]u8) usize { + var pos: usize = 0; var y: u16 = 0; while (y < h.rows) : (y += 1) { - try out.appendSlice(arena, try std.fmt.allocPrint(arena, "|{d}:", .{y})); - var run_buf: [64]u8 = undefined; + var fmt_buf: [64]u8 = undefined; + emitBytes(dst, &pos, std.fmt.bufPrint(&fmt_buf, "|{d}:", .{y}) catch unreachable); var key_buf: [64]u8 = undefined; - var run_key: []const u8 = ""; + var run_key: [64]u8 = undefined; + var run_len: usize = 0; var run_start: u16 = 0; var x: u16 = 0; while (x <= h.cols) : (x += 1) { const key: []const u8 = if (x == h.cols) "\x00end" else cellKey(h, x, y, &key_buf); if (x == 0) { - run_key = try arena.dupe(u8, key); + @memcpy(run_key[0..key.len], key); + run_len = key.len; continue; } - if (std.mem.eql(u8, key, run_key)) continue; - try out.appendSlice(arena, try std.fmt.bufPrint(&run_buf, " {d}-{d} ", .{ run_start, x - 1 })); - try out.appendSlice(arena, run_key); - run_key = try arena.dupe(u8, key); + if (std.mem.eql(u8, key, run_key[0..run_len])) continue; + emitBytes(dst, &pos, std.fmt.bufPrint(&fmt_buf, " {d}-{d} ", .{ run_start, x - 1 }) catch unreachable); + emitBytes(dst, &pos, run_key[0..run_len]); + @memcpy(run_key[0..key.len], key); + run_len = key.len; run_start = x; } - try out.append(arena, '\n'); + emitBytes(dst, &pos, "\n"); } + return pos; +} + +fn emitBytes(dst: ?[]u8, pos: *usize, bytes: []const u8) void { + if (dst) |out| @memcpy(out[pos.*..][0..bytes.len], bytes); + pos.* += bytes.len; } fn cellKey(h: *eh.Harness, x: u16, y: u16, buf: *[64]u8) []const u8 { @@ -810,29 +947,51 @@ fn buttonCode(name: []const u8) ?u16 { } fn unescape(arena: std.mem.Allocator, s: []const u8) ![]const u8 { - var buf: std.ArrayList(u8) = .empty; + var len: usize = 0; var i: usize = 0; while (i < s.len) : (i += 1) { if (s[i] != '\\' or i + 1 >= s.len) { - try buf.append(arena, s[i]); + len += 1; continue; } i += 1; switch (s[i]) { - 'e' => try buf.append(arena, 0x1b), - 'r' => try buf.append(arena, '\r'), - 'n' => try buf.append(arena, '\n'), - 't' => try buf.append(arena, '\t'), - '\\' => try buf.append(arena, '\\'), + 'e', 'r', 'n', 't', '\\' => len += 1, 'x' => { if (i + 2 >= s.len) return error.BadEscape; - try buf.append(arena, try std.fmt.parseInt(u8, s[i + 1 .. i + 3], 16)); + _ = try std.fmt.parseInt(u8, s[i + 1 .. i + 3], 16); + len += 1; i += 2; }, else => return error.BadEscape, } } - return buf.items; + const out = try arena.alloc(u8, len); + var pos: usize = 0; + i = 0; + while (i < s.len) : (i += 1) { + if (s[i] != '\\' or i + 1 >= s.len) { + out[pos] = s[i]; + pos += 1; + continue; + } + i += 1; + out[pos] = switch (s[i]) { + 'e' => 0x1b, + 'r' => '\r', + 'n' => '\n', + 't' => '\t', + '\\' => '\\', + 'x' => value: { + const value = try std.fmt.parseInt(u8, s[i + 1 .. i + 3], 16); + i += 2; + break :value value; + }, + else => unreachable, + }; + pos += 1; + } + return out; } fn absPath(arena: std.mem.Allocator, cwd: []const u8, p: []const u8) ![]const u8 { @@ -840,15 +999,33 @@ fn absPath(arena: std.mem.Allocator, cwd: []const u8, p: []const u8) ![]const u8 return try std.fmt.allocPrint(arena, "{s}/{s}", .{ cwd, p }); } -fn listSnaps(arena: std.mem.Allocator, dir_path: []const u8, scripts: *std.ArrayList([]const u8)) !void { +fn listSnaps(arena: std.mem.Allocator, dir_path: []const u8) ![][]const u8 { const dir_z = try arena.dupeZ(u8, dir_path); + const count = countSnapEntries(dir_z, dir_path); + const scripts = try arena.alloc([]const u8, count); + const dir = libc.opendir(dir_z) orelse fatal("no snapshots/ dir at {s}", .{dir_path}); + defer _ = libc.closedir(dir); + var i: usize = 0; + while (libc.readdir(dir)) |d| { + const name = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&d.name)), 0); + if (!std.mem.endsWith(u8, name, ".snap")) continue; + if (i == scripts.len) return error.DirectoryChanged; + scripts[i] = try std.fmt.allocPrint(arena, "{s}/{s}", .{ dir_path, name }); + i += 1; + } + if (i != scripts.len) return error.DirectoryChanged; + return scripts; +} + +fn countSnapEntries(dir_z: [:0]const u8, dir_path: []const u8) usize { const dir = libc.opendir(dir_z) orelse fatal("no snapshots/ dir at {s}", .{dir_path}); defer _ = libc.closedir(dir); + var count: usize = 0; while (libc.readdir(dir)) |d| { const name = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&d.name)), 0); - if (std.mem.endsWith(u8, name, ".snap")) - try scripts.append(arena, try std.fmt.allocPrint(arena, "{s}/{s}", .{ dir_path, name })); + if (std.mem.endsWith(u8, name, ".snap")) count += 1; } + return count; } fn readFileAlloc(arena: std.mem.Allocator, path: []const u8) ![]u8 { @@ -856,18 +1033,27 @@ fn readFileAlloc(arena: std.mem.Allocator, path: []const u8) ![]u8 { const fd = libc.open(path_z, .{ .ACCMODE = .RDONLY }); if (fd < 0) return error.OpenFailed; defer _ = libc.close(fd); - var buf: std.ArrayList(u8) = .empty; - var chunk: [16384]u8 = undefined; - while (true) { - const n = libc.read(fd, &chunk, chunk.len); + const end = libc.lseek(fd, 0, libc.SEEK.END); + if (end < 0 or libc.lseek(fd, 0, libc.SEEK.SET) < 0) return error.ReadFailed; + const buf = try arena.alloc(u8, @intCast(end)); + var pos: usize = 0; + while (pos < buf.len) { + const n = libc.read(fd, buf[pos..].ptr, buf.len - pos); if (n < 0) { if (libc.errno(n) == .INTR) continue; return error.ReadFailed; } - if (n == 0) break; - try buf.appendSlice(arena, chunk[0..@intCast(n)]); + if (n == 0) return error.ReadFailed; + pos += @intCast(n); + } + var extra: [1]u8 = undefined; + while (true) { + const n = libc.read(fd, &extra, extra.len); + if (n < 0 and libc.errno(n) == .INTR) continue; + if (n != 0) return error.ReadFailed; + break; } - return buf.items; + return buf; } fn mkdir(arena: std.mem.Allocator, path: []const u8, exist_ok: bool) !void { |
