Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
interfaces/udev,osutil: avoid doubled rules and put all in a per snap file #1623
Conversation
|
Can one of the admins verify this patch? |
|
Can one of the admins verify this patch? |
|
I've discussed this issue with @zyga during the Snappy sprint two weeks ago. Are you in sync with him? |
|
@niemeyer Yes. Based on what I got from him I've implemented this now. |
|
@niemeyer Can you give me an initial review on this today? |
niemeyer
reviewed
Aug 3, 2016
| @@ -59,7 +60,7 @@ func (b *Backend) Setup(snapInfo *snap.Info, devMode bool, repo *interfaces.Repo | ||
| if err != nil { | ||
| return fmt.Errorf("cannot obtain expected udev rules for snap %q: %s", snapName, err) | ||
| } | ||
| - glob := fmt.Sprintf("70-%s.rules", interfaces.SecurityTagGlob(snapName)) | ||
| + glob := fmt.Sprintf("70-%s.rules", snap.SecurityTag(snapName)) |
niemeyer
Aug 3, 2016
Contributor
Something here is bogus. The content map has data both for hooks and for a global "snap-wide" rules file.
Do we want to merge the rules for the individual hooks in the same file as well? If so, that needs doing and consideration below. If not, this is broken because the glob now misses entries which may be written by ensureDirState.
morphis
Aug 3, 2016
Contributor
Lets do that if nothing else speaks against this. Wanted to have some initial comments on this if I am right or not.
niemeyer
reviewed
Aug 3, 2016
| @@ -91,22 +92,46 @@ func ensureDirState(dir, glob string, content map[string]*osutil.FileState, snap | ||
| return errReload | ||
| } | ||
| +func generateHash(bytes []byte) uint32 { | ||
| + h := fnv.New32a() |
niemeyer
Aug 3, 2016
Contributor
This hash isn't strong enough to handle uniqueness as intended here.
I suggest going with a simpler implementation:
var unique map[string][]byte
...
for _, snippet := range ... {
unique[string(snippet)] = snippet
}
No need to test for containment. At the end of the process unique will have all the snippets in it.
niemeyer
reviewed
Aug 3, 2016
| @@ -74,7 +75,7 @@ func (b *Backend) Setup(snapInfo *snap.Info, devMode bool, repo *interfaces.Repo | ||
| // | ||
| // If the method fails it should be re-tried (with a sensible strategy) by the caller. | ||
| func (b *Backend) Remove(snapName string) error { | ||
| - glob := fmt.Sprintf("70-%s.rules", interfaces.SecurityTagGlob(snapName)) | ||
| + glob := fmt.Sprintf("70-%s.rules", snap.SecurityTag(snapName)) | ||
| return ensureDirState(dirs.SnapUdevRulesDir, glob, nil, snapName) |
niemeyer
Aug 3, 2016
Contributor
Same as above. Note that if this is all just a single file, ensureDirState is a huge red-herring. This would all be much simpler.
morphis
Aug 3, 2016
Contributor
Absolutely. Didn't wanted to change to much until we agreed on the right way forward. Lets drop ensureDirState here if we keep the single file per snap.
niemeyer
reviewed
Aug 3, 2016
| } | ||
| + snapSecurityTag := snap.SecurityTag(snapInfo.Name()) | ||
| + addContent(snapSecurityTag, finalSnapSnippets, content) | ||
| + | ||
| for _, hookInfo := range snapInfo.Hooks { | ||
| securityTag := hookInfo.SecurityTag() | ||
| hookSnippets := snippets[securityTag] |
niemeyer
Aug 3, 2016
Contributor
Per note above, this needs more consideration. We need to take a higher level view on how to do udev properly, and squash it all into a much simpler implementation if merging all snippets is really what we want.
morphis
Aug 3, 2016
Contributor
From the discussions we had at the sprint (you with zyga and I with zyga) this is the way forward and also @jdstrand was ok with keep a rules file per snap.
kyrofa
Aug 3, 2016
Member
Indeed. As it is now hooks still look like they'll cause duplication-- you probably want to squash the entire thing, not just apps.
morphis
via email
Aug 3, 2016
Contributor
kyrofa
Aug 3, 2016
Member
Yeah I think that makes the most sense. Apps and hooks are pretty much the same, it's just that hooks are typically run by snapd instead of directly by the user.
kyrofa
reviewed
Aug 3, 2016
| +// SecurityTag returns the snap-specific security tag. | ||
| +func SecurityTag(snapName string) string { | ||
| + return fmt.Sprintf("snap.%s", snapName) | ||
| +} |
kyrofa
Aug 3, 2016
Member
If we're going to have a function that does this, can we make sure we use it for the AppSecurityTag() and HookSecurityTag() functions so they can't diverge?
niemeyer
added
the
Reviewed
label
Aug 3, 2016
niemeyer
reviewed
Aug 4, 2016
| + // EnsureFileState will make sure the file will be only updated when its content | ||
| + // has changed and will otherwise return an error which prevents us from reloading | ||
| + // udev rules when not needed. | ||
| + if err := osutil.EnsureFileState(rulesFilePath, rulesFileState); err != nil { |
niemeyer
Aug 4, 2016
Contributor
This looks more clear if broken on a separate line and aggregated:
err := ...
if err != nil && err != osutil.ErrSameState {
return err
}
niemeyer
reviewed
Aug 4, 2016
| @@ -55,16 +62,43 @@ func (b *Backend) Setup(snapInfo *snap.Info, devMode bool, repo *interfaces.Repo | ||
| if err != nil { | ||
| return fmt.Errorf("cannot obtain udev security snippets for snap %q: %s", snapName, err) | ||
| } | ||
| - content, err := b.combineSnippets(snapInfo, snippets) | ||
| + combinedSnippets, err := b.combineSnippets(snapInfo, snippets) |
niemeyer
Aug 4, 2016
Contributor
Let's please keep this variable named content as that's what is used everywhere else around this concept.
niemeyer
reviewed
Aug 4, 2016
| dir := dirs.SnapUdevRulesDir | ||
| if err := os.MkdirAll(dir, 0755); err != nil { | ||
| return fmt.Errorf("cannot create directory for udev rules %q: %s", dir, err) | ||
| } | ||
| - return ensureDirState(dir, glob, content, snapName) | ||
| + | ||
| + if len(combinedSnippets) == 0 { |
niemeyer
Aug 4, 2016
Contributor
What happens on removals? The original logic used ensureDirState, which deletes all files matching the glob which are not provided for creation inside the content map. The new logic seems bogus in that regard.
niemeyer
reviewed
Aug 4, 2016
| + buffer.WriteString("# This file is automatically generated.\n") | ||
| + for _, snippet := range combinedSnippets { | ||
| + buffer.Write(snippet) | ||
| + buffer.WriteRune('\n') |
niemeyer
reviewed
Aug 4, 2016
| - // Try reload the rules regardless of errEnsure. | ||
| - errReload = ReloadRules() | ||
| + rulesFilePath := snapRulesFilePath(snapName) | ||
| + if _, err := os.Stat(rulesFilePath); os.IsNotExist(err) { |
niemeyer
Aug 4, 2016
•
Contributor
Checking for existence before removal is unnecessary. Just remove and check the error there (....; err != nil && !os.IsNotExist {)
niemeyer
Aug 4, 2016
Contributor
Actually, if os.IsNotExist, no need to ReloadRules. Still, no need to check for existence ahead of the removal.
niemeyer
changed the title from
interfaces: udev: avoid doubled rules and put all in a per snap file
to
interfaces/udev,osutil: avoid doubled rules and put all in a per snap file
Aug 4, 2016
|
Looks reasonable, but needs testing fixes covering all these changes, including the particular issues uncovered in the review. |
niemeyer
referenced this pull request
Aug 8, 2016
Closed
Merge master onto #1623 to check tests. #1650
|
Tests confirmed passing after merge in #1650. |
morphis commentedAug 3, 2016
This reworks the udev backend to overcome the following problem: Interfaces can only add rules for all applications a plug belongs to which results in doubled rules in multiple files as the result. Like with the current implementation if an interface generates the following udev rules on plug connection
IMPORT{builtin}="usb_id"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idProduct}=="1111", ATTRS{idVendor}=="2222", SYMLINK+="zigbee/$env{ID_SERIAL}", TAG+="snap_zigbee-test_app-with-specific-plug"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idProduct}=="1111", ATTRS{idVendor}=="2222", SYMLINK+="zigbee/$env{ID_SERIAL}", TAG+="snap_zigbee-test_app2-with-specific-plug"
the same rules end up in a rule file for each app the plug is used in
$ cat /etc/udev/rules.d/70-snap.zigbee-test.app-with-specific-plug.rules
IMPORT{builtin}="usb_id"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idProduct}=="1111", ATTRS{idVendor}=="2222", SYMLINK+="zigbee/$env{ID_SERIAL}", TAG+="snap_zigbee-test_app-with-specific-plug"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idProduct}=="1111", ATTRS{idVendor}=="2222", SYMLINK+="zigbee/$env{ID_SERIAL}", TAG+="snap_zigbee-test_app2-with-specific-plug"
$ cat /etc/udev/rules.d/70-snap.zigbee-test.app2-with-specific-plug.rules
IMPORT{builtin}="usb_id"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idProduct}=="1111", ATTRS{idVendor}=="2222", SYMLINK+="zigbee/$env{ID_SERIAL}", TAG+="snap_zigbee-test_app-with-specific-plug"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idProduct}=="1111", ATTRS{idVendor}=="2222", SYMLINK+="zigbee/$env{ID_SERIAL}", TAG+="snap_zigbee-test_app2-with-specific-plug"
To overcome this problem all udev rules will be stored in a rule file per snap (/etc/udev/rules.d/snap..rules) and not per app (/etc/udev/rules.d/snap...rules). Furthermore the udev backend will detect identical snippets and just adds them once to the snap rules file.
Putting this here for a first early review. Unit tests are still failing and need to be fixed.