/* Copyright The containerd Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package syscallx import ( "syscall" "unsafe" ) type reparseDataBuffer struct { ReparseTag uint32 ReparseDataLength uint16 Reserved uint16 // GenericReparseBuffer reparseBuffer byte } type mountPointReparseBuffer struct { SubstituteNameOffset uint16 SubstituteNameLength uint16 PrintNameOffset uint16 PrintNameLength uint16 PathBuffer [1]uint16 } type symbolicLinkReparseBuffer struct { SubstituteNameOffset uint16 SubstituteNameLength uint16 PrintNameOffset uint16 PrintNameLength uint16 Flags uint32 PathBuffer [1]uint16 } const ( _IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003 _SYMLINK_FLAG_RELATIVE = 1 ) // Readlink returns the destination of the named symbolic link. func Readlink(path string, buf []byte) (n int, err error) { fd, err := syscall.CreateFile(syscall.StringToUTF16Ptr(path), syscall.GENERIC_READ, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0) if err != nil { return -1, err } defer syscall.CloseHandle(fd) rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE) var bytesReturned uint32 err = syscall.DeviceIoControl(fd, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil) if err != nil { return -1, err } rdb := (*reparseDataBuffer)(unsafe.Pointer(&rdbbuf[0])) var s string switch rdb.ReparseTag { case syscall.IO_REPARSE_TAG_SYMLINK: data := (*symbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer)) p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0])) s = syscall.UTF16ToString(p[data.SubstituteNameOffset/2 : (data.SubstituteNameOffset+data.SubstituteNameLength)/2]) if data.Flags&_SYMLINK_FLAG_RELATIVE == 0 { if len(s) >= 4 && s[:4] == `\??\` { s = s[4:] switch { case len(s) >= 2 && s[1] == ':': // \??\C:\foo\bar // do nothing case len(s) >= 4 && s[:4] == `UNC\`: // \??\UNC\foo\bar s = `\\` + s[4:] default: // unexpected; do nothing } } else { // unexpected; do nothing } } case _IO_REPARSE_TAG_MOUNT_POINT: data := (*mountPointReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer)) p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0])) s = syscall.UTF16ToString(p[data.SubstituteNameOffset/2 : (data.SubstituteNameOffset+data.SubstituteNameLength)/2]) if len(s) >= 4 && s[:4] == `\??\` { // \??\C:\foo\bar if len(s) < 48 || s[:11] != `\??\Volume{` { s = s[4:] } } else { // unexpected; do nothing } default: // the path is not a symlink or junction but another type of reparse // point return -1, syscall.ENOENT } n = copy(buf, []byte(s)) return n, nil }