authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-02-19 13:55:12-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-02-19 13:55:12-05:00
logadfc019d6070cf322048bdb36d991354711c4636
tree1c13de6e54ee6852c566087e242e6e1f732478ca
parentf10950526ea781ee2d15df74398527420cca13a1
parentdafefe9c9d3ffd484915ead0474c7e772f1dfcfb
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #11982 from marler8997/ignoreSigpipe

ignore SIGPIPE by default

10 files changed, 153 insertions(+), 32 deletions(-)

lib/std/Build/EmulatableRunStep.zig+2-2
...@@ -26,7 +26,7 @@ builder: *std.Build,...@@ -26,7 +26,7 @@ builder: *std.Build,
26exe: *CompileStep,26exe: *CompileStep,
2727
28/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution28/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution
29expected_exit_code: ?u8 = 0,29expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 },
3030
31/// Override this field to modify the environment31/// Override this field to modify the environment
32env_map: ?*EnvMap,32env_map: ?*EnvMap,
...@@ -131,7 +131,7 @@ fn make(step: *Step) !void {...@@ -131,7 +131,7 @@ fn make(step: *Step) !void {
131 try RunStep.runCommand(131 try RunStep.runCommand(
132 argv_list.items,132 argv_list.items,
133 self.builder,133 self.builder,
134 self.expected_exit_code,134 self.expected_term,
135 self.stdout_action,135 self.stdout_action,
136 self.stderr_action,136 self.stderr_action,
137 .Inherit,137 .Inherit,
lib/std/Build/RunStep.zig+55-28
...@@ -35,7 +35,7 @@ stderr_action: StdIoAction = .inherit,...@@ -35,7 +35,7 @@ stderr_action: StdIoAction = .inherit,
35stdin_behavior: std.ChildProcess.StdIo = .Inherit,35stdin_behavior: std.ChildProcess.StdIo = .Inherit,
3636
37/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution37/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution
38expected_exit_code: ?u8 = 0,38expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 },
3939
40/// Print the command before running it40/// Print the command before running it
41print: bool,41print: bool,
...@@ -290,7 +290,7 @@ fn make(step: *Step) !void {...@@ -290,7 +290,7 @@ fn make(step: *Step) !void {
290 try runCommand(290 try runCommand(
291 argv_list.items,291 argv_list.items,
292 self.builder,292 self.builder,
293 self.expected_exit_code,293 self.expected_term,
294 self.stdout_action,294 self.stdout_action,
295 self.stderr_action,295 self.stderr_action,
296 self.stdin_behavior,296 self.stdin_behavior,
...@@ -304,10 +304,55 @@ fn make(step: *Step) !void {...@@ -304,10 +304,55 @@ fn make(step: *Step) !void {
304 }304 }
305}305}
306306
307fn formatTerm(
308 term: ?std.ChildProcess.Term,
309 comptime fmt: []const u8,
310 options: std.fmt.FormatOptions,
311 writer: anytype,
312) !void {
313 _ = fmt;
314 _ = options;
315 if (term) |t| switch (t) {
316 .Exited => |code| try writer.print("exited with code {}", .{code}),
317 .Signal => |sig| try writer.print("terminated with signal {}", .{sig}),
318 .Stopped => |sig| try writer.print("stopped with signal {}", .{sig}),
319 .Unknown => |code| try writer.print("terminated for unknown reason with code {}", .{code}),
320 } else {
321 try writer.writeAll("exited with any code");
322 }
323}
324fn fmtTerm(term: ?std.ChildProcess.Term) std.fmt.Formatter(formatTerm) {
325 return .{ .data = term };
326}
327
328fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term) bool {
329 return if (expected) |e| switch (e) {
330 .Exited => |expected_code| switch (actual) {
331 .Exited => |actual_code| expected_code == actual_code,
332 else => false,
333 },
334 .Signal => |expected_sig| switch (actual) {
335 .Signal => |actual_sig| expected_sig == actual_sig,
336 else => false,
337 },
338 .Stopped => |expected_sig| switch (actual) {
339 .Stopped => |actual_sig| expected_sig == actual_sig,
340 else => false,
341 },
342 .Unknown => |expected_code| switch (actual) {
343 .Unknown => |actual_code| expected_code == actual_code,
344 else => false,
345 },
346 } else switch (actual) {
347 .Exited => true,
348 else => false,
349 };
350}
351
307pub fn runCommand(352pub fn runCommand(
308 argv: []const []const u8,353 argv: []const []const u8,
309 builder: *std.Build,354 builder: *std.Build,
310 expected_exit_code: ?u8,355 expected_term: ?std.ChildProcess.Term,
311 stdout_action: StdIoAction,356 stdout_action: StdIoAction,
312 stderr_action: StdIoAction,357 stderr_action: StdIoAction,
313 stdin_behavior: std.ChildProcess.StdIo,358 stdin_behavior: std.ChildProcess.StdIo,
...@@ -369,32 +414,14 @@ pub fn runCommand(...@@ -369,32 +414,14 @@ pub fn runCommand(
369 return err;414 return err;
370 };415 };
371416
372 switch (term) {417 if (!termMatches(expected_term, term)) {
373 .Exited => |code| blk: {418 if (builder.prominent_compile_errors) {
374 const expected_code = expected_exit_code orelse break :blk;419 std.debug.print("Run step {} (expected {})\n", .{ fmtTerm(term), fmtTerm(expected_term) });
375420 } else {
376 if (code != expected_code) {421 std.debug.print("The following command {} (expected {}):\n", .{ fmtTerm(term), fmtTerm(expected_term) });
377 if (builder.prominent_compile_errors) {
378 std.debug.print("Run step exited with error code {} (expected {})\n", .{
379 code,
380 expected_code,
381 });
382 } else {
383 std.debug.print("The following command exited with error code {} (expected {}):\n", .{
384 code,
385 expected_code,
386 });
387 printCmd(cwd, argv);
388 }
389
390 return error.UnexpectedExitCode;
391 }
392 },
393 else => {
394 std.debug.print("The following command terminated unexpectedly:\n", .{});
395 printCmd(cwd, argv);422 printCmd(cwd, argv);
396 return error.UncleanExit;423 }
397 },424 return error.UnexpectedExit;
398 }425 }
399426
400 switch (stderr_action) {427 switch (stderr_action) {
lib/std/os.zig+18
...@@ -7056,3 +7056,21 @@ pub fn timerfd_gettime(fd: i32) TimerFdGetError!linux.itimerspec {...@@ -7056,3 +7056,21 @@ pub fn timerfd_gettime(fd: i32) TimerFdGetError!linux.itimerspec {
7056 else => |err| return unexpectedErrno(err),7056 else => |err| return unexpectedErrno(err),
7057 };7057 };
7058}7058}
7059
7060pub const have_sigpipe_support = @hasDecl(@This(), "SIG") and @hasDecl(SIG, "PIPE");
7061
7062fn noopSigHandler(_: c_int) callconv(.C) void {}
7063
7064pub fn maybeIgnoreSigpipe() void {
7065 if (have_sigpipe_support and !std.options.keep_sigpipe) {
7066 const act = Sigaction{
7067 // We set handler to a noop function instead of SIG.IGN so we don't leak our
7068 // signal disposition to a child process
7069 .handler = .{ .handler = noopSigHandler },
7070 .mask = empty_sigset,
7071 .flags = 0,
7072 };
7073 sigaction(SIG.PIPE, &act, null) catch |err|
7074 std.debug.panic("failed to install noop SIGPIPE handler with '{s}'", .{@errorName(err)});
7075 }
7076}
lib/std/start.zig+1
...@@ -496,6 +496,7 @@ fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {...@@ -496,6 +496,7 @@ fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
496 std.os.environ = envp;496 std.os.environ = envp;
497497
498 std.debug.maybeEnableSegfaultHandler();498 std.debug.maybeEnableSegfaultHandler();
499 std.os.maybeIgnoreSigpipe();
499500
500 return initEventLoopAndCallMain();501 return initEventLoopAndCallMain();
501}502}
lib/std/std.zig+16
...@@ -167,6 +167,22 @@ pub const options = struct {...@@ -167,6 +167,22 @@ pub const options = struct {
167 options_override.crypto_always_getrandom167 options_override.crypto_always_getrandom
168 else168 else
169 false;169 false;
170
171 /// By default Zig disables SIGPIPE by setting a "no-op" handler for it. Set this option
172 /// to `true` to prevent that.
173 ///
174 /// Note that we use a "no-op" handler instead of SIG_IGN because it will not be inherited by
175 /// any child process.
176 ///
177 /// SIGPIPE is triggered when a process attempts to write to a broken pipe. By default, SIGPIPE
178 /// will terminate the process instead of exiting. It doesn't trigger the panic handler so in many
179 /// cases it's unclear why the process was terminated. By capturing SIGPIPE instead, functions that
180 /// write to broken pipes will return the EPIPE error (error.BrokenPipe) and the program can handle
181 /// it like any other error.
182 pub const keep_sigpipe: bool = if (@hasDecl(options_override, "keep_sigpipe"))
183 options_override.keep_sigpipe
184 else
185 false;
170};186};
171187
172// This forces the start.zig file to be imported, and the comptime logic inside that188// This forces the start.zig file to be imported, and the comptime logic inside that
test/link/macho/dead_strip_dylibs/build.zig+1-1
...@@ -29,7 +29,7 @@ pub fn build(b: *std.Build) void {...@@ -29,7 +29,7 @@ pub fn build(b: *std.Build) void {
29 exe.dead_strip_dylibs = true;29 exe.dead_strip_dylibs = true;
3030
31 const run_cmd = exe.run();31 const run_cmd = exe.run();
32 run_cmd.expected_exit_code = @bitCast(u8, @as(i8, -2)); // should fail32 run_cmd.expected_term = .{ .Exited = @bitCast(u8, @as(i8, -2)) }; // should fail
33 test_step.dependOn(&run_cmd.step);33 test_step.dependOn(&run_cmd.step);
34 }34 }
35}35}
test/src/compare_output.zig+1-1
...@@ -168,7 +168,7 @@ pub const CompareOutputContext = struct {...@@ -168,7 +168,7 @@ pub const CompareOutputContext = struct {
168 run.addArgs(case.cli_args);168 run.addArgs(case.cli_args);
169 run.stderr_action = .ignore;169 run.stderr_action = .ignore;
170 run.stdout_action = .ignore;170 run.stdout_action = .ignore;
171 run.expected_exit_code = 126;171 run.expected_term = .{ .Exited = 126 };
172172
173 self.step.dependOn(&run.step);173 self.step.dependOn(&run.step);
174 },174 },
test/standalone.zig+3
...@@ -84,6 +84,9 @@ pub fn addCases(cases: *tests.StandaloneContext) void {...@@ -84,6 +84,9 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
84 cases.addBuildFile("test/standalone/pie/build.zig", .{});84 cases.addBuildFile("test/standalone/pie/build.zig", .{});
85 }85 }
86 cases.addBuildFile("test/standalone/issue_12706/build.zig", .{});86 cases.addBuildFile("test/standalone/issue_12706/build.zig", .{});
87 if (std.os.have_sigpipe_support) {
88 cases.addBuildFile("test/standalone/sigpipe/build.zig", .{});
89 }
8790
88 // Ensure the development tools are buildable. Alphabetically sorted.91 // Ensure the development tools are buildable. Alphabetically sorted.
89 // No need to build `tools/spirv/grammar.zig`.92 // No need to build `tools/spirv/grammar.zig`.
test/standalone/sigpipe/breakpipe.zig created+21
...@@ -0,0 +1,21 @@
1const std = @import("std");
2const build_options = @import("build_options");
3
4pub const std_options = if (build_options.keep_sigpipe) struct {
5 pub const keep_sigpipe = true;
6} else struct {
7 // intentionally not setting keep_sigpipe to ensure the default behavior is equivalent to false
8};
9
10pub fn main() !void {
11 const pipe = try std.os.pipe();
12 std.os.close(pipe[0]);
13 _ = std.os.write(pipe[1], "a") catch |err| switch (err) {
14 error.BrokenPipe => {
15 try std.io.getStdOut().writer().writeAll("BrokenPipe\n");
16 std.os.exit(123);
17 },
18 else => |e| return e,
19 };
20 unreachable;
21}
test/standalone/sigpipe/build.zig created+35
...@@ -0,0 +1,35 @@
1const std = @import("std");
2const os = std.os;
3
4pub fn build(b: *std.build.Builder) !void {
5 const test_step = b.step("test", "Run the tests");
6
7 // This test runs "breakpipe" as a child process and that process
8 // depends on inheriting a SIGPIPE disposition of "default".
9 {
10 const act = os.Sigaction{
11 .handler = .{ .handler = os.SIG.DFL },
12 .mask = os.empty_sigset,
13 .flags = 0,
14 };
15 try os.sigaction(os.SIG.PIPE, &act, null);
16 }
17
18 for ([_]bool{ false, true }) |keep_sigpipe| {
19 const options = b.addOptions();
20 options.addOption(bool, "keep_sigpipe", keep_sigpipe);
21 const exe = b.addExecutable(.{
22 .name = "breakpipe",
23 .root_source_file = .{ .path = "breakpipe.zig" },
24 });
25 exe.addOptions("build_options", options);
26 const run = exe.run();
27 if (keep_sigpipe) {
28 run.expected_term = .{ .Signal = std.os.SIG.PIPE };
29 } else {
30 run.stdout_action = .{ .expect_exact = "BrokenPipe\n" };
31 run.expected_term = .{ .Exited = 123 };
32 }
33 test_step.dependOn(&run.step);
34 }
35}