pkg/device/dev_linux.go

Summary

Maintainability
A
1 hr
Test Coverage
package device

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strings"

    "golang.org/x/sys/unix"
)

// LinuxDevicesInfoGetter returns info for Linux devices
type LinuxDevicesInfoGetter struct {
    MountsPath string
}

// Getter is current instance of DevicesInfoGetter
var Getter DevicesInfoGetter = LinuxDevicesInfoGetter{MountsPath: "/proc/mounts"}

// GetMounts returns all mounted filesystems from /proc/mounts
func (t LinuxDevicesInfoGetter) GetMounts() (Devices, error) {
    file, err := os.Open(t.MountsPath)
    if err != nil {
        return nil, err
    }

    devices, err := readMountsFile(file)
    if err != nil {
        if cerr := file.Close(); cerr != nil {
            return nil, fmt.Errorf("%w; %s", err, cerr.Error())
        }
        return nil, err
    }
    if err := file.Close(); err != nil {
        return nil, err
    }
    return devices, nil
}

// GetDevicesInfo returns result of GetMounts with usage info about mounted devices (by calling Statfs syscall)
func (t LinuxDevicesInfoGetter) GetDevicesInfo() (Devices, error) {
    mounts, err := t.GetMounts()
    if err != nil {
        return nil, err
    }

    return processMounts(mounts, false)
}

func readMountsFile(file io.Reader) (Devices, error) {
    mounts := Devices{}

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        parts := strings.Fields(line)

        device := &Device{
            Name:       parts[0],
            MountPoint: unescapeString(parts[1]),
            Fstype:     parts[2],
        }
        mounts = append(mounts, device)
    }

    if err := scanner.Err(); err != nil {
        return nil, err
    }

    return mounts, nil
}

func processMounts(mounts Devices, ignoreErrors bool) (Devices, error) {
    devices := Devices{}

    for _, mount := range mounts {
        if strings.Contains(mount.MountPoint, "/snap/") {
            continue
        }

        if strings.HasPrefix(mount.Name, "/dev") ||
            mount.Fstype == "zfs" ||
            mount.Fstype == "nfs" ||
            mount.Fstype == "nfs4" {
            info := &unix.Statfs_t{}
            err := unix.Statfs(mount.MountPoint, info)
            if err != nil && !ignoreErrors {
                return nil, err
            }

            mount.Size = int64(info.Bsize) * int64(info.Blocks)
            mount.Free = int64(info.Bsize) * int64(info.Bavail)

            devices = append(devices, mount)
        }
    }

    return devices, nil
}

func unescapeString(str string) string {
    return strings.ReplaceAll(str, "\\040", " ")
}