Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
128 lines
3.4 KiB
128 lines
3.4 KiB
package winio |
|
|
|
import ( |
|
"bytes" |
|
"encoding/binary" |
|
"fmt" |
|
"strings" |
|
"unicode/utf16" |
|
"unsafe" |
|
) |
|
|
|
const ( |
|
reparseTagMountPoint = 0xA0000003 |
|
reparseTagSymlink = 0xA000000C |
|
) |
|
|
|
type reparseDataBuffer struct { |
|
ReparseTag uint32 |
|
ReparseDataLength uint16 |
|
Reserved uint16 |
|
SubstituteNameOffset uint16 |
|
SubstituteNameLength uint16 |
|
PrintNameOffset uint16 |
|
PrintNameLength uint16 |
|
} |
|
|
|
// ReparsePoint describes a Win32 symlink or mount point. |
|
type ReparsePoint struct { |
|
Target string |
|
IsMountPoint bool |
|
} |
|
|
|
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or |
|
// mount point reparse point. |
|
type UnsupportedReparsePointError struct { |
|
Tag uint32 |
|
} |
|
|
|
func (e *UnsupportedReparsePointError) Error() string { |
|
return fmt.Sprintf("unsupported reparse point %x", e.Tag) |
|
} |
|
|
|
// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink |
|
// or a mount point. |
|
func DecodeReparsePoint(b []byte) (*ReparsePoint, error) { |
|
tag := binary.LittleEndian.Uint32(b[0:4]) |
|
return DecodeReparsePointData(tag, b[8:]) |
|
} |
|
|
|
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) { |
|
isMountPoint := false |
|
switch tag { |
|
case reparseTagMountPoint: |
|
isMountPoint = true |
|
case reparseTagSymlink: |
|
default: |
|
return nil, &UnsupportedReparsePointError{tag} |
|
} |
|
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6]) |
|
if !isMountPoint { |
|
nameOffset += 4 |
|
} |
|
nameLength := binary.LittleEndian.Uint16(b[6:8]) |
|
name := make([]uint16, nameLength/2) |
|
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil |
|
} |
|
|
|
func isDriveLetter(c byte) bool { |
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') |
|
} |
|
|
|
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or |
|
// mount point. |
|
func EncodeReparsePoint(rp *ReparsePoint) []byte { |
|
// Generate an NT path and determine if this is a relative path. |
|
var ntTarget string |
|
relative := false |
|
if strings.HasPrefix(rp.Target, `\\?\`) { |
|
ntTarget = `\??\` + rp.Target[4:] |
|
} else if strings.HasPrefix(rp.Target, `\\`) { |
|
ntTarget = `\??\UNC\` + rp.Target[2:] |
|
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' { |
|
ntTarget = `\??\` + rp.Target |
|
} else { |
|
ntTarget = rp.Target |
|
relative = true |
|
} |
|
|
|
// The paths must be NUL-terminated even though they are counted strings. |
|
target16 := utf16.Encode([]rune(rp.Target + "\x00")) |
|
ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00")) |
|
|
|
size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8 |
|
size += len(ntTarget16)*2 + len(target16)*2 |
|
|
|
tag := uint32(reparseTagMountPoint) |
|
if !rp.IsMountPoint { |
|
tag = reparseTagSymlink |
|
size += 4 // Add room for symlink flags |
|
} |
|
|
|
data := reparseDataBuffer{ |
|
ReparseTag: tag, |
|
ReparseDataLength: uint16(size), |
|
SubstituteNameOffset: 0, |
|
SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2), |
|
PrintNameOffset: uint16(len(ntTarget16) * 2), |
|
PrintNameLength: uint16((len(target16) - 1) * 2), |
|
} |
|
|
|
var b bytes.Buffer |
|
binary.Write(&b, binary.LittleEndian, &data) |
|
if !rp.IsMountPoint { |
|
flags := uint32(0) |
|
if relative { |
|
flags |= 1 |
|
} |
|
binary.Write(&b, binary.LittleEndian, flags) |
|
} |
|
|
|
binary.Write(&b, binary.LittleEndian, ntTarget16) |
|
binary.Write(&b, binary.LittleEndian, target16) |
|
return b.Bytes() |
|
}
|
|
|