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 } } }