mirror of
https://github.com/rwinkhart/go-winio.git
synced 2026-08-28 04:46:50 -04:00
Add support for LX symlinks (WSL/MSYS2 native symlinks)
- Add reparseTagLxSymlink constant (0xA000001D) - Implement decode logic for LX symlinks (UTF-8 format) - Add IsLxSymlink field to ReparsePoint struct to preserve symlink type - Implement encode logic to recreate LX symlinks on import - Add unit tests for LX symlink round-trip validation Fixes issue where Docker builds with MSYS2 failed with 'unsupported reparse point a000001d' error. Signed-off-by: Varun Gokulnath <vagokuln@microsoft.com> Signed-off-by: Varun Gokulnath <gvarun22@outlook.com>
This commit is contained in:
committed by
Varun Gokulnath
parent
3c9576c934
commit
94113a48c2
+34
-3
@@ -15,6 +15,7 @@ import (
|
||||
const (
|
||||
reparseTagMountPoint = 0xA0000003
|
||||
reparseTagSymlink = 0xA000000C
|
||||
reparseTagLxSymlink = 0xA000001D // WSL/MSYS2 native symlinks
|
||||
)
|
||||
|
||||
type reparseDataBuffer struct {
|
||||
@@ -31,6 +32,7 @@ type reparseDataBuffer struct {
|
||||
type ReparsePoint struct {
|
||||
Target string
|
||||
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
|
||||
@@ -56,6 +58,20 @@ func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
|
||||
case reparseTagMountPoint:
|
||||
isMountPoint = true
|
||||
case reparseTagSymlink:
|
||||
case reparseTagLxSymlink:
|
||||
// LX symlinks store the target as UTF-8 after a 4-byte version field
|
||||
if len(b) < 4 {
|
||||
return nil, fmt.Errorf("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
|
||||
default:
|
||||
return nil, &UnsupportedReparsePointError{tag}
|
||||
}
|
||||
@@ -69,16 +85,31 @@ func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
|
||||
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint, false}, nil
|
||||
}
|
||||
|
||||
func isDriveLetter(c byte) bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||
}
|
||||
|
||||
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
|
||||
// mount point.
|
||||
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink,
|
||||
// mount point, or LX symlink.
|
||||
func EncodeReparsePoint(rp *ReparsePoint) []byte {
|
||||
if rp.IsLxSymlink {
|
||||
// LX symlink: 4-byte version + UTF-8 target
|
||||
version := uint32(2)
|
||||
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, version)
|
||||
_, _ = b.Write(targetBytes)
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// Generate an NT path and determine if this is a relative path.
|
||||
var ntTarget string
|
||||
relative := false
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLxSymlinkRoundTrip(t *testing.T) {
|
||||
// Test LX symlink encode/decode
|
||||
original := &ReparsePoint{
|
||||
Target: "/usr/bin/bash",
|
||||
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: `C:\Windows\System32`,
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user