reparse.go 3.4 KB

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