-
Notifications
You must be signed in to change notification settings - Fork 257
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This change adds a conpty package that houses go friendly wrappers around the Pseudo Console API in Windows. This will be used to support tty scenarios for Host Process containers. There's not many tests I can add here as you need to hook this up to a running process, where that work is coming. Signed-off-by: Daniel Canter <dcanter@microsoft.com>
- Loading branch information
Showing
9 changed files
with
535 additions
and
24 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 |
---|---|---|
@@ -0,0 +1,153 @@ | ||
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") | ||
) | ||
|
||
// CPty is a wrapper around a Windows PseudoConsole handle. Create a new instance by calling `New()`. | ||
type CPty 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 | ||
} | ||
|
||
// New returns a new `CPty` object. This object is not ready for IO until `UpdateProcThreadAttribute` is called and a process has been started. | ||
func New(columns, rows int16, flags uint32) (*CPty, 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: columns, Y: rows} | ||
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 CPty 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 &CPty{ | ||
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 *CPty) UpdateProcThreadAttribute(attributeList *winapi.ProcThreadAttributeList) error { | ||
c.handleLock.RLock() | ||
defer c.handleLock.RUnlock() | ||
|
||
if c.hpc == 0 { | ||
return errClosedConPty | ||
} | ||
|
||
err := winapi.UpdateProcThreadAttribute( | ||
attributeList, | ||
0, | ||
winapi.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, | ||
unsafe.Pointer(c.hpc), | ||
unsafe.Sizeof(c.hpc), | ||
nil, | ||
nil, | ||
) | ||
if 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 *CPty) Resize(columns, rows int16) error { | ||
c.handleLock.RLock() | ||
defer c.handleLock.RUnlock() | ||
|
||
if c.hpc == 0 { | ||
return errClosedConPty | ||
} | ||
|
||
coord := windows.Coord{X: columns, Y: rows} | ||
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 *CPty) 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 *CPty) OutPipe() *os.File { | ||
return c.outPipe | ||
} | ||
|
||
// InPipe returns the input pipe of the pseudo console. | ||
func (c *CPty) 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 *CPty) 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 *CPty) Read(buf []byte) (int, error) { | ||
if c.outPipe == nil { | ||
return 0, errNotInitialized | ||
} | ||
return c.outPipe.Read(buf) | ||
} |
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,42 @@ | ||
package winapi | ||
|
||
import ( | ||
"unsafe" | ||
|
||
"golang.org/x/sys/windows" | ||
) | ||
|
||
const PSEUDOCONSOLE_INHERIT_CURSOR = 0x1 | ||
|
||
// CreatePseudoConsole creates a windows pseudo console. | ||
func CreatePseudoConsole(size windows.Coord, hInput windows.Handle, hOutput windows.Handle, dwFlags uint32, hpcon *windows.Handle) error { | ||
// We need this wrapper as the function takes a COORD struct and not a pointer to one, so we need to cast to something beforehand. | ||
return createPseudoConsole(*((*uint32)(unsafe.Pointer(&size))), hInput, hOutput, 0, hpcon) | ||
} | ||
|
||
func ResizePseudoConsole(hpcon windows.Handle, size windows.Coord) error { | ||
return resizePseudoConsole(hpcon, *((*uint32)(unsafe.Pointer(&size)))) | ||
} | ||
|
||
// HRESULT WINAPI CreatePseudoConsole( | ||
// _In_ COORD size, | ||
// _In_ HANDLE hInput, | ||
// _In_ HANDLE hOutput, | ||
// _In_ DWORD dwFlags, | ||
// _Out_ HPCON* phPC | ||
// ); | ||
// | ||
//sys createPseudoConsole(size uint32, hInput windows.Handle, hOutput windows.Handle, dwFlags uint32, hpcon *windows.Handle) (hr error) = kernel32.CreatePseudoConsole | ||
|
||
// void WINAPI ClosePseudoConsole( | ||
// _In_ HPCON hPC | ||
// ); | ||
// | ||
//sys ClosePseudoConsole(hpc windows.Handle) = kernel32.ClosePseudoConsole | ||
|
||
// HRESULT WINAPI ResizePseudoConsole( | ||
// _In_ HPCON hPC , | ||
// _In_ COORD size | ||
// ); | ||
// | ||
//sys resizePseudoConsole(hPc windows.Handle, size uint32) (hr error) = kernel32.ResizePseudoConsole |
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,10 +1,80 @@ | ||
package winapi | ||
|
||
import ( | ||
"errors" | ||
"unsafe" | ||
|
||
"golang.org/x/sys/windows" | ||
) | ||
|
||
const PROCESS_ALL_ACCESS uint32 = 2097151 | ||
|
||
// DWORD GetProcessImageFileNameW( | ||
// HANDLE hProcess, | ||
// LPWSTR lpImageFileName, | ||
// DWORD nSize | ||
const ( | ||
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x20016 | ||
PROC_THREAD_ATTRIBUTE_JOB_LIST = 0x2000D | ||
) | ||
|
||
type ProcThreadAttributeList struct { | ||
_ [1]byte | ||
} | ||
|
||
// typedef struct _STARTUPINFOEXW { | ||
// STARTUPINFOW StartupInfo; | ||
// LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; | ||
// } STARTUPINFOEXW, *LPSTARTUPINFOEXW; | ||
type StartupInfoEx struct { | ||
// This is a recreation of the same binding from the stdlib. The x/sys/windows variant for whatever reason | ||
// doesn't work when updating the list for the pseudo console attribute. It has the process immediately exit | ||
// with exit code 0xc0000142 shortly after start. | ||
windows.StartupInfo | ||
ProcThreadAttributeList *ProcThreadAttributeList | ||
} | ||
|
||
// NewProcThreadAttributeList allocates a new ProcThreadAttributeList, with | ||
// the requested maximum number of attributes. This must be cleaned up by calling | ||
// DeleteProcThreadAttributeList. | ||
func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeList, error) { | ||
var size uintptr | ||
err := InitializeProcThreadAttributeList(nil, maxAttrCount, 0, &size) | ||
if err != windows.ERROR_INSUFFICIENT_BUFFER { | ||
if err == nil { | ||
return nil, errors.New("unable to query buffer size from InitializeProcThreadAttributeList") | ||
} | ||
return nil, err | ||
} | ||
al := (*ProcThreadAttributeList)(unsafe.Pointer(&make([]byte, size)[0])) | ||
err = InitializeProcThreadAttributeList(al, maxAttrCount, 0, &size) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return al, nil | ||
} | ||
|
||
// BOOL InitializeProcThreadAttributeList( | ||
// [out, optional] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, | ||
// [in] DWORD dwAttributeCount, | ||
// DWORD dwFlags, | ||
// [in, out] PSIZE_T lpSize | ||
// ); | ||
//sys GetProcessImageFileName(hProcess windows.Handle, imageFileName *uint16, nSize uint32) (size uint32, err error) = kernel32.GetProcessImageFileNameW | ||
// | ||
//sys InitializeProcThreadAttributeList(lpAttributeList *ProcThreadAttributeList, dwAttributeCount uint32, dwFlags uint32, lpSize *uintptr) (err error) = kernel32.InitializeProcThreadAttributeList | ||
|
||
// void DeleteProcThreadAttributeList( | ||
// [in, out] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList | ||
// ); | ||
// | ||
//sys DeleteProcThreadAttributeList(lpAttributeList *ProcThreadAttributeList) = kernel32.DeleteProcThreadAttributeList | ||
|
||
// BOOL UpdateProcThreadAttribute( | ||
// [in, out] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, | ||
// [in] DWORD dwFlags, | ||
// [in] DWORD_PTR Attribute, | ||
// [in] PVOID lpValue, | ||
// [in] SIZE_T cbSize, | ||
// [out, optional] PVOID lpPreviousValue, | ||
// [in, optional] PSIZE_T lpReturnSize | ||
// ); | ||
// | ||
//sys UpdateProcThreadAttribute(lpAttributeList *ProcThreadAttributeList, dwFlags uint32, attribute uintptr, lpValue unsafe.Pointer, cbSize uintptr, lpPreviousValue unsafe.Pointer, lpReturnSize *uintptr) (err error) = kernel32.UpdateProcThreadAttribute | ||
|
||
//sys CreateProcessAsUser(token windows.Token, appName *uint16, commandLine *uint16, procSecurity *windows.SecurityAttributes, threadSecurity *windows.SecurityAttributes, inheritHandles bool, creationFlags uint32, env *uint16, currentDir *uint16, startupInfo *windows.StartupInfo, outProcInfo *windows.ProcessInformation) (err error) = advapi32.CreateProcessAsUserW |
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
Oops, something went wrong.