reparse.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  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. isMountPoint := false
  40. tag := binary.LittleEndian.Uint32(b[0:4])
  41. switch tag {
  42. case reparseTagMountPoint:
  43. isMountPoint = true
  44. case reparseTagSymlink:
  45. default:
  46. return nil, &UnsupportedReparsePointError{tag}
  47. }
  48. nameOffset := 16 + binary.LittleEndian.Uint16(b[12:14])
  49. if !isMountPoint {
  50. nameOffset += 4
  51. }
  52. nameLength := binary.LittleEndian.Uint16(b[14:16])
  53. name := make([]uint16, nameLength/2)
  54. err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
  55. if err != nil {
  56. return nil, err
  57. }
  58. return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
  59. }
  60. func isDriveLetter(c byte) bool {
  61. return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
  62. }
  63. // EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
  64. // mount point.
  65. func EncodeReparsePoint(rp *ReparsePoint) []byte {
  66. // Generate an NT path and determine if this is a relative path.
  67. var ntTarget string
  68. relative := false
  69. if strings.HasPrefix(rp.Target, `\\?\`) {
  70. ntTarget = rp.Target
  71. } else if strings.HasPrefix(rp.Target, `\\`) {
  72. ntTarget = `\??\UNC\` + rp.Target[2:]
  73. } else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
  74. ntTarget = `\??\` + rp.Target
  75. } else {
  76. ntTarget = rp.Target
  77. relative = true
  78. }
  79. // The paths must be NUL-terminated even though they are counted strings.
  80. target16 := utf16.Encode([]rune(rp.Target + "\x00"))
  81. ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
  82. size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
  83. size += len(ntTarget16)*2 + len(target16)*2
  84. tag := uint32(reparseTagMountPoint)
  85. if !rp.IsMountPoint {
  86. tag = reparseTagSymlink
  87. size += 4 // Add room for symlink flags
  88. }
  89. data := reparseDataBuffer{
  90. ReparseTag: tag,
  91. ReparseDataLength: uint16(size),
  92. SubstituteNameOffset: 0,
  93. SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
  94. PrintNameOffset: uint16(len(ntTarget16) * 2),
  95. PrintNameLength: uint16((len(target16) - 1) * 2),
  96. }
  97. var b bytes.Buffer
  98. binary.Write(&b, binary.LittleEndian, &data)
  99. if !rp.IsMountPoint {
  100. flags := uint32(0)
  101. if relative {
  102. flags |= 1
  103. }
  104. binary.Write(&b, binary.LittleEndian, flags)
  105. }
  106. binary.Write(&b, binary.LittleEndian, ntTarget16)
  107. binary.Write(&b, binary.LittleEndian, target16)
  108. return b.Bytes()
  109. }