1package kvconf
2
3import (
4 "errors"
5 "os"
6 "syscall"
7 "time"
8 "unsafe"
9)
10
11// These functions are missing from package syscall: they are loaded from
12// 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 ev, err := createEvent()
62 if err != nil {
63 return nil, err
64 }
65 return &winWatch{wakeEv: ev}, nil
66}
67
68func (w *winWatch) set(targets []*target) (missing bool, err error) {
69 w.closeDirs()
70 for _, t := range targets {
71 if len(w.dirs) == maximumWait-1 {
72 return missing, errors.New("kvconf: too many directories to watch")
73 }
74 d, err := openDir(t.dir)
75 if err != nil {
76 // Missing, or removed while a handle keeps it "delete pending"
77 // (access denied): look again later.
78 missing = true
79 continue
80 }
81 if err := d.read(); err != nil {
82 d.close()
83 missing = true
84 continue
85 }
86 w.dirs = append(w.dirs, d)
87 }
88 return missing, nil
89}
90
91func openDir(dir string) (*dirWatch, error) {
92 p, err := syscall.UTF16PtrFromString(dir)
93 if err != nil {
94 return nil, err
95 }
96 h, err := syscall.CreateFile(p, syscall.FILE_LIST_DIRECTORY,
97 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
98 nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
99 if err != nil {
100 return nil, &os.PathError{Op: "CreateFile", Path: dir, Err: err}
101 }
102 ev, err := createEvent()
103 if err != nil {
104 syscall.CloseHandle(h)
105 return nil, err
106 }
107 d := &dirWatch{dir: dir, handle: h, buf: make([]uint32, 16<<10)}
108 d.ov.HEvent = ev
109 return d, nil
110}
111
112func (d *dirWatch) read() error {
113 err := syscall.ReadDirectoryChanges(d.handle, (*byte)(unsafe.Pointer(&d.buf[0])),
114 uint32(len(d.buf)*4), false, notifyMask, nil, &d.ov, 0)
115 if err != nil && !errors.Is(err, errorIOPending) {
116 return err
117 }
118 d.pending = true
119 return nil
120}
121
122// result returns the size of the completed read.
123func (d *dirWatch) result(wait bool) (uint32, error) {
124 var n uint32
125 b := uintptr(0)
126 if wait {
127 b = 1
128 }
129 r, _, err := procGetOverlappedResult.Call(uintptr(d.handle), uintptr(unsafe.Pointer(&d.ov)), uintptr(unsafe.Pointer(&n)), b)
130 if r == 0 {
131 return 0, err
132 }
133 return n, nil
134}
135
136func (d *dirWatch) close() {
137 if d.pending {
138 _ = syscall.CancelIoEx(d.handle, &d.ov)
139 _, _ = d.result(true) // the kernel is done with ov and buf
140 d.pending = false
141 }
142 syscall.CloseHandle(d.handle)
143 syscall.CloseHandle(d.ov.HEvent)
144}
145
146func (w *winWatch) closeDirs() {
147 for _, d := range w.dirs {
148 d.close()
149 }
150 w.dirs = w.dirs[:0]
151}
152
153func (w *winWatch) wait(timeout time.Duration, evs []event) ([]event, error) {
154 w.handles = append(w.handles[:0], w.wakeEv)
155 for _, d := range w.dirs {
156 w.handles = append(w.handles, d.ov.HEvent)
157 }
158 ms := uintptr(infinite)
159 if timeout >= 0 {
160 ms = uintptr((timeout + time.Millisecond - 1) / time.Millisecond)
161 }
162 r, _, err := procWaitForMultipleObjects.Call(uintptr(len(w.handles)),
163 uintptr(unsafe.Pointer(&w.handles[0])), 0, ms)
164 switch {
165 case r == waitFailed:
166 return evs, os.NewSyscallError("WaitForMultipleObjects", err)
167 case r == uintptr(syscall.WAIT_TIMEOUT):
168 return evs, nil
169 case r == waitObject0:
170 procResetEvent.Call(uintptr(w.wakeEv))
171 return evs, nil
172 }
173 i := int(r - waitObject0 - 1)
174 if i < 0 || i >= len(w.dirs) {
175 return evs, nil
176 }
177 d := w.dirs[i]
178 n, err := d.result(false)
179 d.pending = false
180 if err != nil {
181 if errors.Is(err, errorIOIncomplete) {
182 d.pending = true
183 return evs, nil
184 }
185 // The directory was removed or became unreadable.
186 return append(evs, event{}), nil
187 }
188 if n == 0 {
189 evs = append(evs, event{dir: d.dir}) // the buffer overflowed
190 }
191 buf := unsafe.Slice((*byte)(unsafe.Pointer(&d.buf[0])), len(d.buf)*4)
192 for off := uint32(0); n > 0 && int(off)+int(unsafe.Sizeof(syscall.FileNotifyInformation{})) <= int(n); {
193 info := (*syscall.FileNotifyInformation)(unsafe.Pointer(&buf[off]))
194 name := unsafe.Slice(&info.FileName, info.FileNameLength/2)
195 moved := info.Action != syscall.FILE_ACTION_MODIFIED
196 evs = append(evs, event{dir: d.dir, name: syscall.UTF16ToString(name), moved: moved})
197 if info.NextEntryOffset == 0 {
198 break
199 }
200 off += info.NextEntryOffset
201 }
202 if err := d.read(); err != nil {
203 evs = append(evs, event{})
204 }
205 return evs, nil
206}
207
208func (w *winWatch) wake() { procSetEvent.Call(uintptr(w.wakeEv)) }
209
210func (w *winWatch) close() error {
211 w.closeDirs()
212 return syscall.CloseHandle(w.wakeEv)
213}