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 := ¬ifier{ 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 }