| // SPDX-License-Identifier: BSD-3-Clause |
| //go:build linux |
| |
| package disk |
| |
| import ( |
| "bufio" |
| "context" |
| "errors" |
| "fmt" |
| "os" |
| "path" |
| "path/filepath" |
| "slices" |
| "strconv" |
| "strings" |
| |
| "golang.org/x/sys/unix" |
| |
| "github.com/shirou/gopsutil/v4/internal/common" |
| ) |
| |
| const ( |
| sectorSize = 512 |
| ) |
| |
| const ( |
| // man statfs |
| ADFS_SUPER_MAGIC = 0xadf5 |
| AFFS_SUPER_MAGIC = 0xADFF |
| BDEVFS_MAGIC = 0x62646576 |
| BEFS_SUPER_MAGIC = 0x42465331 |
| BFS_MAGIC = 0x1BADFACE |
| BINFMTFS_MAGIC = 0x42494e4d |
| BTRFS_SUPER_MAGIC = 0x9123683E |
| CGROUP_SUPER_MAGIC = 0x27e0eb |
| CIFS_MAGIC_NUMBER = 0xFF534D42 |
| CODA_SUPER_MAGIC = 0x73757245 |
| COH_SUPER_MAGIC = 0x012FF7B7 |
| CRAMFS_MAGIC = 0x28cd3d45 |
| DEBUGFS_MAGIC = 0x64626720 |
| DEVFS_SUPER_MAGIC = 0x1373 |
| DEVPTS_SUPER_MAGIC = 0x1cd1 |
| EFIVARFS_MAGIC = 0xde5e81e4 |
| EFS_SUPER_MAGIC = 0x00414A53 |
| EXT_SUPER_MAGIC = 0x137D |
| EXT2_OLD_SUPER_MAGIC = 0xEF51 |
| EXT2_SUPER_MAGIC = 0xEF53 |
| EXT3_SUPER_MAGIC = 0xEF53 |
| EXT4_SUPER_MAGIC = 0xEF53 |
| FUSE_SUPER_MAGIC = 0x65735546 |
| FUTEXFS_SUPER_MAGIC = 0xBAD1DEA |
| HFS_SUPER_MAGIC = 0x4244 |
| HFSPLUS_SUPER_MAGIC = 0x482b |
| HOSTFS_SUPER_MAGIC = 0x00c0ffee |
| HPFS_SUPER_MAGIC = 0xF995E849 |
| HUGETLBFS_MAGIC = 0x958458f6 |
| ISOFS_SUPER_MAGIC = 0x9660 |
| JFFS2_SUPER_MAGIC = 0x72b6 |
| JFS_SUPER_MAGIC = 0x3153464a |
| MINIX_SUPER_MAGIC = 0x137F /* orig. minix */ |
| MINIX_SUPER_MAGIC2 = 0x138F /* 30 char minix */ |
| MINIX2_SUPER_MAGIC = 0x2468 /* minix V2 */ |
| MINIX2_SUPER_MAGIC2 = 0x2478 /* minix V2, 30 char names */ |
| MINIX3_SUPER_MAGIC = 0x4d5a /* minix V3 fs, 60 char names */ |
| MQUEUE_MAGIC = 0x19800202 |
| MSDOS_SUPER_MAGIC = 0x4d44 |
| NCP_SUPER_MAGIC = 0x564c |
| NFS_SUPER_MAGIC = 0x6969 |
| NILFS_SUPER_MAGIC = 0x3434 |
| NTFS_SB_MAGIC = 0x5346544e |
| OCFS2_SUPER_MAGIC = 0x7461636f |
| OPENPROM_SUPER_MAGIC = 0x9fa1 |
| PIPEFS_MAGIC = 0x50495045 |
| PROC_SUPER_MAGIC = 0x9fa0 |
| PSTOREFS_MAGIC = 0x6165676C |
| QNX4_SUPER_MAGIC = 0x002f |
| QNX6_SUPER_MAGIC = 0x68191122 |
| RAMFS_MAGIC = 0x858458f6 |
| REISERFS_SUPER_MAGIC = 0x52654973 |
| ROMFS_MAGIC = 0x7275 |
| SELINUX_MAGIC = 0xf97cff8c |
| SMACK_MAGIC = 0x43415d53 |
| SMB_SUPER_MAGIC = 0x517B |
| SOCKFS_MAGIC = 0x534F434B |
| SQUASHFS_MAGIC = 0x73717368 |
| SYSFS_MAGIC = 0x62656572 |
| SYSV2_SUPER_MAGIC = 0x012FF7B6 |
| SYSV4_SUPER_MAGIC = 0x012FF7B5 |
| TMPFS_MAGIC = 0x01021994 |
| UDF_SUPER_MAGIC = 0x15013346 |
| UFS_MAGIC = 0x00011954 |
| USBDEVICE_SUPER_MAGIC = 0x9fa2 |
| V9FS_MAGIC = 0x01021997 |
| VXFS_SUPER_MAGIC = 0xa501FCF5 |
| XENFS_SUPER_MAGIC = 0xabba1974 |
| XENIX_SUPER_MAGIC = 0x012FF7B4 |
| XFS_SUPER_MAGIC = 0x58465342 |
| _XIAFS_SUPER_MAGIC = 0x012FD16D |
| |
| AFS_SUPER_MAGIC = 0x5346414F |
| AUFS_SUPER_MAGIC = 0x61756673 |
| ANON_INODE_FS_SUPER_MAGIC = 0x09041934 |
| BCACHEFS_SUPER_MAGIC = 0xCA451A4E |
| BPF_FS_MAGIC = 0xCAFE4A11 |
| CEPH_SUPER_MAGIC = 0x00C36400 |
| CGROUP2_SUPER_MAGIC = 0x63677270 |
| CONFIGFS_MAGIC = 0x62656570 |
| ECRYPTFS_SUPER_MAGIC = 0xF15F |
| F2FS_SUPER_MAGIC = 0xF2F52010 |
| FAT_SUPER_MAGIC = 0x4006 |
| FHGFS_SUPER_MAGIC = 0x19830326 |
| FUSEBLK_SUPER_MAGIC = 0x65735546 |
| FUSECTL_SUPER_MAGIC = 0x65735543 |
| GFS_SUPER_MAGIC = 0x1161970 |
| GPFS_SUPER_MAGIC = 0x47504653 |
| MTD_INODE_FS_SUPER_MAGIC = 0x11307854 |
| INOTIFYFS_SUPER_MAGIC = 0x2BAD1DEA |
| ISOFS_R_WIN_SUPER_MAGIC = 0x4004 |
| ISOFS_WIN_SUPER_MAGIC = 0x4000 |
| JFFS_SUPER_MAGIC = 0x07C0 |
| KAFS_SUPER_MAGIC = 0x6B414653 |
| LUSTRE_SUPER_MAGIC = 0x0BD00BD0 |
| NFSD_SUPER_MAGIC = 0x6E667364 |
| NSFS_MAGIC = 0x6E736673 |
| PANFS_SUPER_MAGIC = 0xAAD7AAEA |
| RPC_PIPEFS_SUPER_MAGIC = 0x67596969 |
| SECURITYFS_SUPER_MAGIC = 0x73636673 |
| TRACEFS_MAGIC = 0x74726163 |
| UFS_BYTESWAPPED_SUPER_MAGIC = 0x54190100 |
| VMHGFS_SUPER_MAGIC = 0xBACBACBC |
| VZFS_SUPER_MAGIC = 0x565A4653 |
| ZFS_SUPER_MAGIC = 0x2FC12FC1 |
| ) |
| |
| // coreutils/src/stat.c |
| var fsTypeMap = map[int64]string{ |
| ADFS_SUPER_MAGIC: "adfs", /* 0xADF5 local */ |
| AFFS_SUPER_MAGIC: "affs", /* 0xADFF local */ |
| AFS_SUPER_MAGIC: "afs", /* 0x5346414F remote */ |
| ANON_INODE_FS_SUPER_MAGIC: "anon-inode FS", /* 0x09041934 local */ |
| AUFS_SUPER_MAGIC: "aufs", /* 0x61756673 remote */ |
| // AUTOFS_SUPER_MAGIC: "autofs", /* 0x0187 local */ |
| BCACHEFS_SUPER_MAGIC: "bcachefs", /* 0xCA451A4E local */ |
| BEFS_SUPER_MAGIC: "befs", /* 0x42465331 local */ |
| BDEVFS_MAGIC: "bdevfs", /* 0x62646576 local */ |
| BFS_MAGIC: "bfs", /* 0x1BADFACE local */ |
| BINFMTFS_MAGIC: "binfmt_misc", /* 0x42494E4D local */ |
| BPF_FS_MAGIC: "bpf", /* 0xCAFE4A11 local */ |
| BTRFS_SUPER_MAGIC: "btrfs", /* 0x9123683E local */ |
| CEPH_SUPER_MAGIC: "ceph", /* 0x00C36400 remote */ |
| CGROUP_SUPER_MAGIC: "cgroupfs", /* 0x0027E0EB local */ |
| CGROUP2_SUPER_MAGIC: "cgroup2fs", /* 0x63677270 local */ |
| CIFS_MAGIC_NUMBER: "cifs", /* 0xFF534D42 remote */ |
| CODA_SUPER_MAGIC: "coda", /* 0x73757245 remote */ |
| COH_SUPER_MAGIC: "coh", /* 0x012FF7B7 local */ |
| CONFIGFS_MAGIC: "configfs", /* 0x62656570 local */ |
| CRAMFS_MAGIC: "cramfs", /* 0x28CD3D45 local */ |
| DEBUGFS_MAGIC: "debugfs", /* 0x64626720 local */ |
| DEVFS_SUPER_MAGIC: "devfs", /* 0x1373 local */ |
| DEVPTS_SUPER_MAGIC: "devpts", /* 0x1CD1 local */ |
| ECRYPTFS_SUPER_MAGIC: "ecryptfs", /* 0xF15F local */ |
| EFIVARFS_MAGIC: "efivarfs", /* 0xDE5E81E4 local */ |
| EFS_SUPER_MAGIC: "efs", /* 0x00414A53 local */ |
| EXT_SUPER_MAGIC: "ext", /* 0x137D local */ |
| EXT2_SUPER_MAGIC: "ext2/ext3", /* 0xEF53 local */ |
| EXT2_OLD_SUPER_MAGIC: "ext2", /* 0xEF51 local */ |
| F2FS_SUPER_MAGIC: "f2fs", /* 0xF2F52010 local */ |
| FAT_SUPER_MAGIC: "fat", /* 0x4006 local */ |
| FHGFS_SUPER_MAGIC: "fhgfs", /* 0x19830326 remote */ |
| FUSEBLK_SUPER_MAGIC: "fuseblk", /* 0x65735546 remote */ |
| FUSECTL_SUPER_MAGIC: "fusectl", /* 0x65735543 remote */ |
| FUTEXFS_SUPER_MAGIC: "futexfs", /* 0x0BAD1DEA local */ |
| GFS_SUPER_MAGIC: "gfs/gfs2", /* 0x1161970 remote */ |
| GPFS_SUPER_MAGIC: "gpfs", /* 0x47504653 remote */ |
| HFS_SUPER_MAGIC: "hfs", /* 0x4244 local */ |
| HFSPLUS_SUPER_MAGIC: "hfsplus", /* 0x482b local */ |
| HPFS_SUPER_MAGIC: "hpfs", /* 0xF995E849 local */ |
| HUGETLBFS_MAGIC: "hugetlbfs", /* 0x958458F6 local */ |
| MTD_INODE_FS_SUPER_MAGIC: "inodefs", /* 0x11307854 local */ |
| INOTIFYFS_SUPER_MAGIC: "inotifyfs", /* 0x2BAD1DEA local */ |
| ISOFS_SUPER_MAGIC: "isofs", /* 0x9660 local */ |
| ISOFS_R_WIN_SUPER_MAGIC: "isofs", /* 0x4004 local */ |
| ISOFS_WIN_SUPER_MAGIC: "isofs", /* 0x4000 local */ |
| JFFS_SUPER_MAGIC: "jffs", /* 0x07C0 local */ |
| JFFS2_SUPER_MAGIC: "jffs2", /* 0x72B6 local */ |
| JFS_SUPER_MAGIC: "jfs", /* 0x3153464A local */ |
| KAFS_SUPER_MAGIC: "k-afs", /* 0x6B414653 remote */ |
| LUSTRE_SUPER_MAGIC: "lustre", /* 0x0BD00BD0 remote */ |
| MINIX_SUPER_MAGIC: "minix", /* 0x137F local */ |
| MINIX_SUPER_MAGIC2: "minix (30 char.)", /* 0x138F local */ |
| MINIX2_SUPER_MAGIC: "minix v2", /* 0x2468 local */ |
| MINIX2_SUPER_MAGIC2: "minix v2 (30 char.)", /* 0x2478 local */ |
| MINIX3_SUPER_MAGIC: "minix3", /* 0x4D5A local */ |
| MQUEUE_MAGIC: "mqueue", /* 0x19800202 local */ |
| MSDOS_SUPER_MAGIC: "msdos", /* 0x4D44 local */ |
| NCP_SUPER_MAGIC: "novell", /* 0x564C remote */ |
| NFS_SUPER_MAGIC: "nfs", /* 0x6969 remote */ |
| NFSD_SUPER_MAGIC: "nfsd", /* 0x6E667364 remote */ |
| NILFS_SUPER_MAGIC: "nilfs", /* 0x3434 local */ |
| NSFS_MAGIC: "nsfs", /* 0x6E736673 local */ |
| NTFS_SB_MAGIC: "ntfs", /* 0x5346544E local */ |
| OPENPROM_SUPER_MAGIC: "openprom", /* 0x9FA1 local */ |
| OCFS2_SUPER_MAGIC: "ocfs2", /* 0x7461636f remote */ |
| PANFS_SUPER_MAGIC: "panfs", /* 0xAAD7AAEA remote */ |
| PIPEFS_MAGIC: "pipefs", /* 0x50495045 remote */ |
| PROC_SUPER_MAGIC: "proc", /* 0x9FA0 local */ |
| PSTOREFS_MAGIC: "pstorefs", /* 0x6165676C local */ |
| QNX4_SUPER_MAGIC: "qnx4", /* 0x002F local */ |
| QNX6_SUPER_MAGIC: "qnx6", /* 0x68191122 local */ |
| RAMFS_MAGIC: "ramfs", /* 0x858458F6 local */ |
| REISERFS_SUPER_MAGIC: "reiserfs", /* 0x52654973 local */ |
| ROMFS_MAGIC: "romfs", /* 0x7275 local */ |
| RPC_PIPEFS_SUPER_MAGIC: "rpc_pipefs", /* 0x67596969 local */ |
| SECURITYFS_SUPER_MAGIC: "securityfs", /* 0x73636673 local */ |
| SELINUX_MAGIC: "selinux", /* 0xF97CFF8C local */ |
| SMB_SUPER_MAGIC: "smb", /* 0x517B remote */ |
| SOCKFS_MAGIC: "sockfs", /* 0x534F434B local */ |
| SQUASHFS_MAGIC: "squashfs", /* 0x73717368 local */ |
| SYSFS_MAGIC: "sysfs", /* 0x62656572 local */ |
| SYSV2_SUPER_MAGIC: "sysv2", /* 0x012FF7B6 local */ |
| SYSV4_SUPER_MAGIC: "sysv4", /* 0x012FF7B5 local */ |
| TMPFS_MAGIC: "tmpfs", /* 0x01021994 local */ |
| TRACEFS_MAGIC: "tracefs", /* 0x74726163 local */ |
| UDF_SUPER_MAGIC: "udf", /* 0x15013346 local */ |
| UFS_MAGIC: "ufs", /* 0x00011954 local */ |
| UFS_BYTESWAPPED_SUPER_MAGIC: "ufs", /* 0x54190100 local */ |
| USBDEVICE_SUPER_MAGIC: "usbdevfs", /* 0x9FA2 local */ |
| V9FS_MAGIC: "v9fs", /* 0x01021997 local */ |
| VMHGFS_SUPER_MAGIC: "vmhgfs", /* 0xBACBACBC remote */ |
| VXFS_SUPER_MAGIC: "vxfs", /* 0xA501FCF5 local */ |
| VZFS_SUPER_MAGIC: "vzfs", /* 0x565A4653 local */ |
| XENFS_SUPER_MAGIC: "xenfs", /* 0xABBA1974 local */ |
| XENIX_SUPER_MAGIC: "xenix", /* 0x012FF7B4 local */ |
| XFS_SUPER_MAGIC: "xfs", /* 0x58465342 local */ |
| _XIAFS_SUPER_MAGIC: "xia", /* 0x012FD16D local */ |
| ZFS_SUPER_MAGIC: "zfs", /* 0x2FC12FC1 local */ |
| } |
| |
| // readMountFile reads mountinfo or mounts file under the specified root path |
| // (eg, /proc/1, /proc/self, etc) |
| func readMountFile(root string) (lines []string, useMounts bool, filename string, err error) { |
| filename = path.Join(root, "mountinfo") |
| lines, err = common.ReadLines(filename) |
| if err != nil { |
| var pathErr *os.PathError |
| if !errors.As(err, &pathErr) { |
| return lines, useMounts, filename, err |
| } |
| // if kernel does not support 1/mountinfo, fallback to 1/mounts (<2.6.26) |
| useMounts = true |
| filename = path.Join(root, "mounts") |
| lines, err = common.ReadLines(filename) |
| if err != nil { |
| return lines, useMounts, filename, err |
| } |
| return lines, useMounts, filename, err |
| } |
| return lines, useMounts, filename, err |
| } |
| |
| func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, error) { |
| // by default, try "/proc/1/..." first |
| root := common.HostProcWithContext(ctx, path.Join("1")) |
| |
| // force preference for dirname of HOST_PROC_MOUNTINFO, if set #1271 |
| hpmPath := common.HostProcMountInfoWithContext(ctx) |
| if hpmPath != "" { |
| root = filepath.Dir(hpmPath) |
| } |
| |
| lines, useMounts, filename, err := readMountFile(root) |
| if err != nil { |
| if hpmPath != "" { // don't fallback with HOST_PROC_MOUNTINFO |
| return nil, err |
| } |
| // fallback to "/proc/self/..." #1159 |
| lines, useMounts, filename, err = readMountFile(common.HostProcWithContext(ctx, path.Join("self"))) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| fs, err := getFileSystems(ctx) |
| if err != nil && !all { |
| return nil, err |
| } |
| |
| var ret []PartitionStat |
| if useMounts { // use mounts file |
| ret = parseFieldsOnMounts(lines, all, fs) |
| return ret, nil |
| } |
| |
| // use mountinfo |
| ret, err = parseFieldsOnMountinfo(ctx, lines, all, 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 { |
| fields := strings.Fields(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) |
| } |
| |
| return ret |
| } |
| |
| func parseFieldsOnMountinfo(ctx context.Context, lines []string, all bool, filename string) ([]PartitionStat, error) { |
| ret := make([]PartitionStat, 0, len(lines)) |
| seenDevIDs := make(map[string]string) |
| |
| for _, line := range lines { |
| // See proc_pid_mountinfo(5) (proc(5) on EL) |
| // A line of <filename> (<procfs root>/<pid>/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) |
| // Documentation is unclear if (6) is optional/may not be present, so it is conditionally parsed if present. |
| // (7) is optional and may not be present, but this function does not currently use it. |
| // Documentation is unclear if (11) is optional or not but this function does not currently use it. |
| |
| // split the mountinfo line by the separator hyphen (`(8)` above) |
| parts := strings.SplitN(line, " - ", 2) |
| if len(parts) != 2 { |
| return nil, fmt.Errorf("found invalid mountinfo line in file %s (bad parts len): %s ", filename, line) |
| } |
| |
| fields := strings.Fields(parts[0]) |
| if len(fields) < 5 { // field (7) is optional, field (6) may(?) be optional |
| return nil, fmt.Errorf("found invalid mountinfo line in file %s (bad fields(1) len): %s ", filename, line) |
| } |
| blockDeviceID := fields[2] |
| rootDir := fields[3] |
| mountPoint := fields[4] |
| mountOpts := []string{} |
| if len(fields) >= 6 { |
| mountOpts = strings.Split(fields[5], ",") |
| } |
| fields = strings.Fields(parts[1]) |
| if len(fields) < 2 { |
| return nil, fmt.Errorf("found invalid mountinfo line in file %s (bad fields(2) len): %s ", filename, line) |
| } |
| fsType := fields[0] |
| mntSrc := fields[1] |
| superOpts := []string{} |
| if len(fields) >= 3 { |
| superOpts = strings.Split(fields[2], ",") |
| } |
| // Since Linux 2.6.16, read-only can be set independently on the |
| // mount point and the filesystem superblock. The mount is writable |
| // only when both option sets are read-write. |
| mountOpts = effectiveMountOpts(mountOpts, superOpts) |
| isBind := false |
| // Per fstab(5), the device can be any string for non-storage-backed filesystems. |
| if !all && !strings.HasPrefix(mntSrc, "/") { |
| continue |
| } |
| // Some virtual/non-storage filesystems do still have real sources (e.g. nsfs binds), |
| // but need to use the "root" field (field 4) instead of the "source" field (field 10). |
| // The "source" field is actually "*filesystem-specific" information". |
| device := rootDir |
| if strings.HasPrefix(mntSrc, "/") { |
| device = mntSrc |
| } else if rootDir == "/" { |
| device = mntSrc |
| } |
| |
| // Track device paths by block device ID to resolve bind mounts. |
| // A bind mount is identified by rootDir != "/" (mounting a subdirectory of a filesystem). |
| // Only apply bind detection for storage-backed filesystems (mntSrc starts with "/"), |
| // since virtual filesystems (e.g. nsfs) use rootDir for non-path identifiers. |
| if firstDev, ok := seenDevIDs[blockDeviceID]; ok { |
| // Same block device seen before - use the original device path. |
| device = firstDev |
| } |
| if strings.HasPrefix(mntSrc, "/") && rootDir != "/" { |
| isBind = true |
| mountOpts = append(mountOpts, "bind") |
| } |
| if _, ok := seenDevIDs[blockDeviceID]; !ok { |
| seenDevIDs[blockDeviceID] = device |
| } |
| |
| if !all && isBind { |
| continue |
| } |
| |
| d := PartitionStat{ |
| Device: device, |
| Mountpoint: unescapeFstab(mountPoint), |
| Fstype: fsType, |
| Opts: mountOpts, |
| } |
| |
| 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) |
| } |
| } |
| ret = append(ret, d) |
| } |
| return ret, nil |
| } |
| |
| func effectiveMountOpts(mountOpts, superOpts []string) []string { |
| mode := effectiveReadWriteMode(mountOpts, superOpts) |
| if mode == "" { |
| return mountOpts |
| } |
| |
| updated := make([]string, 0, len(mountOpts)+1) |
| modeSet := false |
| for _, opt := range mountOpts { |
| if opt == "ro" || opt == "rw" { |
| if !modeSet { |
| updated = append(updated, mode) |
| modeSet = true |
| } |
| continue |
| } |
| updated = append(updated, opt) |
| } |
| if !modeSet { |
| updated = append([]string{mode}, updated...) |
| } |
| return updated |
| } |
| |
| func effectiveReadWriteMode(mountOpts, superOpts []string) string { |
| if slices.Contains(mountOpts, "ro") || slices.Contains(superOpts, "ro") { |
| return "ro" |
| } |
| if slices.Contains(mountOpts, "rw") && slices.Contains(superOpts, "rw") { |
| return "rw" |
| } |
| return "" |
| } |
| |
| // getFileSystems returns supported filesystems from /proc/filesystems |
| func getFileSystems(ctx context.Context) ([]string, error) { |
| filename := common.HostProcWithContext(ctx, "filesystems") |
| lines, err := common.ReadLines(filename) |
| if err != nil { |
| return nil, err |
| } |
| var ret []string |
| for _, line := range lines { |
| if !strings.HasPrefix(line, "nodev") { |
| ret = append(ret, strings.TrimSpace(line)) |
| continue |
| } |
| t := strings.Split(line, "\t") |
| if len(t) != 2 || t[1] != "zfs" { |
| continue |
| } |
| ret = append(ret, strings.TrimSpace(t[1])) |
| } |
| |
| return ret, nil |
| } |
| |
| func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) { |
| filename := common.HostProcWithContext(ctx, "diskstats") |
| lines, err := common.ReadLines(filename) |
| if err != nil { |
| return nil, err |
| } |
| ret := make(map[string]IOCountersStat) |
| empty := IOCountersStat{} |
| |
| // use only basename such as "/dev/sda1" to "sda1" |
| for i, name := range names { |
| names[i] = filepath.Base(name) |
| } |
| |
| for _, line := range lines { |
| fields := strings.Fields(line) |
| if len(fields) < 14 { |
| // malformed line in /proc/diskstats, avoid panic by ignoring. |
| continue |
| } |
| name := fields[2] |
| |
| if len(names) > 0 && !common.StringsHas(names, name) { |
| continue |
| } |
| |
| reads, err := strconv.ParseUint((fields[3]), 10, 64) |
| if err != nil { |
| return ret, err |
| } |
| mergedReads, err := strconv.ParseUint((fields[4]), 10, 64) |
| if err != nil { |
| return ret, err |
| } |
| rbytes, err := strconv.ParseUint((fields[5]), 10, 64) |
| if err != nil { |
| return ret, err |
| } |
| rtime, err := strconv.ParseUint((fields[6]), 10, 64) |
| if err != nil { |
| return ret, err |
| } |
| writes, err := strconv.ParseUint((fields[7]), 10, 64) |
| if err != nil { |
| return ret, err |
| } |
| mergedWrites, err := strconv.ParseUint((fields[8]), 10, 64) |
| if err != nil { |
| return ret, err |
| } |
| wbytes, err := strconv.ParseUint((fields[9]), 10, 64) |
| if err != nil { |
| return ret, err |
| } |
| wtime, err := strconv.ParseUint((fields[10]), 10, 64) |
| if err != nil { |
| return ret, err |
| } |
| iopsInProgress, err := strconv.ParseUint((fields[11]), 10, 64) |
| if err != nil { |
| return ret, err |
| } |
| iotime, err := strconv.ParseUint((fields[12]), 10, 64) |
| if err != nil { |
| return ret, err |
| } |
| weightedIO, err := strconv.ParseUint((fields[13]), 10, 64) |
| if err != nil { |
| return ret, err |
| } |
| d := IOCountersStat{ |
| ReadBytes: rbytes * sectorSize, |
| WriteBytes: wbytes * sectorSize, |
| ReadCount: reads, |
| WriteCount: writes, |
| MergedReadCount: mergedReads, |
| MergedWriteCount: mergedWrites, |
| ReadTime: rtime, |
| WriteTime: wtime, |
| IopsInProgress: iopsInProgress, |
| IoTime: iotime, |
| WeightedIO: weightedIO, |
| } |
| if d == empty { |
| continue |
| } |
| d.Name = name |
| |
| // Names passed in can be full paths (/dev/sda) or just device names (sda). |
| // Since `name` here is already a basename, re-add the /dev path. |
| // This is not ideal, but we may break the API by changing how SerialNumberWithContext |
| // works. |
| deviceName := getDeviceName(name) |
| d.SerialNumber, _ = SerialNumberWithContext(ctx, common.HostDevWithContext(ctx, deviceName)) |
| d.Label, _ = LabelWithContext(ctx, deviceName) |
| |
| ret[name] = d |
| } |
| return ret, nil |
| } |
| |
| func udevData(ctx context.Context, major, minor uint32, name string) (string, error) { |
| udevDataPath := common.HostRunWithContext(ctx, fmt.Sprintf("udev/data/b%d:%d", major, minor)) |
| if f, err := os.Open(udevDataPath); err == nil { |
| defer f.Close() |
| scanner := bufio.NewScanner(f) |
| for scanner.Scan() { |
| values := strings.SplitN(scanner.Text(), "=", 3) |
| if len(values) == 2 && values[0] == name { |
| return values[1], nil |
| } |
| } |
| return "", scanner.Err() |
| } else if !os.IsNotExist(err) { |
| return "", err |
| } |
| return "", nil |
| } |
| |
| func SerialNumberWithContext(ctx context.Context, name string) (string, error) { |
| var stat unix.Stat_t |
| if err := unix.Stat(name, &stat); err != nil { |
| return "", err |
| } |
| major := unix.Major(uint64(stat.Rdev)) |
| minor := unix.Minor(uint64(stat.Rdev)) |
| |
| sserial, _ := udevData(ctx, major, minor, "E:ID_SERIAL") |
| if sserial != "" { |
| return sserial, nil |
| } |
| |
| // Try to get the serial from sysfs, look at the disk device (minor 0) directly |
| // because if it is a partition it is not going to contain any device information |
| devicePath := common.HostSysWithContext(ctx, fmt.Sprintf("dev/block/%d:0/device", major)) |
| model, _ := os.ReadFile(filepath.Join(devicePath, "model")) |
| serial, _ := os.ReadFile(filepath.Join(devicePath, "serial")) |
| if len(model) > 0 && len(serial) > 0 { |
| return fmt.Sprintf("%s_%s", string(model), string(serial)), nil |
| } |
| return "", nil |
| } |
| |
| func LabelWithContext(ctx context.Context, name string) (string, error) { |
| // Try label based on devicemapper name |
| dmnameFilename := common.HostSysWithContext(ctx, fmt.Sprintf("block/%s/dm/name", name)) |
| // Could errors.Join errs with Go >= 1.20 |
| if common.PathExists(dmnameFilename) { |
| dmname, err := os.ReadFile(dmnameFilename) |
| if err == nil { |
| return strings.TrimSpace(string(dmname)), nil |
| } |
| } |
| // Try udev data |
| var stat unix.Stat_t |
| if err := unix.Stat(common.HostDevWithContext(ctx, name), &stat); err != nil { |
| return "", err |
| } |
| major := unix.Major(uint64(stat.Rdev)) |
| minor := unix.Minor(uint64(stat.Rdev)) |
| |
| label, err := udevData(ctx, major, minor, "E:ID_FS_LABEL") |
| if err != nil { |
| return "", err |
| } |
| return label, nil |
| } |
| |
| func getFsType(stat unix.Statfs_t) string { |
| t := int64(stat.Type) |
| ret, ok := fsTypeMap[t] |
| if !ok { |
| return "" |
| } |
| return ret |
| } |
| |
| // getDeviceName normalizes NVMe device names by converting controller notation |
| // from diskstats format (nvmeXcYnZ) to actual device format (nvmeXnZ). |
| // This handles the discrepancy where /proc/diskstats reports nvmeXcYnZ |
| // but actual device files exist as /dev/nvmeXnZ. |
| func getDeviceName(name string) string { |
| if !strings.HasPrefix(name, "nvme") { |
| return name |
| } |
| |
| // Look for controller notation pattern: nvmeXcYnZ |
| if cIdx := strings.Index(name, "c"); cIdx > 4 { // "nvme" is 4 chars |
| if nIdx := strings.Index(name[cIdx:], "n"); nIdx > 0 { |
| // Convert nvmeXcYnZ to nvmeXnZ by removing "cY" part |
| return name[:cIdx] + name[cIdx+nIdx:] |
| } |
| } |
| |
| return name |
| } |