230 lines
4.2 KiB
Go
230 lines
4.2 KiB
Go
package proxy
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/sirupsen/logrus"
|
|
"gopkg.in/natefinch/lumberjack.v2"
|
|
"hy2xs-admin/model/constant"
|
|
"io"
|
|
"os/exec"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
var logger logrus.Logger
|
|
|
|
func initLogger() {
|
|
logger.SetOutput(&lumberjack.Logger{
|
|
Filename: constant.Hysteria2LogPath,
|
|
MaxSize: 1,
|
|
MaxBackups: 2,
|
|
MaxAge: 30,
|
|
Compress: true,
|
|
LocalTime: true,
|
|
})
|
|
logger.SetFormatter(&logrus.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"})
|
|
logger.SetLevel(logrus.InfoLevel)
|
|
}
|
|
|
|
func init() {
|
|
initLogger()
|
|
}
|
|
|
|
type process struct {
|
|
mutex *sync.Mutex
|
|
cmd *exec.Cmd
|
|
}
|
|
|
|
func (p *process) isRunning() bool {
|
|
return p.cmd != nil && p.cmd.Process != nil && p.cmd.ProcessState == nil
|
|
}
|
|
|
|
func (p *process) start(name string, arg ...string) error {
|
|
if !p.mutex.TryLock() {
|
|
logrus.Errorf("start cmd err: lock not acquired")
|
|
return errors.New("start cmd err")
|
|
}
|
|
defer p.mutex.Unlock()
|
|
|
|
if p.isRunning() {
|
|
return nil
|
|
}
|
|
|
|
cmd := exec.Command(name, arg...)
|
|
if cmd.Err != nil {
|
|
logrus.Errorf("cmd err: %v", cmd.Err)
|
|
return errors.New("cmd err")
|
|
}
|
|
|
|
// Получение stdout и stderr команды
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
logrus.Errorf("Error obtaining stdout: %v", err)
|
|
return err
|
|
}
|
|
stderr, err := cmd.StderrPipe()
|
|
if err != nil {
|
|
logrus.Errorf("Error obtaining stderr: %v", err)
|
|
return err
|
|
}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
logrus.Errorf("cmd start err: %v", err)
|
|
return errors.New("cmd start err")
|
|
}
|
|
|
|
p.cmd = cmd
|
|
|
|
go p.handleLogs(stdout, stderr)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *process) stop() error {
|
|
if !p.mutex.TryLock() {
|
|
return errors.New("cmd stop err: lock not acquired")
|
|
}
|
|
|
|
if !p.isRunning() {
|
|
p.mutex.Unlock()
|
|
return nil
|
|
}
|
|
|
|
cmd := p.cmd
|
|
p.mutex.Unlock()
|
|
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- cmd.Wait()
|
|
}()
|
|
|
|
if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
|
logrus.Warnf("send SIGTERM failed: %v", err)
|
|
}
|
|
|
|
timer := time.NewTimer(3 * time.Second)
|
|
defer timer.Stop()
|
|
|
|
select {
|
|
case err := <-done:
|
|
if normalizeExitErr(err, syscall.SIGTERM) != nil {
|
|
return fmt.Errorf("process exit failed: %w", err)
|
|
}
|
|
|
|
case <-timer.C:
|
|
if err := cmd.Process.Kill(); err != nil {
|
|
return fmt.Errorf("SIGKILL failed: %w", err)
|
|
}
|
|
|
|
err := <-done
|
|
if normalizeExitErr(err, syscall.SIGKILL) != nil {
|
|
return fmt.Errorf("process killed but exit abnormal: %w", err)
|
|
}
|
|
}
|
|
|
|
p.mutex.Lock()
|
|
p.cmd = nil
|
|
p.mutex.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
func normalizeExitErr(err error, allowedSignals ...syscall.Signal) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
exitErr, ok := err.(*exec.ExitError)
|
|
if !ok {
|
|
return err
|
|
}
|
|
|
|
status, ok := exitErr.Sys().(syscall.WaitStatus)
|
|
if !ok {
|
|
return err
|
|
}
|
|
|
|
if status.Signaled() {
|
|
sig := status.Signal()
|
|
for _, s := range allowedSignals {
|
|
if sig == s {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (p *process) release() error {
|
|
if !p.mutex.TryLock() {
|
|
logrus.Errorf("cmd release err: lock not acquired")
|
|
return errors.New("cmd release err")
|
|
}
|
|
defer p.mutex.Unlock()
|
|
|
|
if !p.isRunning() {
|
|
return nil
|
|
}
|
|
|
|
if err := p.cmd.Process.Release(); err != nil {
|
|
logrus.Errorf("cmd release err: %v", err)
|
|
return errors.New("cmd release err")
|
|
}
|
|
p.cmd = nil
|
|
return nil
|
|
}
|
|
|
|
func (p *process) handleLogs(stdout, stderr io.ReadCloser) {
|
|
// Логи
|
|
stdoutChan := make(chan string)
|
|
stderrChan := make(chan string)
|
|
|
|
go func() {
|
|
scanner := bufio.NewScanner(stdout)
|
|
for scanner.Scan() {
|
|
stdoutChan <- scanner.Text()
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
logrus.Errorf("Error reading stdout: %v", err)
|
|
}
|
|
close(stdoutChan)
|
|
}()
|
|
|
|
go func() {
|
|
scanner := bufio.NewScanner(stderr)
|
|
for scanner.Scan() {
|
|
stderrChan <- scanner.Text()
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
logrus.Errorf("Error reading stderr: %v", err)
|
|
}
|
|
close(stderrChan)
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case line, ok := <-stdoutChan:
|
|
if !ok {
|
|
stdoutChan = nil
|
|
} else {
|
|
logger.Infof(line)
|
|
}
|
|
case line, ok := <-stderrChan:
|
|
if !ok {
|
|
stderrChan = nil
|
|
} else {
|
|
logger.Errorf(line)
|
|
}
|
|
}
|
|
|
|
// Когда оба channel закрыты, выходим из цикла
|
|
if stdoutChan == nil && stderrChan == nil {
|
|
break
|
|
}
|
|
}
|
|
}
|