Merge lp:~c-lobrano/snappy/hw-assign-symlink into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by Carlo Lobrano
Status: Rejected
Rejected by: Leo Arias
Proposed branch: lp:~c-lobrano/snappy/hw-assign-symlink
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Diff against target: 731 lines (+504/-59)
5 files modified
cmd/snappy/cmd_hwassign.go (+32/-16)
po/snappy.pot (+15/-7)
snappy/errors.go (+16/-0)
snappy/hwaccess.go (+223/-36)
snappy/hwaccess_test.go (+218/-0)
To merge this branch: bzr merge lp:~c-lobrano/snappy/hw-assign-symlink
Reviewer Review Type Date Requested Status
Snappy Developers Pending
Review via email: mp+274908@code.launchpad.net

Description of the change

Proposal fix for Bug #1496319

This branch extends hw-assign in order to have also a symlink to a "hw-assigned" device node.

The new signature of the command is:

   snappy hw-assign <device> <symlink>

the <symlink> parameter is optional, this way the new hw-assign is backward compatible.

Best regards

To post a comment you must log in.
Revision history for this message
Michael Vogt (mvo) wrote :

Hey Carlo!

Thanks for the branch and sorry for the slow reply. We moved the code to github, but no worries, I imported your branch and pushed it github and created a pull request at https://github.com/ubuntu-core/snappy/pull/26

Lets continue in there. Feel free to branch from this above branch with your own, there are some merge conflicts right now, I can help resolve them if you want, just let me know.

Thanks again for your work on this and sorry for the trouble that this move might cause :)

Unmerged revisions

724. By Carlo Lobrano

Some fixes

1. add missing includes and calls due to previous merge
2. avoid raising errors when adding symlink to a device already in write path
3. avoid raising "file not found" error when the last UDEV rule has been removed
4. add new error when creating multiple symlinks to the same device
5. add test for the new error at point 4

723. By Carlo Lobrano

Merge master branch

722. By Carlo Lobrano

Revert add deviceProperties for hw-assign

Better to apply this features to a different snappy command (UDEV related maybe)

721. By Carlo Lobrano

Update hwaccess_test.go

updated tests that make use of AddSymlinkToHWDevice to manage the new parameter DeviceProperties

720. By Carlo Lobrano

Add deviceProperties to hw-assign

used to define the characteristics of the device to trigger the Udev symlink rule

719. By Carlo Lobrano

Rework of hw-unassign for symlinks and other fixes

cmd_hwassign.go:
 - Inverted hw assignement and symlink creation. The device is supposed to be already assigned to the snap, before creating the symlink.

errors.go:
 - added custom errors for symlinks.go

hwaccess.go:
 - reworked RemoveHWAccess to manage symlinks
 - add some wrapper functions to make things simpler

hwaccess_test.go:
 - add tests for new functions

718. By Carlo Lobrano

- Updated snappy.pot
- Add wrapper for checking whether a Snap has an apparmor json file
- Add wrapper for adding a new writepath for a Snap
- Added tests of new wrapper functions

717. By Carlo Lobrano

Udev rule to add symlink to HW device with hw-assign command

