-
Notifications
You must be signed in to change notification settings - Fork 601
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
interfaces/hotplug: add hotplug Specification and HotplugDeviceInfo #5416
Changes from 21 commits
275f7ad
280ed48
8163720
1edbf70
9a5f9da
63b33d0
07adf32
7dd7efe
e408116
e3adc77
23e8ec6
06eb95c
4c637dd
4c23113
ee9c8b6
dc6a005
4bff39a
7ceb603
9c027cf
8d1b22d
00276aa
237c82b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
|
||
/* | ||
* Copyright (C) 2018 Canonical Ltd | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 3 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
|
||
package hotplug | ||
|
||
import ( | ||
"path/filepath" | ||
|
||
"github.com/snapcore/snapd/dirs" | ||
) | ||
|
||
// HotplugDeviceInfo carries information about added/removed device detected at runtime. | ||
type HotplugDeviceInfo struct { | ||
// map of all attributes returned for given uevent. | ||
data map[string]string | ||
} | ||
|
||
// NewHotplugDeviceInfo creates HotplugDeviceInfo structure related to udev add or remove event. | ||
func NewHotplugDeviceInfo(env map[string]string) *HotplugDeviceInfo { | ||
return &HotplugDeviceInfo{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a sanity check this should ensure that |
||
data: env, | ||
} | ||
} | ||
|
||
// Returns the value of "SUBSYSTEM" attribute of the udev event associated with the device, e.g. "usb". | ||
// Subsystem value is always present. | ||
func (h *HotplugDeviceInfo) Subsystem() string { | ||
return h.data["SUBSYSTEM"] | ||
} | ||
|
||
// Returns full device path under /sysfs, e.g /sys/devices/pci0000:00/0000:00:14.0/usb1/1-2. | ||
// The path is derived from DEVPATH attribute of the udev event. | ||
func (h *HotplugDeviceInfo) DevicePath() string { | ||
path, ok := h.Attribute("DEVPATH") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The check doesn't do any harm, but Isn't DEVPATH one of the attributes that's supposed to be there always? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we have Path and Object as separate functions if they must always return the exact same value? |
||
if ok { | ||
return filepath.Join(dirs.SysfsDir, path) | ||
} | ||
return "" | ||
} | ||
|
||
// Returns the value of "MINOR" attribute of the udev event associated with the device. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please indicate that this and everything below is optional and may return an empty string. EDIT: I have a feeling we should just expose an attribute getter and not have APIs for things that are optional (and by looking at udev database, mostly absent). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comments. Made |
||
// The Minor value may be empty. | ||
func (h *HotplugDeviceInfo) Minor() string { | ||
return h.data["MINOR"] | ||
} | ||
|
||
// Returns the value of "MAJOR" attribute of the udev event associated with the device. | ||
// The Major value may be empty. | ||
func (h *HotplugDeviceInfo) Major() string { | ||
return h.data["MAJOR"] | ||
} | ||
|
||
// Returns the value of "DEVNAME" attribute of the udev event associated with the device, e.g. "ttyUSB0". | ||
// The DeviceName value may be empty. | ||
func (h *HotplugDeviceInfo) DeviceName() string { | ||
return h.data["DEVNAME"] | ||
} | ||
|
||
// Returns the value of "DEVTYPE" attribute of the udev event associated with the device, e.g. "usb_device". | ||
// The DeviceType value may be empty. | ||
func (h *HotplugDeviceInfo) DeviceType() string { | ||
return h.data["DEVTYPE"] | ||
} | ||
|
||
// Generic method for getting arbitrary attribute from the uevent data. | ||
func (h *HotplugDeviceInfo) Attribute(name string) (string, bool) { | ||
val, ok := h.data[name] | ||
return val, ok | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
|
||
/* | ||
* Copyright (C) 2018 Canonical Ltd | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 3 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
|
||
package hotplug | ||
|
||
import ( | ||
"path/filepath" | ||
"testing" | ||
|
||
. "gopkg.in/check.v1" | ||
|
||
"github.com/snapcore/snapd/dirs" | ||
"github.com/snapcore/snapd/testutil" | ||
) | ||
|
||
func Test(t *testing.T) { TestingT(t) } | ||
|
||
type hotplugSuite struct { | ||
testutil.BaseTest | ||
} | ||
|
||
var _ = Suite(&hotplugSuite{}) | ||
|
||
func (s *hotplugSuite) SetUpTest(c *C) { | ||
s.BaseTest.SetUpTest(c) | ||
dirs.SetRootDir(c.MkDir()) | ||
} | ||
|
||
func (s *hotplugSuite) TearDownTest(c *C) { | ||
s.BaseTest.TearDownTest(c) | ||
dirs.SetRootDir("") | ||
} | ||
|
||
func (s *hotplugSuite) TestBasicProperties(c *C) { | ||
env := map[string]string{ | ||
"DEVPATH": "/devices/pci0000:00/0000:00:14.0/usb2/2-3", "DEVNAME": "bus/usb/002/003", | ||
"DEVTYPE": "usb_device", | ||
"PRODUCT": "1d50/6108/0", "DEVNUM": "003", | ||
"SEQNUM": "4053", | ||
"ACTION": "add", "SUBSYSTEM": "usb", | ||
"MAJOR": "189", "MINOR": "130", | ||
"TYPE": "0/0/0", "BUSNUM": "002", | ||
} | ||
|
||
di := NewHotplugDeviceInfo(env) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a test with all of the optional things absent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added. |
||
c.Assert(di.DeviceName(), Equals, "bus/usb/002/003") | ||
c.Assert(di.DeviceType(), Equals, "usb_device") | ||
c.Assert(di.DevicePath(), Equals, filepath.Join(dirs.SysfsDir, "/devices/pci0000:00/0000:00:14.0/usb2/2-3")) | ||
c.Assert(di.Subsystem(), Equals, "usb") | ||
c.Assert(di.Major(), Equals, "189") | ||
c.Assert(di.Minor(), Equals, "130") | ||
|
||
minor, ok := di.Attribute("MINOR") | ||
c.Assert(ok, Equals, true) | ||
c.Assert(minor, Equals, "130") | ||
|
||
_, ok = di.Attribute("FOO") | ||
c.Assert(ok, Equals, false) | ||
} | ||
|
||
func (s *hotplugSuite) TestPropertiesMissing(c *C) { | ||
env := map[string]string{ | ||
"DEVPATH": "/devices/pci0000:00/0000:00:14.0/usb2/2-3", | ||
"ACTION": "add", "SUBSYSTEM": "usb", | ||
} | ||
|
||
di := NewHotplugDeviceInfo(env) | ||
|
||
c.Assert(di.DeviceName(), Equals, "") | ||
c.Assert(di.DeviceType(), Equals, "") | ||
c.Assert(di.DevicePath(), Equals, filepath.Join(dirs.SysfsDir, "/devices/pci0000:00/0000:00:14.0/usb2/2-3")) | ||
c.Assert(di.Subsystem(), Equals, "usb") | ||
c.Assert(di.Major(), Equals, "") | ||
c.Assert(di.Minor(), Equals, "") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
|
||
/* | ||
* Copyright (C) 2018 Canonical Ltd | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 3 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
|
||
package hotplug | ||
|
||
import ( | ||
"fmt" | ||
"sort" | ||
|
||
"github.com/snapcore/snapd/interfaces/utils" | ||
"github.com/snapcore/snapd/snap" | ||
) | ||
|
||
// Definer can be implemented by interfaces that need to create slots in response to hotplug events | ||
type Definer interface { | ||
HotplugDeviceDetected(di *HotplugDeviceInfo, spec *Specification) error | ||
} | ||
|
||
// SlotSpec is a definition of the slot to create in response to udev event. | ||
type SlotSpec struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Document public things please. This helps you and your reviewers alike. |
||
// XXX: Name is the name the interface wants to give to the slot; we | ||
// might want to mediate this though (e.g. generate automatically), so this | ||
// may change/go away. | ||
Name string | ||
Label string | ||
Attrs map[string]interface{} | ||
} | ||
|
||
// Specification contains data about all slots that a hotplug interface wants to have created in response to uevent. | ||
type Specification struct { | ||
// slots are indexed by slot name to ensure unique names | ||
slots map[string]*SlotSpec | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please document what the key in the map is. Is it DEVPATH? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Documented. |
||
} | ||
|
||
// NewSpecification creates an empty hotplug Specification. | ||
func NewSpecification() *Specification { | ||
return &Specification{ | ||
slots: make(map[string]*SlotSpec), | ||
} | ||
} | ||
|
||
// AddSlot adds a specification of a slot. | ||
func (h *Specification) AddSlot(slotSpec *SlotSpec) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This must absolutely associate with a sysfs device. How is this being ensured? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is going to happen outside in the hotplug event processing loop. Hotplug event handler will iterate over all hotplug interfaces, determine device key, create a Specification per-device. Interface fills in the Specification by adding slot(s), the outher code knows device key for given set of slots. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add this response as a comment please. |
||
if _, ok := h.slots[slotSpec.Name]; ok { | ||
return fmt.Errorf("slot %q already exists", slotSpec.Name) | ||
} | ||
if err := snap.ValidateSlotName(slotSpec.Name); err != nil { | ||
return err | ||
} | ||
attrs := slotSpec.Attrs | ||
if attrs == nil { | ||
attrs = make(map[string]interface{}) | ||
} else { | ||
attrs = utils.CopyAttributes(slotSpec.Attrs) | ||
} | ||
h.slots[slotSpec.Name] = &SlotSpec{ | ||
Name: slotSpec.Name, | ||
Label: slotSpec.Label, | ||
Attrs: utils.NormalizeInterfaceAttributes(attrs).(map[string]interface{}), | ||
} | ||
return nil | ||
} | ||
|
||
// Slots returns specifications of all slots created by given interface. | ||
func (h *Specification) Slots() []*SlotSpec { | ||
keys := make([]string, 0, len(h.slots)) | ||
for k := range h.slots { | ||
keys = append(keys, k) | ||
} | ||
sort.Strings(keys) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do these need to be sorted or is it just to get a stable output for the tests? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's about making it test friendly only. |
||
|
||
slots := make([]*SlotSpec, 0, len(h.slots)) | ||
for _, k := range keys { | ||
slots = append(slots, h.slots[k]) | ||
} | ||
return slots | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document this somehow.