@@ -32,11 +32,61 @@ func readSysRotational(devName string) (bool, bool) {
3232 return s == "1" , true
3333}
3434
35+ func readSysRemovable (devName string ) (bool , bool ) {
36+ data , err := os .ReadFile ("/sys/class/block/" + devName + "/removable" )
37+ if err != nil {
38+ return false , false
39+ }
40+ s := strings .TrimSpace (string (data ))
41+ return s == "1" , true
42+ }
43+
44+ func sysDevicePathContains (devName string , needle string ) bool {
45+ devName = strings .TrimSpace (devName )
46+ if devName == "" {
47+ return false
48+ }
49+ // /sys/class/block/<dev>/device is a symlink into the bus topology (usb, pci, mmc, ...)
50+ resolved , err := filepath .EvalSymlinks ("/sys/class/block/" + devName + "/device" )
51+ if err != nil || strings .TrimSpace (resolved ) == "" {
52+ return false
53+ }
54+ return strings .Contains (resolved , needle )
55+ }
56+
3557func baseBlockDeviceName (name string ) string {
36- if strings .HasPrefix (name , "nvme" ) || strings .HasPrefix (name , "mmcblk" ) || strings .HasPrefix (name , "md" ) {
58+ // Keep whole-disk names for these Linux devices (they naturally contain digits).
59+ // Examples: mmcblk1, md0, nvme0n1
60+ if strings .HasPrefix (name , "mmcblk" ) {
61+ // mmcblk<disk> (disk) or mmcblk<disk>p<part> (partition)
62+ if i := strings .LastIndex (name , "p" ); i > 0 && allDigits (name [i + 1 :]) {
63+ return name [:i ]
64+ }
65+ if allDigits (strings .TrimPrefix (name , "mmcblk" )) {
66+ return name
67+ }
68+ }
69+ if strings .HasPrefix (name , "md" ) {
3770 if i := strings .LastIndex (name , "p" ); i > 0 && allDigits (name [i + 1 :]) {
3871 return name [:i ]
3972 }
73+ if allDigits (strings .TrimPrefix (name , "md" )) {
74+ return name
75+ }
76+ }
77+ if strings .HasPrefix (name , "nvme" ) {
78+ // nvme<ctrl>n<ns> (disk) or nvme<ctrl>n<ns>p<part> (partition)
79+ if i := strings .LastIndex (name , "p" ); i > 0 && allDigits (name [i + 1 :]) {
80+ return name [:i ]
81+ }
82+ // If it has an 'n' suffix like nvme0n1, keep it intact.
83+ if i := strings .LastIndex (name , "n" ); i > 0 {
84+ left := strings .TrimPrefix (name [:i ], "nvme" )
85+ right := name [i + 1 :]
86+ if allDigits (left ) && allDigits (right ) {
87+ return name
88+ }
89+ }
4090 }
4191 j := len (name ) - 1
4292 for j >= 0 {
@@ -65,7 +115,7 @@ func allDigits(s string) bool {
65115}
66116
67117// getDriveType determines the high-level drive technology for the given source.
68- // Returns one of: "Remote", "HDD", "SSD", or "Unknown".
118+ // Returns one of: "Remote", "HDD", "SSD", "USB", or "Unknown".
69119func getDriveType (src string , isRemote bool ) string {
70120 if isRemote {
71121 return "Remote"
@@ -113,16 +163,37 @@ func getDriveType(src string, isRemote bool) string {
113163 return "Unknown"
114164 }
115165
116- // Regular block or partition device
166+ // Regular block or partition device.
167+ // Prefer parent device when name refers to a partition.
168+ parent := baseBlockDeviceName (name )
169+ if parent == "" {
170+ parent = name
171+ }
172+
173+ // If this is a removable USB mass-storage device, label as USB.
174+ // This avoids showing "HDD" for thumb drives where rotational reporting is unreliable.
175+ if sysDevicePathContains (parent , "/usb" ) {
176+ if rm , ok := readSysRemovable (parent ); ok && rm {
177+ return "USB"
178+ }
179+ }
180+
181+ if rot , ok := readSysRotational (parent ); ok {
182+ if rot {
183+ return "HDD"
184+ }
185+ return "SSD"
186+ }
187+
188+ // Try the leaf node as fallback.
117189 if rot , ok := readSysRotational (name ); ok {
118190 if rot {
119191 return "HDD"
120192 }
121193 return "SSD"
122194 }
123195
124- // Try parent device when name refers to a partition
125- parent := baseBlockDeviceName (name )
196+ // Try parent device when name refers to a partition (already computed).
126197 if parent != name {
127198 if rot , ok := readSysRotational (parent ); ok {
128199 if rot {
0 commit comments