github.com/qiniu/go-sdk/v7@v7.27.0/sandbox/pty.go (about) 1 package sandbox 2 3 import ( 4 "context" 5 "fmt" 6 7 "connectrpc.com/connect" 8 9 "github.com/qiniu/go-sdk/v7/sandbox/internal/envdapi/process" 10 "github.com/qiniu/go-sdk/v7/sandbox/internal/envdapi/process/processconnect" 11 ) 12 13 // PtySize PTY 终端大小。 14 type PtySize struct { 15 Cols uint32 16 Rows uint32 17 } 18 19 // Pty 提供沙箱 PTY(伪终端)操作。 20 type Pty struct { 21 sandbox *Sandbox 22 rpc processconnect.ProcessClient 23 } 24 25 // newPty 创建 Pty 实例。 26 func newPty(s *Sandbox, rpc processconnect.ProcessClient) *Pty { 27 return &Pty{sandbox: s, rpc: rpc} 28 } 29 30 // Create 创建一个 PTY 终端会话。 31 // PTY 输出通过 WithOnPtyData 回调接收;若未设置,回退使用 WithOnStdout 以保持兼容。 32 func (p *Pty) Create(ctx context.Context, size PtySize, opts ...CommandOption) (*CommandHandle, error) { 33 o := applyCommandOpts(opts) 34 35 ptyCtx, ptyCancel := context.WithCancel(ctx) 36 37 // 合并默认 PTY 环境变量和用户自定义环境变量 38 envs := map[string]string{ 39 "TERM": "xterm", 40 "LANG": "C.UTF-8", 41 "LC_ALL": "C.UTF-8", 42 } 43 for k, v := range o.envs { 44 envs[k] = v 45 } 46 47 startReq := &process.StartRequest{ 48 Process: &process.ProcessConfig{ 49 Cmd: "/bin/bash", 50 Args: []string{"-i", "-l"}, 51 Envs: envs, 52 }, 53 Pty: &process.PTY{ 54 Size: &process.PTY_Size{ 55 Cols: size.Cols, 56 Rows: size.Rows, 57 }, 58 }, 59 } 60 if o.cwd != "" { 61 startReq.Process.Cwd = &o.cwd 62 } 63 if o.tag != "" { 64 startReq.Tag = &o.tag 65 } 66 67 req := connect.NewRequest(startReq) 68 p.sandbox.setEnvdAuth(req, o.user) 69 70 stream, err := p.rpc.Start(ptyCtx, req) 71 if err != nil { 72 ptyCancel() 73 return nil, fmt.Errorf("create pty: %w", err) 74 } 75 76 commands := &Commands{sandbox: p.sandbox, rpc: p.rpc} 77 78 // 优先使用 onPtyData,回退到 onStdout 以保持兼容 79 ptyDataFn := o.onPtyData 80 if ptyDataFn == nil { 81 ptyDataFn = o.onStdout 82 } 83 84 handle := &CommandHandle{ 85 commands: commands, 86 cancel: ptyCancel, 87 done: make(chan struct{}), 88 pidCh: make(chan struct{}), 89 onPtyData: ptyDataFn, 90 } 91 92 go processEventStream(stream, handle) 93 94 return handle, nil 95 } 96 97 // Connect 连接到已有的 PTY 会话。 98 func (p *Pty) Connect(ctx context.Context, pid uint32) (*CommandHandle, error) { 99 commands := &Commands{sandbox: p.sandbox, rpc: p.rpc} 100 return commands.Connect(ctx, pid) 101 } 102 103 // SendInput 向 PTY 发送输入。 104 func (p *Pty) SendInput(ctx context.Context, pid uint32, data []byte) error { 105 req := connect.NewRequest(&process.SendInputRequest{ 106 Process: pidSelector(pid), 107 Input: &process.ProcessInput{ 108 Input: &process.ProcessInput_Pty{Pty: data}, 109 }, 110 }) 111 p.sandbox.setEnvdAuth(req, DefaultUser) 112 113 _, err := p.rpc.SendInput(ctx, req) 114 if err != nil { 115 return fmt.Errorf("send pty input: %w", err) 116 } 117 return nil 118 } 119 120 // Resize 调整 PTY 终端大小。 121 func (p *Pty) Resize(ctx context.Context, pid uint32, size PtySize) error { 122 req := connect.NewRequest(&process.UpdateRequest{ 123 Process: pidSelector(pid), 124 Pty: &process.PTY{ 125 Size: &process.PTY_Size{ 126 Cols: size.Cols, 127 Rows: size.Rows, 128 }, 129 }, 130 }) 131 p.sandbox.setEnvdAuth(req, DefaultUser) 132 133 _, err := p.rpc.Update(ctx, req) 134 if err != nil { 135 return fmt.Errorf("resize pty: %w", err) 136 } 137 return nil 138 } 139 140 // Kill 终止 PTY 会话。 141 func (p *Pty) Kill(ctx context.Context, pid uint32) error { 142 commands := &Commands{sandbox: p.sandbox, rpc: p.rpc} 143 return commands.Kill(ctx, pid) 144 }