github.com/openebs/node-disk-manager@v1.9.1-0.20230225014141-4531f06ffa1e/pkg/sysfs/syspath.go (about) 1 /* 2 Copyright 2020 The OpenEBS Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package sysfs 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "strings" 25 26 "github.com/openebs/node-disk-manager/blockdevice" 27 ) 28 29 const ( 30 // BlockSubSystem is the key used to represent block subsystem in sysfs 31 BlockSubSystem = "block" 32 // NVMeSubSystem is the key used to represent nvme subsystem in sysfs 33 NVMeSubSystem = "nvme" 34 NVMeSubSysClass = "nvme-subsystem" 35 // sectorSize is the sector size as understood by the unix systems. 36 // It is kept as 512 bytes. all entries in /sys/class/block/sda/size 37 // are in 512 byte blocks 38 sectorSize int64 = 512 39 ) 40 41 var sysFSDirectoryPath = "/sys/" 42 43 // getDeviceSysPath gets the syspath struct for the given blockdevice. 44 // It is generated by evaluating the symlink in /sys/class/block. 45 func getDeviceSysPath(devicePath string) (string, error) { 46 47 var blockDeviceSymLink string 48 49 if strings.HasPrefix(devicePath, "/dev/") { 50 blockDeviceName := strings.Replace(devicePath, "/dev/", "", 1) 51 blockDeviceSymLink = sysFSDirectoryPath + "class/block/" + blockDeviceName 52 } else { 53 blockDeviceSymLink = devicePath 54 } 55 // after evaluation the syspath we get will be similar to 56 // /sys/devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda/ 57 sysPath, err := filepath.EvalSymlinks(blockDeviceSymLink) 58 if err != nil { 59 return "", err 60 } 61 62 return sysPath + "/", nil 63 } 64 65 // getParent gets the parent of this device if it has parent 66 func (s Device) getParent() (string, bool) { 67 parts := strings.Split(s.sysPath, "/") 68 69 var parentBlockDevice string 70 ok := false 71 72 // checking for block subsystem, return the next part after subsystem only 73 // if the length is greater. This check is to avoid an index out of range panic. 74 for i, part := range parts { 75 if part == BlockSubSystem { 76 // check if the length is greater to avoid panic. Also need to make sure that 77 // the same device is not returned if the given device is a parent. 78 if len(parts)-1 >= i+1 && s.deviceName != parts[i+1] { 79 ok = true 80 parentBlockDevice = parts[i+1] 81 } 82 return parentBlockDevice, ok 83 } 84 } 85 86 // checking for nvme subsystem, return the 2nd item in hierarchy, which will be the 87 // nvme namespace. Length checking is to avoid index out of range in case of malformed 88 // links (extremely rare case) 89 for i, part := range parts { 90 if part == NVMeSubSystem || part == NVMeSubSysClass { 91 // check if the length is greater to avoid panic. Also need to make sure that 92 // the same device is not returned if the given device is a parent. 93 if len(parts)-1 >= i+2 && s.deviceName != parts[i+2] { 94 ok = true 95 parentBlockDevice = parts[i+2] 96 } 97 return parentBlockDevice, ok 98 } 99 } 100 101 return parentBlockDevice, ok 102 } 103 104 // getPartitions gets the partitions of this device if it has any 105 func (s Device) getPartitions() ([]string, bool) { 106 107 // if partition file has value 0, or the file doesn't exist, 108 // can return from there itself 109 // partitionPath := s.SysPath + "partition" 110 // if _, err := os.Stat(partitionPath); os.IsNotExist(err) { 111 // } 112 113 partitions := make([]string, 0) 114 115 files, err := ioutil.ReadDir(s.sysPath) 116 if err != nil { 117 return nil, false 118 } 119 for _, file := range files { 120 if strings.HasPrefix(file.Name(), s.deviceName) { 121 partitions = append(partitions, file.Name()) 122 } 123 } 124 125 return partitions, true 126 } 127 128 // getHolders gets the devices that are held by this device 129 func (s Device) getHolders() ([]string, bool) { 130 holderPath := s.sysPath + "holders/" 131 holders := make([]string, 0) 132 133 // check if holders are available for this device 134 if _, err := os.Stat(holderPath); os.IsNotExist(err) { 135 return nil, false 136 } 137 138 files, err := ioutil.ReadDir(holderPath) 139 if err != nil { 140 return nil, false 141 } 142 143 for _, file := range files { 144 holders = append(holders, file.Name()) 145 } 146 return holders, true 147 } 148 149 // getSlaves gets the devices to which this device is a slave. Or, the devices 150 // which holds this device 151 func (s Device) getSlaves() ([]string, bool) { 152 slavePath := s.sysPath + "slaves/" 153 slaves := make([]string, 0) 154 155 // check if slaves are available for this device 156 if _, err := os.Stat(slavePath); os.IsNotExist(err) { 157 return nil, false 158 } 159 160 files, err := ioutil.ReadDir(slavePath) 161 if err != nil { 162 return nil, false 163 } 164 165 for _, file := range files { 166 slaves = append(slaves, file.Name()) 167 } 168 return slaves, true 169 } 170 171 // GetDependents gets all the dependent devices for a given Device 172 func (s Device) GetDependents() (blockdevice.DependentBlockDevices, error) { 173 dependents := blockdevice.DependentBlockDevices{} 174 175 // parent device 176 if parent, ok := s.getParent(); ok { 177 dependents.Parent = parent 178 } 179 180 // get the partitions 181 if partitions, ok := s.getPartitions(); ok { 182 dependents.Partitions = partitions 183 } 184 185 // get the holder devices 186 if holders, ok := s.getHolders(); ok { 187 dependents.Holders = append(dependents.Holders, holders...) 188 } 189 190 // get the slaves 191 if slaves, ok := s.getSlaves(); ok { 192 dependents.Slaves = append(dependents.Slaves, slaves...) 193 } 194 195 // adding /dev prefix 196 if len(dependents.Parent) != 0 { 197 dependents.Parent = "/dev/" + dependents.Parent 198 } 199 200 // adding /devprefix to partition, slaves and holders 201 dependents.Partitions = addDevPrefix(dependents.Partitions) 202 dependents.Holders = addDevPrefix(dependents.Holders) 203 dependents.Slaves = addDevPrefix(dependents.Slaves) 204 205 return dependents, nil 206 } 207 208 // GetLogicalBlockSize gets the logical block size, the caller should handle if 0 LB size is returned 209 func (s Device) GetLogicalBlockSize() (int64, error) { 210 logicalBlockSize, err := readSysFSFileAsInt64(s.sysPath + "queue/logical_block_size") 211 if err != nil { 212 return 0, err 213 } 214 return logicalBlockSize, nil 215 } 216 217 // GetPhysicalBlockSize gets the physical block size of the device 218 func (s Device) GetPhysicalBlockSize() (int64, error) { 219 physicalBlockSize, err := readSysFSFileAsInt64(s.sysPath + "queue/physical_block_size") 220 if err != nil { 221 return 0, err 222 } 223 return physicalBlockSize, nil 224 } 225 226 // GetHardwareSectorSize gets the hardware sector size of the device 227 func (s Device) GetHardwareSectorSize() (int64, error) { 228 hardwareSectorSize, err := readSysFSFileAsInt64(s.sysPath + "queue/hw_sector_size") 229 if err != nil { 230 return 0, err 231 } 232 return hardwareSectorSize, nil 233 } 234 235 // GetDriveType gets the drive type of the device based on the rotational value. Can be HDD or SSD 236 func (s Device) GetDriveType() (string, error) { 237 rotational, err := readSysFSFileAsInt64(s.sysPath + "queue/rotational") 238 if err != nil { 239 return blockdevice.DriveTypeUnknown, err 240 } 241 242 if rotational == 1 { 243 return blockdevice.DriveTypeHDD, nil 244 } else if rotational == 0 { 245 return blockdevice.DriveTypeSSD, nil 246 } 247 return blockdevice.DriveTypeUnknown, fmt.Errorf("undefined rotational value %d", rotational) 248 } 249 250 // GetCapacityInBytes gets the capacity of the device in bytes 251 func (s Device) GetCapacityInBytes() (int64, error) { 252 // The size (/size) entry returns the `nr_sects` field of the block device structure. 253 // Ref: https://elixir.bootlin.com/linux/v4.4/source/fs/block_dev.c#L1267 254 // 255 // Traditionally, in Unix disk size contexts, “sector” or “block” means 512 bytes, 256 // regardless of what the manufacturer of the underlying hardware might call a “sector” or “block” 257 // Ref: https://elixir.bootlin.com/linux/v4.4/source/fs/block_dev.c#L487 258 // 259 // Therefore, to get the capacity of the device it needs to always multiplied with 512 260 numberOfBlocks, err := readSysFSFileAsInt64(s.sysPath + "size") 261 if err != nil { 262 return 0, err 263 } else if numberOfBlocks == 0 { 264 return 0, fmt.Errorf("block count reported as zero") 265 } 266 return numberOfBlocks * sectorSize, nil 267 268 } 269 270 // GetDeviceType gets the device type, as shown in lsblk 271 // devtype should be prefilled by udev probe (DEVTYPE) as disk/part for this to work 272 // 273 // Ported from https://github.com/karelzak/util-linux/blob/master/misc-utils/lsblk.c 274 func (s Device) GetDeviceType(devType string) (string, error) { 275 276 var result string 277 278 if devType == blockdevice.BlockDeviceTypePartition { 279 return blockdevice.BlockDeviceTypePartition, nil 280 } 281 282 // TODO may need to distinguish between normal partitions and partitions on DM devices. The original 283 // lsblk implementation does not have this distinction. 284 if isDM(s.deviceName) { 285 dmUuid, err := readSysFSFileAsString(s.sysPath + "dm/uuid") 286 if err != nil { 287 return "", fmt.Errorf("unable to get DM_UUID, error: %v", err) 288 } 289 if len(dmUuid) > 0 { 290 dmUuidPrefix := strings.Split(dmUuid, "-")[0] 291 if len(dmUuidPrefix) != 0 { 292 if len(dmUuidPrefix) > 4 && dmUuidPrefix[0:4] == "part" { 293 result = blockdevice.BlockDeviceTypePartition 294 } else { 295 result = dmUuidPrefix 296 } 297 } 298 } 299 if len(result) == 0 { 300 result = blockdevice.BlockDeviceTypeDMDevice 301 } 302 } else if len(s.deviceName) >= 4 && s.deviceName[0:4] == "loop" { 303 result = blockdevice.BlockDeviceTypeLoop 304 } else if len(s.deviceName) >= 2 && s.deviceName[0:2] == "md" { 305 mdLevel, err := readSysFSFileAsString(s.sysPath + "md/level") 306 if err != nil { 307 return "", fmt.Errorf("unable to get raid level, error: %v", err) 308 } 309 if len(mdLevel) != 0 { 310 result = mdLevel 311 } else { 312 result = "md" 313 } 314 } else { 315 // TODO Ideally should read device/type file and find the device type using blkdev_scsi_type_to_name() 316 result = "disk" 317 } 318 return strings.ToLower(result), nil 319 } 320 321 func isDM(devName string) bool { 322 return devName[0:3] == "dm-" 323 }