main
Raw Download raw file
  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}