Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/hostagent/inotify.go
2610 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package hostagent
5
6
import (
7
"context"
8
"os"
9
"path"
10
"path/filepath"
11
"strings"
12
13
"github.com/rjeczalik/notify"
14
"github.com/sirupsen/logrus"
15
"google.golang.org/protobuf/types/known/timestamppb"
16
17
guestagentapi "github.com/lima-vm/lima/v2/pkg/guestagent/api"
18
)
19
20
const CacheSize = 10000
21
22
var (
23
inotifyCache = make(map[string]int64)
24
mountSymlinks = make(map[string]string)
25
mountLocations = make(map[string]string)
26
)
27
28
func (a *HostAgent) startInotify(ctx context.Context) error {
29
mountWatchCh := make(chan notify.EventInfo, 128)
30
err := a.setupWatchers(mountWatchCh)
31
if err != nil {
32
return err
33
}
34
client, err := a.getOrCreateClient(ctx)
35
if err != nil {
36
logrus.WithError(err).Error("failed to create client for inotify")
37
}
38
inotifyClient, err := client.Inotify(ctx)
39
if err != nil {
40
return err
41
}
42
43
for {
44
select {
45
case <-ctx.Done():
46
return nil
47
case watchEvent := <-mountWatchCh:
48
watchPath := watchEvent.Path()
49
stat, err := os.Stat(watchPath)
50
if err != nil {
51
continue
52
}
53
54
if filterEvents(watchEvent, stat) {
55
continue
56
}
57
58
watchPath = translateToGuestPath(watchPath, mountSymlinks, mountLocations)
59
60
utcTimestamp := timestamppb.New(stat.ModTime().UTC())
61
event := &guestagentapi.Inotify{MountPath: watchPath, Time: utcTimestamp}
62
err = inotifyClient.Send(event)
63
if err != nil {
64
logrus.WithError(err).Warn("failed to send inotify")
65
}
66
}
67
}
68
}
69
70
func (a *HostAgent) setupWatchers(events chan notify.EventInfo) error {
71
for _, m := range a.instConfig.Mounts {
72
if !*m.Writable {
73
continue
74
}
75
symlink, err := filepath.EvalSymlinks(m.Location)
76
if err != nil {
77
return err
78
}
79
if m.Location != symlink {
80
mountSymlinks[symlink] = m.Location
81
}
82
if m.MountPoint != nil && m.Location != *m.MountPoint {
83
mountLocations[m.Location] = *m.MountPoint
84
}
85
86
logrus.Infof("enable inotify for writable mount: %s", m.Location)
87
err = notify.Watch(path.Join(m.Location, "..."), events, GetNotifyEvent())
88
if err != nil {
89
return err
90
}
91
}
92
return nil
93
}
94
95
func translateToGuestPath(hostPath string, symlinks, locations map[string]string) string {
96
result := hostPath
97
98
for symlink, original := range symlinks {
99
if strings.HasPrefix(result, symlink) {
100
result = strings.ReplaceAll(result, symlink, original)
101
}
102
}
103
104
for location, mountPoint := range locations {
105
if suffix, ok := strings.CutPrefix(result, location); ok {
106
return mountPoint + suffix
107
}
108
}
109
110
return result
111
}
112
113
func filterEvents(event notify.EventInfo, stat os.FileInfo) bool {
114
currTime := stat.ModTime()
115
eventPath := event.Path()
116
cacheMilli, ok := inotifyCache[eventPath]
117
if ok {
118
// Ignore repeated events for 10ms to exclude recursive inotify events
119
if currTime.UnixMilli()-cacheMilli < 10 {
120
return true
121
}
122
}
123
inotifyCache[eventPath] = currTime.UnixMilli()
124
125
if len(inotifyCache) >= CacheSize {
126
inotifyCache = make(map[string]int64)
127
}
128
return false
129
}
130
131