1//go:build darwin || ios || freebsd || netbsd || openbsd || dragonfly
2
3package kvconf
4
5import (
6 "errors"
7 "os"
8 "path/filepath"
9 "syscall"
10 "time"
11)
12
13// kqueue watches with BSD/macOS kqueue. A vnode watch needs a descriptor
14// per file, so it watches each directory (for files created, removed or
15// renamed) and each file that matters (for writes in place). Events carry no
16// name: the watcher compares file stamps to see what changed, and asks for a
17// rewatch when a file was replaced, since its descriptor then points to the
18// old one.
19type kqueue struct {
20 kq int
21 wakeR int
22 wakeW int
23 fds map[int]string // descriptor -> directory it reports for
24 events []syscall.Kevent_t
25}
26
27const kqueueFflags = syscall.NOTE_WRITE | syscall.NOTE_EXTEND | syscall.NOTE_ATTRIB |
28 syscall.NOTE_DELETE | syscall.NOTE_RENAME | syscall.NOTE_REVOKE
29
30func newNative() (backend, error) {
31 kq, err := syscall.Kqueue()
32 if err != nil {
33 return nil, os.NewSyscallError("kqueue", err)
34 }
35 syscall.CloseOnExec(kq)
36 var p [2]int
37 if err := syscall.Pipe(p[:]); err != nil {
38 syscall.Close(kq)
39 return nil, os.NewSyscallError("pipe", err)
40 }
41 for _, fd := range p {
42 syscall.CloseOnExec(fd)
43 _ = syscall.SetNonblock(fd, true)
44 }
45 k := &kqueue{kq: kq, wakeR: p[0], wakeW: p[1], fds: map[int]string{}, events: make([]syscall.Kevent_t, 64)}
46 var ev syscall.Kevent_t
47 syscall.SetKevent(&ev, k.wakeR, syscall.EVFILT_READ, syscall.EV_ADD|syscall.EV_CLEAR)
48 if _, err := syscall.Kevent(kq, []syscall.Kevent_t{ev}, nil, nil); err != nil {
49 _ = k.close()
50 return nil, os.NewSyscallError("kevent", err)
51 }
52 return k, nil
53}
54
55func (k *kqueue) set(targets []*target) (missing bool, err error) {
56 // Descriptors are cheap to reopen and a replaced file needs a new one.
57 for fd := range k.fds {
58 syscall.Close(fd) // closing removes its kevents
59 }
60 clear(k.fds)
61 var changes []syscall.Kevent_t
62 watch := func(path, dir string) bool {
63 fd, err := syscall.Open(path, openEventOnly|syscall.O_CLOEXEC|syscall.O_NONBLOCK, 0)
64 if err != nil {
65 return false
66 }
67 k.fds[fd] = dir
68 var ev syscall.Kevent_t
69 syscall.SetKevent(&ev, fd, syscall.EVFILT_VNODE, syscall.EV_ADD|syscall.EV_CLEAR)
70 ev.Fflags = kqueueFflags
71 changes = append(changes, ev)
72 return true
73 }
74 for _, t := range targets {
75 if !watch(t.dir, t.dir) {
76 missing = true
77 continue
78 }
79 for _, name := range t.names {
80 watch(filepath.Join(t.dir, name), t.dir)
81 }
82 if t.all {
83 entries, _ := os.ReadDir(t.dir)
84 for _, e := range entries {
85 if visible(e.Name()) && !e.IsDir() {
86 watch(filepath.Join(t.dir, e.Name()), t.dir)
87 }
88 }
89 }
90 }
91 if len(changes) > 0 {
92 if _, err := syscall.Kevent(k.kq, changes, nil, nil); err != nil {
93 return missing, os.NewSyscallError("kevent", err)
94 }
95 }
96 return missing, nil
97}
98
99func (k *kqueue) wait(timeout time.Duration, evs []event) ([]event, error) {
100 var ts *syscall.Timespec
101 if timeout >= 0 {
102 t := syscall.NsecToTimespec(int64(timeout))
103 ts = &t
104 }
105 n, err := syscall.Kevent(k.kq, nil, k.events, ts)
106 if errors.Is(err, syscall.EINTR) {
107 return evs, nil
108 }
109 if err != nil {
110 return evs, os.NewSyscallError("kevent", err)
111 }
112 for _, ev := range k.events[:n] {
113 fd := int(ev.Ident)
114 if fd == k.wakeR {
115 var buf [64]byte
116 for {
117 if n, _ := syscall.Read(k.wakeR, buf[:]); n <= 0 {
118 break
119 }
120 }
121 continue
122 }
123 dir, ok := k.fds[fd]
124 if !ok {
125 continue
126 }
127 lost := ev.Fflags&(syscall.NOTE_DELETE|syscall.NOTE_RENAME|syscall.NOTE_REVOKE) != 0
128 evs = append(evs, event{dir: dir, lost: lost})
129 }
130 return evs, nil
131}
132
133func (k *kqueue) wake() {
134 _, _ = syscall.Write(k.wakeW, []byte{0})
135}
136
137func (k *kqueue) close() error {
138 for fd := range k.fds {
139 syscall.Close(fd)
140 }
141 syscall.Close(k.wakeR)
142 syscall.Close(k.wakeW)
143 return syscall.Close(k.kq)
144}