10

I would like my program to iterate through all drives on a Windows system and search for a particular file type. Right now, I can run the program and pass it a drive letter to start from, but I want it to search on all drives automatically. Currently, I would need to do something like this:

C:\> program.exe C:
C:\> program.exe D:
C:\> program.exe E:

I want the program to get a list of all drives and iterate through all of them without the user having to specify the drive letter. Is this possible using Go?

Similar to this question Listing All Physical Drives (Windows) but using Go instead of C.

Community
  • 1
  • 1
roartechs
  • 1,315
  • 1
  • 12
  • 15
  • 1
    Why not `for _, drive in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" { /* try some op on drive and continue on failure */ }`? – Volker Apr 17 '14 at 08:35
  • Use cgo to use the C API from Go. –  Apr 17 '14 at 08:50
  • @Volker I was looking for a nicer way? – roartechs Apr 17 '14 at 08:56
  • @rightfold Thanks, but I'd prefer using standard go packages and not C. – roartechs Apr 17 '14 at 08:57
  • @roartechs That is as nice as it will get. Your last option would be to use package syscall. Good luck! – Volker Apr 17 '14 at 09:21
  • @roartechs There's a decent Go package for this sort of Win32 API stuff: https://github.com/AllenDang/w32 -- https://github.com/AllenDang/w32/blob/master/kernel32.go#L80 is what you want. – Hut8 Nov 29 '15 at 17:44

5 Answers5

14

You can call the function GetLogicalDrives and match the letters according to the bit map.

Something like:

package main

import (
    "fmt"
    "syscall"
)

func main() {

    kernel32, _ := syscall.LoadLibrary("kernel32.dll")
    getLogicalDrivesHandle, _ := syscall.GetProcAddress(kernel32, "GetLogicalDrives")

    var drives []string

    if ret, _, callErr := syscall.Syscall(uintptr(getLogicalDrivesHandle), 0, 0, 0, 0); callErr != 0 {
        // handle error
    } else {
        drives = bitsToDrives(uint32(ret))
    }

    fmt.Printf("%v", drives)

}

func bitsToDrives(bitMap uint32) (drives []string) {
    availableDrives := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}

    for i := range availableDrives {
        if bitMap & 1 == 1 {
            drives = append(drives, availableDrives[i])
        }
        bitMap >>= 1
    }

    return
}
nemo
  • 55,207
  • 13
  • 135
  • 135
10

You can use the gopsutil lib:

package main

import (
    "fmt"
    "github.com/shirou/gopsutil/disk"
)

func main() {
    partitions, _ := disk.Partitions(false)
    for _, partition := range partitions {
        fmt.Println(partition.Mountpoint)
    }
}
6

The easist way is write own function with try to open "drive" folder mentioned by Volker.

import "os"

func getdrives() (r []string){
    for _, drive := range "ABCDEFGHIJKLMNOPQRSTUVWXYZ"{
        f, err := os.Open(string(drive)+":\\")
        if err == nil {
            r = append(r, string(drive))
            f.Close()
        }
    }
    return
}
Hinogary
  • 442
  • 1
  • 3
  • 7
  • Accepted this answer since it doesn't look like there is a nicer way to do it. – roartechs Apr 17 '14 at 18:27
  • 1
    I suspect this might cause delays on some systems, e.g. waiting for a DVD drive to spin up. I remember how clicking on the A: drive would be slow when no floppy inserted ... – RichVel Jun 07 '14 at 14:15
  • 3
    Doesn't this leave a bunch of open files that need to be `Close()`ed? – Matt Jul 21 '17 at 00:21
5

Or you can call GetLogicalDriveStrings directly:

package main

import (
    "fmt"
    "syscall"
    "unsafe"
)

func main() {
    kernel32, err := syscall.LoadDLL("kernel32.dll")
    getLogicalDriveStringsHandle, err := kernel32.FindProc("GetLogicalDriveStringsA")

    buffer := [1024]byte{}
    bufferSize := uint32(len(buffer))
    drives := []string{}

    hr, _, _ := getLogicalDriveStringsHandle.Call(uintptr(unsafe.Pointer(&bufferSize)), uintptr(unsafe.Pointer(&buffer)))
    if hr == 0 {
        fmt.Print("There was an error")
    } else {
        // Deal with the buffer, you need to split it
    }
}
  • The first param to the Call should be `unitptr(bufferSize)` not `uintptr(unsafe.Pointer(&bufferSize))`. – crunk1 Nov 13 '17 at 23:48
2

You can use the windows package:

package main

import (
   "golang.org/x/sys/windows"
   "strings"
   "unicode/utf16"
)

func drives() ([]string, error) {
   n, e := windows.GetLogicalDriveStrings(0, nil)
   if e != nil { return nil, e }
   a := make([]uint16, n)
   windows.GetLogicalDriveStrings(n, &a[0])
   s := string(utf16.Decode(a))
   return strings.Split(strings.TrimRight(s, "\x00"), "\x00"), nil
}

func main() {
   a, e := drives()
   if e != nil {
      panic(e)
   }
   for _, s := range a {
      println(s)
   }
}

https://pkg.go.dev/golang.org/x/sys/windows#GetLogicalDriveStrings

Zombo
  • 1
  • 62
  • 391
  • 407