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