main
1//go:build windows
2// +build windows
3
4package winio
5
6import (
7 "bytes"
8 "encoding/binary"
9 "fmt"
10 "strings"
11 "unicode/utf16"
12 "unsafe"
13)
14
15const (
16 reparseTagMountPoint = 0xA0000003
17 reparseTagSymlink = 0xA000000C
18)
19
20type reparseDataBuffer struct {
21 ReparseTag uint32
22 ReparseDataLength uint16
23 Reserved uint16
24 SubstituteNameOffset uint16
25 SubstituteNameLength uint16
26 PrintNameOffset uint16
27 PrintNameLength uint16
28}
29
30// ReparsePoint describes a Win32 symlink or mount point.
31type ReparsePoint struct {
32 Target string
33 IsMountPoint bool
34}
35
36// UnsupportedReparsePointError is returned when trying to decode a non-symlink or
37// mount point reparse point.
38type UnsupportedReparsePointError struct {
39 Tag uint32
40}
41
42func (e *UnsupportedReparsePointError) Error() string {
43 return fmt.Sprintf("unsupported reparse point %x", e.Tag)
44}
45
46// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
47// or a mount point.
48func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
49 tag := binary.LittleEndian.Uint32(b[0:4])
50 return DecodeReparsePointData(tag, b[8:])
51}
52
53func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
54 isMountPoint := false
55 switch tag {
56 case reparseTagMountPoint:
57 isMountPoint = true
58 case reparseTagSymlink:
59 default:
60 return nil, &UnsupportedReparsePointError{tag}
61 }
62 nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
63 if !isMountPoint {
64 nameOffset += 4
65 }
66 nameLength := binary.LittleEndian.Uint16(b[6:8])
67 name := make([]uint16, nameLength/2)
68 err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
69 if err != nil {
70 return nil, err
71 }
72 return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
73}
74
75func isDriveLetter(c byte) bool {
76 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
77}
78
79// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
80// mount point.
81func EncodeReparsePoint(rp *ReparsePoint) []byte {
82 // Generate an NT path and determine if this is a relative path.
83 var ntTarget string
84 relative := false
85 if strings.HasPrefix(rp.Target, `\\?\`) {
86 ntTarget = `\??\` + rp.Target[4:]
87 } else if strings.HasPrefix(rp.Target, `\\`) {
88 ntTarget = `\??\UNC\` + rp.Target[2:]
89 } else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
90 ntTarget = `\??\` + rp.Target
91 } else {
92 ntTarget = rp.Target
93 relative = true
94 }
95
96 // The paths must be NUL-terminated even though they are counted strings.
97 target16 := utf16.Encode([]rune(rp.Target + "\x00"))
98 ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
99
100 size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
101 size += len(ntTarget16)*2 + len(target16)*2
102
103 tag := uint32(reparseTagMountPoint)
104 if !rp.IsMountPoint {
105 tag = reparseTagSymlink
106 size += 4 // Add room for symlink flags
107 }
108
109 data := reparseDataBuffer{
110 ReparseTag: tag,
111 ReparseDataLength: uint16(size),
112 SubstituteNameOffset: 0,
113 SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
114 PrintNameOffset: uint16(len(ntTarget16) * 2),
115 PrintNameLength: uint16((len(target16) - 1) * 2),
116 }
117
118 var b bytes.Buffer
119 _ = binary.Write(&b, binary.LittleEndian, &data)
120 if !rp.IsMountPoint {
121 flags := uint32(0)
122 if relative {
123 flags |= 1
124 }
125 _ = binary.Write(&b, binary.LittleEndian, flags)
126 }
127
128 _ = binary.Write(&b, binary.LittleEndian, ntTarget16)
129 _ = binary.Write(&b, binary.LittleEndian, target16)
130 return b.Bytes()
131}