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  }