starssh/terminal_input.go

226 lines
5.6 KiB
Go
Raw Normal View History

refactor: 重构 starssh 核心运行时并补强 ssh/exec/terminal/sftp 能力 - 拆分原有单体 ssh.go,按职责重组为 types、utils、transport、login、keepalive、session、exec、pool、shell、terminal、forward、hostkey、state 等模块,并补充平台相关实现 - 重做登录与连接运行时,补齐基于 context 的建连、jump/proxy 链路、可配置认证顺序,以及 Unix/Windows 下的 ssh-agent 支持 - 新增正式非交互执行模型 ExecRequest/ExecResult,支持流式输出、溢出统计、超时控制,以及 posix/powershell/cmd/raw 多方言执行 - 保留旧 shell 风格兼容接口,同时让路径/用户探测等 helper 具备跨 shell fallback,避免 Windows 目标继续硬依赖 POSIX 命令 - 新增 TerminalSession 作为原始交互终端基座,提供 IO attach、resize、signal/control、退出状态与关闭原因管理 - 重构端口转发语义,默认复用当前 SSH 连接,并显式提供 detached 的本地/动态转发模式承载隔离场景 - 梳理 keepalive 与取消语义,区分仅取消本次操作和关闭整条连接,并统一连接状态与传输关闭路径 - 围绕新的 session/连接生命周期重做执行池与运行时支撑 - 大幅增强 SFTP 传输链路,补齐更安全的原子替换、校验、进度回调、重试隔离、可复用 client 生命周期与失败语义 - 新增取消语义、keepalive、SFTP、forward、terminal input 等关键回归测试,提升核心链路稳定性
2026-04-26 10:45:39 +08:00
package starssh
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"reflect"
"sync"
"unsafe"
)
// TerminalInputSourceProvider lets wrapper readers expose a closer-friendly source reader.
// Implementations that buffer data should return a source that already includes any prefetched bytes.
type TerminalInputSourceProvider interface {
TerminalInputSource() io.Reader
}
// TerminalInputCanceler lets wrapper readers expose an explicit cancellation hook.
// It is useful for line editors or custom buffered readers that cannot safely expose a raw io.ReadCloser.
type TerminalInputCanceler interface {
TerminalInputCancel() error
}
// TerminalInputAdapter adapts wrapper readers into a cancelable terminal input source.
// Reader is what TerminalSession consumes, Source is the closer-friendly underlying reader when available.
type TerminalInputAdapter struct {
Reader io.Reader
Source io.Reader
Cancel func() error
}
func (a TerminalInputAdapter) Read(p []byte) (int, error) {
if a.Reader == nil {
return 0, io.EOF
}
return a.Reader.Read(p)
}
func (a TerminalInputAdapter) TerminalInputSource() io.Reader {
if a.Source != nil {
return a.Source
}
return a.Reader
}
func (a TerminalInputAdapter) TerminalInputCancel() error {
if a.Cancel != nil {
return a.Cancel()
}
if closer, ok := a.Source.(io.Closer); ok && closer != nil {
return closer.Close()
}
if closer, ok := a.Reader.(io.Closer); ok && closer != nil {
return closer.Close()
}
return nil
}
func prepareTerminalInputReader(in io.Reader) (io.Reader, func(), bool, error) {
if in == nil {
return nil, func() {}, false, nil
}
var cancelOnce sync.Once
wrapCancel := func(fn func()) func() {
return func() {
cancelOnce.Do(fn)
}
}
if provider, ok := in.(TerminalInputSourceProvider); ok {
source := provider.TerminalInputSource()
if source == nil || sameReader(source, in) {
return prepareDirectTerminalInputReader(in, wrapCancel)
}
prepared, cancel, cancelable, err := prepareTerminalInputReader(source)
if err != nil {
return nil, nil, false, err
}
if canceler, ok := in.(TerminalInputCanceler); ok {
return prepared, wrapCancel(func() {
cancel()
_ = canceler.TerminalInputCancel()
}), true, nil
}
return prepared, cancel, cancelable, nil
}
return prepareDirectTerminalInputReader(in, wrapCancel)
}
func prepareDirectTerminalInputReader(in io.Reader, wrapCancel func(func()) func()) (io.Reader, func(), bool, error) {
if in == nil {
return nil, func() {}, false, nil
}
switch typed := in.(type) {
case *bufio.Reader:
return prepareBufferedTerminalInputReader(typed)
case *bufio.ReadWriter:
if typed.Reader == nil {
return in, func() {}, false, nil
}
return prepareBufferedTerminalInputReader(typed.Reader)
}
if canceler, ok := in.(TerminalInputCanceler); ok {
return in, wrapCancel(func() {
_ = canceler.TerminalInputCancel()
}), true, nil
}
if file, ok := in.(*os.File); ok {
dup, err := duplicateTerminalInputFile(file)
if err != nil {
return nil, nil, false, fmt.Errorf("duplicate terminal input: %w", err)
}
return dup, wrapCancel(func() {
_ = dup.Close()
}), true, nil
}
if closer, ok := in.(io.ReadCloser); ok {
return closer, wrapCancel(func() {
_ = closer.Close()
}), true, nil
}
return in, func() {}, false, nil
}
func prepareBufferedTerminalInputReader(reader *bufio.Reader) (io.Reader, func(), bool, error) {
if reader == nil {
return nil, func() {}, false, nil
}
bufferedPrefix, err := snapshotBufferedPrefix(reader)
if err != nil {
return nil, nil, false, err
}
underlying := unwrapBufioReader(reader)
if underlying == nil {
if len(bufferedPrefix) == 0 {
return reader, func() {}, false, nil
}
return io.MultiReader(bytes.NewReader(bufferedPrefix), reader), func() {}, false, nil
}
prepared, cancel, cancelable, err := prepareTerminalInputReader(underlying)
if err != nil {
return nil, nil, false, err
}
if len(bufferedPrefix) == 0 {
return prepared, cancel, cancelable, nil
}
if prepared == nil {
return bytes.NewReader(bufferedPrefix), cancel, cancelable, nil
}
return io.MultiReader(bytes.NewReader(bufferedPrefix), prepared), cancel, cancelable, nil
}
func snapshotBufferedPrefix(reader *bufio.Reader) ([]byte, error) {
if reader == nil {
return nil, nil
}
buffered := reader.Buffered()
if buffered == 0 {
return nil, nil
}
chunk, err := reader.Peek(buffered)
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("peek terminal input buffer: %w", err)
}
prefix := append([]byte(nil), chunk...)
if _, err := reader.Discard(len(prefix)); err != nil {
return nil, fmt.Errorf("discard terminal input buffer: %w", err)
}
return prefix, nil
}
func unwrapBufioReader(reader *bufio.Reader) io.Reader {
if reader == nil {
return nil
}
value := reflect.ValueOf(reader)
if value.Kind() != reflect.Pointer || value.IsNil() {
return nil
}
field := value.Elem().FieldByName("rd")
if !field.IsValid() {
return nil
}
underlyingValue := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem()
underlying, ok := underlyingValue.Interface().(io.Reader)
if !ok || underlying == nil || sameReader(underlying, reader) {
return nil
}
return underlying
}
func sameReader(left io.Reader, right io.Reader) bool {
if left == nil || right == nil {
return false
}
leftValue := reflect.ValueOf(left)
rightValue := reflect.ValueOf(right)
if !leftValue.IsValid() || !rightValue.IsValid() {
return false
}
if leftValue.Kind() != reflect.Pointer || rightValue.Kind() != reflect.Pointer {
return false
}
return leftValue.Pointer() == rightValue.Pointer()
}