// Copyright 2022 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build linux // +build linux package sysfs import ( "os" "path/filepath" "regexp" "github.com/prometheus/procfs/internal/util" ) const ( sasDeviceClassPath = "class/sas_device" sasEndDeviceClassPath = "class/sas_end_device" sasExpanderClassPath = "class/sas_expander" ) type SASDevice struct { Name string // /sys/class/sas_device/ SASAddress string // /sys/class/sas_device//sas_address SASPhys []string // /sys/class/sas_device//device/phy-* SASPorts []string // /sys/class/sas_device//device/ports-* BlockDevices []string // /sys/class/sas_device//device/target*/*/block/* } type SASDeviceClass map[string]*SASDevice var ( sasTargetDeviceRegexp = regexp.MustCompile(`^target[0-9:]+$`) sasTargetSubDeviceRegexp = regexp.MustCompile(`[0-9]+:.*`) ) // sasDeviceClasses reads all of the SAS devices from a specific set // of /sys/class/sas*/ entries. The sas_device, sas_end_device, and // sas_expander classes are all nearly identical and can be handled by the same basic code. func (fs FS) parseSASDeviceClass(dir string) (SASDeviceClass, error) { path := fs.sys.Path(dir) dirs, err := os.ReadDir(path) if err != nil { return nil, err } sdc := make(SASDeviceClass, len(dirs)) for _, d := range dirs { device, err := fs.parseSASDevice(d.Name()) if err != nil { return nil, err } sdc[device.Name] = device } return sdc, nil } // SASDeviceClass parses devices in /sys/class/sas_device. func (fs FS) SASDeviceClass() (SASDeviceClass, error) { return fs.parseSASDeviceClass(sasDeviceClassPath) } // SASEndDeviceClass parses devices in /sys/class/sas_end_device. // This is a subset of sas_device, and excludes expanders. func (fs FS) SASEndDeviceClass() (SASDeviceClass, error) { return fs.parseSASDeviceClass(sasEndDeviceClassPath) } // SASExpanderClass parses devices in /sys/class/sas_expander. // This is a subset of sas_device, but only includes expanders. func (fs FS) SASExpanderClass() (SASDeviceClass, error) { return fs.parseSASDeviceClass(sasExpanderClassPath) } // Parse a single sas_device. This uses /sys/class/sas_device, as // it's a superset of the other two directories so there's no reason // to plumb the path through to here. func (fs FS) parseSASDevice(name string) (*SASDevice, error) { device := SASDevice{Name: name} devicepath := fs.sys.Path(filepath.Join(sasDeviceClassPath, name, "device")) dirs, err := os.ReadDir(devicepath) if err != nil { return nil, err } for _, d := range dirs { if sasPhyDeviceRegexp.MatchString(d.Name()) { device.SASPhys = append(device.SASPhys, d.Name()) } if sasPortDeviceRegexp.MatchString(d.Name()) { device.SASPorts = append(device.SASPorts, d.Name()) } } address := fs.sys.Path(sasDeviceClassPath, name, "sas_address") value, err := util.SysReadFile(address) if err != nil { return nil, err } device.SASAddress = value device.BlockDevices, err = fs.blockSASDeviceBlockDevices(name) if err != nil { return nil, err } return &device, nil } // Identify block devices that map to a specific SAS Device // This info comes from (for example) // /sys/class/sas_device/end_device-11:2/device/target11:0:0/11:0:0:0/block/sdp // // To find that, we have to look in the device directory for target$X // subdirs, then specific subdirs of $X, then read from directory // names in the 'block/' subdirectory under that. This really // shouldn't be this hard. func (fs FS) blockSASDeviceBlockDevices(name string) ([]string, error) { var devices []string devicepath := fs.sys.Path(filepath.Join(sasDeviceClassPath, name, "device")) dirs, err := os.ReadDir(devicepath) if err != nil { return nil, err } for _, d := range dirs { if sasTargetDeviceRegexp.MatchString(d.Name()) { targetdir := d.Name() subtargets, err := os.ReadDir(filepath.Join(devicepath, targetdir)) if err != nil { return nil, err } for _, targetsubdir := range subtargets { if !sasTargetSubDeviceRegexp.MatchString(targetsubdir.Name()) { // need to skip 'power', 'subsys', etc. continue } blocks, err := os.ReadDir(filepath.Join(devicepath, targetdir, targetsubdir.Name(), "block")) if err != nil { if os.IsNotExist(err) { continue } return nil, err } for _, blockdevice := range blocks { devices = append(devices, blockdevice.Name()) } } } } return devices, nil } // GetByName returns the SASDevice with the provided name. func (sdc *SASDeviceClass) GetByName(name string) *SASDevice { return (*sdc)[name] } // GetByPhy finds the SASDevice that contains the provided PHY name. func (sdc *SASDeviceClass) GetByPhy(name string) *SASDevice { for _, d := range *sdc { for _, p := range d.SASPhys { if p == name { return d } } } return nil } // GetByPort finds the SASDevice that contains the provided SAS Port name. func (sdc *SASDeviceClass) GetByPort(name string) *SASDevice { for _, d := range *sdc { for _, p := range d.SASPorts { if p == name { return d } } } return nil }