Skip to content

Instantly share code, notes, and snippets.

@kostix
Created July 9, 2020 19:51
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kostix/398fa1c96911240a402f7801e5404323 to your computer and use it in GitHub Desktop.
Save kostix/398fa1c96911240a402f7801e5404323 to your computer and use it in GitHub Desktop.
Getting the list of fixed volumes and their mounts
package main
import (
"errors"
"log"
"strings"
"syscall"
"unsafe"
)
func main() {
mounts, err := getFixedDriveMounts()
if err != nil {
log.Fatal(err)
}
for _, m := range mounts {
log.Println("volume:", m.volume,
"mounts:", strings.Join(m.mounts, ", "))
}
}
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
findFirstVolumeWProc = kernel32.NewProc("FindFirstVolumeW")
findNextVolumeWProc = kernel32.NewProc("FindNextVolumeW")
findVolumeCloseProc = kernel32.NewProc("FindVolumeClose")
getVolumePathNamesForVolumeNameWProc = kernel32.NewProc("GetVolumePathNamesForVolumeNameW")
getDriveTypeWProc = kernel32.NewProc("GetDriveTypeW")
)
const guidBufLen = syscall.MAX_PATH + 1
func findFirstVolume() (uintptr, []uint16, error) {
const invalidHandleValue = ^uintptr(0)
guid := make([]uint16, guidBufLen)
handle, _, err := findFirstVolumeWProc.Call(
uintptr(unsafe.Pointer(&guid[0])),
uintptr(guidBufLen*2),
)
if handle == invalidHandleValue {
return invalidHandleValue, nil, err
}
return handle, guid, nil
}
func findNextVolume(handle uintptr) ([]uint16, bool, error) {
const noMoreFiles = 18
guid := make([]uint16, guidBufLen)
rc, _, err := findNextVolumeWProc.Call(
handle,
uintptr(unsafe.Pointer(&guid[0])),
uintptr(guidBufLen*2),
)
if rc == 1 {
return guid, true, nil
}
if err.(syscall.Errno) == noMoreFiles {
return nil, false, nil
}
return nil, false, err
}
func findVolumeClose(handle uintptr) error {
ok, _, err := findVolumeCloseProc.Call(handle)
if ok == 0 {
return err
}
return nil
}
func getVolumePathNamesForVolumeName(volName []uint16) ([][]uint16, error) {
const (
errorMoreData = 234
NUL = 0x0000
)
var (
pathNamesLen uint32
pathNames []uint16
)
pathNamesLen = 2
for {
pathNames = make([]uint16, pathNamesLen)
pathNamesLen *= 2
rc, _, err := getVolumePathNamesForVolumeNameWProc.Call(
uintptr(unsafe.Pointer(&volName[0])),
uintptr(unsafe.Pointer(&pathNames[0])),
uintptr(pathNamesLen),
uintptr(unsafe.Pointer(&pathNamesLen)),
)
if rc == 0 {
if err.(syscall.Errno) == errorMoreData {
continue
}
return nil, err
}
pathNames = pathNames[:pathNamesLen]
break
}
var out [][]uint16
i := 0
for j, c := range pathNames {
if c == NUL && i < j {
out = append(out, pathNames[i:j+1])
i = j + 1
}
}
return out, nil
}
func getDriveType(rootPathName []uint16) (int, error) {
rc, _, _ := getDriveTypeWProc.Call(
uintptr(unsafe.Pointer(&rootPathName[0])),
)
dt := int(rc)
if dt == driveUnknown || dt == driveNoRootDir {
return -1, driveTypeErrors[dt]
}
return dt, nil
}
var (
errUnknownDriveType = errors.New("unknown drive type")
errNoRootDir = errors.New("invalid root drive path")
driveTypeErrors = [...]error{
0: errUnknownDriveType,
1: errNoRootDir,
}
)
const (
driveUnknown = iota
driveNoRootDir
driveRemovable
driveFixed
driveRemote
driveCDROM
driveRamdisk
driveLastKnownType = driveRamdisk
)
type fixedDriveVolume struct {
volName string
mountedPathnames []string
}
type fixedVolumeMounts struct {
volume string
mounts []string
}
func getFixedDriveMounts() ([]fixedVolumeMounts, error) {
var out []fixedVolumeMounts
err := enumVolumes(func(guid []uint16) error {
mounts, err := maybeGetFixedVolumeMounts(guid)
if err != nil {
return err
}
if len(mounts) > 0 {
out = append(out, fixedVolumeMounts{
volume: syscall.UTF16ToString(guid),
mounts: LPSTRsToStrings(mounts),
})
}
return nil
})
if err != nil {
return nil, err
}
return out, nil
}
func enumVolumes(handleVolume func(guid []uint16) error) error {
handle, guid, err := findFirstVolume()
if err != nil {
return err
}
defer func() {
err = findVolumeClose(handle)
}()
if err := handleVolume(guid); err != nil {
return err
}
for {
guid, more, err := findNextVolume(handle)
if err != nil {
return err
}
if !more {
break
}
if err := handleVolume(guid); err != nil {
return err
}
}
return nil
}
func maybeGetFixedVolumeMounts(guid []uint16) ([][]uint16, error) {
paths, err := getVolumePathNamesForVolumeName(guid)
if err != nil {
return nil, err
}
if len(paths) == 0 {
return nil, nil
}
var lastErr error
for _, path := range paths {
dt, err := getDriveType(path)
if err == nil {
if dt == driveFixed {
return paths, nil
}
return nil, nil
}
lastErr = err
}
return nil, lastErr
}
func LPSTRsToStrings(in [][]uint16) []string {
if len(in) == 0 {
return nil
}
out := make([]string, len(in))
for i, s := range in {
out[i] = syscall.UTF16ToString(s)
}
return out
}
@michaeldcanady
Copy link

Hey man,
This is really interesting. Would you want to/would it be ok if I were to tweak this code and use it in a repo I'm deving? it's integrating the full windows-api into golang. this is the link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment