-
Notifications
You must be signed in to change notification settings - Fork 264
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1261 from dcantah/backport-exec-and-cpty
[release/0.9] Backport TTY support for Host Process Containers
- Loading branch information
Showing
341 changed files
with
26,168 additions
and
16,300 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
run: | ||
timeout: 8m | ||
|
||
linters: | ||
enable: | ||
- stylecheck | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package conpty | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
"sync" | ||
"unsafe" | ||
|
||
"github.com/Microsoft/hcsshim/internal/winapi" | ||
"golang.org/x/sys/windows" | ||
) | ||
|
||
var ( | ||
errClosedConPty = errors.New("pseudo console is closed") | ||
errNotInitialized = errors.New("pseudo console hasn't been initialized") | ||
) | ||
|
||
// Pty is a wrapper around a Windows PseudoConsole handle. Create a new instance by calling `Create()`. | ||
type Pty struct { | ||
// handleLock guards hpc | ||
handleLock sync.RWMutex | ||
// hpc is the pseudo console handle | ||
hpc windows.Handle | ||
// inPipe and outPipe are our end of the pipes to read/write to the pseudo console. | ||
inPipe *os.File | ||
outPipe *os.File | ||
} | ||
|
||
// Create returns a new `Pty` object. This object is not ready for IO until `UpdateProcThreadAttribute` is called and a process has been started. | ||
func Create(width, height int16, flags uint32) (*Pty, error) { | ||
// First we need to make both ends of the conpty's pipes, two to get passed into a process to use as input/output, and two for us to keep to | ||
// make use of this data. | ||
ptyIn, inPipeOurs, err := os.Pipe() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create pipes for pseudo console: %w", err) | ||
} | ||
|
||
outPipeOurs, ptyOut, err := os.Pipe() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create pipes for pseudo console: %w", err) | ||
} | ||
|
||
var hpc windows.Handle | ||
coord := windows.Coord{X: width, Y: height} | ||
err = winapi.CreatePseudoConsole(coord, windows.Handle(ptyIn.Fd()), windows.Handle(ptyOut.Fd()), 0, &hpc) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create pseudo console: %w", err) | ||
} | ||
|
||
// The pty's end of its pipes can be closed here without worry. They're duped into the conhost | ||
// that will be launched and will be released on a call to ClosePseudoConsole() (Close() on the Pty object). | ||
if err := ptyOut.Close(); err != nil { | ||
return nil, fmt.Errorf("failed to close pseudo console handle: %w", err) | ||
} | ||
if err := ptyIn.Close(); err != nil { | ||
return nil, fmt.Errorf("failed to close pseudo console handle: %w", err) | ||
} | ||
|
||
return &Pty{ | ||
hpc: hpc, | ||
inPipe: inPipeOurs, | ||
outPipe: outPipeOurs, | ||
}, nil | ||
} | ||
|
||
// UpdateProcThreadAttribute updates the passed in attribute list to contain the entry necessary for use with | ||
// CreateProcess. | ||
func (c *Pty) UpdateProcThreadAttribute(attrList *windows.ProcThreadAttributeListContainer) error { | ||
c.handleLock.RLock() | ||
defer c.handleLock.RUnlock() | ||
|
||
if c.hpc == 0 { | ||
return errClosedConPty | ||
} | ||
|
||
if err := attrList.Update( | ||
winapi.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, | ||
unsafe.Pointer(c.hpc), | ||
unsafe.Sizeof(c.hpc), | ||
); err != nil { | ||
return fmt.Errorf("failed to update proc thread attributes for pseudo console: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Resize resizes the internal buffers of the pseudo console to the passed in size | ||
func (c *Pty) Resize(width, height int16) error { | ||
c.handleLock.RLock() | ||
defer c.handleLock.RUnlock() | ||
|
||
if c.hpc == 0 { | ||
return errClosedConPty | ||
} | ||
|
||
coord := windows.Coord{X: width, Y: height} | ||
if err := winapi.ResizePseudoConsole(c.hpc, coord); err != nil { | ||
return fmt.Errorf("failed to resize pseudo console: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
// Close closes the pseudo-terminal and cleans up all attached resources | ||
func (c *Pty) Close() error { | ||
c.handleLock.Lock() | ||
defer c.handleLock.Unlock() | ||
|
||
if c.hpc == 0 { | ||
return errClosedConPty | ||
} | ||
|
||
// Close the pseudo console, set the handle to 0 to invalidate this object and then close the side of the pipes that we own. | ||
winapi.ClosePseudoConsole(c.hpc) | ||
c.hpc = 0 | ||
if err := c.inPipe.Close(); err != nil { | ||
return fmt.Errorf("failed to close pseudo console input pipe: %w", err) | ||
} | ||
if err := c.outPipe.Close(); err != nil { | ||
return fmt.Errorf("failed to close pseudo console output pipe: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
// OutPipe returns the output pipe of the pseudo console. | ||
func (c *Pty) OutPipe() *os.File { | ||
return c.outPipe | ||
} | ||
|
||
// InPipe returns the input pipe of the pseudo console. | ||
func (c *Pty) InPipe() *os.File { | ||
return c.inPipe | ||
} | ||
|
||
// Write writes the contents of `buf` to the pseudo console. Returns the number of bytes written and an error if there is one. | ||
func (c *Pty) Write(buf []byte) (int, error) { | ||
if c.inPipe == nil { | ||
return 0, errNotInitialized | ||
} | ||
return c.inPipe.Write(buf) | ||
} | ||
|
||
// Read reads from the pseudo console into `buf`. Returns the number of bytes read and an error if there is one. | ||
func (c *Pty) Read(buf []byte) (int, error) { | ||
if c.outPipe == nil { | ||
return 0, errNotInitialized | ||
} | ||
return c.outPipe.Read(buf) | ||
} |
Oops, something went wrong.