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  }