David K. Bainbridge | 215e024 | 2017-09-05 23:18:24 -0700 | [diff] [blame] | 1 | package winio |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "encoding/binary" |
| 6 | "fmt" |
| 7 | "strings" |
| 8 | "unicode/utf16" |
| 9 | "unsafe" |
| 10 | ) |
| 11 | |
| 12 | const ( |
| 13 | reparseTagMountPoint = 0xA0000003 |
| 14 | reparseTagSymlink = 0xA000000C |
| 15 | ) |
| 16 | |
| 17 | type 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. |
| 28 | type 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. |
| 35 | type UnsupportedReparsePointError struct { |
| 36 | Tag uint32 |
| 37 | } |
| 38 | |
| 39 | func (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. |
| 45 | func DecodeReparsePoint(b []byte) (*ReparsePoint, error) { |
| 46 | tag := binary.LittleEndian.Uint32(b[0:4]) |
| 47 | return DecodeReparsePointData(tag, b[8:]) |
| 48 | } |
| 49 | |
| 50 | func 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 | |
| 72 | func 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. |
| 78 | func 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 | } |