diff --git a/disk/disk_linux.go b/disk/disk_linux.go index 018de7e..3186060 100644 --- a/disk/disk_linux.go +++ b/disk/disk_linux.go @@ -285,81 +285,99 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro return nil, err } - ret := make([]PartitionStat, 0, len(lines)) + var ret []PartitionStat + if useMounts { // use mounts file + ret = parseFieldsOnMounts(lines, all, fs) + } else { // use mountinfo + ret, err = parseFieldsOnMountinfo(ctx, lines, all, fs, filename) + if err != nil { + return nil, fmt.Errorf("error parsing mountinfo file %s: %w", filename, err) + } + } + return ret, nil +} + +func parseFieldsOnMounts(lines []string, all bool, fs []string) []PartitionStat { + ret := make([]PartitionStat, 0, len(lines)) for _, line := range lines { - var d PartitionStat - if useMounts { - fields := strings.Fields(line) - - d = PartitionStat{ - Device: fields[0], - Mountpoint: unescapeFstab(fields[1]), - Fstype: fields[2], - Opts: strings.Split(fields[3], ","), - } + fields := strings.Fields(line) - if !all { - if d.Device == "none" || !common.StringsHas(fs, d.Fstype) { - continue - } - } - } else { - // a line of 1/mountinfo has the following structure: - // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue - // (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) - - // split the mountinfo line by the separator hyphen - parts := strings.Split(line, " - ") - if len(parts) != 2 { - return nil, fmt.Errorf("found invalid mountinfo line in file %s: %s ", filename, line) + d := PartitionStat{ + Device: fields[0], + Mountpoint: unescapeFstab(fields[1]), + Fstype: fields[2], + Opts: strings.Split(fields[3], ","), + } + + if !all { + if d.Device == "none" || !common.StringsHas(fs, d.Fstype) { + continue } + } + ret = append(ret, d) + } - fields := strings.Fields(parts[0]) - blockDeviceID := fields[2] - mountPoint := fields[4] - mountOpts := strings.Split(fields[5], ",") + return ret +} - if rootDir := fields[3]; rootDir != "" && rootDir != "/" { - mountOpts = append(mountOpts, "bind") - } +func parseFieldsOnMountinfo(ctx context.Context, lines []string, all bool, fs []string, filename string) ([]PartitionStat, error) { + ret := make([]PartitionStat, 0, len(lines)) - fields = strings.Fields(parts[1]) - fstype := fields[0] - device := fields[1] + for _, line := range lines { + // a line of 1/mountinfo has the following structure: + // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue + // (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) + + // split the mountinfo line by the separator hyphen + parts := strings.Split(line, " - ") + if len(parts) != 2 { + return nil, fmt.Errorf("found invalid mountinfo line in file %s: %s ", filename, line) + } - d = PartitionStat{ - Device: device, - Mountpoint: unescapeFstab(mountPoint), - Fstype: fstype, - Opts: mountOpts, - } + fields := strings.Fields(parts[0]) + blockDeviceID := fields[2] + mountPoint := fields[4] + mountOpts := strings.Split(fields[5], ",") - if !all { - if d.Device == "none" || !common.StringsHas(fs, d.Fstype) { - continue - } + if rootDir := fields[3]; rootDir != "" && rootDir != "/" { + mountOpts = append(mountOpts, "bind") + } + + fields = strings.Fields(parts[1]) + fstype := fields[0] + device := fields[1] + + d := PartitionStat{ + Device: device, + Mountpoint: unescapeFstab(mountPoint), + Fstype: fstype, + Opts: mountOpts, + } + + if !all { + if d.Device == "none" || !common.StringsHas(fs, d.Fstype) { + continue } + } - if strings.HasPrefix(d.Device, "/dev/mapper/") { - devpath, err := filepath.EvalSymlinks(common.HostDevWithContext(ctx, strings.Replace(d.Device, "/dev", "", 1))) - if err == nil { - d.Device = devpath - } + if strings.HasPrefix(d.Device, "/dev/mapper/") { + devpath, err := filepath.EvalSymlinks(common.HostDevWithContext(ctx, strings.Replace(d.Device, "/dev", "", 1))) + if err == nil { + d.Device = devpath } + } - // /dev/root is not the real device name - // so we get the real device name from its major/minor number - if d.Device == "/dev/root" { - devpath, err := os.Readlink(common.HostSysWithContext(ctx, "/dev/block/"+blockDeviceID)) - if err == nil { - d.Device = strings.Replace(d.Device, "root", filepath.Base(devpath), 1) - } + // /dev/root is not the real device name + // so we get the real device name from its major/minor number + if d.Device == "/dev/root" { + devpath, err := os.Readlink(common.HostSysWithContext(ctx, "/dev/block/"+blockDeviceID)) + if err == nil { + d.Device = strings.Replace(d.Device, "root", filepath.Base(devpath), 1) } } ret = append(ret, d) } - return ret, nil } diff --git a/disk/disk_linux_test.go b/disk/disk_linux_test.go new file mode 100644 index 0000000..932d08f --- /dev/null +++ b/disk/disk_linux_test.go @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build linux + +package disk + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_parseFieldsOnMountinfo(t *testing.T) { + fs := []string{"sysfs", "tmpfs"} + + lines := []string{ + "111 80 0:22 / /sys rw,nosuid,nodev,noexec,noatime shared:15 - sysfs sysfs rw", + "114 80 0:61 / /run rw,nosuid,nodev shared:18 - tmpfs none rw,mode=755", + } + + cases := map[string]struct { + all bool + expect []PartitionStat + }{ + "all": { + all: true, + expect: []PartitionStat{ + {Device: "sysfs", Mountpoint: "/sys", Fstype: "sysfs", Opts: []string{"rw", "nosuid", "nodev", "noexec", "noatime"}}, + {Device: "none", Mountpoint: "/run", Fstype: "tmpfs", Opts: []string{"rw", "nosuid", "nodev"}}, + }, + }, + "not all": { + all: false, + expect: []PartitionStat{ + {Device: "sysfs", Mountpoint: "/sys", Fstype: "sysfs", Opts: []string{"rw", "nosuid", "nodev", "noexec", "noatime"}}, + }, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + actual, err := parseFieldsOnMountinfo(context.Background(), lines, c.all, fs, "") + require.NoError(t, err) + assert.Equal(t, c.expect, actual) + }) + } +} + +func Test_parseFieldsOnMounts(t *testing.T) { + fs := []string{"sysfs", "tmpfs"} + + lines := []string{ + "sysfs /sys sysfs rw,nosuid,nodev,noexec,noatime 0 0", + "none /run tmpfs rw,nosuid,nodev,mode=755 0 0", + } + + cases := map[string]struct { + all bool + expect []PartitionStat + }{ + "all": { + all: true, + expect: []PartitionStat{ + {Device: "sysfs", Mountpoint: "/sys", Fstype: "sysfs", Opts: []string{"rw", "nosuid", "nodev", "noexec", "noatime"}}, + {Device: "none", Mountpoint: "/run", Fstype: "tmpfs", Opts: []string{"rw", "nosuid", "nodev", "mode=755"}}, + }, + }, + "not all": { + all: false, + expect: []PartitionStat{ + {Device: "sysfs", Mountpoint: "/sys", Fstype: "sysfs", Opts: []string{"rw", "nosuid", "nodev", "noexec", "noatime"}}, + }, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + actual := parseFieldsOnMounts(lines, c.all, fs) + assert.Equal(t, c.expect, actual) + }) + } +}