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
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
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
37 type device struct {
38 path string
39 properties map[string]string
40 }
41
42
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
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
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
80 func (d *device) Path() string {
81 return d.path
82 }
83
84
85 func (d *device) Property(prop string) (string, bool, error) {
86 propVal, hasProp := d.properties[prop]
87 return propVal, hasProp, nil
88 }
89
90
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
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
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
125 func (n *node) GroupID() (int64, error) {
126 return filesystem.GroupID(n.Path())
127 }
128
129
130 func (n *node) UserID() (int64, error) {
131 return filesystem.UserID(n.Path())
132 }
133
134
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
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
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
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