mirror of
https://github.com/yudai/gotty.git
synced 2025-12-21 18:10:11 +01:00
Refactor
This commit is contained in:
parent
54403dd678
commit
a6133f34b7
54 changed files with 2140 additions and 1334 deletions
3
backend/localcommand/doc.go
Normal file
3
backend/localcommand/doc.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// Package localcommand provides an implementation of webtty.Slave
|
||||
// that launches a local command with a PTY.
|
||||
package localcommand
|
||||
44
backend/localcommand/factory.go
Normal file
44
backend/localcommand/factory.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package localcommand
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/yudai/gotty/server"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
CloseSignal int `hcl:"close_signal" flagName:"close-signal" flagSName:"" flagDescribe:"Signal sent to the command process when gotty close it (default: SIGHUP)" default:"1"`
|
||||
CloseTimeout int `hcl:"close_timeout" flagName:"close-timeout" flagSName:"" flagDescribe:"Time in seconds to force kill process after client is disconnected (default: -1)" default:"-1"`
|
||||
}
|
||||
|
||||
type Factory struct {
|
||||
command string
|
||||
argv []string
|
||||
options *Options
|
||||
}
|
||||
|
||||
func NewFactory(command string, argv []string, options *Options) (*Factory, error) {
|
||||
return &Factory{
|
||||
command: command,
|
||||
argv: argv,
|
||||
options: options,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (factory *Factory) Name() string {
|
||||
return "local command"
|
||||
}
|
||||
|
||||
func (factory *Factory) New(params map[string][]string) (server.Slave, error) {
|
||||
argv := make([]string, len(factory.argv))
|
||||
copy(argv, factory.argv)
|
||||
if params["arg"] != nil && len(params["arg"]) > 0 {
|
||||
argv = append(argv, params["arg"]...)
|
||||
}
|
||||
return New(
|
||||
factory.command,
|
||||
argv,
|
||||
WithCloseSignal(syscall.Signal(factory.options.CloseSignal)),
|
||||
WithCloseSignal(syscall.Signal(factory.options.CloseTimeout)),
|
||||
)
|
||||
}
|
||||
136
backend/localcommand/local_command.go
Normal file
136
backend/localcommand/local_command.go
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
package localcommand
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/kr/pty"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultCloseSignal = syscall.SIGINT
|
||||
DefaultCloseTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
type LocalCommand struct {
|
||||
command string
|
||||
argv []string
|
||||
|
||||
closeSignal syscall.Signal
|
||||
closeTimeout time.Duration
|
||||
|
||||
cmd *exec.Cmd
|
||||
pty *os.File
|
||||
ptyClosed chan struct{}
|
||||
}
|
||||
|
||||
func New(command string, argv []string, options ...Option) (*LocalCommand, error) {
|
||||
cmd := exec.Command(command, argv...)
|
||||
|
||||
pty, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
// todo close cmd?
|
||||
return nil, errors.Wrapf(err, "failed to start command `%s`", command)
|
||||
}
|
||||
ptyClosed := make(chan struct{})
|
||||
|
||||
lcmd := &LocalCommand{
|
||||
command: command,
|
||||
argv: argv,
|
||||
|
||||
closeSignal: DefaultCloseSignal,
|
||||
closeTimeout: DefaultCloseTimeout,
|
||||
|
||||
cmd: cmd,
|
||||
pty: pty,
|
||||
ptyClosed: ptyClosed,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(lcmd)
|
||||
}
|
||||
|
||||
// When the process is closed by the user,
|
||||
// close pty so that Read() on the pty breaks with an EOF.
|
||||
go func() {
|
||||
defer func() {
|
||||
lcmd.pty.Close()
|
||||
close(lcmd.ptyClosed)
|
||||
}()
|
||||
|
||||
lcmd.cmd.Wait()
|
||||
}()
|
||||
|
||||
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) WindowTitleVariables() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"command": lcmd.command,
|
||||
"argv": lcmd.argv,
|
||||
"pid": lcmd.cmd.Process.Pid,
|
||||
}
|
||||
}
|
||||
|
||||
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) GetTerminalSize() (int, int, error) {
|
||||
return pty.Getsize(lcmd.pty)
|
||||
}
|
||||
|
||||
func (lcmd *LocalCommand) closeTimeoutC() <-chan time.Time {
|
||||
if lcmd.closeTimeout >= 0 {
|
||||
return time.After(lcmd.closeTimeout)
|
||||
}
|
||||
|
||||
return make(chan time.Time)
|
||||
}
|
||||
20
backend/localcommand/options.go
Normal file
20
backend/localcommand/options.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package localcommand
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Option func(*LocalCommand)
|
||||
|
||||
func WithCloseSignal(signal syscall.Signal) Option {
|
||||
return func(lcmd *LocalCommand) {
|
||||
lcmd.closeSignal = signal
|
||||
}
|
||||
}
|
||||
|
||||
func WithCloseTimeout(timeout time.Duration) Option {
|
||||
return func(lcmd *LocalCommand) {
|
||||
lcmd.closeTimeout = timeout
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue