-
Notifications
You must be signed in to change notification settings - Fork 293
Add conpty (pseudo console) package #1228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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") | ||
| ) | ||
|
|
||
| // ConPTY is a wrapper around a Windows PseudoConsole handle. Create a new instance by calling `New()`. | ||
| type ConPTY 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 `ConPTY` object. This object is not ready for IO until `UpdateProcThreadAttribute` is called and a process has been started. | ||
| func New(width, height int16, flags uint32) (*ConPTY, 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 ConPTY 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 &ConPTY{ | ||
| 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 *ConPTY) 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 *ConPTY) 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 *ConPTY) 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 *ConPTY) OutPipe() *os.File { | ||
| return c.outPipe | ||
| } | ||
|
|
||
| // InPipe returns the input pipe of the pseudo console. | ||
| func (c *ConPTY) 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 *ConPTY) 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 *ConPTY) Read(buf []byte) (int, error) { | ||
| if c.outPipe == nil { | ||
| return 0, errNotInitialized | ||
| } | ||
| return c.outPipe.Read(buf) | ||
| } |
This file contains hidden or 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,44 @@ | ||
| 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) | ||
| } | ||
|
|
||
| // ResizePseudoConsole resizes the internal buffers of the pseudo console to the width and height specified in `size`. | ||
| func ResizePseudoConsole(hpcon windows.Handle, size windows.Coord) 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 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 hidden or 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,83 @@ | ||
| 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. | ||
| // | ||
| // TODO (dcantah): Swap to the x/sys/windows definitions after https://go-review.googlesource.com/c/sys/+/371276/1/windows/exec_windows.go#153 | ||
| // gets in. | ||
| 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) | ||
|
dcantah marked this conversation as resolved.
|
||
| 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 hidden or 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.