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  }