Search code examples
unixgosignalscross-platform

How can I list available operating system signals by name in a cross-platform way in Go?


Let's say I'm implementing the kill program in Go. I can accept numeric signals and PIDs from the commandline and send them to syscall.Kill no problem.

However, I don't know how to implement the "string" form of signal dispatch, e.g. kill -INT 12345.

The real use case is a part of a larger program that prompts the user to send kill signals; not a replacement for kill.

Question:

How can I convert valid signal names to signal numbers on any supported platform, at runtime (or at least without writing per-platform code to be run at compile time)?

What I've tried:

  • Keep a static map of signal names to numbers. This doesn't work in a cross-platform way (for example, different signal lists are returned by kill -l on Mac OSX versus a modern Linux versus an older Linux, for example). The only way to make this solution work in general would be to make maps for every OS, which would require me to know the behavior of every OS, and keep up to date as they add new signal support.
  • Shell out to the GNU kill tool and capture the signal lists from it. This is inelegant and kind of a paradox, and also requires a) being able to find kill, b) having the ability/permission to exec subprocesses, and c) being able to predict/parse the output of kill-the-binary.
  • Use the various Signal types' String method. This just returns strings containing the signal number, e.g. os.Signal(4).String() == "signal 4", which is not useful.
  • Call the private function runtime.signame, which does exactly what I want. go://linkname hacks will work, but I'm assuming that this sort of thing is frowned-upon for a reason.

Ideas/Things I Haven't Tried:

  • Use CGo somehow. I'd rather not venture into CGO territory for a project that is otherwise not low-level/needful of native integration at all. If that's the only option, I will, but have no idea where to start.
  • Use templating and code generation to build lists of signals based on external sources at compile time. This is not preferable for the same reasons as CGo.
  • Reflect and parse the members of syscall that start with SIG somehow. I am told that this is not possible because names are compiled away; is it possible that, for something as fundamental as signal names, there's someplace they're not compiled away?

Solution

  • Commit d455e41 added this feature in March 2019 as sys/unix.SignalNum() and is thus available at least since Go 1.13. More details in GitHub issue #28027.

    From the documentation of the golang.org/x/sys/unix package:

    func SignalNum(s string) syscall.Signal

    SignalNum returns the syscall.Signal for signal named s, or 0 if a signal with such name is not found. The signal name should start with "SIG".

    To answer a similar question, "how can I list the names of all available signals (on a given Unix-like platform)", we can use the inverse function sys/unix.SignalName():

        import "golang.org/x/sys/unix"
    
        // See https://github.com/golang/go/issues/28027#issuecomment-427377759
        // for why looping in range 0,255 is enough.
        for i := syscall.Signal(0); i < syscall.Signal(255); i++ {
            name := unix.SignalName(i)
            // Signal numbers are not guaranteed to be contiguous.
            if name != "" {
                fmt.Println(name)
            }
        }