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