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
=== modified file 'cmd/snappy/cmd_hwassign.go'
--- cmd/snappy/cmd_hwassign.go 2015-07-09 06:05:53 +0000
+++ cmd/snappy/cmd_hwassign.go 2015-10-19 15:22:22 +0000
@@ -29,14 +29,15 @@
2929
30type cmdHWAssign struct {30type cmdHWAssign struct {
31 Positional struct {31 Positional struct {
32 PackageName string `positional-arg-name:"package name"`32 PackageName string `positional-arg-name:"package_name" required:"true"`
33 DevicePath string `positional-arg-name:"device path"`33 DevicePath string `positional-arg-name:"device_path" required:"true"`
34 } `required:"true" positional-args:"yes"`34 SymlinkPath string `positional-arg-name:"symlink_path" required:"false"`
35 } `positional-args:"yes"`
35}36}
3637
37var shortHWAssignHelp = i18n.G("Assign a hardware device to a package")38var shortHWAssignHelp = i18n.G("Assign a hardware device to a package")
3839
39var longHWAssignHelp = i18n.G("This command adds access to a specific hardware device (e.g. /dev/ttyUSB0) for an installed package.")40var 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.")
4041
41func init() {42func init() {
42 arg, err := parser.AddCommand("hw-assign",43 arg, err := parser.AddCommand("hw-assign",
@@ -46,8 +47,9 @@
46 if err != nil {47 if err != nil {
47 logger.Panicf("Unable to hwassign: %v", err)48 logger.Panicf("Unable to hwassign: %v", err)
48 }49 }
49 addOptionDescription(arg, "package name", i18n.G("Assign hardware to a specific installed package"))50 addOptionDescription(arg, "package_name", i18n.G("Assign hardware to a specific installed package"))
50 addOptionDescription(arg, "device path", i18n.G("The hardware device path (e.g. /dev/ttyUSB0)"))51 addOptionDescription(arg, "device_path", i18n.G("The hardware device path (e.g. /dev/ttyUSB0)"))
52 addOptionDescription(arg, "symlink_path", i18n.G("The optional symlink to device path (e.g. /dev/symlink)"))
51}53}
5254
53func (x *cmdHWAssign) Execute(args []string) error {55func (x *cmdHWAssign) Execute(args []string) error {
@@ -57,15 +59,29 @@
57func (x *cmdHWAssign) doHWAssign() error {59func (x *cmdHWAssign) doHWAssign() error {
58 if err := snappy.AddHWAccess(x.Positional.PackageName, x.Positional.DevicePath); err != nil {60 if err := snappy.AddHWAccess(x.Positional.PackageName, x.Positional.DevicePath); err != nil {
59 if err == snappy.ErrHWAccessAlreadyAdded {61 if err == snappy.ErrHWAccessAlreadyAdded {
60 // TRANSLATORS: the first %s is a pkgname, the second %s is a path62 if "" == x.Positional.SymlinkPath {
61 fmt.Printf(i18n.G("'%s' previously allowed access to '%s'. Skipping\n"), x.Positional.PackageName, x.Positional.DevicePath)63 // TRANSLATORS: the first %s is a pkgname, the second %s is a path
62 return nil64 fmt.Printf(i18n.G("'%s' previously allowed access to '%s'. Skipping\n"), x.Positional.PackageName, x.Positional.DevicePath)
63 }65 return nil
6466 }
65 return err67 } else {
66 }68 return err
6769 }
68 // TRANSLATORS: the first %s is a pkgname, the second %s is a path70 }
69 fmt.Printf(i18n.G("'%s' is now allowed to access '%s'\n"), x.Positional.PackageName, x.Positional.DevicePath)71
72 if "" != x.Positional.SymlinkPath {
73 if err := snappy.AddSymlinkToHWDevice(
74 x.Positional.PackageName,
75 x.Positional.DevicePath,
76 x.Positional.SymlinkPath); err != nil {
77 return err
78 }
79 // TRANSLATORS: the first %s is a pkgname, the second %s is a path
80 fmt.Printf(i18n.G("'%s' is allowed to access '%s' through symlink '%s' \n"), x.Positional.PackageName, x.Positional.DevicePath, x.Positional.SymlinkPath)
81 } else {
82 // TRANSLATORS: the first %s is a pkgname, the second %s is a path
83 fmt.Printf(i18n.G("'%s' is allowed to access '%s'\n"), x.Positional.PackageName, x.Positional.DevicePath)
84 }
85
70 return nil86 return nil
71}87}
7288
=== modified file 'po/snappy.pot'
--- po/snappy.pot 2015-09-29 09:45:15 +0000
+++ po/snappy.pot 2015-10-19 15:22:22 +0000
@@ -7,7 +7,7 @@
7msgid ""7msgid ""
8msgstr "Project-Id-Version: snappy\n"8msgstr "Project-Id-Version: snappy\n"
9 "Report-Msgid-Bugs-To: snappy-devel@lists.ubuntu.com\n"9 "Report-Msgid-Bugs-To: snappy-devel@lists.ubuntu.com\n"
10 "POT-Creation-Date: 2015-09-29 10:18+0100\n"10 "POT-Creation-Date: 2015-10-19 17:05+0200\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"13 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -28,16 +28,21 @@
2828
29#. TRANSLATORS: the first %s is a pkgname, the second %s is a path29#. TRANSLATORS: the first %s is a pkgname, the second %s is a path
30#, c-format30#, c-format
31msgid "'%s' is allowed to access '%s' through symlink '%s' \n"
32msgstr ""
33
34#. TRANSLATORS: the first %s is a pkgname, the second %s is a path
35#, c-format
36msgid "'%s' is allowed to access '%s'\n"
37msgstr ""
38
39#. TRANSLATORS: the first %s is a pkgname, the second %s is a path
40#, c-format
31msgid "'%s' is no longer allowed to access '%s'\n"41msgid "'%s' is no longer allowed to access '%s'\n"
32msgstr ""42msgstr ""
3343
34#. TRANSLATORS: the first %s is a pkgname, the second %s is a path44#. TRANSLATORS: the first %s is a pkgname, the second %s is a path
35#, c-format45#, c-format
36msgid "'%s' is now allowed to access '%s'\n"
37msgstr ""
38
39#. TRANSLATORS: the first %s is a pkgname, the second %s is a path
40#, c-format
41msgid "'%s' previously allowed access to '%s'. Skipping\n"46msgid "'%s' previously allowed access to '%s'. Skipping\n"
42msgstr ""47msgstr ""
4348
@@ -287,13 +292,16 @@
287msgid "The hardware device path (e.g. /dev/ttyUSB0)"292msgid "The hardware device path (e.g. /dev/ttyUSB0)"
288msgstr ""293msgstr ""
289294
295msgid "The optional symlink to device path (e.g. /dev/symlink)"
296msgstr ""
297
290msgid "The package to rollback "298msgid "The package to rollback "
291msgstr ""299msgstr ""
292300
293msgid "The version to rollback to"301msgid "The version to rollback to"
294msgstr ""302msgstr ""
295303
296msgid "This command adds access to a specific hardware device (e.g. /dev/ttyUSB0) for an installed package."304msgid "This command adds access to a specific hardware device (e.g. /dev/ttyUSB0) for an installed package, possibly through symlink if provided."
297msgstr ""305msgstr ""
298306
299msgid "This command is no longer available, please use the \"list\" command"307msgid "This command is no longer available, please use the \"list\" command"
300308
=== modified file 'snappy/errors.go'
--- snappy/errors.go 2015-09-15 12:55:09 +0000
+++ snappy/errors.go 2015-10-19 15:22:22 +0000
@@ -51,6 +51,14 @@
51 // is given in the hw-assign command51 // is given in the hw-assign command
52 ErrInvalidHWDevice = errors.New("invalid hardware device")52 ErrInvalidHWDevice = errors.New("invalid hardware device")
5353
54 // ErrInvalidSymlinkToHWDevice is returned when a invalid symlink to hardware device
55 // is given in the hw-assign command
56 ErrInvalidSymlinkToHWDevice = errors.New("invalid symlink to hardware device")
57
58 // ErrSymlinkToHWNameCollision is returned when a symlink with the same name of a
59 // device in the write path is requested
60 ErrSymlinkToHWNameCollision = errors.New("symlink's name collides with on of the snap's write paths")
61
54 // ErrHWAccessRemoveNotFound is returned if the user tries to62 // ErrHWAccessRemoveNotFound is returned if the user tries to
55 // remove a device that does not exist63 // remove a device that does not exist
56 ErrHWAccessRemoveNotFound = errors.New("can not find device in hw-access list")64 ErrHWAccessRemoveNotFound = errors.New("can not find device in hw-access list")
@@ -59,6 +67,14 @@
59 // that is already in the hwaccess list67 // that is already in the hwaccess list
60 ErrHWAccessAlreadyAdded = errors.New("device is already in hw-access list")68 ErrHWAccessAlreadyAdded = errors.New("device is already in hw-access list")
6169
70 // ErrHwDeviceAlreadySymlinked is returned when the device has already a symlink
71 // with a different name
72 ErrHwDeviceAlreadySymlinked = errors.New("device has a symlink already")
73
74 // ErrSymlinkToHWAlreadyAdded is returned if you try to add a symlink
75 // that is already in the hwaccess list
76 ErrSymlinkToHWAlreadyAdded = errors.New("symlink is already in hw-access list")
77
62 // ErrReadmeInvalid is returned if the package contains a invalid78 // ErrReadmeInvalid is returned if the package contains a invalid
63 // meta/readme.md79 // meta/readme.md
64 ErrReadmeInvalid = errors.New("meta/readme.md invalid")80 ErrReadmeInvalid = errors.New("meta/readme.md invalid")
6581
=== modified file 'snappy/hwaccess.go'
--- snappy/hwaccess.go 2015-09-29 07:07:13 +0000
+++ snappy/hwaccess.go 2015-10-19 15:22:22 +0000
@@ -38,8 +38,9 @@
38var aaClickHookCmd = "aa-clickhook"38var aaClickHookCmd = "aa-clickhook"
3939
40type appArmorAdditionalJSON struct {40type appArmorAdditionalJSON struct {
41 WritePath []string `json:"write_path,omitempty"`41 WritePath []string `json:"write_path,omitempty"`
42 ReadPath []string `json:"read_path,omitempty"`42 ReadPath []string `json:"read_path,omitempty"`
43 SymlinkPath map[string]string `json:"symlink_path,omitempty"`
43}44}
4445
45// return the json filename to add to the security json46// return the json filename to add to the security json
@@ -137,16 +138,25 @@
137 return nil138 return nil
138}139}
139140
141// StripSnapName extracts the snapname from a full
142// appname_binary-or-service_version string
143func stripSnapName(snapname string) string {
144 strippedSnapname := snapname
145 if strings.Contains(snapname, "_") {
146 l := strings.Split(snapname, "_")
147 strippedSnapname = l[0]
148 }
149
150 return strippedSnapname
151}
152
140func writeUdevRuleForDeviceCgroup(snapname, device string) error {153func writeUdevRuleForDeviceCgroup(snapname, device string) error {
141 os.MkdirAll(dirs.SnapUdevRulesDir, 0755)154 os.MkdirAll(dirs.SnapUdevRulesDir, 0755)
142155
143 // the device cgroup/launcher etc support only the apps level,156 // the device cgroup/launcher etc support only the apps level,
144 // not a binary/service or version, so if we get a full157 // not a binary/service or version, so if we get a full
145 // appname_binary-or-service_version string we need to split that158 // appname_binary-or-service_version string we need to split that
146 if strings.Contains(snapname, "_") {159 snapname = stripSnapName(snapname)
147 l := strings.Split(snapname, "_")
148 snapname = l[0]
149 }
150160
151 acl := fmt.Sprintf(`161 acl := fmt.Sprintf(`
152KERNEL=="%v", TAG:="snappy-assign", ENV{SNAPPY_APP}:="%s"162KERNEL=="%v", TAG:="snappy-assign", ENV{SNAPPY_APP}:="%s"
@@ -159,16 +169,27 @@
159 return activateOemHardwareUdevRules()169 return activateOemHardwareUdevRules()
160}170}
161171
172func writeSymlinkUdevRuleForDeviceCgroup(snapname, device, symlink string) error {
173 os.MkdirAll(dirs.SnapUdevRulesDir, 0755)
174
175 snapname = stripSnapName(snapname)
176
177 acl := fmt.Sprintf(`
178ACTION=="add", KERNEL=="%v", TAG:="snappy-assign", ENV{SNAPPY_APP}:="%s", SYMLINK+="%v"
179`, filepath.Base(device), snapname, filepath.Base(symlink))
180
181 if err := addUdevRuleForSnap(snapname, acl); err != nil {
182 return err
183 }
184
185 return activateOemHardwareUdevRules()
186
187}
188
162var regenerateAppArmorRules = regenerateAppArmorRulesImpl189var regenerateAppArmorRules = regenerateAppArmorRulesImpl
163190
164// AddHWAccess allows the given snap package to access the given hardware191// check if there is anything apparmor related to add to
165// device192func hasSnapApparmorJSON(snapname string) error {
166func AddHWAccess(snapname, device string) error {
167 if !validDevice(device) {
168 return ErrInvalidHWDevice
169 }
170
171 // check if there is anything apparmor related to add to
172 globExpr := filepath.Join(dirs.SnapAppArmorDir, fmt.Sprintf("%s_*.json", snapname))193 globExpr := filepath.Join(dirs.SnapAppArmorDir, fmt.Sprintf("%s_*.json", snapname))
173 matches, err := filepath.Glob(globExpr)194 matches, err := filepath.Glob(globExpr)
174 if err != nil {195 if err != nil {
@@ -178,6 +199,10 @@
178 return ErrPackageNotFound199 return ErrPackageNotFound
179 }200 }
180201
202 return nil
203}
204
205func addNewWritePathForSnap(snapname, device string) error {
181 // read .additional file, its ok if the file does not exist (yet)206 // read .additional file, its ok if the file does not exist (yet)
182 appArmorAdditional, err := readHWAccessJSONFile(snapname)207 appArmorAdditional, err := readHWAccessJSONFile(snapname)
183 if err != nil && !os.IsNotExist(err) {208 if err != nil && !os.IsNotExist(err) {
@@ -199,6 +224,75 @@
199 return err224 return err
200 }225 }
201226
227 return nil
228}
229
230func isStringInSlice(key string, slice []string) bool {
231 for _, p := range slice {
232 if p == key {
233 return true
234 }
235 }
236 return false
237}
238
239func addNewSymlinkPathForSnap(snapname, symlink, device string) error {
240 // read .additional file, its ok if the file does not exist (yet)
241 appArmorAdditional, err := readHWAccessJSONFile(snapname)
242 if err != nil && !os.IsNotExist(err) {
243 return err
244 }
245
246 // It is expected to have already access to the
247 // real hw device before creating a symlink to it
248 if !isStringInSlice(device, appArmorAdditional.WritePath) {
249 return ErrHWAccessRemoveNotFound
250 }
251
252 if isStringInSlice(symlink, appArmorAdditional.WritePath) {
253 return ErrSymlinkToHWNameCollision
254 }
255
256 if nil != appArmorAdditional.SymlinkPath {
257 for key, item := range appArmorAdditional.SymlinkPath {
258 if key == symlink {
259 return ErrSymlinkToHWAlreadyAdded
260 }
261 if item == device {
262 return ErrHwDeviceAlreadySymlinked
263 }
264 }
265 } else {
266 appArmorAdditional.SymlinkPath = make(map[string]string)
267 }
268
269 // add the new symlink:hw-device pair
270 appArmorAdditional.SymlinkPath[symlink] = device
271
272 // and write the data out
273 err = writeHWAccessJSONFile(snapname, appArmorAdditional)
274 if err != nil {
275 return err
276 }
277
278 return nil
279}
280
281// AddHWAccess allows the given snap package to access the given hardware
282// device
283func AddHWAccess(snapname, device string) error {
284 if !validDevice(device) {
285 return ErrInvalidHWDevice
286 }
287
288 if err := hasSnapApparmorJSON(snapname); nil != err {
289 return err
290 }
291
292 if err := addNewWritePathForSnap(snapname, device); nil != err {
293 return err
294 }
295
202 // add udev rule for device cgroup296 // add udev rule for device cgroup
203 if err := writeUdevRuleForDeviceCgroup(snapname, device); err != nil {297 if err := writeUdevRuleForDeviceCgroup(snapname, device); err != nil {
204 return err298 return err
@@ -208,6 +302,32 @@
208 return regenerateAppArmorRules()302 return regenerateAppArmorRules()
209}303}
210304
305// AddSymlinkToHWDevice writes an Udev rule to create a symlink to the
306// given hardware device
307func AddSymlinkToHWDevice(snapname, device, symlink string) error {
308 if !validDevice(device) {
309 return ErrInvalidHWDevice
310 }
311
312 if !validDevice(symlink) {
313 return ErrInvalidSymlinkToHWDevice
314 }
315
316 if err := hasSnapApparmorJSON(snapname); nil != err {
317 return err
318 }
319
320 if err := addNewSymlinkPathForSnap(snapname, symlink, device); nil != err {
321 return err
322 }
323
324 if err := writeSymlinkUdevRuleForDeviceCgroup(snapname, device, symlink); nil != err {
325 return err
326 }
327
328 return nil
329}
330
211// ListHWAccess returns a list of hardware-device strings that the snap331// ListHWAccess returns a list of hardware-device strings that the snap
212// can access332// can access
213func ListHWAccess(snapname string) ([]string, error) {333func ListHWAccess(snapname string) ([]string, error) {
@@ -264,8 +384,8 @@
264384
265// RemoveHWAccess allows the given snap package to access the given hardware385// RemoveHWAccess allows the given snap package to access the given hardware
266// device386// device
267func RemoveHWAccess(snapname, device string) error {387func RemoveHWAccess(snapname, path string) error {
268 if !validDevice(device) {388 if !validDevice(path) {
269 return ErrInvalidHWDevice389 return ErrInvalidHWDevice
270 }390 }
271391
@@ -274,26 +394,71 @@
274 return err394 return err
275 }395 }
276396
277 // remove write path, please golang make this easier!397 // Check whether it is a symlink first
278 newWritePath := []string{}398 device, symlink, err := resolveSymlink(snapname, path)
279 for _, p := range appArmorAdditional.WritePath {399 if nil != err {
280 if p != device {400 return err
281 newWritePath = append(newWritePath, p)401 }
282 }402
283 }403 // When unassigning a symlink, path == symlink and only the
284 if len(newWritePath) == len(appArmorAdditional.WritePath) {404 // symlink has to be removed.
285 return ErrHWAccessRemoveNotFound405 // When unassigning an HW device, path == device (while symlink
286 }406 // can be another path or a null string) and both device
287 appArmorAdditional.WritePath = newWritePath407 // and symlink (if any) have to be removed.
288408 if path == device {
289 // and write it out again409 // remove write path, please golang make this easier!
290 err = writeHWAccessJSONFile(snapname, appArmorAdditional)410 newWritePath := []string{}
291 if err != nil {411 for _, p := range appArmorAdditional.WritePath {
292 return err412 if p != device {
293 }413 newWritePath = append(newWritePath, p)
294414 }
295 if err = removeUdevRuleForSnap(snapname, device); nil != err {415 }
296 return err416
417 if len(newWritePath) == len(appArmorAdditional.WritePath) {
418 return ErrHWAccessRemoveNotFound
419 }
420
421 // Update WritePath
422 appArmorAdditional.WritePath = newWritePath
423
424 // and write it out again
425 err = writeHWAccessJSONFile(snapname, appArmorAdditional)
426 if err != nil {
427 return err
428 }
429
430 if err = removeUdevRuleForSnap(snapname, device); nil != err {
431 return err
432 }
433
434 }
435
436 if "" != symlink {
437 newSymlinkPath := make(map[string]string)
438 for key, value := range appArmorAdditional.SymlinkPath {
439 if key != symlink {
440 newSymlinkPath[key] = value
441 }
442 }
443
444 if len(appArmorAdditional.SymlinkPath) == len(newSymlinkPath) {
445 return ErrHWAccessRemoveNotFound
446 }
447
448 appArmorAdditional.SymlinkPath = newSymlinkPath
449
450 err = writeHWAccessJSONFile(snapname, appArmorAdditional)
451 if err != nil {
452 return err
453 }
454
455 if err := removeUdevRuleForSnap(snapname, symlink); nil != err {
456 // When there are only rules for the same HW device, the UDEV
457 // rule file might not exist anymore at this point.
458 if !os.IsNotExist(err) {
459 return err
460 }
461 }
297 }462 }
298463
299 if err := activateOemHardwareUdevRules(); err != nil {464 if err := activateOemHardwareUdevRules(); err != nil {
@@ -317,3 +482,25 @@
317482
318 return regenerateAppArmorRules()483 return regenerateAppArmorRules()
319}484}
485
486func resolveSymlink(snapname, path string) (device, symlink string, err error) {
487 appArmorAdditional, err := readHWAccessJSONFile(snapname)
488 if nil != err {
489 return "", "", err
490 }
491
492 if nil != appArmorAdditional.SymlinkPath {
493 for symlink, device := range appArmorAdditional.SymlinkPath {
494 if path == symlink || path == device {
495 return device, symlink, nil
496 }
497 }
498 }
499
500 // no symlink found, path must be an HW device
501 if !isStringInSlice(path, appArmorAdditional.WritePath) {
502 return "", "", ErrHWAccessRemoveNotFound
503 }
504
505 return path, "", nil
506}
320507
=== modified file 'snappy/hwaccess_test.go'
--- snappy/hwaccess_test.go 2015-09-29 07:07:13 +0000
+++ snappy/hwaccess_test.go 2015-10-19 15:22:22 +0000
@@ -290,6 +290,24 @@
290 verifyUdevAdmActivateRules(c, runUdevAdmCalls)290 verifyUdevAdmActivateRules(c, runUdevAdmCalls)
291}291}
292292
293func (s *SnapTestSuite) TestWriteSymlinkUdevRuleForDeviceCgroup(c *C) {
294 var runUdevAdmCalls [][]string
295 runUdevAdm = makeRunUdevAdmMock(&runUdevAdmCalls)
296
297 snapapp := "foo-app_meep_1.0"
298
299 err := writeSymlinkUdevRuleForDeviceCgroup(snapapp, "/dev/ttyS0", "/dev/symS0")
300 c.Assert(err, IsNil)
301
302 got, err := ioutil.ReadFile(filepath.Join(dirs.SnapUdevRulesDir, "70-snappy_hwassign_foo-app.rules"))
303 c.Assert(err, IsNil)
304 c.Assert(string(got), Equals, `
305ACTION=="add", KERNEL=="ttyS0", TAG:="snappy-assign", ENV{SNAPPY_APP}:="foo-app", SYMLINK+="symS0"
306`)
307
308 verifyUdevAdmActivateRules(c, runUdevAdmCalls)
309}
310
293func (s *SnapTestSuite) TestRemoveAllHWAccess(c *C) {311func (s *SnapTestSuite) TestRemoveAllHWAccess(c *C) {
294 makeInstalledMockSnap(s.tempdir, "")312 makeInstalledMockSnap(s.tempdir, "")
295313
@@ -320,3 +338,203 @@
320 Output: []byte("meep\n"),338 Output: []byte("meep\n"),
321 })339 })
322}340}
341
342func (s *SnapTestSuite) TestHasSnapApparmorJSON(c *C) {
343 err := hasSnapApparmorJSON("non-existent-app")
344 c.Assert(err, Equals, ErrPackageNotFound)
345
346 makeInstalledMockSnap(s.tempdir, "")
347 err = AddHWAccess("hello-app", "/dev/ttyUSB0")
348 c.Assert(err, IsNil)
349
350 err = hasSnapApparmorJSON("hello-app")
351 c.Assert(err, IsNil)
352}
353
354func (s *SnapTestSuite) TestAddNewWritePathForSnap(c *C) {
355 // try add same path twice
356 makeInstalledMockSnap(s.tempdir, "")
357 err := addNewWritePathForSnap("hello-app", "/dev/ttyUSB0")
358 c.Assert(err, IsNil)
359 err = addNewWritePathForSnap("hello-app", "/dev/ttyUSB0")
360 c.Assert(err, Equals, ErrHWAccessAlreadyAdded)
361
362 // check .additional file is written right
363 content, err := ioutil.ReadFile(filepath.Join(dirs.SnapAppArmorDir, "hello-app.json.additional"))
364 c.Assert(err, IsNil)
365 c.Assert(string(content), Equals, `{
366 "write_path": [
367 "/dev/ttyUSB0"
368 ],
369 "read_path": [
370 "/run/udev/data/*"
371 ]
372}
373`)
374}
375
376func (s *SnapTestSuite) TestAddNewSymlinkPathForSnap(c *C) {
377 makeInstalledMockSnap(s.tempdir, "")
378
379 // try add as symlink to an hw device not in write path, yet
380 err := addNewSymlinkPathForSnap("hello-app", "/dev/symtest0", "/dev/ttyUSB0")
381 c.Assert(err, Equals, ErrHWAccessRemoveNotFound)
382
383 // try add the same symlink twice
384 err = addNewWritePathForSnap("hello-app", "/dev/ttyUSB0")
385 c.Assert(err, IsNil)
386 err = addNewSymlinkPathForSnap("hello-app", "/dev/symtest0", "/dev/ttyUSB0")
387 c.Assert(err, IsNil)
388 err = addNewSymlinkPathForSnap("hello-app", "/dev/symtest0", "/dev/ttyUSB0")
389 c.Assert(err, Equals, ErrSymlinkToHWAlreadyAdded)
390
391 // try add a new symlink to a device that already has one
392 err = addNewSymlinkPathForSnap("hello-app", "/dev/symtest1", "/dev/ttyUSB0")
393 c.Assert(err, Equals, ErrHwDeviceAlreadySymlinked)
394
395 // check .additional file is written right
396 content, err := ioutil.ReadFile(filepath.Join(dirs.SnapAppArmorDir, "hello-app.json.additional"))
397 c.Assert(err, IsNil)
398 c.Assert(string(content), Equals, `{
399 "write_path": [
400 "/dev/ttyUSB0"
401 ],
402 "read_path": [
403 "/run/udev/data/*"
404 ],
405 "symlink_path": {
406 "/dev/symtest0": "/dev/ttyUSB0"
407 }
408}
409`)
410
411 // try add a second symlink
412 err = AddHWAccess("hello-app", "/dev/ttyUSB1")
413 c.Assert(err, IsNil)
414
415 err = AddSymlinkToHWDevice("hello-app", "/dev/ttyUSB1", "/dev/symtest1")
416 c.Assert(err, IsNil)
417
418 // check .additional file is written right
419 content, err = ioutil.ReadFile(filepath.Join(dirs.SnapAppArmorDir, "hello-app.json.additional"))
420 c.Assert(err, IsNil)
421 c.Assert(string(content), Equals, `{
422 "write_path": [
423 "/dev/ttyUSB0",
424 "/dev/ttyUSB1"
425 ],
426 "read_path": [
427 "/run/udev/data/*"
428 ],
429 "symlink_path": {
430 "/dev/symtest0": "/dev/ttyUSB0",
431 "/dev/symtest1": "/dev/ttyUSB1"
432 }
433}
434`)
435 // try add symlink with the same name of one of snap's write paths
436 err = addNewSymlinkPathForSnap("hello-app", "/dev/ttyUSB1", "/dev/ttyUSB1")
437 c.Assert(err, Equals, ErrSymlinkToHWNameCollision)
438}
439
440func (s *SnapTestSuite) TestRemoveSymlinkToHWDevice(c *C) {
441 aaClickHookCmd = "true"
442 makeInstalledMockSnap(s.tempdir, "")
443
444 // Add access to 2 devices
445 err := AddHWAccess("hello-app", "/dev/ttyUSB0")
446 c.Assert(err, IsNil)
447 err = AddHWAccess("hello-app", "/dev/ttyUSB1")
448 c.Assert(err, IsNil)
449
450 // Adding a symlinks
451 err = AddSymlinkToHWDevice("hello-app", "/dev/ttyUSB0", "/dev/symtest0")
452 c.Assert(err, IsNil)
453
454 err = AddSymlinkToHWDevice("hello-app", "/dev/ttyUSB1", "/dev/symtest1")
455 c.Assert(err, IsNil)
456
457 // Remove hw device with symlink: the symlink is expected to be removed too
458 err = RemoveHWAccess("hello-app", "/dev/ttyUSB1")
459 c.Assert(err, IsNil)
460
461 content, err := ioutil.ReadFile(filepath.Join(dirs.SnapAppArmorDir, "hello-app.json.additional"))
462 c.Assert(err, IsNil)
463 c.Assert(string(content), Equals, `{
464 "write_path": [
465 "/dev/ttyUSB0"
466 ],
467 "read_path": [
468 "/run/udev/data/*"
469 ],
470 "symlink_path": {
471 "/dev/symtest0": "/dev/ttyUSB0"
472 }
473}
474`)
475
476 // Remove only the symlink
477 err = RemoveHWAccess("hello-app", "/dev/symtest0")
478 c.Assert(err, IsNil)
479
480 // having removed the last symlink, SymlinkPath is expected to be nil
481 appArmorAdditional, err := readHWAccessJSONFile("hello-app")
482 c.Assert(err, IsNil)
483 c.Assert(appArmorAdditional.SymlinkPath, IsNil)
484
485 // expecting write path unchanged
486 content, err = ioutil.ReadFile(filepath.Join(dirs.SnapAppArmorDir, "hello-app.json.additional"))
487 c.Assert(err, IsNil)
488 c.Assert(string(content), Equals, `{
489 "write_path": [
490 "/dev/ttyUSB0"
491 ],
492 "read_path": [
493 "/run/udev/data/*"
494 ]
495}
496`)
497}
498
499func (s *SnapTestSuite) TestRemoveUdevRuleForSnap(c *C) {
500 aaClickHookCmd = "true"
501 makeInstalledMockSnap(s.tempdir, "")
502
503 // Add access to 3 devices and 2 symlinks
504 err := AddHWAccess("hello-app", "/dev/ttyUSB0")
505 c.Assert(err, IsNil)
506 err = AddHWAccess("hello-app", "/dev/ttyUSB1")
507 c.Assert(err, IsNil)
508 err = AddHWAccess("hello-app", "/dev/ttyUSB2")
509 c.Assert(err, IsNil)
510 err = AddSymlinkToHWDevice("hello-app", "/dev/ttyUSB0", "/dev/symlink0")
511 c.Assert(err, IsNil)
512 err = AddSymlinkToHWDevice("hello-app", "/dev/ttyUSB1", "/dev/symlink1")
513 c.Assert(err, IsNil)
514
515 // Remove the device without symlink
516 err = RemoveHWAccess("hello-app", "/dev/ttyUSB2")
517 c.Assert(err, IsNil)
518 content, err := ioutil.ReadFile(filepath.Join(dirs.SnapUdevRulesDir, "70-snappy_hwassign_hello-app.rules"))
519 c.Assert(err, IsNil)
520 c.Assert(string(content), Equals, `KERNEL=="ttyUSB0", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app"
521KERNEL=="ttyUSB1", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app"
522ACTION=="add", KERNEL=="ttyUSB0", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app", SYMLINK+="symlink0"
523ACTION=="add", KERNEL=="ttyUSB1", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app", SYMLINK+="symlink1"
524`)
525 // Remove a device with a symlink
526 err = RemoveHWAccess("hello-app", "/dev/ttyUSB1")
527 c.Assert(err, IsNil)
528 content, err = ioutil.ReadFile(filepath.Join(dirs.SnapUdevRulesDir, "70-snappy_hwassign_hello-app.rules"))
529 c.Assert(err, IsNil)
530 c.Assert(string(content), Equals, `KERNEL=="ttyUSB0", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app"
531ACTION=="add", KERNEL=="ttyUSB0", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app", SYMLINK+="symlink0"
532`)
533 // Remove the symlink
534 err = RemoveHWAccess("hello-app", "/dev/symlink0")
535 c.Assert(err, IsNil)
536 content, err = ioutil.ReadFile(filepath.Join(dirs.SnapUdevRulesDir, "70-snappy_hwassign_hello-app.rules"))
537 c.Assert(err, IsNil)
538 c.Assert(string(content), Equals, `KERNEL=="ttyUSB0", TAG:="snappy-assign", ENV{SNAPPY_APP}:="hello-app"
539`)
540}

Subscribers

People subscribed via source and target branches