/
serial_port.go
187 lines (164 loc) · 5.99 KB
/
serial_port.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2016 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"
)
// 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) 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]+$")
// SanitizeSlot checks validity of the defined slot
func (iface *SerialPortInterface) SanitizeSlot(slot *interfaces.Slot) error {
// Check slot is of right type
if iface.Name() != slot.Interface {
panic(fmt.Sprintf("slot is not of interface %q", iface))
}
// We will only allow creation of this type of slot by a gadget or OS snap
if !(slot.Snap.Type == "gadget" || slot.Snap.Type == "os") {
return fmt.Errorf("serial-port slots only allowed on gadget or core snaps")
}
// 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)
}
} 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
}
// SanitizePlug checks and possibly modifies a plug.
func (iface *SerialPortInterface) SanitizePlug(plug *interfaces.Plug) error {
if iface.Name() != plug.Interface {
panic(fmt.Sprintf("plug is not of interface %q", iface))
}
// NOTE: currently we don't check anything on the plug side.
return nil
}
func (iface *SerialPortInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error {
usbVendor, vOk := slot.Attrs["usb-vendor"].(int64)
if !vOk {
return nil
}
usbProduct, pOk := slot.Attrs["usb-product"].(int64)
if !pOk {
return nil
}
path, ok := slot.Attrs["path"].(string)
if !ok || path == "" {
return nil
}
spec.AddSnippet(string(udevUsbDeviceSnippet("tty", usbVendor, usbProduct, "SYMLINK", strings.TrimPrefix(path, "/dev/"))))
return nil
}
func (iface *SerialPortInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, slot *interfaces.Slot) 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 (no udev tagging)
path, pathOk := slot.Attrs["path"].(string)
if !pathOk {
return nil
}
cleanedPath := filepath.Clean(path)
spec.AddSnippet(fmt.Sprintf("%s rw,", cleanedPath))
return nil
}
func (iface *SerialPortInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error {
usbVendor, vOk := slot.Attrs["usb-vendor"].(int64)
if !vOk {
return nil
}
usbProduct, pOk := slot.Attrs["usb-product"].(int64)
if !pOk {
return nil
}
for appName := range plug.Apps {
tag := udevSnapSecurityName(plug.Snap.Name(), appName)
spec.AddSnippet(udevUsbDeviceSnippet("tty", usbVendor, usbProduct, "TAG", tag))
}
return nil
}
func (iface *SerialPortInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool {
// allow what declarations allowed
return true
}
func (iface *SerialPortInterface) hasUsbAttrs(slot *interfaces.Slot) bool {
if _, ok := slot.Attrs["usb-vendor"]; ok {
return true
}
if _, ok := slot.Attrs["usb-product"]; ok {
return true
}
return false
}