...

Source file src/edge-infra.dev/pkg/lib/kernel/devices/devices.go

Documentation: edge-infra.dev/pkg/lib/kernel/devices

     1  package devices
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"golang.org/x/sys/unix"
    14  
    15  	"edge-infra.dev/pkg/lib/filesystem"
    16  	"edge-infra.dev/pkg/lib/kernel/udev"
    17  )
    18  
    19  // Device interface
    20  type Device interface {
    21  	Path() string
    22  	Node() (Node, error)
    23  	Attribute(attributeKey string) (string, bool, error)
    24  	Property(propertyKey string) (string, bool, error)
    25  }
    26  
    27  // Device node implementation
    28  type Node interface {
    29  	Path() string
    30  	GroupID() (int64, error)
    31  	UserID() (int64, error)
    32  	FileMode() (os.FileMode, error)
    33  	Type() (string, error)
    34  }
    35  
    36  // Device represent a unix device
    37  type device struct {
    38  	path       string
    39  	properties map[string]string
    40  }
    41  
    42  // device node
    43  type node struct {
    44  	path string
    45  }
    46  
    47  var ErrNotADevice = errors.New("not a device node")
    48  
    49  var (
    50  	SysPath            = "/sys"
    51  	DevPath            = "/dev"
    52  	sysDevicePath      = filepath.Join(SysPath, "devices")
    53  	sysClassPath       = filepath.Join(SysPath, "class")
    54  	sysBusPath         = filepath.Join(SysPath, "bus")
    55  	subSystemSymlinkFn = filepath.EvalSymlinks
    56  	deviceTypeFn       = deviceType
    57  )
    58  
    59  // Returns a new device from sys path including child devices
    60  func New(sysPath string) (Device, error) {
    61  	dev := device{
    62  		sysPath,
    63  		map[string]string{},
    64  	}
    65  	if err := dev.readProps(); err != nil {
    66  		return &dev, err
    67  	}
    68  	return &dev, nil
    69  }
    70  
    71  // FromUEvent creates device object from the uevent
    72  func FromUEvent(ue *udev.UEvent) (d Device, err error) {
    73  	if !strings.HasPrefix(ue.SysPath, SysPath) {
    74  		return New(filepath.Join(SysPath, ue.SysPath))
    75  	}
    76  	return New(ue.SysPath)
    77  }
    78  
    79  // Returns the sys path of device
    80  func (d *device) Path() string {
    81  	return d.path
    82  }
    83  
    84  // Returns a devices property
    85  func (d *device) Property(prop string) (string, bool, error) {
    86  	propVal, hasProp := d.properties[prop]
    87  	return propVal, hasProp, nil
    88  }
    89  
    90  // Attempts to get attribute for a device if the value is not already present
    91  func (d *device) Attribute(attribute string) (string, bool, error) {
    92  	readAttr, err := d.readAttribute(attribute)
    93  	if err != nil {
    94  		return "", false, err
    95  	}
    96  	return readAttr, true, nil
    97  }
    98  
    99  // Returns the device node path
   100  func (n *node) Path() string {
   101  	return n.path
   102  }
   103  
   104  func (d *device) Node() (Node, error) {
   105  	devNodePath, exists, err := d.Property("DEVNAME")
   106  	if err != nil || !exists {
   107  		return nil, fmt.Errorf("device node %s does not exist", devNodePath)
   108  	}
   109  	if !strings.HasPrefix(DevPath, devNodePath) {
   110  		devNodePath = filepath.Join(DevPath, devNodePath)
   111  	}
   112  	return &node{path: devNodePath}, nil
   113  }
   114  
   115  // FileMode returns the FileMode
   116  func (n *node) FileMode() (os.FileMode, error) {
   117  	fileInfo, err := os.Stat(n.Path())
   118  	if err != nil {
   119  		return 0, fmt.Errorf("could not stat device node: %s : %w", n.Path(), err)
   120  	}
   121  	return fileInfo.Mode(), nil
   122  }
   123  
   124  // GroupID returns the group owner id of device node
   125  func (n *node) GroupID() (int64, error) {
   126  	return filesystem.GroupID(n.Path())
   127  }
   128  
   129  // UserID returns the user id of device node
   130  func (n *node) UserID() (int64, error) {
   131  	return filesystem.UserID(n.Path())
   132  }
   133  
   134  // Returns the device type i.e. char/block
   135  func (n *node) Type() (string, error) {
   136  	devType, err := deviceTypeFn(n.path)
   137  	if err != nil {
   138  		return "", fmt.Errorf("error fetching device type for path %s: %w", n.path, err)
   139  	}
   140  	return devType, nil
   141  }
   142  
   143  // Reads a devices attribute from the sys path
   144  func (d *device) readAttribute(attribute string) (string, error) {
   145  	file, err := os.Open(filepath.Join(d.path, attribute))
   146  	if err != nil {
   147  		return "", err
   148  	}
   149  	defer file.Close()
   150  
   151  	s := bufio.NewScanner(file)
   152  	for s.Scan() {
   153  		return s.Text(), nil
   154  	}
   155  	return "", fmt.Errorf("could not read attribute")
   156  }
   157  
   158  // Reads properties from device sysfs path/uevent
   159  func (d *device) readProps() error {
   160  	f, err := os.Open(filepath.Join(d.path, "uevent"))
   161  	if err != nil {
   162  		return fmt.Errorf("error reading uevent file: %s: %w", d.path, err)
   163  	}
   164  	defer f.Close()
   165  	data, err := io.ReadAll(f)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	buf := bufio.NewScanner(bytes.NewBuffer(data))
   170  	var line string
   171  	for buf.Scan() {
   172  		line = buf.Text()
   173  		field := strings.SplitN(line, "=", 2)
   174  		if len(field) != 2 {
   175  			continue
   176  		}
   177  		d.properties[field[0]] = field[1]
   178  	}
   179  	subsystem, err := subSystemSymlinkFn(filepath.Join(d.path, "subsystem"))
   180  	if err != nil {
   181  		return fmt.Errorf("error reading subsystem: %s: %w", d.path, err)
   182  	}
   183  	d.properties[subsystemProperty] = filepath.Base(subsystem)
   184  	return nil
   185  }
   186  
   187  // fetches device type i.e. block, char, fifo
   188  func deviceType(devNodePath string) (string, error) {
   189  	var stat unix.Stat_t
   190  	if err := unix.Lstat(devNodePath, &stat); err != nil {
   191  		return "", ErrNotADevice
   192  	}
   193  	var mode = stat.Mode
   194  	switch mode & unix.S_IFMT {
   195  	case unix.S_IFBLK:
   196  		return "b", nil
   197  	case unix.S_IFCHR:
   198  		return "c", nil
   199  	case unix.S_IFIFO:
   200  		return "p", nil
   201  	}
   202  	return "", ErrNotADevice
   203  }
   204  

View as plain text