2 Commits
Author SHA1 Message Date
RandyTheSilly 55a7dee738 Update pipe-handles-helper.patch 2025-11-09 15:21:39 -05:00
RandyTheSilly 46e8191923 Add patch for getting pipe handles 2025-11-09 15:08:28 -05:00
5 changed files with 33 additions and 306 deletions
+23
View File
@@ -0,0 +1,23 @@
diff --git a/pipe.go b/pipe.go
index 58c68e9..01cabd7 100644
--- a/pipe.go
+++ b/pipe.go
@@ -123,6 +123,18 @@ type win32MessageBytePipe struct {
type pipeAddress string
+// GetPipeHandle returns the underlying handle of a pipe.
+func GetPipeHandle(f net.Conn) windows.Handle {
+ switch v := f.(type) {
+ case *win32MessageBytePipe:
+ return v.handle
+ case *win32Pipe:
+ return v.handle
+ default:
+ return 0
+ }
+}
+
func (f *win32Pipe) LocalAddr() net.Addr {
return pipeAddress(f.path)
}
+5 -87
View File
@@ -1,89 +1,7 @@
# go-winio [![Build Status](https://github.com/microsoft/go-winio/actions/workflows/ci.yml/badge.svg)](https://github.com/microsoft/go-winio/actions/workflows/ci.yml) # go-winio - rwinkhart patches
This repository holds my custom patches atop the go-winio module.
This repository contains utilities for efficiently performing Win32 IO operations in The only changes in the master branch are this README and the patches in the `1patches` directory. The patches are applied on other branches and tagged releases are available for import from the releases page.
Go. Currently, this is focused on accessing named pipes and other file handles, and
for using named pipes as a net transport.
This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go # How To?
to reuse the thread to schedule another goroutine. This limits support to Windows Vista and Copy the desired patch file(s) into a new branch (cloned from upstream master) and apply with `patch -p1 < patchfile.patch`.
newer operating systems. This is similar to the implementation of network sockets in Go's net
package.
Please see the LICENSE file for licensing information.
## Contributing
This project welcomes contributions and suggestions.
Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that
you have the right to, and actually do, grant us the rights to use your contribution.
For details, visit [Microsoft CLA](https://cla.microsoft.com).
When you submit a pull request, a CLA-bot will automatically determine whether you need to
provide a CLA and decorate the PR appropriately (e.g., label, comment).
Simply follow the instructions provided by the bot.
You will only need to do this once across all repos using our CLA.
Additionally, the pull request pipeline requires the following steps to be performed before
mergining.
### Code Sign-Off
We require that contributors sign their commits using [`git commit --signoff`][git-commit-s]
to certify they either authored the work themselves or otherwise have permission to use it in this project.
A range of commits can be signed off using [`git rebase --signoff`][git-rebase-s].
Please see [the developer certificate](https://developercertificate.org) for more info,
as well as to make sure that you can attest to the rules listed.
Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off.
### Linting
Code must pass a linting stage, which uses [`golangci-lint`][lint].
The linting settings are stored in [`.golangci.yaml`](./.golangci.yaml), and can be run
automatically with VSCode by adding the following to your workspace or folder settings:
```json
"go.lintTool": "golangci-lint",
"go.lintOnSave": "package",
```
Additional editor [integrations options are also available][lint-ide].
Alternatively, `golangci-lint` can be [installed locally][lint-install] and run from the repo root:
```shell
# use . or specify a path to only lint a package
# to show all lint errors, use flags "--max-issues-per-linter=0 --max-same-issues=0"
> golangci-lint run ./...
```
### Go Generate
The pipeline checks that auto-generated code, via `go generate`, are up to date.
This can be done for the entire repo:
```shell
> go generate ./...
```
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Special Thanks
Thanks to [natefinch][natefinch] for the inspiration for this library.
See [npipe](https://github.com/natefinch/npipe) for another named pipe implementation.
[lint]: https://golangci-lint.run/
[lint-ide]: https://golangci-lint.run/usage/integrations/#editor-integration
[lint-install]: https://golangci-lint.run/usage/install/#local-installation
[git-commit-s]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s
[git-rebase-s]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---signoff
[natefinch]: https://github.com/natefinch
-12
View File
@@ -123,18 +123,6 @@ type win32MessageBytePipe struct {
type pipeAddress string type pipeAddress string
// GetPipeHandle returns the underlying handle of a pipe.
func GetPipeHandle(f net.Conn) windows.Handle {
switch v := f.(type) {
case *win32MessageBytePipe:
return v.handle
case *win32Pipe:
return v.handle
default:
return 0
}
}
func (f *win32Pipe) LocalAddr() net.Addr { func (f *win32Pipe) LocalAddr() net.Addr {
return pipeAddress(f.path) return pipeAddress(f.path)
} }
+5 -55
View File
@@ -5,7 +5,6 @@ package winio
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"strings" "strings"
"unicode/utf16" "unicode/utf16"
@@ -15,9 +14,6 @@ import (
const ( const (
reparseTagMountPoint = 0xA0000003 reparseTagMountPoint = 0xA0000003
reparseTagSymlink = 0xA000000C reparseTagSymlink = 0xA000000C
reparseTagLxSymlink = 0xA000001D // WSL/MSYS2 native symlinks
lxSymlinkVersion = 2 // LX symlink format version
) )
type reparseDataBuffer struct { type reparseDataBuffer struct {
@@ -34,7 +30,6 @@ type reparseDataBuffer struct {
type ReparsePoint struct { type ReparsePoint struct {
Target string Target string
IsMountPoint bool IsMountPoint bool
IsLxSymlink bool // True if this is an LX symlink (WSL/MSYS2 native)
} }
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or // UnsupportedReparsePointError is returned when trying to decode a non-symlink or
@@ -55,19 +50,14 @@ func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
} }
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) { func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
isMountPoint := false
switch tag { switch tag {
case reparseTagMountPoint: case reparseTagMountPoint:
return decodeWindowsReparsePointData(b, true) isMountPoint = true
case reparseTagSymlink: case reparseTagSymlink:
return decodeWindowsReparsePointData(b, false)
case reparseTagLxSymlink:
return decodeLxReparsePointData(b)
default: default:
return nil, &UnsupportedReparsePointError{tag} return nil, &UnsupportedReparsePointError{tag}
} }
}
func decodeWindowsReparsePointData(b []byte, isMountPoint bool) (*ReparsePoint, error) {
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6]) nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
if !isMountPoint { if !isMountPoint {
nameOffset += 4 nameOffset += 4
@@ -78,56 +68,16 @@ func decodeWindowsReparsePointData(b []byte, isMountPoint bool) (*ReparsePoint,
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &ReparsePoint{Target: string(utf16.Decode(name)), IsMountPoint: isMountPoint, IsLxSymlink: false}, nil return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
}
func decodeLxReparsePointData(b []byte) (*ReparsePoint, error) {
// LX symlinks store the target as UTF-8 after a 4-byte version field
if len(b) < 4 {
return nil, errors.New("LX symlink buffer too short")
}
targetBytes := b[4:]
for i, c := range targetBytes {
if c == 0 {
targetBytes = targetBytes[:i]
break
}
}
target := string(targetBytes)
return &ReparsePoint{Target: target, IsMountPoint: false, IsLxSymlink: true}, nil
} }
func isDriveLetter(c byte) bool { func isDriveLetter(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
} }
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink, // EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
// mount point, or LX symlink. // mount point.
func EncodeReparsePoint(rp *ReparsePoint) []byte { func EncodeReparsePoint(rp *ReparsePoint) []byte {
if rp == nil {
return nil
}
if rp.IsLxSymlink {
return encodeLxReparsePoint(rp)
}
return encodeWindowsReparsePoint(rp)
}
func encodeLxReparsePoint(rp *ReparsePoint) []byte {
// LX symlink: 4-byte version + UTF-8 target
targetBytes := []byte(rp.Target)
dataLength := 4 + len(targetBytes)
var b bytes.Buffer
_ = binary.Write(&b, binary.LittleEndian, uint32(reparseTagLxSymlink))
_ = binary.Write(&b, binary.LittleEndian, uint16(dataLength))
_ = binary.Write(&b, binary.LittleEndian, uint16(0))
_ = binary.Write(&b, binary.LittleEndian, uint32(lxSymlinkVersion))
_, _ = b.Write(targetBytes)
return b.Bytes()
}
func encodeWindowsReparsePoint(rp *ReparsePoint) []byte {
// Generate an NT path and determine if this is a relative path. // Generate an NT path and determine if this is a relative path.
var ntTarget string var ntTarget string
relative := false relative := false
-152
View File
@@ -1,152 +0,0 @@
//go:build windows
package winio
import (
"testing"
)
const (
testLxSymlinkAbsolutePath = "/usr/bin/bash"
testWindowsSymlinkPath = `C:\Windows\System32`
testLxSymlinkRelativePath = "../bin/sh"
testLxSymlinkSpecialCharsPath = "/path/with spaces/and-special!@#$%/файл.txt"
)
func TestLxSymlinkRoundTrip(t *testing.T) {
// Test LX symlink encode/decode
original := &ReparsePoint{
Target: testLxSymlinkAbsolutePath,
IsMountPoint: false,
IsLxSymlink: true,
}
// Encode
encoded := EncodeReparsePoint(original)
// Decode
decoded, err := DecodeReparsePoint(encoded)
if err != nil {
t.Fatalf("Failed to decode: %v", err)
}
// Verify
if decoded.Target != original.Target {
t.Errorf("Target mismatch: got %q, want %q", decoded.Target, original.Target)
}
if decoded.IsLxSymlink != original.IsLxSymlink {
t.Errorf("IsLxSymlink mismatch: got %v, want %v", decoded.IsLxSymlink, original.IsLxSymlink)
}
if decoded.IsMountPoint != original.IsMountPoint {
t.Errorf("IsMountPoint mismatch: got %v, want %v", decoded.IsMountPoint, original.IsMountPoint)
}
}
func TestWindowsSymlinkNotLx(t *testing.T) {
// Test that regular Windows symlinks are not marked as LX
original := &ReparsePoint{
Target: testWindowsSymlinkPath,
IsMountPoint: false,
IsLxSymlink: false,
}
// Encode
encoded := EncodeReparsePoint(original)
// Decode
decoded, err := DecodeReparsePoint(encoded)
if err != nil {
t.Fatalf("Failed to decode: %v", err)
}
// Verify it's NOT an LX symlink
if decoded.IsLxSymlink {
t.Errorf("Windows symlink incorrectly marked as LX symlink")
}
}
func TestLxSymlinkEmptyTarget(t *testing.T) {
// Test LX symlink with empty target
original := &ReparsePoint{
Target: "",
IsMountPoint: false,
IsLxSymlink: true,
}
// Encode
encoded := EncodeReparsePoint(original)
// Decode
decoded, err := DecodeReparsePoint(encoded)
if err != nil {
t.Fatalf("Failed to decode: %v", err)
}
// Verify
if decoded.Target != original.Target {
t.Errorf("Target mismatch: got %q, want %q", decoded.Target, original.Target)
}
if !decoded.IsLxSymlink {
t.Errorf("IsLxSymlink should be true")
}
}
func TestLxSymlinkRelativePath(t *testing.T) {
// Test LX symlink with relative path
original := &ReparsePoint{
Target: testLxSymlinkRelativePath,
IsMountPoint: false,
IsLxSymlink: true,
}
// Encode
encoded := EncodeReparsePoint(original)
// Decode
decoded, err := DecodeReparsePoint(encoded)
if err != nil {
t.Fatalf("Failed to decode: %v", err)
}
// Verify
if decoded.Target != original.Target {
t.Errorf("Target mismatch: got %q, want %q", decoded.Target, original.Target)
}
if !decoded.IsLxSymlink {
t.Errorf("IsLxSymlink should be true")
}
}
func TestLxSymlinkSpecialCharacters(t *testing.T) {
// Test LX symlink with special characters and Unicode
original := &ReparsePoint{
Target: testLxSymlinkSpecialCharsPath,
IsMountPoint: false,
IsLxSymlink: true,
}
// Encode
encoded := EncodeReparsePoint(original)
// Decode
decoded, err := DecodeReparsePoint(encoded)
if err != nil {
t.Fatalf("Failed to decode: %v", err)
}
// Verify
if decoded.Target != original.Target {
t.Errorf("Target mismatch: got %q, want %q", decoded.Target, original.Target)
}
if !decoded.IsLxSymlink {
t.Errorf("IsLxSymlink should be true")
}
}
func TestEncodeReparsePointNil(t *testing.T) {
// Test encoding a nil ReparsePoint
encoded := EncodeReparsePoint(nil)
if encoded != nil {
t.Errorf("Expected nil result for nil input, got %v", encoded)
}
}