11from __future__ import annotations
22
3+ import ctypes
34import select
45
5- from typing import TYPE_CHECKING , Dict , KeysView
6+ from typing import TYPE_CHECKING , Dict , KeysView , NamedTuple
67
8+ from qiling .os import struct
79from qiling .os .posix .const import *
810from qiling .os .filestruct import PersistentQlFile , ql_file
911
1012
1113if TYPE_CHECKING :
1214 from qiling import Qiling
15+ from qiling .arch .arch import QlArch
1316 from qiling .os .posix .posix import QlFileDes
1417
18+
19+ class QlEpollEntry (NamedTuple ):
20+ """A named tuple to represent an epoll entry.
21+
22+ This is used to store the events mask and the data for each entry in
23+ the epoll instance.
24+ """
25+
26+ events : int
27+ data : int
28+
29+
30+ @struct .cache
31+ def __make_epoll_event (arch : QlArch ):
32+ """Create a structure to represent an epoll event.
33+ """
34+
35+ Struct = struct .get_packed_struct (arch .endian )
36+
37+ class epoll_event (Struct ):
38+ _fields_ = (
39+ ('events' , ctypes .c_uint32 ),
40+ ('data' , ctypes .c_uint64 )
41+ )
42+
43+ return epoll_event
44+
45+
1546class QlEpollObj :
1647 def __init__ (self , epoll_object : select .epoll ):
1748 self ._epoll_object = epoll_object
1849
1950 # maps fd to eventmask
2051 # keep track of which fds have what eventmasks,
2152 # since this isn't directly supported in select.epoll
22- self ._fds : Dict [int , int ] = {}
53+ self ._fds : Dict [int , QlEpollEntry ] = {}
2354
2455 @property
2556 def fds (self ) -> KeysView [int ]:
@@ -29,31 +60,30 @@ def fds(self) -> KeysView[int]:
2960 def epoll_instance (self ) -> select .epoll :
3061 return self ._epoll_object
3162
32- def get_eventmask (self , fd : int ) -> int :
63+ def close (self ) -> None :
64+ self ._epoll_object .close ()
65+
66+ def __getitem__ (self , fd : int ) -> QlEpollEntry :
3367 return self ._fds [fd ]
3468
35- def set_eventmask (self , fd : int , newmask : int ):
36- # the mask for an FD shouldn't ever be undefined
37- # as it is set whenever an FD is added for a QlEpollObj instance
69+ def __setitem__ (self , fd : int , entry : QlEpollEntry ) -> None :
70+ # if fd is already being watched, modify its eventmask.
71+ if fd in self :
72+ self ._epoll_object .modify (fd , entry .events )
3873
39- newmask = self .get_eventmask (fd ) | newmask
74+ # otherwise, register it with the epoll object
75+ else :
76+ self ._epoll_object .register (fd , entry .events )
4077
41- self ._epoll_object .modify (fd , newmask )
42- self ._fds [fd ] = newmask
78+ self ._fds [fd ] = entry
4379
44- def monitor_fd (self , fd : int , eventmask : int ) -> None :
45- # tell the epoll object to watch the fd arg, looking for events matching the eventmask
46- self ._epoll_object .register (fd , eventmask )
47- self ._fds [fd ] = eventmask
80+ def __delitem__ (self , fd : int ) -> None :
81+ """Remove an fd from the epoll instance.
82+ """
4883
49- def delist_fd (self , fd : int ) -> None :
5084 self ._fds .pop (fd )
5185 self ._epoll_object .unregister (fd )
5286
53- def close (self ) -> None :
54- self ._epoll_object .close ()
55-
56-
5787 def __contains__ (self , fd : int ) -> bool :
5888 """Test whether a specific fd is already being watched by this epoll instance.
5989 """
@@ -153,23 +183,25 @@ def ql_syscall_epoll_ctl(ql: Qiling, epfd: int, op: int, fd: int, event: int):
153183 if not event :
154184 return - EINVAL
155185
156- event_ptr = ql .mem .read_ptr (event )
157- events = ql .mem .read_ptr (event_ptr , 4 )
186+ # dereference the event pointer to get structure fields
187+ epoll_event_cls = __make_epoll_event (ql .arch )
188+ epoll_event = epoll_event_cls .load_from (ql .mem , event )
158189
159190 # EPOLLEXCLUSIVE was specified in event and fd refers to an epoll instance
160- if isinstance (fd_obj , QlEpollObj ) and (op & EPOLLEXCLUSIVE ):
191+ if isinstance (fd_obj , QlEpollObj ) and (epoll_event . events & EPOLLEXCLUSIVE ):
161192 return - EINVAL
162193
163- # add to list of fds to be monitored with per-fd eventmask register will actual epoll
164- # instance and add eventmask accordingly
165- epoll_parent_obj .monitor_fd (fd , events )
194+ epoll_parent_obj [fd ] = QlEpollEntry (
195+ epoll_event .events ,
196+ epoll_event .data
197+ )
166198
167199 elif op == EPOLL_CTL_DEL :
168200 if fd not in epoll_parent_obj :
169201 return - ENOENT
170202
171203 # remove from fds list and do so in the underlying epoll instance
172- epoll_parent_obj . delist_fd ( fd )
204+ del epoll_parent_obj [ fd ]
173205
174206 elif op == EPOLL_CTL_MOD :
175207 if fd not in epoll_parent_obj :
@@ -178,14 +210,18 @@ def ql_syscall_epoll_ctl(ql: Qiling, epfd: int, op: int, fd: int, event: int):
178210 if not event :
179211 return - EINVAL
180212
181- event_ptr = ql .mem .read_ptr (event )
182- events = ql .mem .read_ptr (event_ptr , 4 )
213+ # dereference the event pointer to get structure fields
214+ epoll_event_cls = __make_epoll_event (ql .arch )
215+ epoll_event = epoll_event_cls .load_from (ql .mem , event )
183216
184217 # EPOLLEXCLUSIVE cannot be set on MOD operation, only on ADD
185- if events & EPOLLEXCLUSIVE :
218+ if epoll_event . events & EPOLLEXCLUSIVE :
186219 return - EINVAL
187220
188- epoll_parent_obj .set_eventmask (fd , events )
221+ epoll_parent_obj [fd ] = QlEpollEntry (
222+ epoll_event .events ,
223+ epoll_event .data
224+ )
189225
190226 return 0
191227
@@ -216,20 +252,22 @@ def ql_syscall_epoll_wait(ql: Qiling, epfd: int, epoll_events: int, maxevents: i
216252
217253 ready_fds = epoll_obj .poll (timeout , maxevents )
218254
255+ epoll_event_cls = __make_epoll_event (ql .arch )
256+
219257 # Each tuple in ready_fds consists of (file descriptor, eventmask) so we iterate
220258 # through these to indicate which fds are ready and 'why'
221-
259+ #
260+ # FIXME: emulated system fds are not the same as hosted system fds
222261 for i , (fd , events ) in enumerate (ready_fds ):
223- # if no longer interested in this fd, remove from list
224- if events & EPOLLONESHOT :
225- epoll_parent_obj .delist_fd (fd )
262+ entry = epoll_parent_obj [fd ]
263+ epoll_event = epoll_event_cls (events , entry .data )
226264
227- # FIXME: the data packed after events should be the one passed on epoll_ctl
228- # for that specific fd. currently this does not align with the spec
229- data = ql .pack32 (events ) + ql .pack64 (fd )
230- offset = len (data ) * i
265+ offset = epoll_event_cls .sizeof () * i
266+ ql .mem .write (epoll_events + offset , bytes (epoll_event ))
231267
232- ql .mem .write (epoll_events + offset , data )
268+ # if no longer interested in this fd, remove from list
269+ if events & EPOLLONESHOT :
270+ del epoll_parent_obj [fd ]
233271
234272 return len (ready_fds )
235273
0 commit comments