summaryrefslogtreecommitdiff
path: root/vendor/github.com/Microsoft/go-winio/reparse.go
blob: fc1ee4d3a3e919135ca95d30c745bb77e3a53960 (plain)
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
package winio

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"strings"
	"unicode/utf16"
	"unsafe"
)

const (
	reparseTagMountPoint = 0xA0000003
	reparseTagSymlink    = 0xA000000C
)

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.
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()
}