How to really trigger Term.Stopped & Term.Unknown for std.childProcess

I am writing something with zig using std.childProcess, like below simplified version

const rr = std.childProcess.run(.{
  .allocator = std.testing.allocator,
  .argv = &[_][]const u8{
     "my-test-external-program"
  },
});

Then I have some other code try to log the execution result like below

    switch (rr.term) {
        .Exited => |ret| {
           log_writer.print("Command exit with {d}!", .{ret}) catch {};
        },
        .Signal => |ret| {
            log_writer.print("Command exited with signal {d}! Error!", .{ ret }) catch {};
        },
        .Stopped => |ret| {
            log_writer.print("Command stopped with {d}! Error!", .{ ret }) catch {};
        },
        .Unknown => |ret| {
            log_writer.print("Command exited with unknown reason {d}! Error!", .{ ret }) catch {};
        },
    }

The key here is to cover all possible cases and also leave something for later log analysis.

Now I need to test my code, and I want to also cover all. For .Exited and .Signal cases are easy, for former I write a testing c program will exit from 0 to 127. For later, I also write a program use raise(SIGXXX) for signals.

The real problem is that I do not know how to test .Stopped and .Unknown. I know it is hard to see them, but still want to somehow test them, or just understand them (for curiosity).

I have read the zig source code, know that the ret code of my program should be something different. So I have tried some exp like

test "try" {
    const s: u32 = 0x8f7f;
    const ii = @as(u16, @truncate(((s & 0xffff) *% 0x10001) >> 8));
    std.debug.print("\n{x} {any} {any}\n", .{ ii, std.os.linux.W.IFEXITED(s), std.os.linux.W.IFSTOPPED(s) });
}

and I can get out like 7f8f false true, means I can use this code (or make more) to trigger expected behaivor in zig if I can ret them from my c code.

and I write a simple c program like below (which is using aarch64 assembly)

#include <unistd.h>

void main() {
    int ret_value = 0x8f7f;

    asm volatile(
        "mov w0, %w[ret_value]\n"  // move ret_value to w0
        "mov x16, #0x1\n"          // move 0x1 to x16, syscall no. 1 = exit
        "svc #0x80\n"              // arm64 system call [x16]
        "ret"
        : [ret_value] "=r"(ret_value)  // Input operand constraint for the variable
        // :                              // No output operand constraints
        // : "x16"                        // List of clobbered registers
    );
}

but I will never get zig side to get the 0x8f7f ret code, in face, as I debug from child_process.zig:418, shows, when I return 0x8f7f, the os.waitpid get is 0x7f00, which I can not understand.

try my luck here see whether someone knows how to get this part done, for my curiosity I thank you in advance.

Did you try std.os.system.W.EXITSTATUS(child.status)

Thanks. The question is not about when I get correct child.status how to check what kind it is. The questions is when (or in what kind of action can reproduce) can I get a child.status will be kind .Stopped or .Unknown.

If you need to know if the process was signaled or stopped you need to use W.IFSIGNALED W.IFSTOPPED, e.g. pid-defer/src/common.zig at master · Cloudef/pid-defer · GitHub

I have found that this may be a deeper than expected issue to zig std.childProcess, see the issue reported here std.ChildProcess after spawn SIGSTOP signal program will never return thus hang the host program · Issue #18548 · ziglang/zig (github.com)

1 Like

@liyu1981
Note that you don’t have to use a separate C program; use the shell, instead.

Here is an example:

const std = @import("std");
const log = std.log;
const process = std.process;
const os = std.os;
const testing = std.testing;

test {
    const cmd = "kill -STOP 0";
    const argv: []const []const u8 = &.{ "dash", "-c", cmd };
    const rr = try process.Child.run(.{
        .argv = argv,
        .allocator = testing.allocator,
    });

    switch (rr.term) {
        .Exited => |ret| {
            log.debug("Command exit with {d}!", .{ret});
        },
        .Signal => |ret| {
            log.debug("Command exited with signal {d}! Error!", .{ret});
        },
        .Stopped => |ret| {
            log.debug("Command stopped with {d}! Error!", .{ret});
        },
        .Unknown => |ret| {
            log.debug("Command exited with unknown reason {d}! Error!", .{ret});
        },
    }
}

Using SIGSTOP I get:

$zig test debug-sigstop.zig                                                                                                                 [1]
Test [1/1] test_0... zsh: suspended (signal)  zig test debug-sigstop.zig

Using cmd = "read&" (that should raise SIGTTIN) I get:

Test [1/1] test_0... [gpa] (err): memory address 0x7ff9f9a3d000 leaked:
...

UPDATE: this happens only with dash.
For bash you need to add the -i flag.