mirror of
https://github.com/rwinkhart/go-winio.git
synced 2026-09-06 09:07:18 -04:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99e90c1cba | ||
|
|
75610162e7 | ||
|
|
164281c34b | ||
|
|
a83edf96d5 | ||
|
|
4efa80daf1 | ||
|
|
3ffe560692 | ||
|
|
94113a48c2 |
+55
-5
@@ -5,6 +5,7 @@ package winio
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
@@ -14,6 +15,9 @@ 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 {
|
||||||
@@ -30,6 +34,7 @@ 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
|
||||||
@@ -50,14 +55,19 @@ 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:
|
||||||
isMountPoint = true
|
return decodeWindowsReparsePointData(b, 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
|
||||||
@@ -68,16 +78,56 @@ func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
|
return &ReparsePoint{Target: string(utf16.Decode(name)), IsMountPoint: isMountPoint, IsLxSymlink: false}, 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 or
|
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink,
|
||||||
// mount point.
|
// mount point, or LX symlink.
|
||||||
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
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
//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