Skip to content

Commit

Permalink
Implement Volume Mount Point API wrappers
Browse files Browse the repository at this point in the history
Fixes: #176

These provide Go-typed wrappers around the Win32 APIs backing them,
protecting the user from unsafe pointers and UTF-16 conversions.

utf16ToStringArray adapted from ConvertStringSetToSlice in hcsshim

Signed-off-by: Paul "TBBle" Hampson <[email protected]>
  • Loading branch information
TBBle committed Apr 23, 2021
1 parent 58dba89 commit f98fb90
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 0 deletions.
29 changes: 29 additions & 0 deletions pkg/volmount/deletevolumemountpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package volmount

import (
"path/filepath"

"github.com/pkg/errors"
"golang.org/x/sys/windows"
)

// DeleteVolumeMountPoint removes the volume mount at targetPath
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-deletevolumemountpointa
func DeleteVolumeMountPoint(targetPath string) error {
// Must end in a backslash
slashedTarget := filepath.Clean(targetPath)
if slashedTarget[len(slashedTarget)-1] != filepath.Separator {
slashedTarget = slashedTarget + string(filepath.Separator)
}

targetP, err := windows.UTF16PtrFromString(slashedTarget)
if err != nil {
return errors.Wrapf(err, "unable to utf16-ise %s", slashedTarget)
}

if err := windows.DeleteVolumeMountPoint(targetP); err != nil {
return errors.Wrapf(err, "failed calling DeleteVolumeMountPoint('%s')", slashedTarget)
}

return nil
}
33 changes: 33 additions & 0 deletions pkg/volmount/getvolumenameforvolumemountpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package volmount

import (
"path/filepath"

"github.com/pkg/errors"
"golang.org/x/sys/windows"
)

// GetVolumeNameForVolumeMountPoint returns a volume path (in format '\\?\Volume{GUID}'
// for the volume mounted at targetPath.
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumenameforvolumemountpointw
func GetVolumeNameForVolumeMountPoint(targetPath string) (string, error) {
// Must end in a backslash
slashedTarget := filepath.Clean(targetPath)
if slashedTarget[len(slashedTarget)-1] != filepath.Separator {
slashedTarget = slashedTarget + string(filepath.Separator)
}

targetP, err := windows.UTF16PtrFromString(slashedTarget)
if err != nil {
return "", errors.Wrapf(err, "unable to utf16-ise %s", slashedTarget)
}

bufferlength := uint32(50) // "A reasonable size for the buffer" per the documentation.
buffer := make([]uint16, bufferlength)

if err = windows.GetVolumeNameForVolumeMountPoint(targetP, &buffer[0], bufferlength); err != nil {
return "", errors.Wrapf(err, "failed calling GetVolumeNameForVolumeMountPoint('%s', ..., %d)", slashedTarget, bufferlength)
}

return windows.UTF16ToString(buffer), nil
}
66 changes: 66 additions & 0 deletions pkg/volmount/getvolumepathnamesforvolumename.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package volmount

import (
"path/filepath"
"syscall"
"unicode/utf16"

"github.com/pkg/errors"
"golang.org/x/sys/windows"
)

// utf16ToStringArray returns the UTF-8 encoding of the sequence of UTF-16 sequences s,
// with a terminating NUL removed. The sequences are terminated by an additional NULL.
func utf16ToStringArray(s []uint16) ([]string, error) {
var results []string
prev := 0
for i := range s {
if s[i] == 0 {
if prev == i {
// found two null characters in a row, return result
return results, nil
}
results = append(results, string(utf16.Decode(s[prev:i])))
prev = i + 1
}
}
return nil, errors.New("string set malformed: missing null terminator at end of buffer")
}

// GetMountPathsFromVolumeName returns a list of mount points for the volumePath
// (in format '\\?\Volume{GUID}).
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumepathnamesforvolumenamew
func GetMountPathsFromVolumeName(volumePath string) ([]string, error) {
// Must end in a backslash
slashedVolume := filepath.Clean(volumePath)
if slashedVolume[len(slashedVolume)-1] != filepath.Separator {
slashedVolume = slashedVolume + string(filepath.Separator)
}

volumeP, err := windows.UTF16PtrFromString(slashedVolume)
if err != nil {
return nil, errors.Wrapf(err, "unable to utf16-ise %s", slashedVolume)
}

var bufferLength uint32
err = windows.GetVolumePathNamesForVolumeName(volumeP, nil, 0, &bufferLength)
if err == nil {
// This should never happen. An empty list would have a single 0 in it.
return nil, errors.Errorf("unexpected success of GetVolumePathNamesForVolumeName('%s', nil, 0, ...)", slashedVolume)
} else if err != syscall.ERROR_MORE_DATA {
return nil, errors.Wrapf(err, "failed calling GetVolumePathNamesForVolumeName('%s', nil, 0, ...)", slashedVolume)
}

buffer := make([]uint16, bufferLength)
err = windows.GetVolumePathNamesForVolumeName(volumeP, &buffer[0], bufferLength, nil)
if err != nil {
return nil, errors.Wrapf(err, "failed calling GetVolumePathNamesForVolumeName('%s', ..., %d, nil)", slashedVolume, bufferLength)
}

result, err := utf16ToStringArray(buffer)
if err != nil {
return nil, errors.Wrapf(err, "failed decoding result of GetVolumePathNamesForVolumeName('%s', ..., %d, nil)", slashedVolume, bufferLength)
}

return result, nil
}
43 changes: 43 additions & 0 deletions pkg/volmount/setvolumemountpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package volmount

import (
"path/filepath"
"strings"

"github.com/pkg/errors"
"golang.org/x/sys/windows"
)

// SetVolumeMountPoint mounts volumePath (in format '\\?\Volume{GUID}' at targetPath.
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setvolumemountpointw
func SetVolumeMountPoint(targetPath string, volumePath string) error {
if !strings.HasPrefix(volumePath, "\\\\?\\Volume{") {
return errors.Errorf("unable to mount non-volume path %s", volumePath)
}

// Both must end in a backslash
slashedTarget := filepath.Clean(targetPath)
if slashedTarget[len(slashedTarget)-1] != filepath.Separator {
slashedTarget = slashedTarget + string(filepath.Separator)
}
slashedVolume := filepath.Clean(volumePath)
if slashedVolume[len(slashedVolume)-1] != filepath.Separator {
slashedVolume = slashedVolume + string(filepath.Separator)
}

targetP, err := windows.UTF16PtrFromString(slashedTarget)
if err != nil {
return errors.Wrapf(err, "unable to utf16-ise %s", slashedTarget)
}

volumeP, err := windows.UTF16PtrFromString(slashedVolume)
if err != nil {
return errors.Wrapf(err, "unable to utf16-ise %s", slashedVolume)
}

if err := windows.SetVolumeMountPoint(targetP, volumeP); err != nil {
return errors.Wrapf(err, "failed calling SetVolumeMount('%s', '%s')", slashedTarget, slashedVolume)
}

return nil
}

0 comments on commit f98fb90

Please sign in to comment.