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  }