1package kvconf
2
3import (
4 "errors"
5 "os"
6 "syscall"
7 "time"
8 "unsafe"
9)
10
11// The standard syscall package lacks these, or declares them with a wrong
12// signature on 64-bit Windows; they are loaded from kernel32 directly.
13var (
14 kernel32 = syscall.NewLazyDLL("kernel32.dll")
15 procCreateEventW = kernel32.NewProc("CreateEventW")
16 procSetEvent = kernel32.NewProc("SetEvent")
17 procResetEvent = kernel32.NewProc("ResetEvent")
18 procWaitForMultipleObjects = kernel32.NewProc("WaitForMultipleObjects")
19 procGetOverlappedResult = kernel32.NewProc("GetOverlappedResult")
20)
21
22const (
23 waitObject0 = 0
24 waitFailed = 0xFFFFFFFF
25 infinite = 0xFFFFFFFF
26 maximumWait = 64 // MAXIMUM_WAIT_OBJECTS
27 errorIOIncomplete = syscall.Errno(996)
28 errorIOPending = syscall.Errno(997)
29
30 notifyMask = syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME |
31 syscall.FILE_NOTIFY_CHANGE_SIZE | syscall.FILE_NOTIFY_CHANGE_LAST_WRITE |
32 syscall.FILE_NOTIFY_CHANGE_CREATION | syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
33)
34
35// dirWatch is one directory with its pending overlapped ReadDirectoryChangesW.
36// It lives on the heap, which Go never moves, so the kernel may write into
37// ov and buf until the read completes or is cancelled and drained.
38type dirWatch struct {
39 dir string
40 handle syscall.Handle
41 ov syscall.Overlapped
42 buf []uint32 // DWORD aligned, as the API requires
43 pending bool
44}
45
46type winWatch struct {
47 wakeEv syscall.Handle
48 dirs []*dirWatch
49 handles []syscall.Handle
50}
51
52func createEvent() (syscall.Handle, error) {
53 h, _, err := procCreateEventW.Call(0, 1, 0, 0) // manual reset, not signaled
54 if h == 0 {
55 return 0, os.NewSyscallError("CreateEventW", err)
56 }
57 return syscall.Handle(h), nil
58}
59
60func newNative() (backend, error) {
61 if err := procWaitForMultipleObjects.Find(); err != nil {
62 return nil, nil // poll instead
63 }
64 ev, err := createEvent()
65 if err != nil {
66 return nil, err
67 }
68 return &winWatch{wakeEv: ev}, nil
69}
70
71func (w *winWatch) set(targets []*target) (missing bool, err error) {
72 w.closeDirs()
73 for _, t := range targets {
74 if len(w.dirs) == maximumWait-1 {
75 return missing, errors.New("kvconf: too many directories to watch")
76 }
77 d, err := openDir(t.dir)
78 if err != nil {
79 // Missing, or removed while a handle keeps it "delete pending"
80 // (access denied): look again later.
81 missing = true
82 continue
83 }
84 if err := d.read(); err != nil {
85 d.close()
86 missing = true
87 continue
88 }
89 w.dirs = append(w.dirs, d)
90 }
91 return missing, nil
92}
93
94func openDir(dir string) (*dirWatch, error) {
95 p, err := syscall.UTF16PtrFromString(dir)
96 if err != nil {
97 return nil, err
98 }
99 h, err := syscall.CreateFile(p, syscall.FILE_LIST_DIRECTORY,
100 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
101 nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
102 if err != nil {
103 return nil, &os.PathError{Op: "CreateFile", Path: dir, Err: err}
104 }
105 ev, err := createEvent()
106 if err != nil {
107 syscall.CloseHandle(h)
108 return nil, err
109 }
110 d := &dirWatch{dir: dir, handle: h, buf: make([]uint32, 16<<10)}
111 d.ov.HEvent = ev
112 return d, nil
113}
114
115func (d *dirWatch) read() error {
116 err := syscall.ReadDirectoryChanges(d.handle, (*byte)(unsafe.Pointer(&d.buf[0])),
117 uint32(len(d.buf)*4), false, notifyMask, nil, &d.ov, 0)
118 if err != nil && !errors.Is(err, errorIOPending) {
119 return err
120 }
121 d.pending = true
122 return nil
123}
124
125// result returns the size of the completed read.
126func (d *dirWatch) result(wait bool) (uint32, error) {
127 var n uint32
128 b := uintptr(0)
129 if wait {
130 b = 1
131 }
132 r, _, err := procGetOverlappedResult.Call(uintptr(d.handle), uintptr(unsafe.Pointer(&d.ov)), uintptr(unsafe.Pointer(&n)), b)
133 if r == 0 {
134 return 0, err
135 }
136 return n, nil
137}
138
139func (d *dirWatch) close() {
140 if d.pending {
141 _ = syscall.CancelIoEx(d.handle, &d.ov)
142 _, _ = d.result(true) // the kernel is done with ov and buf
143 d.pending = false
144 }
145 syscall.CloseHandle(d.handle)
146 syscall.CloseHandle(d.ov.HEvent)
147}
148
149func (w *winWatch) closeDirs() {
150 for _, d := range w.dirs {
151 d.close()
152 }
153 w.dirs = w.dirs[:0]
154}
155
156func (w *winWatch) wait(timeout time.Duration, evs []event) ([]event, error) {
157 w.handles = append(w.handles[:0], w.wakeEv)
158 for _, d := range w.dirs {
159 w.handles = append(w.handles, d.ov.HEvent)
160 }
161 ms := uintptr(infinite)
162 if timeout >= 0 {
163 ms = uintptr((timeout + time.Millisecond - 1) / time.Millisecond)
164 }
165 r, _, err := procWaitForMultipleObjects.Call(uintptr(len(w.handles)),
166 uintptr(unsafe.Pointer(&w.handles[0])), 0, ms)
167 switch {
168 case r == waitFailed:
169 return evs, os.NewSyscallError("WaitForMultipleObjects", err)
170 case r == uintptr(syscall.WAIT_TIMEOUT):
171 return evs, nil
172 case r == waitObject0:
173 procResetEvent.Call(uintptr(w.wakeEv))
174 return evs, nil
175 }
176 i := int(r - waitObject0 - 1)
177 if i < 0 || i >= len(w.dirs) {
178 return evs, nil
179 }
180 d := w.dirs[i]
181 n, err := d.result(false)
182 d.pending = false
183 if err != nil {
184 if errors.Is(err, errorIOIncomplete) {
185 d.pending = true
186 return evs, nil
187 }
188 // The directory was removed or became unreadable.
189 return append(evs, event{dir: d.dir, lost: true}), nil
190 }
191 if n == 0 {
192 evs = append(evs, event{dir: d.dir}) // the buffer overflowed
193 }
194 buf := unsafe.Slice((*byte)(unsafe.Pointer(&d.buf[0])), len(d.buf)*4)
195 for off := uint32(0); n > 0 && int(off)+int(unsafe.Sizeof(syscall.FileNotifyInformation{})) <= int(n); {
196 info := (*syscall.FileNotifyInformation)(unsafe.Pointer(&buf[off]))
197 name := unsafe.Slice(&info.FileName, info.FileNameLength/2)
198 moved := info.Action != syscall.FILE_ACTION_MODIFIED
199 evs = append(evs, event{dir: d.dir, name: syscall.UTF16ToString(name), moved: moved})
200 if info.NextEntryOffset == 0 {
201 break
202 }
203 off += info.NextEntryOffset
204 }
205 if err := d.read(); err != nil {
206 evs = append(evs, event{dir: d.dir, lost: true})
207 }
208 return evs, nil
209}
210
211func (w *winWatch) wake() { procSetEvent.Call(uintptr(w.wakeEv)) }
212
213func (w *winWatch) close() error {
214 w.closeDirs()
215 return syscall.CloseHandle(w.wakeEv)
216}