Permalink
| // -*- Mode: Go; indent-tabs-mode: t -*- | |
| /* | |
| * Copyright (C) 2016-2017 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 builtin | |
| import ( | |
| "fmt" | |
| "path/filepath" | |
| "regexp" | |
| "strings" | |
| "github.com/snapcore/snapd/interfaces" | |
| "github.com/snapcore/snapd/interfaces/apparmor" | |
| "github.com/snapcore/snapd/interfaces/udev" | |
| "github.com/snapcore/snapd/snap" | |
| ) | |
| const serialPortSummary = `allows accessing a specific serial port` | |
| const serialPortBaseDeclarationSlots = ` | |
| serial-port: | |
| allow-installation: | |
| slot-snap-type: | |
| - core | |
| - gadget | |
| deny-auto-connection: true | |
| ` | |
| // serialPortInterface is the type for serial port interfaces. | |
| type serialPortInterface struct{} | |
| // Name of the serial-port interface. | |
| func (iface *serialPortInterface) Name() string { | |
| return "serial-port" | |
| } | |
| func (iface *serialPortInterface) StaticInfo() interfaces.StaticInfo { | |
| return interfaces.StaticInfo{ | |
| Summary: serialPortSummary, | |
| BaseDeclarationSlots: serialPortBaseDeclarationSlots, | |
| } | |
| } | |
| func (iface *serialPortInterface) String() string { | |
| return iface.Name() | |
| } | |
| // Pattern to match allowed serial device nodes, path attributes will be | |
| // compared to this for validity when not using udev identification | |
| // Known device node patterns we need to support | |
| // - ttyUSBX (UART over USB devices) | |
| // - ttyACMX (ACM modem devices ) | |
| // - ttyXRUSBx (Exar Corp. USB UART devices) | |
| // - ttySX (UART serial ports) | |
| // - ttyOX (UART serial ports on ARM) | |
| var serialDeviceNodePattern = regexp.MustCompile("^/dev/tty(USB|ACM|AMA|XRUSB|S|O)[0-9]+$") | |
| // Pattern that is considered valid for the udev symlink to the serial device, | |
| // path attributes will be compared to this for validity when usb vid and pid | |
| // are also specified | |
| var serialUDevSymlinkPattern = regexp.MustCompile("^/dev/serial-port-[a-z0-9]+$") | |
| // BeforePrepareSlot checks validity of the defined slot | |
| func (iface *serialPortInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { | |
| if err := sanitizeSlotReservedForOSOrGadget(iface, slot); err != nil { | |
| return err | |
| } | |
| // Check slot has a path attribute identify serial device | |
| path, ok := slot.Attrs["path"].(string) | |
| if !ok || path == "" { | |
| return fmt.Errorf("serial-port slot must have a path attribute") | |
| } | |
| // Clean the path before further checks | |
| path = filepath.Clean(path) | |
| if iface.hasUsbAttrs(slot) { | |
| // Must be path attribute where symlink will be placed and usb vendor and product identifiers | |
| // Check the path attribute is in the allowable pattern | |
| if !serialUDevSymlinkPattern.MatchString(path) { | |
| return fmt.Errorf("serial-port path attribute specifies invalid symlink location") | |
| } | |
| usbVendor, vOk := slot.Attrs["usb-vendor"].(int64) | |
| if !vOk { | |
| return fmt.Errorf("serial-port slot failed to find usb-vendor attribute") | |
| } | |
| if (usbVendor < 0x1) || (usbVendor > 0xFFFF) { | |
| return fmt.Errorf("serial-port usb-vendor attribute not valid: %d", usbVendor) | |
| } | |
| usbProduct, pOk := slot.Attrs["usb-product"].(int64) | |
| if !pOk { | |
| return fmt.Errorf("serial-port slot failed to find usb-product attribute") | |
| } | |
| if (usbProduct < 0x0) || (usbProduct > 0xFFFF) { | |
| return fmt.Errorf("serial-port usb-product attribute not valid: %d", usbProduct) | |
| } | |
| usbInterfaceNumber, ok := slot.Attrs["usb-interface-number"].(int64) | |
| if ok && (usbInterfaceNumber < 0 || usbInterfaceNumber >= UsbMaxInterfaces) { | |
| return fmt.Errorf("serial-port usb-interface-number attribute cannot be negative or larger than %d", UsbMaxInterfaces-1) | |
| } | |
| } else { | |
| // Just a path attribute - must be a valid usb device node | |
| // Check the path attribute is in the allowable pattern | |
| if !serialDeviceNodePattern.MatchString(path) { | |
| return fmt.Errorf("serial-port path attribute must be a valid device node") | |
| } | |
| } | |
| return nil | |
| } | |
| func (iface *serialPortInterface) UDevPermanentSlot(spec *udev.Specification, slot *snap.SlotInfo) error { | |
| var usbVendor, usbProduct, usbInterfaceNumber int64 | |
| var path string | |
| if err := slot.Attr("usb-vendor", &usbVendor); err != nil { | |
| return nil | |
| } | |
| if err := slot.Attr("usb-product", &usbProduct); err != nil { | |
| return nil | |
| } | |
| if err := slot.Attr("path", &path); err != nil || path == "" { | |
| return nil | |
| } | |
| if err := slot.Attr("usb-interface-number", &usbInterfaceNumber); err == nil { | |
| spec.AddSnippet(fmt.Sprintf(`# serial-port | |
| IMPORT{builtin}="usb_id" | |
| SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="%04x", ATTRS{idProduct}=="%04x", ENV{ID_USB_INTERFACE_NUM}=="%02x", SYMLINK+="%s"`, usbVendor, usbProduct, usbInterfaceNumber, strings.TrimPrefix(path, "/dev/"))) | |
| } else { | |
| spec.AddSnippet(fmt.Sprintf(`# serial-port | |
| IMPORT{builtin}="usb_id" | |
| SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="%04x", ATTRS{idProduct}=="%04x", SYMLINK+="%s"`, usbVendor, usbProduct, strings.TrimPrefix(path, "/dev/"))) | |
| } | |
| return nil | |
| } | |
| func (iface *serialPortInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { | |
| if iface.hasUsbAttrs(slot) { | |
| // This apparmor rule is an approximation of serialDeviceNodePattern | |
| // (AARE is different than regex, so we must approximate). | |
| // UDev tagging and device cgroups will restrict down to the specific device | |
| spec.AddSnippet("/dev/tty[A-Z]*[0-9] rw,") | |
| return nil | |
| } | |
| // Path to fixed device node | |
| var path string | |
| if err := slot.Attr("path", &path); err != nil { | |
| return nil | |
| } | |
| cleanedPath := filepath.Clean(path) | |
| spec.AddSnippet(fmt.Sprintf("%s rw,", cleanedPath)) | |
| return nil | |
| } | |
| func (iface *serialPortInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { | |
| // For connected plugs, we use vendor and product ids if available, | |
| // otherwise add the kernel device | |
| hasOnlyPath := !iface.hasUsbAttrs(slot) | |
| var usbVendor, usbProduct int64 | |
| var path string | |
| if err := slot.Attr("usb-vendor", &usbVendor); err != nil && !hasOnlyPath { | |
| return nil | |
| } | |
| if err := slot.Attr("usb-product", &usbProduct); err != nil && !hasOnlyPath { | |
| return nil | |
| } | |
| if err := slot.Attr("path", &path); err != nil && hasOnlyPath { | |
| return nil | |
| } | |
| if hasOnlyPath { | |
| spec.TagDevice(fmt.Sprintf(`SUBSYSTEM=="tty", KERNEL=="%s"`, strings.TrimPrefix(path, "/dev/"))) | |
| } else { | |
| var usbInterfaceNumber int64 | |
| if err := slot.Attr("usb-interface-number", &usbInterfaceNumber); err == nil { | |
| spec.TagDevice(fmt.Sprintf(`IMPORT{builtin}="usb_id" | |
| SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="%04x", ATTRS{idProduct}=="%04x", ENV{ID_USB_INTERFACE_NUM}=="%02x"`, usbVendor, usbProduct, usbInterfaceNumber)) | |
| } else { | |
| spec.TagDevice(fmt.Sprintf(`IMPORT{builtin}="usb_id" | |
| SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="%04x", ATTRS{idProduct}=="%04x"`, usbVendor, usbProduct)) | |
| } | |
| } | |
| return nil | |
| } | |
| func (iface *serialPortInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { | |
| // allow what declarations allowed | |
| return true | |
| } | |
| func (iface *serialPortInterface) hasUsbAttrs(attrs interfaces.Attrer) bool { | |
| var v int64 | |
| if err := attrs.Attr("usb-vendor", &v); err == nil { | |
| return true | |
| } | |
| if err := attrs.Attr("usb-product", &v); err == nil { | |
| return true | |
| } | |
| if err := attrs.Attr("usb-interface-number", &v); err == nil { | |
| return true | |
| } | |
| return false | |
| } | |
| func init() { | |
| registerIface(&serialPortInterface{}) | |
| } |