forked from microsoft/go-winio
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Volume Mount Point API wrappers
Fixes: microsoft#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 <Paul.Hampson@Pobox.com>
- Loading branch information
Showing
4 changed files
with
171 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |