File: udev.go

package info (click to toggle)
snapd 2.71-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 79,536 kB
  • sloc: ansic: 16,114; sh: 16,105; python: 9,941; makefile: 1,890; exp: 190; awk: 40; xml: 22
file content (130 lines) | stat: -rw-r--r-- 4,801 bytes parent folder | download
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
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
 * Copyright (C) 2016-2018 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 udev

import (
	"fmt"
	"os/exec"
)

// udevadmTrigger runs "udevadm trigger" but ignores an non-zero exit codes.
// udevadm only started reporting errors in systemd 248 and in order to
// work correctly in LXD these errors need to be ignored. See
// https://github.com/systemd/systemd/pull/18684 for some more background
// (and https://github.com/lxc/lxd/issues/9526)
func udevadmTrigger(args ...string) error {
	args = append([]string{"trigger"}, args...)
	output, err := exec.Command("udevadm", args...).CombinedOutput()
	// can happen when events for some of the devices or all of
	// them could not be triggered, but we cannot distinguish which of
	// those happened, in any case snapd invoked udevadm and tried its
	// best
	if exitErr, ok := err.(*exec.ExitError); ok {
		// ignore "normal" exit codes but report e.g. segfaults
		// that are reported as -1
		if exitErr.ExitCode() > 0 {
			return nil
		}
	}
	if err != nil {
		return fmt.Errorf("%s\nudev output:\n%s", err, string(output))
	}
	return nil
}

// reloadRules runs three commands that reload udev rule database.
//
// The commands are: udevadm control --reload-rules
//
//	udevadm trigger --subsystem-nomatch=input
//	udevadm settle --timeout=3
//
// and optionally trigger other subsystems as defined in the interfaces. Eg:
//
//	udevadm trigger --subsystem-match=input
//	udevadm trigger --property-match=ID_INPUT_JOYSTICK=1
func (b *Backend) reloadRules(subsystemTriggers []string) error {
	// When running in a container observe the state of host's udev, as such
	// reloading rules makes no sense, since they are ineffective.
	// Similarly in preseeding, there is no point in reloading rules which
	// only affect the runtime standbox.
	if b.preseed || b.isContainer {
		return nil
	}

	output, err := exec.Command("udevadm", "control", "--reload-rules").CombinedOutput()
	if err != nil {
		return fmt.Errorf("cannot reload udev rules: %s\nudev output:\n%s", err, string(output))
	}

	// By default, trigger for all events except the input subsystem since
	// it can cause noticeable blocked input on, for example, classic
	// desktop.
	if err = udevadmTrigger("--subsystem-nomatch=input"); err != nil {
		return fmt.Errorf("cannot run udev triggers: %s", err)
	}

	mustTriggerForInputSubsystem := false
	mustTriggerForInputKeys := false

	for _, subsystem := range subsystemTriggers {
		if subsystem == "input/key" {
			mustTriggerForInputKeys = true
		} else if subsystem == "input" {
			mustTriggerForInputSubsystem = true
		}
		// no `else` branch: we already triggered udevadm for all other
		// subsystems before by running it with the `--subsystem-nomatch=input`
		// option, so there's no need to do anything here.
	}

	if mustTriggerForInputSubsystem {
		// Trigger for the whole input subsystem
		if err := udevadmTrigger("--subsystem-match=input"); err != nil {
			return fmt.Errorf("cannot run udev triggers for input subsystem: %s", err)
		}
	} else {
		// More specific triggers, to avoid blocking keyboards and mice

		if mustTriggerForInputKeys {
			// If one of the interfaces said it uses the input
			// subsystem for input keys, then trigger the keys
			// events in a way that is specific to input keys
			// to not block other inputs.
			if err = udevadmTrigger("--property-match=ID_INPUT_KEY=1", "--property-match=ID_INPUT_KEYBOARD!=1"); err != nil {
				return fmt.Errorf("cannot run udev triggers for keys: %s", err)
			}
		}
		// FIXME: if not already triggered, trigger the joystick property if it
		// wasn't already since we are not able to detect interfaces that are
		// removed and set subsystemTriggers correctly. When we can, remove
		// this. Allows joysticks to be removed from the device cgroup on
		// interface disconnect.
		if err := udevadmTrigger("--property-match=ID_INPUT_JOYSTICK=1"); err != nil {
			return fmt.Errorf("cannot run udev triggers for joysticks: %s", err)
		}
	}

	// give our triggered events a chance to be handled before exiting.
	// Ignore errors since we don't want to error on still pending events.
	_ = exec.Command("udevadm", "settle", "--timeout=10").Run()

	return nil
}