main: improve detection of filesystems

This is a rewrite of how filesystems are detected. Specifically, it
fixes an issue on Linux where the location of the FAT filesystem can
vary between distributions (for example, we supported most distros by
checking two different paths, but NixOS uses a different path): it now
uses the data in /proc/mounts instead which should be universal.
Этот коммит содержится в:
Ayke van Laethem 2023-06-30 13:54:17 +02:00 коммит произвёл Ron Evans
родитель 6efa94035e
коммит 9aadea930f

201
main.go
Просмотреть файл

@ -19,6 +19,7 @@ import (
"regexp" "regexp"
"runtime" "runtime"
"runtime/pprof" "runtime/pprof"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -946,112 +947,130 @@ func touchSerialPortAt1200bps(port string) (err error) {
} }
func flashUF2UsingMSD(volumes []string, tmppath string, options *compileopts.Options) error { func flashUF2UsingMSD(volumes []string, tmppath string, options *compileopts.Options) error {
// find standard UF2 info path for start := time.Now(); time.Since(start) < options.Timeout; {
infoPaths := make([]string, 0, len(volumes)) // Find a UF2 mount point.
for _, volume := range volumes { mounts, err := findFATMounts(options)
switch runtime.GOOS { if err != nil {
case "linux", "freebsd": return err
fi, err := os.Stat("/run/media") }
if err != nil || !fi.IsDir() { for _, mount := range mounts {
infoPaths = append(infoPaths, "/media/*/"+volume+"/INFO_UF2.TXT") for _, volume := range volumes {
} else { if mount.name != volume {
infoPaths = append(infoPaths, "/run/media/*/"+volume+"/INFO_UF2.TXT") continue
} }
case "darwin": if _, err := os.Stat(filepath.Join(mount.path, "INFO_UF2.TXT")); err != nil {
infoPaths = append(infoPaths, "/Volumes/"+volume+"/INFO_UF2.TXT") // No INFO_UF2.TXT found, which is expected on a UF2
case "windows": // filesystem.
path, err := windowsFindUSBDrive(volume, options) continue
if err == nil { }
infoPaths = append(infoPaths, path+"/INFO_UF2.TXT") // Found the filesystem, so flash the device!
return moveFile(tmppath, filepath.Join(mount.path, "flash.uf2"))
} }
} }
time.Sleep(500 * time.Millisecond)
} }
return errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]")
d, err := locateDevice(volumes, infoPaths, options.Timeout)
if err != nil {
return err
}
return moveFile(tmppath, filepath.Dir(d)+"/flash.uf2")
} }
func flashHexUsingMSD(volumes []string, tmppath string, options *compileopts.Options) error { func flashHexUsingMSD(volumes []string, tmppath string, options *compileopts.Options) error {
// find expected volume path for start := time.Now(); time.Since(start) < options.Timeout; {
destPaths := make([]string, 0, len(volumes)) // Find all mount points.
for _, volume := range volumes { mounts, err := findFATMounts(options)
switch runtime.GOOS { if err != nil {
case "linux", "freebsd": return err
fi, err := os.Stat("/run/media") }
if err != nil || !fi.IsDir() { for _, mount := range mounts {
destPaths = append(destPaths, "/media/*/"+volume) for _, volume := range volumes {
} else { if mount.name != volume {
destPaths = append(destPaths, "/run/media/*/"+volume) continue
} }
case "darwin": // Found the filesystem, so flash the device!
destPaths = append(destPaths, "/Volumes/"+volume) return moveFile(tmppath, filepath.Join(mount.path, "flash.hex"))
case "windows":
path, err := windowsFindUSBDrive(volume, options)
if err == nil {
destPaths = append(destPaths, path+"/")
} }
} }
}
d, err := locateDevice(volumes, destPaths, options.Timeout)
if err != nil {
return err
}
return moveFile(tmppath, d+"/flash.hex")
}
func locateDevice(volumes, paths []string, timeout time.Duration) (string, error) {
var d []string
var err error
for start := time.Now(); time.Since(start) < timeout; {
for _, path := range paths {
d, err = filepath.Glob(path)
if err != nil {
return "", err
}
if d != nil {
break
}
}
if d != nil {
break
}
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
} }
if d == nil { return errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]")
return "", errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]")
}
return d[0], nil
} }
func windowsFindUSBDrive(volume string, options *compileopts.Options) (string, error) { type mountPoint struct {
cmd := executeCommand(options, "wmic", name string
"PATH", "Win32_LogicalDisk", "WHERE", "VolumeName = '"+volume+"'", path string
"get", "DeviceID,VolumeName,FileSystem,DriveType") }
var out bytes.Buffer // Find all the mount points on the system that use the FAT filesystem.
cmd.Stdout = &out func findFATMounts(options *compileopts.Options) ([]mountPoint, error) {
err := cmd.Run() var points []mountPoint
if err != nil { switch runtime.GOOS {
return "", err case "darwin":
} list, err := os.ReadDir("/Volumes")
if err != nil {
for _, line := range strings.Split(out.String(), "\n") { return nil, fmt.Errorf("could not list mount points: %w", err)
words := strings.Fields(line)
if len(words) >= 3 {
if words[1] == "2" && words[2] == "FAT" {
return words[0], nil
}
} }
for _, elem := range list {
// TODO: find a way to check for the filesystem type.
// (Only return FAT filesystems).
points = append(points, mountPoint{
name: elem.Name(),
path: filepath.Join("/Volumes", elem.Name()),
})
}
sort.Slice(points, func(i, j int) bool {
return points[i].path < points[j].name
})
return points, nil
case "linux":
tab, err := os.ReadFile("/proc/mounts") // symlink to /proc/self/mounts on my system
if err != nil {
return nil, fmt.Errorf("could not list mount points: %w", err)
}
for _, line := range strings.Split(string(tab), "\n") {
fields := strings.Fields(line)
if len(fields) <= 2 {
continue
}
fstype := fields[2]
if fstype != "vfat" {
continue
}
points = append(points, mountPoint{
name: filepath.Base(fields[1]),
path: fields[1],
})
}
return points, nil
case "windows":
// Obtain a list of all currently mounted volumes.
cmd := executeCommand(options, "wmic",
"PATH", "Win32_LogicalDisk",
"get", "DeviceID,VolumeName,FileSystem,DriveType")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, fmt.Errorf("could not list mount points: %w", err)
}
// Extract data to convert to a []mountPoint slice.
for _, line := range strings.Split(out.String(), "\n") {
words := strings.Fields(line)
if len(words) < 3 {
continue
}
if words[1] != "2" || words[2] != "FAT" {
// - DriveType 2 is removable (which we're looking for).
// - We only want to return FAT filesystems.
continue
}
points = append(points, mountPoint{
name: words[3],
path: words[0],
})
}
return points, nil
default:
return nil, fmt.Errorf("unknown GOOS for listing mount points: %s", runtime.GOOS)
} }
return "", errors.New("unable to locate a USB device to be flashed")
} }
// getDefaultPort returns the default serial port depending on the operating system. // getDefaultPort returns the default serial port depending on the operating system.