| author | |
| committer | |
| log | 687123a85eaac8b7c290b21c346e8aeb8470dfcb |
| tree | 4f93c738f4b6f55cf96aeb68f9b8c55455681e52 |
| parent | b2816f26980f7ac9f0d87c844320e29dfbb6269e |
and delete the special-cased function2 files changed, 30 insertions(+), 93 deletions(-)
lib/std/process.zig+30-23| ... | ... | @@ -453,16 +453,16 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { |
| 453 | 453 | return io.vtable.processSpawnPath(io.userdata, dir, options); |
| 454 | 454 | } |
| 455 | 455 | |
| 456 | pub const RunError = SpawnError || Child.CollectOutputError; | |
| 456 | pub const RunError = SpawnError || error{ | |
| 457 | StreamTooLong, | |
| 458 | } || Io.ConcurrentError || Allocator.Error || Io.File.Reader.Error || Io.Timeout.Error; | |
| 457 | 459 | |
| 458 | 460 | pub const RunOptions = struct { |
| 459 | 461 | argv: []const []const u8, |
| 460 | 462 | stderr_limit: Io.Limit = .unlimited, |
| 461 | 463 | stdout_limit: Io.Limit = .unlimited, |
| 462 | /// How many bytes to initially allocate for stderr. | |
| 463 | stderr_reserve_amount: usize = 1, | |
| 464 | /// How many bytes to initially allocate for stdout. | |
| 465 | stdout_reserve_amount: usize = 1, | |
| 464 | /// How many bytes to initially allocate for stderr and stdout. | |
| 465 | reserve_amount: usize = 64, | |
| 466 | 466 | |
| 467 | 467 | /// Set to change the current working directory when spawning the child process. |
| 468 | 468 | cwd: ?[]const u8 = null, |
| ... | ... | @@ -516,29 +516,36 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { |
| 516 | 516 | }); |
| 517 | 517 | defer child.kill(io); |
| 518 | 518 | |
| 519 | var stdout: std.ArrayList(u8) = .empty; | |
| 520 | defer stdout.deinit(gpa); | |
| 521 | var stderr: std.ArrayList(u8) = .empty; | |
| 522 | defer stderr.deinit(gpa); | |
| 523 | ||
| 524 | try stdout.ensureUnusedCapacity(gpa, options.stdout_reserve_amount); | |
| 525 | try stderr.ensureUnusedCapacity(gpa, options.stderr_reserve_amount); | |
| 526 | ||
| 527 | try child.collectOutput(io, .{ | |
| 528 | .allocator = gpa, | |
| 529 | .stdout = &stdout, | |
| 530 | .stderr = &stderr, | |
| 531 | .stdout_limit = options.stdout_limit, | |
| 532 | .stderr_limit = options.stderr_limit, | |
| 533 | .timeout = options.timeout, | |
| 534 | }); | |
| 519 | var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; | |
| 520 | var multi_reader: Io.File.MultiReader = undefined; | |
| 521 | multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); | |
| 522 | defer multi_reader.deinit(); | |
| 523 | ||
| 524 | const stdout_reader = multi_reader.reader(0); | |
| 525 | const stderr_reader = multi_reader.reader(1); | |
| 526 | ||
| 527 | while (multi_reader.fill(options.reserve_amount, options.timeout)) |_| { | |
| 528 | if (options.stdout_limit.toInt()) |limit| { | |
| 529 | if (stdout_reader.buffered().len > limit) | |
| 530 | return error.StreamTooLong; | |
| 531 | } | |
| 532 | if (options.stderr_limit.toInt()) |limit| { | |
| 533 | if (stderr_reader.buffered().len > limit) | |
| 534 | return error.StreamTooLong; | |
| 535 | } | |
| 536 | } else |err| switch (err) { | |
| 537 | error.EndOfStream => {}, | |
| 538 | else => |e| return e, | |
| 539 | } | |
| 540 | ||
| 541 | try multi_reader.checkAnyError(); | |
| 535 | 542 | |
| 536 | 543 | const term = try child.wait(io); |
| 537 | 544 | |
| 538 | const stdout_slice = try stdout.toOwnedSlice(gpa); | |
| 545 | const stdout_slice = try multi_reader.toOwnedSlice(0); | |
| 539 | 546 | errdefer gpa.free(stdout_slice); |
| 540 | 547 | |
| 541 | const stderr_slice = try stderr.toOwnedSlice(gpa); | |
| 548 | const stderr_slice = try multi_reader.toOwnedSlice(1); | |
| 542 | 549 | errdefer gpa.free(stderr_slice); |
| 543 | 550 | |
| 544 | 551 | return .{ |
lib/std/process/Child.zig-70| ... | ... | @@ -124,73 +124,3 @@ pub fn wait(child: *Child, io: Io) WaitError!Term { |
| 124 | 124 | assert(child.id != null); |
| 125 | 125 | return io.vtable.childWait(io.userdata, child); |
| 126 | 126 | } |
| 127 | ||
| 128 | pub const CollectOutputError = error{ | |
| 129 | StreamTooLong, | |
| 130 | } || Io.ConcurrentError || Allocator.Error || Io.File.Reader.Error || Io.Timeout.Error; | |
| 131 | ||
| 132 | pub const CollectOutputOptions = struct { | |
| 133 | stdout: *std.ArrayList(u8), | |
| 134 | stderr: *std.ArrayList(u8), | |
| 135 | /// Used for `stdout` and `stderr`. If not provided, only the existing | |
| 136 | /// capacity will be used. | |
| 137 | allocator: ?Allocator = null, | |
| 138 | stdout_limit: Io.Limit = .unlimited, | |
| 139 | stderr_limit: Io.Limit = .unlimited, | |
| 140 | timeout: Io.Timeout = .none, | |
| 141 | }; | |
| 142 | ||
| 143 | /// Collect the output from the process's stdout and stderr. Will return once | |
| 144 | /// all output has been collected. This does not mean that the process has | |
| 145 | /// ended. `wait` should still be called to wait for and clean up the process. | |
| 146 | /// | |
| 147 | /// The process must have been started with stdout and stderr set to | |
| 148 | /// `process.SpawnOptions.StdIo.pipe`. | |
| 149 | pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void { | |
| 150 | const files: [2]Io.File = .{ child.stdout.?, child.stderr.? }; | |
| 151 | const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; | |
| 152 | const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; | |
| 153 | var reads: [2]Io.Operation = undefined; | |
| 154 | var vecs: [2][1][]u8 = undefined; | |
| 155 | var ring: [2]u32 = undefined; | |
| 156 | var batch: Io.Batch = .init(&reads, &ring); | |
| 157 | defer { | |
| 158 | batch.cancel(io); | |
| 159 | while (batch.next()) |op| { | |
| 160 | lists[op].items.len += reads[op].file_read_streaming.status.result catch continue; | |
| 161 | } | |
| 162 | } | |
| 163 | var remaining: usize = 0; | |
| 164 | for (0.., &reads, &lists, &files, &vecs) |op, *read, list, file, *vec| { | |
| 165 | if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); | |
| 166 | const cap = list.unusedCapacitySlice(); | |
| 167 | if (cap.len == 0) return error.StreamTooLong; | |
| 168 | vec[0] = cap; | |
| 169 | read.* = .{ .file_read_streaming = .{ | |
| 170 | .file = file, | |
| 171 | .data = vec, | |
| 172 | } }; | |
| 173 | batch.add(op); | |
| 174 | remaining += 1; | |
| 175 | } | |
| 176 | while (remaining > 0) { | |
| 177 | try batch.wait(io, options.timeout); | |
| 178 | while (batch.next()) |op| { | |
| 179 | const n = reads[op].file_read_streaming.status.result catch |err| switch (err) { | |
| 180 | error.EndOfStream => { | |
| 181 | remaining -= 1; | |
| 182 | continue; | |
| 183 | }, | |
| 184 | else => |e| return e, | |
| 185 | }; | |
| 186 | lists[op].items.len += n; | |
| 187 | if (lists[op].items.len > @intFromEnum(limits[op])) return error.StreamTooLong; | |
| 188 | if (options.allocator) |gpa| try lists[op].ensureUnusedCapacity(gpa, 1); | |
| 189 | const cap = lists[op].unusedCapacitySlice(); | |
| 190 | if (cap.len == 0) return error.StreamTooLong; | |
| 191 | vecs[op][0] = cap; | |
| 192 | reads[op].file_read_streaming.status = .{ .unstarted = {} }; | |
| 193 | batch.add(op); | |
| 194 | } | |
| 195 | } | |
| 196 | } |