...

Source file src/github.com/Microsoft/hcsshim/internal/conpty/conpty.go

Documentation: github.com/Microsoft/hcsshim/internal/conpty

     1  //go:build windows
     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  // Pty is a wrapper around a Windows PseudoConsole handle. Create a new instance by calling `Create()`.
    22  type Pty struct {
    23  	// handleLock guards hpc
    24  	handleLock sync.RWMutex
    25  	// hpc is the pseudo console handle
    26  	hpc windows.Handle
    27  	// inPipe and outPipe are our end of the pipes to read/write to the pseudo console.
    28  	inPipe  *os.File
    29  	outPipe *os.File
    30  }
    31  
    32  // Create returns a new `Pty` object. This object is not ready for IO until `UpdateProcThreadAttribute` is called and a process has been started.
    33  func Create(width, height int16, flags uint32) (*Pty, error) {
    34  	// 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
    35  	// make use of this data.
    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  	// The pty's end of its pipes can be closed here without worry. They're duped into the conhost
    54  	// that will be launched and will be released on a call to ClosePseudoConsole() (Close() on the Pty object).
    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  // UpdateProcThreadAttribute updates the passed in attribute list to contain the entry necessary for use with
    70  // CreateProcess.
    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  // Resize resizes the internal buffers of the pseudo console to the passed in size
    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  // Close closes the pseudo-terminal and cleans up all attached resources
   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  	// Close the pseudo console, set the handle to 0 to invalidate this object and then close the side of the pipes that we own.
   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  // OutPipe returns the output pipe of the pseudo console.
   128  func (c *Pty) OutPipe() *os.File {
   129  	return c.outPipe
   130  }
   131  
   132  // InPipe returns the input pipe of the pseudo console.
   133  func (c *Pty) InPipe() *os.File {
   134  	return c.inPipe
   135  }
   136  
   137  // Write writes the contents of `buf` to the pseudo console. Returns the number of bytes written and an error if there is one.
   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  // Read reads from the pseudo console into `buf`. Returns the number of bytes read and an error if there is one.
   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