github.com/liloew/wireguard-go@v0.0.0-20220224014633-9cd745e6f114/ipc/namedpipe/file.go (about) 1 // Copyright 2021 The Go Authors. All rights reserved. 2 // Copyright 2015 Microsoft 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 //go:build windows 7 // +build windows 8 9 package namedpipe 10 11 import ( 12 "io" 13 "os" 14 "runtime" 15 "sync" 16 "sync/atomic" 17 "time" 18 "unsafe" 19 20 "golang.org/x/sys/windows" 21 ) 22 23 type timeoutChan chan struct{} 24 25 var ( 26 ioInitOnce sync.Once 27 ioCompletionPort windows.Handle 28 ) 29 30 // ioResult contains the result of an asynchronous IO operation 31 type ioResult struct { 32 bytes uint32 33 err error 34 } 35 36 // ioOperation represents an outstanding asynchronous Win32 IO 37 type ioOperation struct { 38 o windows.Overlapped 39 ch chan ioResult 40 } 41 42 func initIo() { 43 h, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0) 44 if err != nil { 45 panic(err) 46 } 47 ioCompletionPort = h 48 go ioCompletionProcessor(h) 49 } 50 51 // file implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall. 52 // It takes ownership of this handle and will close it if it is garbage collected. 53 type file struct { 54 handle windows.Handle 55 wg sync.WaitGroup 56 wgLock sync.RWMutex 57 closing uint32 // used as atomic boolean 58 socket bool 59 readDeadline deadlineHandler 60 writeDeadline deadlineHandler 61 } 62 63 type deadlineHandler struct { 64 setLock sync.Mutex 65 channel timeoutChan 66 channelLock sync.RWMutex 67 timer *time.Timer 68 timedout uint32 // used as atomic boolean 69 } 70 71 // makeFile makes a new file from an existing file handle 72 func makeFile(h windows.Handle) (*file, error) { 73 f := &file{handle: h} 74 ioInitOnce.Do(initIo) 75 _, err := windows.CreateIoCompletionPort(h, ioCompletionPort, 0, 0) 76 if err != nil { 77 return nil, err 78 } 79 err = windows.SetFileCompletionNotificationModes(h, windows.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS|windows.FILE_SKIP_SET_EVENT_ON_HANDLE) 80 if err != nil { 81 return nil, err 82 } 83 f.readDeadline.channel = make(timeoutChan) 84 f.writeDeadline.channel = make(timeoutChan) 85 return f, nil 86 } 87 88 // closeHandle closes the resources associated with a Win32 handle 89 func (f *file) closeHandle() { 90 f.wgLock.Lock() 91 // Atomically set that we are closing, releasing the resources only once. 92 if atomic.SwapUint32(&f.closing, 1) == 0 { 93 f.wgLock.Unlock() 94 // cancel all IO and wait for it to complete 95 windows.CancelIoEx(f.handle, nil) 96 f.wg.Wait() 97 // at this point, no new IO can start 98 windows.Close(f.handle) 99 f.handle = 0 100 } else { 101 f.wgLock.Unlock() 102 } 103 } 104 105 // Close closes a file. 106 func (f *file) Close() error { 107 f.closeHandle() 108 return nil 109 } 110 111 // prepareIo prepares for a new IO operation. 112 // The caller must call f.wg.Done() when the IO is finished, prior to Close() returning. 113 func (f *file) prepareIo() (*ioOperation, error) { 114 f.wgLock.RLock() 115 if atomic.LoadUint32(&f.closing) == 1 { 116 f.wgLock.RUnlock() 117 return nil, os.ErrClosed 118 } 119 f.wg.Add(1) 120 f.wgLock.RUnlock() 121 c := &ioOperation{} 122 c.ch = make(chan ioResult) 123 return c, nil 124 } 125 126 // ioCompletionProcessor processes completed async IOs forever 127 func ioCompletionProcessor(h windows.Handle) { 128 for { 129 var bytes uint32 130 var key uintptr 131 var op *ioOperation 132 err := windows.GetQueuedCompletionStatus(h, &bytes, &key, (**windows.Overlapped)(unsafe.Pointer(&op)), windows.INFINITE) 133 if op == nil { 134 panic(err) 135 } 136 op.ch <- ioResult{bytes, err} 137 } 138 } 139 140 // asyncIo processes the return value from ReadFile or WriteFile, blocking until 141 // the operation has actually completed. 142 func (f *file) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) { 143 if err != windows.ERROR_IO_PENDING { 144 return int(bytes), err 145 } 146 147 if atomic.LoadUint32(&f.closing) == 1 { 148 windows.CancelIoEx(f.handle, &c.o) 149 } 150 151 var timeout timeoutChan 152 if d != nil { 153 d.channelLock.Lock() 154 timeout = d.channel 155 d.channelLock.Unlock() 156 } 157 158 var r ioResult 159 select { 160 case r = <-c.ch: 161 err = r.err 162 if err == windows.ERROR_OPERATION_ABORTED { 163 if atomic.LoadUint32(&f.closing) == 1 { 164 err = os.ErrClosed 165 } 166 } else if err != nil && f.socket { 167 // err is from Win32. Query the overlapped structure to get the winsock error. 168 var bytes, flags uint32 169 err = windows.WSAGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags) 170 } 171 case <-timeout: 172 windows.CancelIoEx(f.handle, &c.o) 173 r = <-c.ch 174 err = r.err 175 if err == windows.ERROR_OPERATION_ABORTED { 176 err = os.ErrDeadlineExceeded 177 } 178 } 179 180 // runtime.KeepAlive is needed, as c is passed via native 181 // code to ioCompletionProcessor, c must remain alive 182 // until the channel read is complete. 183 runtime.KeepAlive(c) 184 return int(r.bytes), err 185 } 186 187 // Read reads from a file handle. 188 func (f *file) Read(b []byte) (int, error) { 189 c, err := f.prepareIo() 190 if err != nil { 191 return 0, err 192 } 193 defer f.wg.Done() 194 195 if atomic.LoadUint32(&f.readDeadline.timedout) == 1 { 196 return 0, os.ErrDeadlineExceeded 197 } 198 199 var bytes uint32 200 err = windows.ReadFile(f.handle, b, &bytes, &c.o) 201 n, err := f.asyncIo(c, &f.readDeadline, bytes, err) 202 runtime.KeepAlive(b) 203 204 // Handle EOF conditions. 205 if err == nil && n == 0 && len(b) != 0 { 206 return 0, io.EOF 207 } else if err == windows.ERROR_BROKEN_PIPE { 208 return 0, io.EOF 209 } else { 210 return n, err 211 } 212 } 213 214 // Write writes to a file handle. 215 func (f *file) Write(b []byte) (int, error) { 216 c, err := f.prepareIo() 217 if err != nil { 218 return 0, err 219 } 220 defer f.wg.Done() 221 222 if atomic.LoadUint32(&f.writeDeadline.timedout) == 1 { 223 return 0, os.ErrDeadlineExceeded 224 } 225 226 var bytes uint32 227 err = windows.WriteFile(f.handle, b, &bytes, &c.o) 228 n, err := f.asyncIo(c, &f.writeDeadline, bytes, err) 229 runtime.KeepAlive(b) 230 return n, err 231 } 232 233 func (f *file) SetReadDeadline(deadline time.Time) error { 234 return f.readDeadline.set(deadline) 235 } 236 237 func (f *file) SetWriteDeadline(deadline time.Time) error { 238 return f.writeDeadline.set(deadline) 239 } 240 241 func (f *file) Flush() error { 242 return windows.FlushFileBuffers(f.handle) 243 } 244 245 func (f *file) Fd() uintptr { 246 return uintptr(f.handle) 247 } 248 249 func (d *deadlineHandler) set(deadline time.Time) error { 250 d.setLock.Lock() 251 defer d.setLock.Unlock() 252 253 if d.timer != nil { 254 if !d.timer.Stop() { 255 <-d.channel 256 } 257 d.timer = nil 258 } 259 atomic.StoreUint32(&d.timedout, 0) 260 261 select { 262 case <-d.channel: 263 d.channelLock.Lock() 264 d.channel = make(chan struct{}) 265 d.channelLock.Unlock() 266 default: 267 } 268 269 if deadline.IsZero() { 270 return nil 271 } 272 273 timeoutIO := func() { 274 atomic.StoreUint32(&d.timedout, 1) 275 close(d.channel) 276 } 277 278 now := time.Now() 279 duration := deadline.Sub(now) 280 if deadline.After(now) { 281 // Deadline is in the future, set a timer to wait 282 d.timer = time.AfterFunc(duration, timeoutIO) 283 } else { 284 // Deadline is in the past. Cancel all pending IO now. 285 timeoutIO() 286 } 287 return nil 288 }