diff --git a/disk/disk.go b/disk/disk.go index 0d4b25345..992f2f692 100644 --- a/disk/disk.go +++ b/disk/disk.go @@ -96,3 +96,7 @@ func SerialNumber(name string) (string, error) { func Label(name string) (string, error) { return LabelWithContext(context.Background(), name) } + +func Model(name string) (map[string]string, error) { + return ModelWithContext(context.Background(), name) +} \ No newline at end of file diff --git a/disk/disk_darwin.go b/disk/disk_darwin.go index 933cb0454..7e875d0e4 100644 --- a/disk/disk_darwin.go +++ b/disk/disk_darwin.go @@ -5,9 +5,11 @@ package disk import ( "context" - + "errors" "github.com/shirou/gopsutil/v3/internal/common" "golang.org/x/sys/unix" + "os/exec" + "strings" ) // PartitionsWithContext returns disk partition. @@ -91,3 +93,46 @@ func SerialNumberWithContext(ctx context.Context, name string) (string, error) { func LabelWithContext(ctx context.Context, name string) (string, error) { return "", common.ErrNotImplementedError } + +func ModelWithContext(ctx context.Context, name string) (map[string]string, error) { + out, err := exec.Command("diskutil", "list").Output() + if err != nil { + return nil, errors.New("failed to execute 'diskutil list' command: " + err.Error()) + } + outStr := string(out) + lines := strings.Split(outStr, "\n") + diskMap := make(map[string]string) + for _, line := range lines { + if strings.HasPrefix(line, "/dev/") { + fields := strings.Fields(line) + if len(fields) >= 1 { + partitionPath := fields[0] + if name != "" && !strings.Contains(partitionPath, name) { + continue + } + infoOut, err := exec.Command("diskutil", "info", partitionPath).Output() + if err != nil { + return nil, errors.New("failed to execute 'diskutil info' command: " + err.Error()) + } + infoOutStr := string(infoOut) + infoLines := strings.Split(infoOutStr, "\n") + var diskName, model string + for _, infoLine := range infoLines { + if strings.HasPrefix(infoLine, " Device Node:") { + diskName = strings.TrimSpace(strings.TrimPrefix(infoLine, " Device Node:")) + } + if strings.HasPrefix(infoLine, " Device / Media Name:") { + model = strings.TrimSpace(strings.TrimPrefix(infoLine, " Device / Media Name:")) + } + } + if diskName != "" && model != "" { + diskMap[diskName] = model + if name != "" { + return diskMap, nil + } + } + } + } + } + return diskMap, nil +} diff --git a/disk/disk_freebsd.go b/disk/disk_freebsd.go index 9b53106c2..e36423c06 100644 --- a/disk/disk_freebsd.go +++ b/disk/disk_freebsd.go @@ -190,3 +190,7 @@ func SerialNumberWithContext(ctx context.Context, name string) (string, error) { func LabelWithContext(ctx context.Context, name string) (string, error) { return "", common.ErrNotImplementedError } + +func ModelWithContext(ctx context.Context, name string) (map[string]string, error) { + return nil, common.ErrNotImplementedError +} \ No newline at end of file diff --git a/disk/disk_linux.go b/disk/disk_linux.go index 14712f9ea..a2b88c9a3 100644 --- a/disk/disk_linux.go +++ b/disk/disk_linux.go @@ -537,3 +537,7 @@ func getFsType(stat unix.Statfs_t) string { } return ret } + +func ModelWithContext(ctx context.Context, name string) (map[string]string, error) { + return nil, common.ErrNotImplementedError +} diff --git a/disk/disk_test.go b/disk/disk_test.go index 5adae5ca3..cd1ce0978 100644 --- a/disk/disk_test.go +++ b/disk/disk_test.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "runtime" + "strings" "sync" "testing" @@ -130,3 +131,43 @@ func TestDiskIOCountersStat_String(t *testing.T) { t.Errorf("DiskUsageStat string is invalid: %v", v) } } + +func TestModel(t *testing.T) { + diskMap, err := Model("") + if err != nil { + t.Errorf("failed to get disk models: %v", err) + return + } + if len(diskMap) == 0 { + t.Errorf("no disk found") + return + } + for name, model := range diskMap { + t.Logf("%s: %s", name, model) + } + var diskName string + switch runtime.GOOS { + case "darwin": + diskName = "Macintosh HD" + default: + diskName = "" + } + if diskName != "" { + diskMap, err = Model(diskName) + if err != nil { + t.Errorf("failed to get disk model: %v", err) + return + } + for name, model := range diskMap { + if !strings.Contains(name, diskName) { + t.Errorf("expected disk name containing '%s', got '%s'", diskName, name) + return + } + if model == "" { + t.Errorf("expected non-empty disk model, got empty string") + return + } + t.Logf("%s: %s", name, model) + } + } +} diff --git a/disk/disk_windows.go b/disk/disk_windows.go index 8a1a28d69..c6724d0b4 100644 --- a/disk/disk_windows.go +++ b/disk/disk_windows.go @@ -6,7 +6,10 @@ package disk import ( "bytes" "context" + "errors" "fmt" + "os/exec" + "strings" "sync" "syscall" "unsafe" @@ -235,3 +238,30 @@ func SerialNumberWithContext(ctx context.Context, name string) (string, error) { func LabelWithContext(ctx context.Context, name string) (string, error) { return "", common.ErrNotImplementedError } + +func ModelWithContext(ctx context.Context, name string) (map[string]string, error) { + out, err := exec.Command("wmic", "diskdrive", "get", "model,name").Output() + if err != nil { + return nil, errors.New("failed to execute 'wmic diskdrive' command: " + err.Error()) + } + outStr := string(out) + lines := strings.Split(outStr, "\r\n") + diskMap := make(map[string]string) + for _, line := range lines { + if strings.Contains(line, "Model") && strings.Contains(line, "Name") { + fields := strings.Fields(line) + if len(fields) >= 3 { + diskName := strings.Join(fields[2:], " ") + model := fields[1] + if name != "" && !strings.Contains(diskName, name) { + continue + } + diskMap[diskName] = model + if name != "" { + return diskMap, nil + } + } + } + } + return diskMap, nil +}