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