在Go中启动一个流程并从中分离

I need to start a new process in Go with the following requirements:

  • The starting process should run even after the Go process is terminated
  • I need to be able to set the Unix user/group that's running it
  • I need to be able to set the environment variables inherited
  • I need control over std in/out/err

Here is an attempt:

var attr = os.ProcAttr {
Dir: "/bin",
Env: os.Environ(),
Files: []*os.File{
    os.Stdin,
    "stdout.log",
    "stderr.log",
  },
}
process, err := os.StartProcess("sleep", []string{"1"}, &attr)

This works fine but has the following shortcomings from the requirements:

  • No way to set Unix user/group
  • The started process ends when the Go process (parent) stops

This needs to run on Linux only if that simplifies things.

  1. You can use process.Release to detach the child process from the parent one and make it survive after parent death
  2. Look at the definition of *os.ProcAttr.Sys.Credentials attribute : it looks like using the attribute you can set process user and group ID.

Here is a working version of your example (I did not check if process ID's where actually the one set )

package main

import "fmt"
import "os"
import "syscall"

const (
    UID = 501
    GUID = 100
    )


func main() {
    // The Credential fields are used to set UID, GID and attitional GIDS of the process
    // You need to run the program as  root to do this
        var cred =  &syscall.Credential{ UID, GUID, []uint32{} }
    // the Noctty flag is used to detach the process from parent tty
    var sysproc = &syscall.SysProcAttr{  Credential:cred, Noctty:true }
    var attr = os.ProcAttr{
        Dir: ".",
        Env: os.Environ(),
        Files: []*os.File{
            os.Stdin,
            nil,
            nil,
        },
            Sys:sysproc,

    }
    process, err := os.StartProcess("/bin/sleep", []string{"/bin/sleep", "100"}, &attr)
    if err == nil {

        // It is not clear from docs, but Realease actually detaches the process
        err = process.Release();
        if err != nil {
            fmt.Println(err.Error())
        }

    } else {
        fmt.Println(err.Error())
    }
}

What I have found that seems to work cross-platform is to re-run the program with a special flag. In your main program, check for this flag. If present on startup, you're in the "fork". If not present, re-run the command with the flag.

func rerunDetached() error {
    cwd, err := os.Getwd()
    if err != nil {
       return err
    }
    args := append(os.Args, "--detached")
    cmd := exec.Command(args[0], args[1:]...)
    cmd.Dir = cwd
    err = cmd.Start()
    if err != nil {
       return err
    }
    cmd.Process.Release()
    return nil
}

This will simply re-run your process with the exact parameters and append --detached to the arguments. When your program starts, check for the --detached flag to know if you need to call rerunDetached or not. This is sort of like a poor mans fork() which will work across different OS.