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