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. Signed-off-by: Paul "TBBle" Hampson <Paul.Hampson@Pobox.com>
- Loading branch information
Showing
4 changed files
with
174 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,69 @@ | ||
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) (result []string) { | ||
start := 0 | ||
|
||
for i, v := range s { | ||
if v != 0 { | ||
continue | ||
} | ||
|
||
if start == i { | ||
// Empty string, that's the end. | ||
return | ||
} | ||
|
||
result = append(result, string(utf16.Decode(s[start:i]))) | ||
start = i + 1 | ||
} | ||
|
||
// The buffer was incomplete, the expected "additional NULL" wasn't present. | ||
// Just return the complete values we did decode, assume the caller intended | ||
// to do this, e.g. as a best-effort call without resizing the buffer. | ||
return | ||
} | ||
|
||
// GetVolumePathNamesForVolumeName 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-getvolumenameforvolumemountpointw | ||
func GetVolumePathNamesForVolumeName(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) | ||
} | ||
|
||
bufferLength := uint32(256) | ||
|
||
for { | ||
buffer := make([]uint16, bufferLength) | ||
|
||
var returnLength uint32 | ||
err := windows.GetVolumePathNamesForVolumeName(volumeP, &buffer[0], bufferLength, &returnLength) | ||
|
||
if err == nil { | ||
// It's an array! TODO | ||
return utf16ToStringArray(buffer), nil | ||
} else if err != syscall.ERROR_MORE_DATA { | ||
return nil, errors.Wrapf(err, "failed calling GetVolumePathNamesForVolumeName('%s', ..., %d, ...)", slashedVolume, bufferLength) | ||
} | ||
// Try again | ||
bufferLength = returnLength | ||
} | ||
} |
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 | ||
} |