| author | |
| committer | |
| log | adfc019d6070cf322048bdb36d991354711c4636 |
| tree | 1c13de6e54ee6852c566087e242e6e1f732478ca |
| parent | f10950526ea781ee2d15df74398527420cca13a1 |
| parent | dafefe9c9d3ffd484915ead0474c7e772f1dfcfb |
| signature |
ignore SIGPIPE by default10 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, |
| 26 | exe: *CompileStep, | 26 | exe: *CompileStep, |
| 27 | 27 | ||
| 28 | /// Set this to `null` to ignore the exit code for the purpose of determining a successful execution | 28 | /// Set this to `null` to ignore the exit code for the purpose of determining a successful execution |
| 29 | expected_exit_code: ?u8 = 0, | 29 | expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 }, |
| 30 | 30 | ||
| 31 | /// Override this field to modify the environment | 31 | /// Override this field to modify the environment |
| 32 | env_map: ?*EnvMap, | 32 | env_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, |
| 35 | stdin_behavior: std.ChildProcess.StdIo = .Inherit, | 35 | stdin_behavior: std.ChildProcess.StdIo = .Inherit, |
| 36 | 36 | ||
| 37 | /// Set this to `null` to ignore the exit code for the purpose of determining a successful execution | 37 | /// Set this to `null` to ignore the exit code for the purpose of determining a successful execution |
| 38 | expected_exit_code: ?u8 = 0, | 38 | expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 }, |
| 39 | 39 | ||
| 40 | /// Print the command before running it | 40 | /// Print the command before running it |
| 41 | print: bool, | 41 | print: 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 | } |
| 306 | 306 | ||
| 307 | fn 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 | } | ||
| 324 | fn fmtTerm(term: ?std.ChildProcess.Term) std.fmt.Formatter(formatTerm) { | ||
| 325 | return .{ .data = term }; | ||
| 326 | } | ||
| 327 | |||
| 328 | fn 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 | |||
| 307 | pub fn runCommand( | 352 | pub 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 | }; |
| 371 | 416 | ||
| 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) }); |
| 375 | 420 | } 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 | } |
| 399 | 426 | ||
| 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 | |||
| 7060 | pub const have_sigpipe_support = @hasDecl(@This(), "SIG") and @hasDecl(SIG, "PIPE"); | ||
| 7061 | |||
| 7062 | fn noopSigHandler(_: c_int) callconv(.C) void {} | ||
| 7063 | |||
| 7064 | pub 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; |
| 497 | 497 | ||
| 498 | std.debug.maybeEnableSegfaultHandler(); | 498 | std.debug.maybeEnableSegfaultHandler(); |
| 499 | std.os.maybeIgnoreSigpipe(); | ||
| 499 | 500 | ||
| 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_getrandom | 167 | options_override.crypto_always_getrandom |
| 168 | else | 168 | 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 | }; |
| 171 | 187 | ||
| 172 | // This forces the start.zig file to be imported, and the comptime logic inside that | 188 | // 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; |
| 30 | 30 | ||
| 31 | const run_cmd = exe.run(); | 31 | const run_cmd = exe.run(); |
| 32 | run_cmd.expected_exit_code = @bitCast(u8, @as(i8, -2)); // should fail | 32 | 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 }; |
| 172 | 172 | ||
| 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 | } | ||
| 87 | 90 | ||
| 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const build_options = @import("build_options"); | ||
| 3 | |||
| 4 | pub 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 | |||
| 10 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const os = std.os; | ||
| 3 | |||
| 4 | pub 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 | } | ||