golanggohlsrtmpwebrtcmedia-serverobs-studiortcprtmp-proxyrtmp-serverrtprtsprtsp-proxyrtsp-relayrtsp-serversrtstreamingwebrtc-proxy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
131 lines
2.9 KiB
131 lines
2.9 KiB
//go:build windows |
|
// +build windows |
|
|
|
package externalcmd |
|
|
|
import ( |
|
"fmt" |
|
"os" |
|
"os/exec" |
|
"strings" |
|
"syscall" |
|
"unsafe" |
|
|
|
"github.com/kballard/go-shellquote" |
|
"golang.org/x/sys/windows" |
|
) |
|
|
|
// taken from |
|
// https://gist.github.com/hallazzang/76f3970bfc949831808bbebc8ca15209 |
|
func createProcessGroup() (windows.Handle, error) { |
|
h, err := windows.CreateJobObject(nil, nil) |
|
if err != nil { |
|
return 0, err |
|
} |
|
|
|
info := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{ |
|
BasicLimitInformation: windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{ |
|
LimitFlags: windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, |
|
}, |
|
} |
|
_, err = windows.SetInformationJobObject( |
|
h, |
|
windows.JobObjectExtendedLimitInformation, |
|
uintptr(unsafe.Pointer(&info)), |
|
uint32(unsafe.Sizeof(info))) |
|
if err != nil { |
|
return 0, err |
|
} |
|
|
|
return h, nil |
|
} |
|
|
|
func closeProcessGroup(h windows.Handle) error { |
|
return windows.CloseHandle(h) |
|
} |
|
|
|
func addProcessToGroup(h windows.Handle, p *os.Process) error { |
|
type process struct { |
|
Pid int |
|
Handle uintptr |
|
} |
|
|
|
return windows.AssignProcessToJobObject(h, |
|
windows.Handle((*process)(unsafe.Pointer(p)).Handle)) |
|
} |
|
|
|
func (e *Cmd) runOSSpecific(env []string) error { |
|
var cmd *exec.Cmd |
|
|
|
// from Golang documentation: |
|
// On Windows, processes receive the whole command line as a single string and do their own parsing. |
|
// Command combines and quotes Args into a command line string with an algorithm compatible with |
|
// applications using CommandLineToArgvW (which is the most common way). Notable exceptions are |
|
// msiexec.exe and cmd.exe (and thus, all batch files), which have a different unquoting algorithm. |
|
// In these or other similar cases, you can do the quoting yourself and provide the full command |
|
// line in SysProcAttr.CmdLine, leaving Args empty. |
|
if strings.HasPrefix(e.cmdstr, "cmd ") || strings.HasPrefix(e.cmdstr, "cmd.exe ") { |
|
args := strings.TrimPrefix(strings.TrimPrefix(e.cmdstr, "cmd "), "cmd.exe ") |
|
|
|
cmd = exec.Command("cmd.exe") |
|
cmd.SysProcAttr = &syscall.SysProcAttr{ |
|
CmdLine: args, |
|
} |
|
} else { |
|
cmdParts, err := shellquote.Split(e.cmdstr) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
cmd = exec.Command(cmdParts[0], cmdParts[1:]...) |
|
} |
|
|
|
cmd.Env = env |
|
cmd.Stdout = os.Stdout |
|
cmd.Stderr = os.Stderr |
|
|
|
// create a process group to kill all subprocesses |
|
g, err := createProcessGroup() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
err = cmd.Start() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
err = addProcessToGroup(g, cmd.Process) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
cmdDone := make(chan int) |
|
go func() { |
|
cmdDone <- func() int { |
|
err := cmd.Wait() |
|
if err == nil { |
|
return 0 |
|
} |
|
ee, ok := err.(*exec.ExitError) |
|
if !ok { |
|
return 0 |
|
} |
|
return ee.ExitCode() |
|
}() |
|
}() |
|
|
|
select { |
|
case <-e.terminate: |
|
closeProcessGroup(g) |
|
<-cmdDone |
|
return errTerminated |
|
|
|
case c := <-cmdDone: |
|
closeProcessGroup(g) |
|
if c != 0 { |
|
return fmt.Errorf("command exited with code %d", c) |
|
} |
|
return nil |
|
} |
|
}
|
|
|