package terminal import ( "os" "os/exec" "syscall" "time" "unsafe" "github.com/1Panel-dev/1Panel/backend/global" "github.com/kr/pty" "github.com/pkg/errors" ) const ( DefaultCloseSignal = syscall.SIGINT DefaultCloseTimeout = 10 * time.Second ) type LocalCommand struct { command string closeSignal syscall.Signal closeTimeout time.Duration cmd *exec.Cmd pty *os.File ptyClosed chan struct{} } func NewCommand() (*LocalCommand, error) { command := "sh" cmd := exec.Command(command) cmd.Dir = "/" pty, err := pty.Start(cmd) if err != nil { return nil, errors.Wrapf(err, "failed to start command `%s`", command) } ptyClosed := make(chan struct{}) lcmd := &LocalCommand{ command: command, closeSignal: DefaultCloseSignal, closeTimeout: DefaultCloseTimeout, cmd: cmd, pty: pty, ptyClosed: ptyClosed, } return lcmd, nil } func (lcmd *LocalCommand) Read(p []byte) (n int, err error) { return lcmd.pty.Read(p) } func (lcmd *LocalCommand) Write(p []byte) (n int, err error) { return lcmd.pty.Write(p) } func (lcmd *LocalCommand) Close() error { if lcmd.cmd != nil && lcmd.cmd.Process != nil { _ = lcmd.cmd.Process.Signal(lcmd.closeSignal) } for { select { case <-lcmd.ptyClosed: return nil case <-lcmd.closeTimeoutC(): _ = lcmd.cmd.Process.Signal(syscall.SIGKILL) } } } func (lcmd *LocalCommand) ResizeTerminal(width int, height int) error { window := struct { row uint16 col uint16 x uint16 y uint16 }{ uint16(height), uint16(width), 0, 0, } _, _, errno := syscall.Syscall( syscall.SYS_IOCTL, lcmd.pty.Fd(), syscall.TIOCSWINSZ, uintptr(unsafe.Pointer(&window)), ) if errno != 0 { return errno } else { return nil } } func (lcmd *LocalCommand) Wait(quitChan chan bool) { if err := lcmd.cmd.Wait(); err != nil { global.LOG.Errorf("ssh session wait failed, err: %v", err) setQuit(quitChan) } } func (lcmd *LocalCommand) closeTimeoutC() <-chan time.Time { if lcmd.closeTimeout >= 0 { return time.After(lcmd.closeTimeout) } return make(chan time.Time) }