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