1package 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} 129