providers/windows/device_windows.go (137 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 windows
import (
"errors"
"fmt"
"strings"
"unsafe"
"golang.org/x/sys/windows"
)
const (
// DeviceMup is the device used for unmounted network filesystems
DeviceMup = "\\device\\mup"
// LANManRedirector is an string that appears in mounted network filesystems
LANManRedirector = "lanmanredirector"
)
var (
// ErrNoDevice is the error returned by DevicePathToDrivePath when
// an invalid device-path is supplied.
ErrNoDevice = errors.New("not a device path")
// ErrDeviceNotFound is the error returned by DevicePathToDrivePath when
// a path pointing to an unmapped device is passed.
ErrDeviceNotFound = errors.New("logical device not found")
)
type deviceProvider interface {
GetLogicalDrives() (uint32, error)
QueryDosDevice(*uint16, *uint16, uint32) (uint32, error)
}
type deviceMapper struct {
deviceProvider
}
type winapiDeviceProvider struct{}
type testingDeviceProvider map[byte]string
func newDeviceMapper() deviceMapper {
return deviceMapper{
deviceProvider: winapiDeviceProvider{},
}
}
func fixNetworkDrivePath(device string) string {
// For a VirtualBox share:
// device=\device\vboxminirdr\;z:\vboxsvr\share
// path=\device\vboxminirdr\vboxsvr\share
//
// For a network share:
// device=\device\lanmanredirector\;q:nnnnnnn\server\share
// path=\device\mup\server\share
semicolonPos := strings.IndexByte(device, ';')
colonPos := strings.IndexByte(device, ':')
if semicolonPos == -1 || colonPos != semicolonPos+2 {
return device
}
pathStart := strings.IndexByte(device[colonPos+1:], '\\')
if pathStart == -1 {
return device
}
dev := device[:semicolonPos]
path := device[colonPos+pathStart+1:]
n := len(dev)
if n > 0 && dev[n-1] == '\\' {
dev = dev[:n-1]
}
return dev + path
}
func (mapper *deviceMapper) getDevice(driveLetter byte) (string, error) {
driveBuf := [3]uint16{uint16(driveLetter), ':', 0}
for bufSize := 64; bufSize <= 1024; bufSize *= 2 {
deviceBuf := make([]uint16, bufSize)
n, err := mapper.QueryDosDevice(&driveBuf[0], &deviceBuf[0], uint32(len(deviceBuf)))
if err != nil {
if err == windows.ERROR_INSUFFICIENT_BUFFER {
continue
}
return "", err
}
return windows.UTF16ToString(deviceBuf[:n]), nil
}
return "", windows.ERROR_INSUFFICIENT_BUFFER
}
func (mapper *deviceMapper) DevicePathToDrivePath(path string) (string, error) {
pathLower := strings.ToLower(path)
isMUP := strings.Index(pathLower, DeviceMup) == 0
mask, err := mapper.GetLogicalDrives()
if err != nil {
return "", fmt.Errorf("GetLogicalDrives: %w", err)
}
for bit := uint32(0); mask != 0 && bit < uint32('Z'-'A'+1); bit++ {
if mask&(1<<bit) == 0 {
continue
}
mask ^= 1 << bit
driveLetter := byte('A' + bit)
dev, err := mapper.getDevice(driveLetter)
if err != nil {
continue
}
dev = fixNetworkDrivePath(strings.ToLower(dev))
found := strings.Index(pathLower, dev) == 0
if !found && isMUP && strings.Contains(dev, LANManRedirector) {
dev = strings.Replace(dev, LANManRedirector, "mup", 1)
found = strings.Index(pathLower, dev) == 0
}
if found {
off := len(dev)
if off < len(path) && path[off] == '\\' {
off++
}
return string(driveLetter) + ":\\" + path[off:], nil
}
}
// Handle unmapped shares:
// \device\mup\server\share\path -> \\server\share\path
if isMUP {
return "\\" + path[len(DeviceMup):], nil
}
return "", ErrDeviceNotFound
}
func (winapiDeviceProvider) GetLogicalDrives() (uint32, error) {
return windows.GetLogicalDrives()
}
func (winapiDeviceProvider) QueryDosDevice(name *uint16, buf *uint16, length uint32) (uint32, error) {
return windows.QueryDosDevice(name, buf, length)
}
func (m testingDeviceProvider) GetLogicalDrives() (mask uint32, err error) {
for drive := range m {
mask |= 1 << uint32(drive-'A')
}
return mask, nil
}
func ptrOffset(ptr *uint16, off uint32) *uint16 {
return (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + uintptr(off*2)))
}
func (m testingDeviceProvider) QueryDosDevice(nameW *uint16, buf *uint16, length uint32) (uint32, error) {
drive := byte(*nameW)
if byte(*ptrOffset(nameW, 1)) != ':' {
return 0, errors.New("not a drive")
}
if *ptrOffset(nameW, 2) != 0 {
return 0, errors.New("drive not terminated")
}
path, ok := m[drive]
if !ok {
return 0, fmt.Errorf("drive %c not found", drive)
}
n := uint32(len(path))
if n+2 > length {
return 0, windows.ERROR_INSUFFICIENT_BUFFER
}
for i := uint32(0); i < n; i++ {
*ptrOffset(buf, i) = uint16(path[i])
}
*ptrOffset(buf, n) = 0
*ptrOffset(buf, n+1) = 0
return n + 2, nil
}