Skip to content

Commit

Permalink
Merge pull request #1261 from dcantah/backport-exec-and-cpty
Browse files Browse the repository at this point in the history
[release/0.9] Backport TTY support for Host Process Containers
  • Loading branch information
dcantah authored Jan 4, 2022
2 parents a6b230d + 0edbe74 commit e6fba04
Show file tree
Hide file tree
Showing 341 changed files with 26,168 additions and 16,300 deletions.
3 changes: 3 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
run:
timeout: 8m

linters:
enable:
- stylecheck
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ require (
go.opencensus.io v0.22.3
golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210510120138-977fb7262007
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
google.golang.org/grpc v1.40.0
)

Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -812,8 +812,9 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
149 changes: 149 additions & 0 deletions internal/conpty/conpty.go
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)
}
Loading

0 comments on commit e6fba04

Please sign in to comment.