reparse.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. package winio
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "fmt"
  6. "strings"
  7. "unicode/utf16"
  8. "unsafe"
  9. )
  10. const (
  11. reparseTagMountPoint = 0xA0000003
  12. reparseTagSymlink = 0xA000000C
  13. )
  14. type reparseDataBuffer struct {
  15. ReparseTag uint32
  16. ReparseDataLength uint16
  17. Reserved uint16
  18. SubstituteNameOffset uint16
  19. SubstituteNameLength uint16
  20. PrintNameOffset uint16
  21. PrintNameLength uint16
  22. }
  23. // ReparsePoint describes a Win32 symlink or mount point.
  24. type ReparsePoint struct {
  25. Target string
  26. IsMountPoint bool
  27. }
  28. // UnsupportedReparsePointError is returned when trying to decode a non-symlink or
  29. // mount point reparse point.
  30. type UnsupportedReparsePointError struct {
  31. Tag uint32
  32. }
  33. func (e *UnsupportedReparsePointError) Error() string {
  34. return fmt.Sprintf("unsupported reparse point %x", e.Tag)
  35. }
  36. // DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
  37. // or a mount point.
  38. func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
  39. tag := binary.LittleEndian.Uint32(b[0:4])
  40. return DecodeReparsePointData(tag, b[8:])
  41. }
  42. func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
  43. isMountPoint := false
  44. switch tag {
  45. case reparseTagMountPoint:
  46. isMountPoint = true
  47. case reparseTagSymlink:
  48. default:
  49. return nil, &UnsupportedReparsePointError{tag}
  50. }
  51. nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
  52. if !isMountPoint {
  53. nameOffset += 4
  54. }
  55. nameLength := binary.LittleEndian.Uint16(b[6:8])
  56. name := make([]uint16, nameLength/2)
  57. err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
  58. if err != nil {
  59. return nil, err
  60. }
  61. return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
  62. }
  63. func isDriveLetter(c byte) bool {
  64. return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
  65. }
  66. // EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
  67. // mount point.
  68. func EncodeReparsePoint(rp *ReparsePoint) []byte {
  69. // Generate an NT path and determine if this is a relative path.
  70. var ntTarget string
  71. relative := false
  72. if strings.HasPrefix(rp.Target, `\\?\`) {
  73. ntTarget = `\??\` + rp.Target[4:]
  74. } else if strings.HasPrefix(rp.Target, `\\`) {
  75. ntTarget = `\??\UNC\` + rp.Target[2:]
  76. } else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
  77. ntTarget = `\??\` + rp.Target
  78. } else {
  79. ntTarget = rp.Target
  80. relative = true
  81. }
  82. // The paths must be NUL-terminated even though they are counted strings.
  83. target16 := utf16.Encode([]rune(rp.Target + "\x00"))
  84. ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
  85. size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
  86. size += len(ntTarget16)*2 + len(target16)*2
  87. tag := uint32(reparseTagMountPoint)
  88. if !rp.IsMountPoint {
  89. tag = reparseTagSymlink
  90. size += 4 // Add room for symlink flags
  91. }
  92. data := reparseDataBuffer{
  93. ReparseTag: tag,
  94. ReparseDataLength: uint16(size),
  95. SubstituteNameOffset: 0,
  96. SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
  97. PrintNameOffset: uint16(len(ntTarget16) * 2),
  98. PrintNameLength: uint16((len(target16) - 1) * 2),
  99. }
  100. var b bytes.Buffer
  101. binary.Write(&b, binary.LittleEndian, &data)
  102. if !rp.IsMountPoint {
  103. flags := uint32(0)
  104. if relative {
  105. flags |= 1
  106. }
  107. binary.Write(&b, binary.LittleEndian, flags)
  108. }
  109. binary.Write(&b, binary.LittleEndian, ntTarget16)
  110. binary.Write(&b, binary.LittleEndian, target16)
  111. return b.Bytes()
  112. }