...
1
2
3 package conpty
4
5 import (
6 "errors"
7 "fmt"
8 "os"
9 "sync"
10 "unsafe"
11
12 "github.com/Microsoft/hcsshim/internal/winapi"
13 "golang.org/x/sys/windows"
14 )
15
16 var (
17 errClosedConPty = errors.New("pseudo console is closed")
18 errNotInitialized = errors.New("pseudo console hasn't been initialized")
19 )
20
21
22 type Pty struct {
23
24 handleLock sync.RWMutex
25
26 hpc windows.Handle
27
28 inPipe *os.File
29 outPipe *os.File
30 }
31
32
33 func Create(width, height int16, flags uint32) (*Pty, error) {
34
35
36 ptyIn, inPipeOurs, err := os.Pipe()
37 if err != nil {
38 return nil, fmt.Errorf("failed to create pipes for pseudo console: %w", err)
39 }
40
41 outPipeOurs, ptyOut, err := os.Pipe()
42 if err != nil {
43 return nil, fmt.Errorf("failed to create pipes for pseudo console: %w", err)
44 }
45
46 var hpc windows.Handle
47 coord := windows.Coord{X: width, Y: height}
48 err = winapi.CreatePseudoConsole(coord, windows.Handle(ptyIn.Fd()), windows.Handle(ptyOut.Fd()), 0, &hpc)
49 if err != nil {
50 return nil, fmt.Errorf("failed to create pseudo console: %w", err)
51 }
52
53
54
55 if err := ptyOut.Close(); err != nil {
56 return nil, fmt.Errorf("failed to close pseudo console handle: %w", err)
57 }
58 if err := ptyIn.Close(); err != nil {
59 return nil, fmt.Errorf("failed to close pseudo console handle: %w", err)
60 }
61
62 return &Pty{
63 hpc: hpc,
64 inPipe: inPipeOurs,
65 outPipe: outPipeOurs,
66 }, nil
67 }
68
69
70
71 func (c *Pty) UpdateProcThreadAttribute(attrList *windows.ProcThreadAttributeListContainer) error {
72 c.handleLock.RLock()
73 defer c.handleLock.RUnlock()
74
75 if c.hpc == 0 {
76 return errClosedConPty
77 }
78
79 if err := attrList.Update(
80 winapi.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
81 unsafe.Pointer(c.hpc),
82 unsafe.Sizeof(c.hpc),
83 ); err != nil {
84 return fmt.Errorf("failed to update proc thread attributes for pseudo console: %w", err)
85 }
86
87 return nil
88 }
89
90
91 func (c *Pty) Resize(width, height int16) error {
92 c.handleLock.RLock()
93 defer c.handleLock.RUnlock()
94
95 if c.hpc == 0 {
96 return errClosedConPty
97 }
98
99 coord := windows.Coord{X: width, Y: height}
100 if err := winapi.ResizePseudoConsole(c.hpc, coord); err != nil {
101 return fmt.Errorf("failed to resize pseudo console: %w", err)
102 }
103 return nil
104 }
105
106
107 func (c *Pty) Close() error {
108 c.handleLock.Lock()
109 defer c.handleLock.Unlock()
110
111 if c.hpc == 0 {
112 return errClosedConPty
113 }
114
115
116 winapi.ClosePseudoConsole(c.hpc)
117 c.hpc = 0
118 if err := c.inPipe.Close(); err != nil {
119 return fmt.Errorf("failed to close pseudo console input pipe: %w", err)
120 }
121 if err := c.outPipe.Close(); err != nil {
122 return fmt.Errorf("failed to close pseudo console output pipe: %w", err)
123 }
124 return nil
125 }
126
127
128 func (c *Pty) OutPipe() *os.File {
129 return c.outPipe
130 }
131
132
133 func (c *Pty) InPipe() *os.File {
134 return c.inPipe
135 }
136
137
138 func (c *Pty) Write(buf []byte) (int, error) {
139 if c.inPipe == nil {
140 return 0, errNotInitialized
141 }
142 return c.inPipe.Write(buf)
143 }
144
145
146 func (c *Pty) Read(buf []byte) (int, error) {
147 if c.outPipe == nil {
148 return 0, errNotInitialized
149 }
150 return c.outPipe.Read(buf)
151 }
152
View as plain text