agent/pluginmanager/pluginmanager_windows.go (111 lines of code) (raw):
//go:build windows
// +build windows
package pluginmanager
import (
"errors"
"io"
"os"
"strings"
"time"
"unsafe"
"github.com/aliyun/aliyun_assist_client/agent/log"
"github.com/aliyun/aliyun_assist_client/agent/util/process"
"github.com/aliyun/aliyun_assist_client/common/executil"
"github.com/aliyun/aliyun_assist_client/thirdparty/sirupsen/logrus"
"golang.org/x/sys/windows"
)
// We use this struct to retreive process handle(which is unexported)
// from os.Process using unsafe operation.
type processHandle struct {
Pid int
Handle uintptr
}
type waitProcessResult struct {
processState *os.ProcessState
err error
}
type ProcessExitGroup windows.Handle
func NewProcessExitGroup() (ProcessExitGroup, error) {
handle, 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,
},
}
if _, err := windows.SetInformationJobObject(
handle,
windows.JobObjectExtendedLimitInformation,
uintptr(unsafe.Pointer(&info)),
uint32(unsafe.Sizeof(info))); err != nil {
return 0, err
}
return ProcessExitGroup(handle), nil
}
func (g ProcessExitGroup) Dispose() error {
return windows.CloseHandle(windows.Handle(g))
}
func (g ProcessExitGroup) AddProcess(p *os.Process) error {
return windows.AssignProcessToJobObject(
windows.Handle(g),
windows.Handle((*processHandle)(unsafe.Pointer(p)).Handle))
}
func syncRunKillGroup(workingDir string, commandName string, commandArguments []string, stdoutWriter io.Writer, stderrWriter io.Writer,
timeOut int) (exitCode int, status int, err error) {
g, err := NewProcessExitGroup()
if err != nil {
return 1, process.Fail, err
}
defer func() {
log.GetLogger().Infof("syncRunKillGroup: done, workingDir[%s] commandName[%s] commandArguments[%s] timeout[%d]", workingDir, commandName, strings.Join(commandArguments, " "), timeOut)
if exitCode != 0 || status != process.Success || err != nil {
log.GetLogger().Errorf("syncRunKillGroup: exitCode[%d] status[%d] err[%v], not success, will kill all child process", exitCode, status, err)
g.Dispose()
}
}()
cmd := executil.Command(commandName, commandArguments...)
cmd.Stdout = stdoutWriter
cmd.Stderr = stderrWriter
cmd.Dir = workingDir
if err = cmd.Start(); err != nil {
exitCode = -1
return exitCode, process.Fail, err
}
if err = g.AddProcess(cmd.Process); err != nil {
return 1, process.Fail, err
}
finished := make(chan waitProcessResult, 1)
go func() {
processState, err := cmd.Process.Wait()
finished <- waitProcessResult{
processState: processState,
err: err,
}
}()
select {
case waitProcessResult := <-finished:
if waitProcessResult.processState != nil {
if waitProcessResult.err != nil {
log.GetLogger().WithFields(logrus.Fields{
"processState": waitProcessResult.processState,
}).WithError(waitProcessResult.err).Error("os.Process.Wait() returns error with valid process state")
}
exitCode = waitProcessResult.processState.ExitCode()
// Sleep 200ms to allow remaining data to be copied back
time.Sleep(time.Duration(200) * time.Millisecond)
// Explicitly break select statement in case timer also times out
break
} else {
exitCode = -1
return exitCode, process.Fail, waitProcessResult.err
}
case <-time.After(time.Duration(timeOut) * time.Second):
log.GetLogger().Errorln("Timeout in run command.", commandName)
exitCode = -1
status = process.Timeout
err = errors.New("timeout")
}
return exitCode, status, err
}
func GetArch() (formatArch string, rawArch string) {
// 云助手的windows版架构只有amd64的
formatArch = ARCH_64
rawArch = "windows arch"
log.GetLogger().Infof("Get Arch: formatArch[%s] rawArch[%s]: ", formatArch, rawArch)
return
}