-
Notifications
You must be signed in to change notification settings - Fork 178
/
reparse.go
208 lines (182 loc) · 6.76 KB
/
reparse.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
//go:build windows
// +build windows
package winio
import (
"bytes"
"encoding/binary"
"fmt"
"strings"
"unicode/utf16"
"unsafe"
"golang.org/x/sys/windows"
)
const (
reparseTagMountPoint = 0xA0000003
reparseTagSymlink = 0xA000000C
_FSCTL_SET_REPARSE_POINT uint32 = ((0x9 << 16) | (41 << 2))
_FSCTL_GET_REPARSE_POINT uint32 = ((0x9 << 16) | (42 << 2))
_MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024
)
// reparseDataBuffer is only kept for compatibility purposes. When adding new
// code prefer ReparseDataBuffer instead.
type reparseDataBuffer struct {
ReparseTag uint32
ReparseDataLength uint16
Reserved uint16
SubstituteNameOffset uint16
SubstituteNameLength uint16
PrintNameOffset uint16
PrintNameLength uint16
}
// ReparsePoint describes a Win32 symlink or mount point.
// However, there can be other types of reparse points that are specific to
// third party applications. To work with such reparse points use
// ReparseDataBuffer instead.
type ReparsePoint struct {
Target string
IsMountPoint bool
}
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or
// mount point reparse point.
type UnsupportedReparsePointError struct {
Tag uint32
}
func (e *UnsupportedReparsePointError) Error() string {
return fmt.Sprintf("unsupported reparse point %x", e.Tag)
}
// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
// or a mount point.
func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
tag := binary.LittleEndian.Uint32(b[0:4])
return DecodeReparsePointData(tag, b[8:])
}
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
isMountPoint := false
switch tag {
case reparseTagMountPoint:
isMountPoint = true
case reparseTagSymlink:
default:
return nil, &UnsupportedReparsePointError{tag}
}
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
if !isMountPoint {
nameOffset += 4
}
nameLength := binary.LittleEndian.Uint16(b[6:8])
name := make([]uint16, nameLength/2)
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
if err != nil {
return nil, err
}
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, 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.
func EncodeReparsePoint(rp *ReparsePoint) []byte {
// Generate an NT path and determine if this is a relative path.
var ntTarget string
relative := false
if strings.HasPrefix(rp.Target, `\\?\`) {
ntTarget = `\??\` + rp.Target[4:]
} else if strings.HasPrefix(rp.Target, `\\`) {
ntTarget = `\??\UNC\` + rp.Target[2:]
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
ntTarget = `\??\` + rp.Target
} else {
ntTarget = rp.Target
relative = true
}
// The paths must be NUL-terminated even though they are counted strings.
target16 := utf16.Encode([]rune(rp.Target + "\x00"))
ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
size += len(ntTarget16)*2 + len(target16)*2
tag := uint32(reparseTagMountPoint)
if !rp.IsMountPoint {
tag = reparseTagSymlink
size += 4 // Add room for symlink flags
}
data := reparseDataBuffer{
ReparseTag: tag,
ReparseDataLength: uint16(size),
SubstituteNameOffset: 0,
SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
PrintNameOffset: uint16(len(ntTarget16) * 2),
PrintNameLength: uint16((len(target16) - 1) * 2),
}
var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, &data)
if !rp.IsMountPoint {
flags := uint32(0)
if relative {
flags |= 1
}
binary.Write(&b, binary.LittleEndian, flags)
}
binary.Write(&b, binary.LittleEndian, ntTarget16)
binary.Write(&b, binary.LittleEndian, target16)
return b.Bytes()
}
// ReparseDataBuffer corresponds to REPARSE_DATA_BUFFER struct defined here:
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer.
// ReparesPoint struct (and corresponding Encode/Decode functions) provided above only
// handle symlink or mountpoint type of reparse points. To work with other reparse points
// use this struct. When calling SetReparsePoint, if it is a symlink or mountpoint
// reparse point pass in the buffer generated by EncodeReparsePointData. If it is some
// other kind of reparse point pass in the buffer generated by
// ReparseDataBuffer.Encode. (same goes for GetReparsePoints)
type ReparseDataBuffer struct {
ReparseTag uint32
ReparseDataLength uint16
Reserved uint16
// a generic data buffer
DataBuffer []byte
}
// Encode encodes the current ReparseDataBuffer struct that can be passed
// to the SetReparsePoint.
func (r *ReparseDataBuffer) Encode() []byte {
var buf bytes.Buffer
binary.Write(&buf, binary.LittleEndian, r.ReparseTag)
binary.Write(&buf, binary.LittleEndian, r.ReparseDataLength)
binary.Write(&buf, binary.LittleEndian, r.Reserved)
buf.Write(r.DataBuffer)
return buf.Bytes()
}
// Decode decodes the byte array (returned by GetReparsePoint) into
// the current ReparseDataBuffer struct.
func (r *ReparseDataBuffer) Decode(data []byte) error {
rdr := bytes.NewReader(data)
err := binary.Read(rdr, binary.LittleEndian, r)
if err != nil {
fmt.Errorf("failed to decode reparse data buffer: %w", err)
}
return nil
}
// GetReparsePoint returns a byte array that represents a ReparseDataBuffer if
// the file at `path` is a valid reparse point.
func GetReparsePoint(path string) ([]byte, error) {
utf16DestPath := windows.StringToUTF16(path)
// We need to open the file with windows specific permissions so just using
// os.Open won't work.
fileHandle, err := windows.CreateFile(&utf16DestPath[0], windows.GENERIC_READ, 0, nil, windows.OPEN_EXISTING, (windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OPEN_REPARSE_POINT | windows.FILE_FLAG_BACKUP_SEMANTICS), 0)
if err != nil {
return nil, fmt.Errorf("open file failed with: %w", err)
}
defer windows.Close(fileHandle)
outBuf := make([]byte, _MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
var outBufLen uint32
err = windows.DeviceIoControl(fileHandle, _FSCTL_GET_REPARSE_POINT, nil, 0, &outBuf[0], _MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &outBufLen, nil)
if err != nil {
return nil, fmt.Errorf("failed to get reparse point for file %s: %w", path, err)
}
return outBuf[:outBufLen], nil
}
// SetReparsePoint sets a reparse point with `data` on the file/directory represented by
// `handle` . `data` should be an encoded ReparseDataBuffer struct.
func SetReparsePoint(handle windows.Handle, data []byte) error {
return windows.DeviceIoControl(handle, _FSCTL_SET_REPARSE_POINT, &data[0], uint32(len(data)), nil, 0, nil, nil)
}