mirror of
https://github.com/rwinkhart/go-winio.git
synced 2026-09-06 00:57:19 -04:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55a7dee738 | ||
|
|
46e8191923 |
@@ -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)
|
||||||
|
}
|
||||||
@@ -1,89 +1,7 @@
|
|||||||
# go-winio [](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
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user