github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/fdnotifier/fdnotifier.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // +build linux
    16  
    17  // Package fdnotifier contains an adapter that translates IO events (e.g., a
    18  // file became readable/writable) from native FDs to the notifications in the
    19  // waiter package. It uses epoll in edge-triggered mode to receive notifications
    20  // for registered FDs.
    21  package fdnotifier
    22  
    23  import (
    24  	"fmt"
    25  
    26  	"golang.org/x/sys/unix"
    27  	"github.com/SagerNet/gvisor/pkg/sync"
    28  	"github.com/SagerNet/gvisor/pkg/waiter"
    29  )
    30  
    31  type fdInfo struct {
    32  	queue   *waiter.Queue
    33  	waiting bool
    34  }
    35  
    36  // notifier holds all the state necessary to issue notifications when IO events
    37  // occur in the observed FDs.
    38  type notifier struct {
    39  	// epFD is the epoll file descriptor used to register for io
    40  	// notifications.
    41  	epFD int
    42  
    43  	// mu protects fdMap.
    44  	mu sync.Mutex
    45  
    46  	// fdMap maps file descriptors to their notification queues and waiting
    47  	// status.
    48  	fdMap map[int32]*fdInfo
    49  }
    50  
    51  // newNotifier creates a new notifier object.
    52  func newNotifier() (*notifier, error) {
    53  	epfd, err := unix.EpollCreate1(0)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	w := &notifier{
    59  		epFD:  epfd,
    60  		fdMap: make(map[int32]*fdInfo),
    61  	}
    62  
    63  	go w.waitAndNotify() // S/R-SAFE: no waiter exists during save / load.
    64  
    65  	return w, nil
    66  }
    67  
    68  // waitFD waits on mask for fd. The fdMap mutex must be hold.
    69  func (n *notifier) waitFD(fd int32, fi *fdInfo, mask waiter.EventMask) error {
    70  	if !fi.waiting && mask == 0 {
    71  		return nil
    72  	}
    73  
    74  	e := unix.EpollEvent{
    75  		Events: mask.ToLinux() | unix.EPOLLET,
    76  		Fd:     fd,
    77  	}
    78  
    79  	switch {
    80  	case !fi.waiting && mask != 0:
    81  		if err := unix.EpollCtl(n.epFD, unix.EPOLL_CTL_ADD, int(fd), &e); err != nil {
    82  			return err
    83  		}
    84  		fi.waiting = true
    85  	case fi.waiting && mask == 0:
    86  		unix.EpollCtl(n.epFD, unix.EPOLL_CTL_DEL, int(fd), nil)
    87  		fi.waiting = false
    88  	case fi.waiting && mask != 0:
    89  		if err := unix.EpollCtl(n.epFD, unix.EPOLL_CTL_MOD, int(fd), &e); err != nil {
    90  			return err
    91  		}
    92  	}
    93  
    94  	return nil
    95  }
    96  
    97  // addFD adds an FD to the list of FDs observed by n.
    98  func (n *notifier) addFD(fd int32, queue *waiter.Queue) {
    99  	n.mu.Lock()
   100  	defer n.mu.Unlock()
   101  
   102  	// Panic if we're already notifying on this FD.
   103  	if _, ok := n.fdMap[fd]; ok {
   104  		panic(fmt.Sprintf("File descriptor %v added twice", fd))
   105  	}
   106  
   107  	// We have nothing to wait for at the moment. Just add it to the map.
   108  	n.fdMap[fd] = &fdInfo{queue: queue}
   109  }
   110  
   111  // updateFD updates the set of events the fd needs to be notified on.
   112  func (n *notifier) updateFD(fd int32) error {
   113  	n.mu.Lock()
   114  	defer n.mu.Unlock()
   115  
   116  	if fi, ok := n.fdMap[fd]; ok {
   117  		return n.waitFD(fd, fi, fi.queue.Events())
   118  	}
   119  
   120  	return nil
   121  }
   122  
   123  // RemoveFD removes an FD from the list of FDs observed by n.
   124  func (n *notifier) removeFD(fd int32) {
   125  	n.mu.Lock()
   126  	defer n.mu.Unlock()
   127  
   128  	// Remove from map, then from epoll object.
   129  	n.waitFD(fd, n.fdMap[fd], 0)
   130  	delete(n.fdMap, fd)
   131  }
   132  
   133  // hasFD returns true if the fd is in the list of observed FDs.
   134  func (n *notifier) hasFD(fd int32) bool {
   135  	n.mu.Lock()
   136  	defer n.mu.Unlock()
   137  
   138  	_, ok := n.fdMap[fd]
   139  	return ok
   140  }
   141  
   142  // waitAndNotify run is its own goroutine and loops waiting for io event
   143  // notifications from the epoll object. Once notifications arrive, they are
   144  // dispatched to the registered queue.
   145  func (n *notifier) waitAndNotify() error {
   146  	e := make([]unix.EpollEvent, 100)
   147  	for {
   148  		v, err := epollWait(n.epFD, e, -1)
   149  		if err == unix.EINTR {
   150  			continue
   151  		}
   152  
   153  		if err != nil {
   154  			return err
   155  		}
   156  
   157  		n.mu.Lock()
   158  		for i := 0; i < v; i++ {
   159  			if fi, ok := n.fdMap[e[i].Fd]; ok {
   160  				fi.queue.Notify(waiter.EventMaskFromLinux(e[i].Events))
   161  			}
   162  		}
   163  		n.mu.Unlock()
   164  	}
   165  }
   166  
   167  var shared struct {
   168  	notifier *notifier
   169  	once     sync.Once
   170  	initErr  error
   171  }
   172  
   173  // AddFD adds an FD to the list of observed FDs.
   174  func AddFD(fd int32, queue *waiter.Queue) error {
   175  	shared.once.Do(func() {
   176  		shared.notifier, shared.initErr = newNotifier()
   177  	})
   178  
   179  	if shared.initErr != nil {
   180  		return shared.initErr
   181  	}
   182  
   183  	shared.notifier.addFD(fd, queue)
   184  	return nil
   185  }
   186  
   187  // UpdateFD updates the set of events the fd needs to be notified on.
   188  func UpdateFD(fd int32) error {
   189  	return shared.notifier.updateFD(fd)
   190  }
   191  
   192  // RemoveFD removes an FD from the list of observed FDs.
   193  func RemoveFD(fd int32) {
   194  	shared.notifier.removeFD(fd)
   195  }
   196  
   197  // HasFD returns true if the FD is in the list of observed FDs.
   198  //
   199  // This should only be used by tests to assert that FDs are correctly registered.
   200  func HasFD(fd int32) bool {
   201  	return shared.notifier.hasFD(fd)
   202  }