- added tests to verify the presence of such udev rule when the command is issued
- added third optional argument "symlinkpath" to hw-assign command
- added new method AddSymlinkToHWDevice to hwaccess module
- added new error for invalid symlink
- wrapped function to strip snapname from a full appname_binary-or-service_version string

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/snappy/cmd_hwassign.go'
2--- cmd/snappy/cmd_hwassign.go 2015-07-09 06:05:53 +0000
3+++ cmd/snappy/cmd_hwassign.go 2015-10-19 15:22:22 +0000
4@@ -29,14 +29,15 @@
5
6 type cmdHWAssign struct {
7 Positional struct {
8- PackageName string `positional-arg-name:"package name"`
9- DevicePath string `positional-arg-name:"device path"`
10- } `required:"true" positional-args:"yes"`
11+ PackageName string `positional-arg-name:"package_name" required:"true"`
12+ DevicePath string `positional-arg-name:"device_path" required:"true"`
13+ SymlinkPath string `positional-arg-name:"symlink_path" required:"false"`
14+ } `positional-args:"yes"`
15 }
16
17 var shortHWAssignHelp = i18n.G("Assign a hardware device to a package")
18
19-var longHWAssignHelp = i18n.G("This command adds access to a specific hardware device (e.g. /dev/ttyUSB0) for an installed package.")
20+var longHWAssignHelp = i18n.G("This command adds access to a specific hardware device (e.g. /dev/ttyUSB0) for an installed package, possibly through symlink if provided.")
21
22 func init() {
23 arg, err := parser.AddCommand("hw-assign",
24@@ -46,8 +47,9 @@
25 if err != nil {
26 logger.Panicf("Unable to hwassign: %v", err)
27 }
28- addOptionDescription(arg, "package name", i18n.G("Assign hardware to a specific installed package"))
29- addOptionDescription(arg, "device path", i18n.G("The hardware device path (e.g. /dev/ttyUSB0)"))
30+ addOptionDescription(arg, "package_name", i18n.G("Assign hardware to a specific installed package"))
31+ addOptionDescription(arg, "device_path", i18n.G("The hardware device path (e.g. /dev/ttyUSB0)"))
32+ addOptionDescription(arg, "symlink_path", i18n.G("The optional symlink to device path (e.g. /dev/symlink)"))
33 }
34
35 func (x *cmdHWAssign) Execute(args []string) error {
36@@ -57,15 +59,29 @@
37 func (x *cmdHWAssign) doHWAssign() error {
38 if err := snappy.AddHWAccess(x.Positional.PackageName, x.Positional.DevicePath); err != nil {
39 if err == snappy.ErrHWAccessAlreadyAdded {
40- // TRANSLATORS: the first %s is a pkgname, the second %s is a path
41- fmt.Printf(i18n.G("'%s' previously allowed access to '%s'. Skipping\n"), x.Positional.PackageName, x.Positional.DevicePath)
42- return nil
43- }
44-
45- return err
46- }
47-
48- // TRANSLATORS: the first %s is a pkgname, the second %s is a path
49- fmt.Printf(i18n.G("'%s' is now allowed to access '%s'\n"), x.Positional.PackageName, x.Positional.DevicePath)
50+ if "" == x.Positional.SymlinkPath {
51+ // TRANSLATORS: the first %s is a pkgname, the second %s is a path
52+ fmt.Printf(i18n.G("'%s' previously allowed access to '%s'. Skipping\n"), x.Positional.PackageName, x.Positional.DevicePath)
53+ return nil
54+ }
55+ } else {
56+ return err
57+ }
58+ }
59+
60+ if "" != x.Positional.SymlinkPath {
61+ if err := snappy.AddSymlinkToHWDevice(
62+ x.Positional.PackageName,
63+ x.Positional.DevicePath,
64+ x.Positional.SymlinkPath); err != nil {
65+ return err
66+ }
67+ // TRANSLATORS: the first %s is a pkgname, the second %s is a path
68+ fmt.Printf(i18n.G("'%s' is allowed to access '%s' through symlink '%s' \n"), x.Positional.PackageName, x.Positional.DevicePath, x.Positional.SymlinkPath)
69+ } else {
70+ // TRANSLATORS: the first %s is a pkgname, the second %s is a path
71+ fmt.Printf(i18n.G("'%s' is allowed to access '%s'\n"), x.Positional.PackageName, x.Positional.DevicePath)
72+ }
73+
74 return nil
75 }
76
77=== modified file 'po/snappy.pot'
78--- po/snappy.pot 2015-09-29 09:45:15 +0000
79+++ po/snappy.pot 2015-10-19 15:22:22 +0000
80@@ -7,7 +7,7 @@
81 msgid ""
82 msgstr "Project-Id-Version: snappy\n"
83 "Report-Msgid-Bugs-To: snappy-devel@lists.ubuntu.com\n"
84- "POT-Creation-Date: 2015-09-29 10:18+0100\n"
85+ "POT-Creation-Date: 2015-10-19 17:05+0200\n"
86 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
87 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
88 "Language-Team: LANGUAGE <LL@li.org>\n"
89@@ -28,16 +28,21 @@
90
91 #. TRANSLATORS: the first %s is a pkgname, the second %s is a path
92 #, c-format
93+msgid "'%s' is allowed to access '%s' through symlink '%s' \n"
94+msgstr ""
95+
96+#. TRANSLATORS: the first %s is a pkgname, the second %s is a path
97+#, c-format
98+msgid "'%s' is allowed to access '%s'\n"
99+msgstr ""
100+
101+#. TRANSLATORS: the first %s is a pkgname, the second %s is a path
102+#, c-format
103 msgid "'%s' is no longer allowed to access '%s'\n"
104 msgstr ""
105
106 #. TRANSLATORS: the first %s is a pkgname, the second %s is a path
107 #, c-format
108-msgid "'%s' is now allowed to access '%s'\n"
109-msgstr ""
110-
111-#. TRANSLATORS: the first %s is a pkgname, the second %s is a path
112-#, c-format
113 msgid "'%s' previously allowed access to '%s'. Skipping\n"
114 msgstr ""
115
116@@ -287,13 +292,16 @@
117 msgid "The hardware device path (e.g. /dev/ttyUSB0)"
118 msgstr ""
119
120+msgid "The optional symlink to device path (e.g. /dev/symlink)"
121+msgstr ""
122+
123 msgid "The package to rollback "
124 msgstr ""
125
126 msgid "The version to rollback to"
127 msgstr ""
128
129-msgid "This command adds access to a specific hardware device (e.g. /dev/ttyUSB0) for an installed package."
130+msgid "This command adds access to a specific hardware device (e.g. /dev/ttyUSB0) for an installed package, possibly through symlink if provided."
131 msgstr ""
132
133 msgid "This command is no longer available, please use the \"list\" command"
134
135=== modified file 'snappy/errors.go'
136--- snappy/errors.go 2015-09-15 12:55:09 +0000
137+++ snappy/errors.go 2015-10-19 15:22:22 +0000
138@@ -51,6 +51,14 @@
139 // is given in the hw-assign command
140 ErrInvalidHWDevice = errors.New("invalid hardware device")
141
142+ // ErrInvalidSymlinkToHWDevice is returned when a invalid symlink to hardware device
143+ // is given in the hw-assign command
144+ ErrInvalidSymlinkToHWDevice = errors.New("invalid symlink to hardware device")
145+
146+ // ErrSymlinkToHWNameCollision is returned when a symlink with the same name of a
147+ // device in the write path is requested
148+ ErrSymlinkToHWNameCollision = errors.New("symlink's name collides with on of the snap's write paths")
149+
150 // ErrHWAccessRemoveNotFound is returned if the user tries to
151 // remove a device that does not exist
152 ErrHWAccessRemoveNotFound = errors.New("can not find device in hw-access list")
153@@ -59,6 +67,14 @@
154 // that is already in the hwaccess list
155 ErrHWAccessAlreadyAdded = errors.New("device is already in hw-access list")
156
157+ // ErrHwDeviceAlreadySymlinked is returned when the device has already a symlink
158+ // with a different name
159+ ErrHwDeviceAlreadySymlinked = errors.New("device has a symlink already")
160+
161+ // ErrSymlinkToHWAlreadyAdded is returned if you try to add a symlink
162+ // that is already in the hwaccess list
163+ ErrSymlinkToHWAlreadyAdded = errors.New("symlink is already in hw-access list")
164+
165 // ErrReadmeInvalid is returned if the package contains a invalid
166 // meta/readme.md
167 ErrReadmeInvalid = errors.New("meta/readme.md invalid")
168
169=== modified file 'snappy/hwaccess.go'
170--- snappy/hwaccess.go 2015-09-29 07:07:13 +0000
171+++ snappy/hwaccess.go 2015-10-19 15:22:22 +0000
172@@ -38,8 +38,9 @@
173 var aaClickHookCmd = "aa-clickhook"
174
175 type appArmorAdditionalJSON struct {
176- WritePath []string `json:"write_path,omitempty"`
177- ReadPath []string `json:"read_path,omitempty"`
178+ WritePath []string `json:"write_path,omitempty"`
179+ ReadPath []string `json:"read_path,omitempty"`
180+ SymlinkPath map[string]string `json:"symlink_path,omitempty"`
181 }
182
183 // return the json filename to add to the security json
184@@ -137,16 +138,25 @@
185 return nil
186 }
187
188+// StripSnapName extracts the snapname from a full
189+// appname_binary-or-service_version string
190+func stripSnapName(snapname string) string {
191+ strippedSnapname := snapname
192+ if strings.Contains(snapname, "_") {
193+ l := strings.Split(snapname, "_")
194+ strippedSnapname = l[0]
195+ }
196+
197+ return strippedSnapname
198+}
199+
200 func writeUdevRuleForDeviceCgroup(snapname, device string) error {
201 os.MkdirAll(dirs.SnapUdevRulesDir, 0755)
202
203 // the device cgroup/launcher etc support only the apps level,
204 // not a binary/service or version, so if we get a full
205 // appname_binary-or-service_version string we need to split that
206- if strings.Contains(snapname, "_") {
207- l := strings.Split(snapname, "_")
208- snapname = l[0]
209- }
210+ snapname = stripSnapName(snapname)
211
212 acl := fmt.Sprintf(`
213 KERNEL=="%v", TAG:="snappy-assign", ENV{SNAPPY_APP}:="%s"
214@@ -159,16 +169,27 @@
215 return activateOemHardwareUdevRules()
216 }
217
218+func writeSymlinkUdevRuleForDeviceCgroup(snapname, device, symlink string) error {
219+ os.MkdirAll(dirs.SnapUdevRulesDir, 0755)
220+
221+ snapname = stripSnapName(snapname)
222+
223+ acl := fmt.Sprintf(`
224+ACTION=="add", KERNEL=="%v", TAG:="snappy-assign", ENV{SNAPPY_APP}:="%s", SYMLINK+="%v"
225+`, filepath.Base(device), snapname, filepath.Base(symlink))
226+
227+ if err := addUdevRuleForSnap(snapname, acl); err != nil {
228+ return err
229+ }
230+
231+ return activateOemHardwareUdevRules()
232+
233+}
234+
235 var regenerateAppArmorRules = regenerateAppArmorRulesImpl
236
237-// AddHWAccess allows the given snap package to access the given hardware
238-// device
239-func AddHWAccess(snapname, device string) error {
240- if !validDevice(device) {
241- return ErrInvalidHWDevice
242- }
243-
244- // check if there is anything apparmor related to add to
245+// check if there is anything apparmor related to add to
246+func hasSnapApparmorJSON(snapname string) error {
247 globExpr := filepath.Join(dirs.SnapAppArmorDir, fmt.Sprintf("%s_*.json", snapname))
248 matches, err := filepath.Glob(globExpr)
249 if err != nil {
250@@ -178,6 +199,10 @@
251 return ErrPackageNotFound
252 }
253
254+ return nil
255+}
256+
257+func addNewWritePathForSnap(snapname, device string) error {
258 // read .additional file, its ok if the file does not exist (yet)
259 appArmorAdditional, err := readHWAccessJSONFile(snapname)
260 if err != nil && !os.IsNotExist(err) {
261@@ -199,6 +224,75 @@
262 return err
263 }
264
265+ return nil
266+}
267+
268+func isStringInSlice(key string, slice []string) bool {
269+ for _, p := range slice {
270+ if p == key {
271+ return true
272+ }
273+ }
274+ return false
275+}
276+
277+func addNewSymlinkPathForSnap(snapname, symlink, device string) error {
278+ // read .additional file, its ok if the file does not exist (yet)
279+ appArmorAdditional, err := readHWAccessJSONFile(snapname)
280+ if err != nil && !os.IsNotExist(err) {
281+ return err
282+ }
283+
284+ // It is expected to have already access to the
285+ // real hw device before creating a symlink to it
286+ if !isStringInSlice(device, appArmorAdditional.WritePath) {
287+ return ErrHWAccessRemoveNotFound
288+ }
289+
290+ if isStringInSlice(symlink, appArmorAdditional.WritePath) {
291+ return ErrSymlinkToHWNameCollision
292+ }
293+
294+ if nil != appArmorAdditional.SymlinkPath {
295+ for key, item := range appArmorAdditional.SymlinkPath {
296+ if key == symlink {
297+ return ErrSymlinkToHWAlreadyAdded
298+ }
299+ if item == device {
300+ return ErrHwDeviceAlreadySymlinked
301+ }
302+ }
303+ } else {
304+ appArmorAdditional.SymlinkPath = make(map[string]string)
305+ }
306+
307+ // add the new symlink:hw-device pair
308+ appArmorAdditional.SymlinkPath[symlink] = device
309+
310+ // and write the data out
311+ err = writeHWAccessJSONFile(snapname, appArmorAdditional)
312+ if err != nil {
313+ return err
314+ }
315+
316+ return nil
317+}
318+
319+// AddHWAccess allows the given snap package to access the given hardware
320+// device
321+func AddHWAccess(snapname, device string) error {
322+ if !validDevice(device) {
323+ return ErrInvalidHWDevice
324+ }
325+
326+ if err := hasSnapApparmorJSON(snapname); nil != err {
327+ return err
328+ }
329+
330+ if err := addNewWritePathForSnap(snapname, device); nil != err {
331+ return err
332+ }
333+
334 // add udev rule for device cgroup
335 if err := writeUdevRuleForDeviceCgroup(snapname, device); err != nil {
336 return err
337@@ -208,6 +302,32 @@
338 return regenerateAppArmorRules()
339 }
340
341+// AddSymlinkToHWDevice writes an Udev rule to create a symlink to the
342+// given hardware device
343+func AddSymlinkToHWDevice(snapname, device, symlink string) error {
344+ if !validDevice(device) {
345+ return ErrInvalidHWDevice
346+ }
347+
348+ if !validDevice(symlink) {
349+ return ErrInvalidSymlinkToHWDevice
350+ }
351+
352+ if err := hasSnapApparmorJSON(snapname); nil != err {
353+ return err
354+ }
355+
356+ if err := addNewSymlinkPathForSnap(snapname, symlink, device); nil != err {
357+ return err
358+ }
359+
360+ if err := writeSymlinkUdevRuleForDeviceCgroup(snapname, device, symlink); nil != err {
361+ return err
362+ }
363+
364+ return nil
365+}
366+
367 // ListHWAccess returns a list of hardware-device strings that the snap
368 // can access
369 func ListHWAccess(snapname string) ([]string, error) {
370@@ -264,8 +384,8 @@
371
372 // RemoveHWAccess allows the given snap package to access the given hardware
373 // device
374-func RemoveHWAccess(snapname, device string) error {
375- if !validDevice(device) {
376+func RemoveHWAccess(snapname, path string) error {
377+ if !validDevice(path) {
378 return ErrInvalidHWDevice
379 }
380
381@@ -274,26 +394,71 @@
382 return err
383 }
384
385- // remove write path, please golang make this easier!
386- newWritePath := []string{}
387- for _, p := range appArmorAdditional.WritePath {
388- if p != device {
389- newWritePath = append(newWritePath, p)
390- }
391- }
392- if len(newWritePath) == len(appArmorAdditional.WritePath) {
393- return ErrHWAccessRemoveNotFound
394- }
395- appArmorAdditional.WritePath = newWritePath
396-
397- // and write it out again
398- err = writeHWAccessJSONFile(snapname, appArmorAdditional)
399- if err != nil {
400- return err
401- }
402-
403- if err = removeUdevRuleForSnap(snapname, device); nil != err {
404- return err
405+ // Check whether it is a symlink first
406+ device, symlink, err := resolveSymlink(snapname, path)
407+ if nil != err {
408+ return err
409+ }
410+
411+ // When unassigning a symlink, path == symlink and only the
412+ // symlink has to be removed.
413+ // When unassigning an HW device, path == device (while symlink
414+ // can be another path or a null string) and both device
415+ // and symlink (if any) have to be removed.
416+ if path == device {
417+ // remove write path, please golang make this easier!
418+ newWritePath := []string{}
419+ for _, p := range appArmorAdditional.WritePath {
420+ if p != device {
421+ newWritePath = append(newWritePath, p)
422+ }
423+ }
424+
425+ if len(newWritePath) == len(appArmorAdditional.WritePath) {
426+ return ErrHWAccessRemoveNotFound
427+ }
428+
429+ // Update WritePath
430+ appArmorAdditional.WritePath = newWritePath
431+
432+ // and write it out again
433+ err = writeHWAccessJSONFile(snapname, appArmorAdditional)
434+ if err != nil {
435+ return err
436+ }
437+
438+ if err = removeUdevRuleForSnap(snapname, device); nil != err {
439+ return err
440+ }
441+
442+ }
443+
444+ if "" != symlink {
445+ newSymlinkPath := make(map[string]string)
446+ for key, value := range appArmorAdditional.SymlinkPath {
447+ if key != symlink {
448+ newSymlinkPath[key] = value
449+ }
450+ }
451+
452+ if len(appArmorAdditional.SymlinkPath) == len(newSymlinkPath) {
453+ return ErrHWAccessRemoveNotFound
454+ }
455+
456+ appArmorAdditional.SymlinkPath = newSymlinkPath
457+
458+ err = writeHWAccessJSONFile(snapname, appArmorAdditional)
459+ if err != nil {
460+ return err
461+ }
462+
463+ if err := removeUdevRuleForSnap(snapname, symlink); nil != err {
464+ // When there are only rules for the same HW device, the UDEV
465+ // rule file might not exist anymore at this point.
466+ if !os.IsNotExist(err) {
467+ return err
468+ }
469+ }
470 }
471
472 if err := activateOemHardwareUdevRules(); err != nil {
473@@ -317,3 +482,25 @@
474
475 return regenerateAppArmorRules()
476 }
477+
478+func resolveSymlink(snapname, path string) (device, symlink string, err error) {
479+ appArmorAdditional, err := readHWAccessJSONFile(snapname)
480+ if nil != err {
481+ return "", "", err
482+ }
483+
484+ if nil != appArmorAdditional.SymlinkPath {
485+ for symlink, device := range appArmorAdditional.SymlinkPath {
486+ if path == symlink || path == device {
487+ return device, symlink, nil
488+ }
489+ }
490+ }
491+
492+ // no symlink found, path must be an HW device
493+ if !isStringInSlice(path, appArmorAdditional.WritePath) {
494+ return "", "", ErrHWAccessRemoveNotFound
495+ }
496+
497+ return path, "", nil
498+}
499
500=== modified file 'snappy/hwaccess_test.go'
501--- snappy/hwaccess_test.go 2015-09-29 07:07:13 +0000
502+++ snappy/hwaccess_test.go 2015-10-19 15:22:22 +0000
503@@ -290,6 +290,24 @@
504 verifyUdevAdmActivateRules(c, runUdevAdmCalls)
505 }
506
507+func (s *SnapTestSuite) TestWriteSymlinkUdevRuleForDeviceCgroup(c *C) {
508+ var runUdevAdmCalls [][]string
509+ runUdevAdm = makeRunUdevAdmMock(&runUdevAdmCalls)
510+
511+ snapapp := "foo-app_meep_1.0"
512+
513+ err := writeSymlinkUdevRuleForDeviceCgroup(snapapp, "/dev/ttyS0", "/dev/symS0")
514+ c.Assert(err, IsNil)
515+
516+ got, err := ioutil.ReadFile(filepath.Join(dirs.SnapUdevRulesDir, "70-snappy_hwassign_foo-app.rules"))
517+ c.Assert(err, IsNil)
518+ c.Assert(string(got), Equals, `
519+ACTION=="add", KERNEL=="ttyS0", TAG:="snappy-assign", ENV{SNAPPY_APP}:="foo-app", SYMLINK+="symS0"
520+`)
521+
522+ verifyUdevAdmActivateRules(c, runUdevAdmCalls)
523+}
524+
525 func (s *SnapTestSuite) TestRemoveAllHWAccess(c *C) {
526 makeInstalledMockSnap(s.tempdir, "")
527
528@@ -320,3 +338,203 @@
529 Output: []byte("meep\n"),
530 })
531 }
532+
533+func (s *SnapTestSuite) TestHasSnapApparmorJSON(c *C) {
534+ err := hasSnapApparmorJSON("non-existent-app")
535+ c.Assert(err, Equals, ErrPackageNotFound)
536+
537+ makeInstalledMockSnap(s.tempdir, "")
538+ err = AddHWAccess("hello-app", "/dev/ttyUSB0")
539+ c.Assert(err, IsNil)
540+
541+ err = hasSnapApparmorJSON("hello-app")
542+ c.Assert(err, IsNil)
543+}
544+
545+func (s *SnapTestSuite) TestAddNewWritePathForSnap(c *C) {
546+ // try add same path twice
547+ makeInstalledMockSnap(s.tempdir, "")
548+ err := addNewWritePathForSnap("hello-app", "/dev/ttyUSB0")
549+ c.Assert(err, IsNil)
550+ err = addNewWritePathForSnap("hello-app", "/dev/ttyUSB0")
551+ c.Assert(err, Equals, ErrHWAccessAlreadyAdded)
552+
553+ // check .additional file is written right
554+ content, err := ioutil.ReadFile(filepath.Join(dirs.SnapAppArmorDir, "hello-app.json.additional"))
555+ c.Assert(err, IsNil)
556+ c.Assert(string(content), Equals, `{
557+ "write_path": [
558+ "/dev/ttyUSB0"
559+ ],
560+ "read_path": [
561+ "/run/udev/data/*"
562+ ]
563+}
564+`)
565+}
566+
567+func (s *SnapTestSuite) TestAddNewSymlinkPathForSnap(c *C) {
568+ makeInstalledMockSnap(s.tempdir, "")
569+
570+ // try add as symlink to an hw device not in write path, yet
571+ err := addNewSymlinkPathForSnap("hello-app", "/dev/symtest0", "/dev/ttyUSB0")
572+ c.Assert(err, Equals, ErrHWAccessRemoveNotFound)
573+
574+ // try add the same symlink twice
575+ err = addNewWritePathForSnap("hello-app", "/dev/ttyUSB0")
576+ c.Assert(err, IsNil)
577+ err = addNewSymlinkPathForSnap("hello-app", "/dev/symtest0", "/dev/ttyUSB0")
578+ c.Assert(err, IsNil)
579+ err = addNewSymlinkPathForSnap("hello-app", "/dev/symtest0", "/dev/ttyUSB0")
580+ c.Assert(err, Equals, ErrSymlinkToHWAlreadyAdded)
581+
582+ // try add a new symlink to a device that already has one
583+ err = addNewSymlinkPathForSnap("hello-app", "/dev/symtest1", "/dev/ttyUSB0")
584+ c.Assert(err, Equals, ErrHwDeviceAlreadySymlinked)
585+
586+ // check .additional file is written right
587+ content, err := ioutil.ReadFile(filepath.Join(dirs.SnapAppArmorDir, "hello-app.json.additional"))
588+ c.Assert(err, IsNil)
589+ c.Assert(string(content), Equals, `{
590+ "write_path": [
591+ "/dev/ttyUSB0"
592+ ],
593+ "read_path": [
594+ "/run/udev/data/*"
595+ ],
596+ "symlink_path": {
597+ "/dev/symtest0": "/dev/ttyUSB0"
598+ }
599+}
600+`)
601+
602+ // try add a second symlink
603+ err = AddHWAccess("hello-app", "/dev/ttyUSB1")
604+ c.Assert(err, IsNil)
605+
606+ err = AddSymlinkToHWDevice("hello-app", "/dev/ttyUSB1", "/dev/symtest1")
607+ c.Assert(err, IsNil)
608+
609+ // check .additional file is written right
610+ content, err = ioutil.ReadFile(filepath.Join(dirs.SnapAppArmorDir, "hello-app.json.additional"))
611+ c.Assert(err, IsNil)
612+ c.Assert(string(content), Equals, `{
613+ "write_path": [
614+ "/dev/ttyUSB0",
615+ "/dev/ttyUSB1"
616+ ],
617+ "read_path": [
618+ "/run/udev/data/*"
619+ ],
620+ "symlink_path": {
621+ "/dev/symtest0": "/dev/ttyUSB0",
622+ "/dev/symtest1": "/dev/ttyUSB1"
623+ }
624+}
625+`)
626+ // try add symlink with the same name of one of snap's write paths
627+ err = addNewSymlinkPathForSnap("hello-app", "/dev/ttyUSB1", "/dev/ttyUSB1")
628+ c.Assert(err, Equals, ErrSymlinkToHWNameCollision)
629+}
630+
631+func (s *SnapTestSuite) TestRemoveSymlinkToHWDevice(c *C) {
632+ aaClickHookCmd = "true"
633+ makeInstalledMockSnap(s.tempdir, "")
634+
635+ // Add access to 2 devices
636+ err := AddHWAccess("hello-app", "/dev/ttyUSB0")
637+ c.Assert(err, IsNil)
638+ err = AddHWAccess("hello-app", "/dev/ttyUSB1")
639+ c.Assert(err, IsNil)
640+
641+ // Adding a symlinks
642+ err = AddSymlinkToHWDevice("hello-app", "/dev/ttyUSB0", "/dev/symtest0")
643+ c.Assert(err, IsNil)
644+
645+ err = AddSymlinkToHWDevice("hello-app", "/dev/ttyUSB1", "/dev/symtest1")
646+ c.Assert(err, IsNil)
647+
648+ // Remove hw device with symlink: the symlink is expected to be removed too
649+ err = RemoveHWAccess("hello-app", "/dev/ttyUSB1")
650+ c.Assert(err, IsNil)
651+
652+ content, err := ioutil.ReadFile(filepath.Join(dirs.SnapAppArmorDir, "hello-app.json.additional"))
653+ c.Assert(err, IsNil)
654+ c.Assert(string(content), Equals, `{
655+ "write_path": [
656+ "/dev/ttyUSB0"
657+ ],
658+ "read_path": [
659+ "/run/udev/data/*"
660+ ],
661+ "symlink_path": {
662+ "/dev/symtest0": "/dev/ttyUSB0"
663+ }
664+}
665+`)
666+
667+ // Remove only the symlink
668+ err = RemoveHWAccess("hello-app", "/dev/symtest0")
669+ c.Assert(err, IsNil)
670+
671+ // having removed the last symlink, SymlinkPath is expected to be nil
672+ appArmorAdditional, err := readHWAccessJSONFile("hello-app")
673+ c.Assert(err, IsNil)
674+ c.Assert(appArmorAdditional.SymlinkPath, IsNil)
675+
676+ // expecting write path unchanged
677+ content, err = ioutil.ReadFile(filepath.Join(dirs.SnapAppArmorDir, "hello-app.json.additional"))
678+ c.Assert(err, IsNil)
679+ c.Assert(string(content), Equals, `{
680+ "write_path": [
681+ "/dev/ttyUSB0"
682+ ],
683+ "read_path": [
684+ "/run/udev/data/*"
685+ ]
686+}
687+`)
688+}
689+
690+func (s *SnapTestSuite) TestRemoveUdevRuleForSnap(c *C) {
691+ aaClickHookCmd = "true"
692+ makeInstalledMockSnap(s.tempdir, "")
693+
694+ // Add access to 3 devices and 2 symlinks
695+ err := AddHWAccess("hello-app", "/dev/ttyUSB0")
696+ c.Assert(err, IsNil)
697+ err = AddHWAccess("hello-app", "/dev/ttyUSB1")
698+ c.Assert(err, IsNil)
699+ err = AddHWAccess("hello-app", "/dev/ttyUSB2")
700+ c.Assert(err, IsNil)
701+ err = AddSymlinkToHWDevice("hello-app", "/dev/ttyUSB0", "/dev/symlink0")
702+ c.Assert(err, IsNil)
703+ err = AddSymlinkToHWDevice("hello-app", "/dev/ttyUSB1", "/dev/symlink1")
704+ c.Assert(err, IsNil)
705+
706+ // Remove the device without symlink
707+ err = RemoveHWAccess("hello-app", "/dev/ttyUSB2")
708+ c.Assert(err, IsNil)
709+ content, err := ioutil.ReadFile(filepath.Join(dirs.SnapUdevRulesDir, "70-snappy_hwassign_hello-app.rules"))
710+ c.Assert(err, IsNil)
711+ c.Assert(string(content), Equals, `KERNEL=="ttyUSB0", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app"
712+KERNEL=="ttyUSB1", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app"
713+ACTION=="add", KERNEL=="ttyUSB0", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app", SYMLINK+="symlink0"
714+ACTION=="add", KERNEL=="ttyUSB1", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app", SYMLINK+="symlink1"
715+`)
716+ // Remove a device with a symlink
717+ err = RemoveHWAccess("hello-app", "/dev/ttyUSB1")
718+ c.Assert(err, IsNil)
719+ content, err = ioutil.ReadFile(filepath.Join(dirs.SnapUdevRulesDir, "70-snappy_hwassign_hello-app.rules"))
720+ c.Assert(err, IsNil)
721+ c.Assert(string(content), Equals, `KERNEL=="ttyUSB0", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app"
722+ACTION=="add", KERNEL=="ttyUSB0", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app", SYMLINK+="symlink0"
723+`)
724+ // Remove the symlink
725+ err = RemoveHWAccess("hello-app", "/dev/symlink0")
726+ c.Assert(err, IsNil)
727+ content, err = ioutil.ReadFile(filepath.Join(dirs.SnapUdevRulesDir, "70-snappy_hwassign_hello-app.rules"))
728+ c.Assert(err, IsNil)
729+ c.Assert(string(content), Equals, `KERNEL=="ttyUSB0", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app"
730+`)
731+}

Subscribers

People subscribed via source and target branches