-
-
Notifications
You must be signed in to change notification settings - Fork 165
/
device_utils_unix_events.go
159 lines (130 loc) · 4.79 KB
/
device_utils_unix_events.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
package device
import (
"fmt"
"path/filepath"
"strings"
"sync"
deviceConfig "github.com/lxc/lxd/lxd/device/config"
"github.com/lxc/lxd/lxd/instance"
"github.com/lxc/lxd/lxd/state"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/logger"
)
// UnixEvent represents the properties of a Unix device inotify event.
type UnixEvent struct {
Action string // The type of event, either add or remove.
Path string // The absolute source path on the host.
}
// UnixSubscription used to subcribe to specific events.
type UnixSubscription struct {
Path string // The absolute source path on the host.
Handler func(UnixEvent) (*deviceConfig.RunConfig, error) // The function to run when an event occurs.
}
// unixHandlers stores the event handler callbacks for Unix events.
var unixHandlers = map[string]UnixSubscription{}
// unixMutex controls access to the unixHandlers map.
var unixMutex sync.Mutex
// unixRegisterHandler registers a handler function to be called whenever a Unix device event occurs.
func unixRegisterHandler(s *state.State, inst instance.Instance, deviceName, path string, handler func(UnixEvent) (*deviceConfig.RunConfig, error)) error {
if path == "" || handler == nil {
return fmt.Errorf("Invalid subscription")
}
unixMutex.Lock()
// Null delimited string of project name, instance name and device name.
key := fmt.Sprintf("%s\000%s\000%s", inst.Project().Name, inst.Name(), deviceName)
unixHandlers[key] = UnixSubscription{
Path: path,
Handler: handler,
}
unixMutex.Unlock()
identifier := fmt.Sprintf("%d_%s", inst.ID(), deviceName)
path = filepath.Clean(path)
// Add inotify watcher to its nearest existing ancestor.
err := s.DevMonitor.Watch(path, identifier, func(path, event string) bool {
e := unixNewEvent(event, path)
unixRunHandlers(s, &e)
return true
})
if err != nil {
return fmt.Errorf("Failed to add %q to watch targets: %w", path, err)
}
logger.Debug("Added watch target", logger.Ctx{"path": path})
return nil
}
// unixUnregisterHandler removes a registered Unix handler function for a device.
func unixUnregisterHandler(s *state.State, inst instance.Instance, deviceName string) error {
unixMutex.Lock()
// Null delimited string of project name, instance name and device name.
key := fmt.Sprintf("%s\000%s\000%s", inst.Project().Name, inst.Name(), deviceName)
sub, exists := unixHandlers[key]
if !exists {
unixMutex.Unlock()
return nil
}
// Remove active subscription for this device.
delete(unixHandlers, key)
unixMutex.Unlock()
identifier := fmt.Sprintf("%d_%s", inst.ID(), deviceName)
err := s.DevMonitor.Unwatch(sub.Path, identifier)
if err != nil {
return fmt.Errorf("Failed to remove %q from inotify targets: %w", sub.Path, err)
}
return nil
}
// unixRunHandlers executes any handlers registered for Unix events.
func unixRunHandlers(state *state.State, event *UnixEvent) {
unixMutex.Lock()
defer unixMutex.Unlock()
for key, sub := range unixHandlers {
keyParts := strings.SplitN(key, "\000", 3)
projectName := keyParts[0]
instanceName := keyParts[1]
deviceName := keyParts[2]
// Delete subscription if no handler function defined.
if sub.Handler == nil {
delete(unixHandlers, key)
continue
}
// Don't execute handler if subscription path and event paths don't match.
if sub.Path != event.Path {
continue
}
// Run handler function.
runConf, err := sub.Handler(*event)
if err != nil {
logger.Error("Unix event hook failed", logger.Ctx{"project": projectName, "instance": instanceName, "device": deviceName, "path": sub.Path, "action": event.Action, "err": err})
continue
}
// If runConf supplied, load instance and call its Unix event handler function so
// any instance specific device actions can occur.
if runConf != nil {
instance, err := instance.LoadByProjectAndName(state, projectName, instanceName)
if err != nil {
logger.Error("Unix event loading instance failed", logger.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
continue
}
err = instance.DeviceEventHandler(runConf)
if err != nil {
logger.Error("Unix event instance handler failed", logger.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
continue
}
}
}
}
// unixNewEvent returns a newly created Unix device event struct.
// If an empty action is supplied then the action of the event is derived from whether the path
// exists (add) or not (removed). This allows the peculiarities of the inotify API to be somewhat
// masked by the consuming event handler functions.
func unixNewEvent(action string, path string) UnixEvent {
if action == "" {
if shared.PathExists(path) {
action = "add"
} else {
action = "remove"
}
}
return UnixEvent{
Action: action,
Path: path,
}
}