Skip to main content

shadow_rs/host/syscall/handler/
uio.rs

1use linux_api::errno::Errno;
2use shadow_shim_helper_rs::syscall_types::ForeignPtr;
3
4use crate::cshadow as c;
5use crate::host::descriptor::socket::{RecvmsgArgs, RecvmsgReturn, SendmsgArgs, Socket};
6use crate::host::descriptor::{CompatFile, File, FileState, FileStatus};
7use crate::host::syscall::handler::{SyscallContext, SyscallHandler};
8use crate::host::syscall::io::{self, IoVec};
9use crate::host::syscall::types::{ForeignArrayPtr, SyscallError};
10use crate::utility::callback_queue::CallbackQueue;
11
12impl SyscallHandler {
13    log_syscall!(
14        readv,
15        /* rv */ libc::ssize_t,
16        /* fd */ std::ffi::c_int,
17        /* iov */ *const libc::iovec,
18        /* iovcnt */ std::ffi::c_int,
19    );
20    pub fn readv(
21        ctx: &mut SyscallContext,
22        fd: std::ffi::c_int,
23        iov_ptr: ForeignPtr<libc::iovec>,
24        iov_count: std::ffi::c_int,
25    ) -> Result<libc::ssize_t, SyscallError> {
26        // if we were previously blocked, get the active file from the last syscall handler
27        // invocation since it may no longer exist in the descriptor table
28        let file = ctx
29            .objs
30            .thread
31            .syscall_condition()
32            // if this was for a C descriptor, then there won't be an active file object
33            .and_then(|x| x.active_file().cloned());
34
35        let file = match file {
36            // we were previously blocked, so re-use the file from the previous syscall invocation
37            Some(x) => x,
38            // get the file from the descriptor table, or return early if it doesn't exist
39            None => {
40                let desc_table = ctx.objs.thread.descriptor_table_borrow(ctx.objs.host);
41                match Self::get_descriptor(&desc_table, fd)?.file() {
42                    CompatFile::New(file) => file.clone(),
43                    // if it's a legacy file, use the C syscall handler instead
44                    CompatFile::Legacy(_) => {
45                        drop(desc_table);
46                        return Self::legacy_syscall(c::syscallhandler_readv, ctx);
47                    }
48                }
49            }
50        };
51
52        let iov_count = iov_count.try_into().or(Err(Errno::EINVAL))?;
53
54        let iovs = {
55            let mem = ctx.objs.process.memory_borrow_mut();
56            io::read_iovecs(&mem, iov_ptr, iov_count)?
57        };
58        assert_eq!(iovs.len(), iov_count);
59
60        let mut result = Self::readv_helper(ctx, file.inner_file(), &iovs, None, 0);
61
62        // if the syscall will block, keep the file open until the syscall restarts
63        if let Some(err) = result.as_mut().err()
64            && let Some(cond) = err.blocked_condition()
65        {
66            cond.set_active_file(file);
67        }
68
69        let bytes_read = result?;
70        Ok(bytes_read)
71    }
72
73    log_syscall!(
74        preadv,
75        /* rv */ libc::ssize_t,
76        /* fd */ std::ffi::c_int,
77        /* iov */ *const libc::iovec,
78        /* iovcnt */ std::ffi::c_int,
79        /* pos_l */ libc::c_ulong,
80        /* pos_h */ libc::c_ulong,
81    );
82    pub fn preadv(
83        ctx: &mut SyscallContext,
84        fd: std::ffi::c_int,
85        iov_ptr: ForeignPtr<libc::iovec>,
86        iov_count: std::ffi::c_int,
87        offset_l: libc::c_ulong,
88        _offset_h: libc::c_ulong,
89    ) -> Result<libc::ssize_t, SyscallError> {
90        // on Linux x86-64, an `unsigned long` is 64 bits, so we can ignore `offset_h`
91        static_assertions::assert_eq_size!(libc::c_ulong, libc::off_t);
92        let offset = offset_l as libc::off_t;
93
94        // if we were previously blocked, get the active file from the last syscall handler
95        // invocation since it may no longer exist in the descriptor table
96        let file = ctx
97            .objs
98            .thread
99            .syscall_condition()
100            // if this was for a C descriptor, then there won't be an active file object
101            .and_then(|x| x.active_file().cloned());
102
103        let file = match file {
104            // we were previously blocked, so re-use the file from the previous syscall invocation
105            Some(x) => x,
106            // get the file from the descriptor table, or return early if it doesn't exist
107            None => {
108                let desc_table = ctx.objs.thread.descriptor_table_borrow(ctx.objs.host);
109                match Self::get_descriptor(&desc_table, fd)?.file() {
110                    CompatFile::New(file) => file.clone(),
111                    // if it's a legacy file, use the C syscall handler instead
112                    CompatFile::Legacy(_) => {
113                        drop(desc_table);
114                        return Self::legacy_syscall(c::syscallhandler_preadv, ctx);
115                    }
116                }
117            }
118        };
119
120        // make sure the offset is not negative
121        if offset < 0 {
122            return Err(Errno::EINVAL.into());
123        }
124
125        let iov_count = iov_count.try_into().or(Err(Errno::EINVAL))?;
126
127        let iovs = {
128            let mem = ctx.objs.process.memory_borrow_mut();
129            io::read_iovecs(&mem, iov_ptr, iov_count)?
130        };
131        assert_eq!(iovs.len(), iov_count);
132
133        let mut result = Self::readv_helper(ctx, file.inner_file(), &iovs, Some(offset), 0);
134
135        // if the syscall will block, keep the file open until the syscall restarts
136        if let Some(err) = result.as_mut().err()
137            && let Some(cond) = err.blocked_condition()
138        {
139            cond.set_active_file(file);
140        }
141
142        let bytes_read = result?;
143        Ok(bytes_read)
144    }
145
146    log_syscall!(
147        preadv2,
148        /* rv */ libc::ssize_t,
149        /* fd */ std::ffi::c_int,
150        /* iov */ *const libc::iovec,
151        /* iovcnt */ std::ffi::c_int,
152        /* pos_l */ libc::c_ulong,
153        /* pos_h */ libc::c_ulong,
154        /* flags */ std::ffi::c_int,
155    );
156    pub fn preadv2(
157        ctx: &mut SyscallContext,
158        fd: std::ffi::c_int,
159        iov_ptr: ForeignPtr<libc::iovec>,
160        iov_count: std::ffi::c_int,
161        offset_l: libc::c_ulong,
162        _offset_h: libc::c_ulong,
163        flags: std::ffi::c_int,
164    ) -> Result<libc::ssize_t, SyscallError> {
165        // on Linux x86-64, an `unsigned long` is 64 bits, so we can ignore `offset_h`
166        static_assertions::assert_eq_size!(libc::c_ulong, libc::off_t);
167        let offset = offset_l as libc::off_t;
168
169        // if we were previously blocked, get the active file from the last syscall handler
170        // invocation since it may no longer exist in the descriptor table
171        let file = ctx
172            .objs
173            .thread
174            .syscall_condition()
175            // if this was for a C descriptor, then there won't be an active file object
176            .and_then(|x| x.active_file().cloned());
177
178        let file = match file {
179            // we were previously blocked, so re-use the file from the previous syscall invocation
180            Some(x) => x,
181            // get the file from the descriptor table, or return early if it doesn't exist
182            None => {
183                let desc_table = ctx.objs.thread.descriptor_table_borrow(ctx.objs.host);
184                match Self::get_descriptor(&desc_table, fd)?.file() {
185                    CompatFile::New(file) => file.clone(),
186                    // if it's a legacy file, use the C syscall handler instead
187                    CompatFile::Legacy(_) => {
188                        drop(desc_table);
189                        return Self::legacy_syscall(c::syscallhandler_preadv2, ctx);
190                    }
191                }
192            }
193        };
194
195        // readv(2): "Unlike preadv() and pwritev(), if the offset argument is -1, then the current
196        // file offset is used and updated."
197        let offset = (offset != -1).then_some(offset);
198
199        // if the offset is set, make sure it's not negative
200        if let Some(offset) = offset
201            && offset < 0
202        {
203            return Err(Errno::EINVAL.into());
204        }
205
206        let iov_count = iov_count.try_into().or(Err(Errno::EINVAL))?;
207
208        let iovs = {
209            let mem = ctx.objs.process.memory_borrow_mut();
210            io::read_iovecs(&mem, iov_ptr, iov_count)?
211        };
212        assert_eq!(iovs.len(), iov_count);
213
214        let mut result = Self::readv_helper(ctx, file.inner_file(), &iovs, offset, flags);
215
216        // if the syscall will block, keep the file open until the syscall restarts
217        if let Some(err) = result.as_mut().err()
218            && let Some(cond) = err.blocked_condition()
219        {
220            cond.set_active_file(file);
221        }
222
223        let bytes_read = result?;
224        Ok(bytes_read)
225    }
226
227    pub fn readv_helper(
228        ctx: &mut SyscallContext,
229        file: &File,
230        iovs: &[IoVec],
231        offset: Option<libc::off_t>,
232        flags: std::ffi::c_int,
233    ) -> Result<libc::ssize_t, SyscallError> {
234        let mut mem = ctx.objs.process.memory_borrow_mut();
235
236        // if it's a socket, call recvmsg_helper() instead
237        if let File::Socket(socket) = file {
238            if offset.is_some() {
239                // sockets don't support offsets
240                return Err(Errno::ESPIPE.into());
241            }
242
243            // experimentally, it seems that read() calls on sockets with 0-length buffers will
244            // always return 0, even if there would otherwise be an EWOULDBOCK from a recv() call
245            // (see the `test_zero_len_buf_read_and_recv` and `test_zero_len_msg_read_and_recv`
246            // send/recv tests for examples)
247            if iovs.iter().map(|x| x.len).sum::<usize>() == 0 {
248                return Ok(0);
249            }
250
251            let args = RecvmsgArgs {
252                iovs,
253                control_ptr: ForeignArrayPtr::new(ForeignPtr::null(), 0),
254                flags: 0,
255            };
256
257            // call the socket's recvmsg(), and run any resulting events
258            let RecvmsgReturn { return_val, .. } =
259                CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
260                    Socket::recvmsg(socket, args, &mut mem, cb_queue)
261                })?;
262
263            return Ok(return_val);
264        }
265
266        let file_status = file.borrow().status();
267
268        let result =
269            // call the file's read(), and run any resulting events
270            CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
271                file.borrow_mut().readv(
272                    iovs,
273                    offset,
274                    flags,
275                    &mut mem,
276                    cb_queue,
277                )
278            });
279
280        // if the syscall would block and it's a blocking descriptor
281        if result == Err(Errno::EWOULDBLOCK.into()) && !file_status.contains(FileStatus::O_NONBLOCK)
282        {
283            // TODO: should we block on the READABLE, HUP, and RDHUP states?
284            // https://github.com/shadow/shadow/issues/2181
285            let wait_for = FileState::READABLE;
286
287            // check that we're not already in the state that we're going to wait for
288            debug_assert!(!file.borrow().state().intersects(wait_for));
289
290            return Err(SyscallError::new_blocked_on_file(
291                file.clone(),
292                wait_for,
293                file.borrow().supports_sa_restart(),
294            ));
295        }
296
297        result
298    }
299
300    log_syscall!(
301        writev,
302        /* rv */ libc::ssize_t,
303        /* fd */ std::ffi::c_int,
304        /* iov */ *const libc::iovec,
305        /* iovcnt */ std::ffi::c_int,
306    );
307    pub fn writev(
308        ctx: &mut SyscallContext,
309        fd: std::ffi::c_int,
310        iov_ptr: ForeignPtr<libc::iovec>,
311        iov_count: std::ffi::c_int,
312    ) -> Result<libc::ssize_t, SyscallError> {
313        // if we were previously blocked, get the active file from the last syscall handler
314        // invocation since it may no longer exist in the descriptor table
315        let file = ctx
316            .objs
317            .thread
318            .syscall_condition()
319            // if this was for a C descriptor, then there won't be an active file object
320            .and_then(|x| x.active_file().cloned());
321
322        let file = match file {
323            // we were previously blocked, so re-use the file from the previous syscall invocation
324            Some(x) => x,
325            // get the file from the descriptor table, or return early if it doesn't exist
326            None => {
327                let desc_table = ctx.objs.thread.descriptor_table_borrow(ctx.objs.host);
328                match Self::get_descriptor(&desc_table, fd)?.file() {
329                    CompatFile::New(file) => file.clone(),
330                    // if it's a legacy file, use the C syscall handler instead
331                    CompatFile::Legacy(_) => {
332                        drop(desc_table);
333                        return Self::legacy_syscall(c::syscallhandler_writev, ctx);
334                    }
335                }
336            }
337        };
338
339        let iov_count = iov_count.try_into().or(Err(Errno::EINVAL))?;
340
341        let iovs = {
342            let mem = ctx.objs.process.memory_borrow_mut();
343            io::read_iovecs(&mem, iov_ptr, iov_count)?
344        };
345        assert_eq!(iovs.len(), iov_count);
346
347        let mut result = Self::writev_helper(ctx, file.inner_file(), &iovs, None, 0);
348
349        // if the syscall will block, keep the file open until the syscall restarts
350        if let Some(err) = result.as_mut().err()
351            && let Some(cond) = err.blocked_condition()
352        {
353            cond.set_active_file(file);
354        }
355
356        let bytes_written = result?;
357        Ok(bytes_written)
358    }
359
360    log_syscall!(
361        pwritev,
362        /* rv */ libc::ssize_t,
363        /* fd */ std::ffi::c_int,
364        /* iov */ *const libc::iovec,
365        /* iovcnt */ std::ffi::c_int,
366        /* pos_l */ libc::c_ulong,
367        /* pos_h */ libc::c_ulong,
368    );
369    pub fn pwritev(
370        ctx: &mut SyscallContext,
371        fd: std::ffi::c_int,
372        iov_ptr: ForeignPtr<libc::iovec>,
373        iov_count: std::ffi::c_int,
374        offset_l: libc::c_ulong,
375        _offset_h: libc::c_ulong,
376    ) -> Result<libc::ssize_t, SyscallError> {
377        // on Linux x86-64, an `unsigned long` is 64 bits, so we can ignore `offset_h`
378        static_assertions::assert_eq_size!(libc::c_ulong, libc::off_t);
379        let offset = offset_l as libc::off_t;
380
381        // if we were previously blocked, get the active file from the last syscall handler
382        // invocation since it may no longer exist in the descriptor table
383        let file = ctx
384            .objs
385            .thread
386            .syscall_condition()
387            // if this was for a C descriptor, then there won't be an active file object
388            .and_then(|x| x.active_file().cloned());
389
390        let file = match file {
391            // we were previously blocked, so re-use the file from the previous syscall invocation
392            Some(x) => x,
393            // get the file from the descriptor table, or return early if it doesn't exist
394            None => {
395                let desc_table = ctx.objs.thread.descriptor_table_borrow(ctx.objs.host);
396                match Self::get_descriptor(&desc_table, fd)?.file() {
397                    CompatFile::New(file) => file.clone(),
398                    // if it's a legacy file, use the C syscall handler instead
399                    CompatFile::Legacy(_) => {
400                        drop(desc_table);
401                        return Self::legacy_syscall(c::syscallhandler_pwritev, ctx);
402                    }
403                }
404            }
405        };
406
407        // make sure the offset is not negative
408        if offset < 0 {
409            return Err(Errno::EINVAL.into());
410        }
411
412        let iov_count = iov_count.try_into().or(Err(Errno::EINVAL))?;
413
414        let iovs = {
415            let mem = ctx.objs.process.memory_borrow_mut();
416            io::read_iovecs(&mem, iov_ptr, iov_count)?
417        };
418        assert_eq!(iovs.len(), iov_count);
419
420        let mut result = Self::writev_helper(ctx, file.inner_file(), &iovs, Some(offset), 0);
421
422        // if the syscall will block, keep the file open until the syscall restarts
423        if let Some(err) = result.as_mut().err()
424            && let Some(cond) = err.blocked_condition()
425        {
426            cond.set_active_file(file);
427        }
428
429        let bytes_written = result?;
430        Ok(bytes_written)
431    }
432
433    log_syscall!(
434        pwritev2,
435        /* rv */ libc::ssize_t,
436        /* fd */ std::ffi::c_int,
437        /* iov */ *const libc::iovec,
438        /* iovcnt */ std::ffi::c_int,
439        /* pos_l */ libc::c_ulong,
440        /* pos_h */ libc::c_ulong,
441        /* flags */ std::ffi::c_int,
442    );
443    pub fn pwritev2(
444        ctx: &mut SyscallContext,
445        fd: std::ffi::c_int,
446        iov_ptr: ForeignPtr<libc::iovec>,
447        iov_count: std::ffi::c_int,
448        offset_l: libc::c_ulong,
449        _offset_h: libc::c_ulong,
450        flags: std::ffi::c_int,
451    ) -> Result<libc::ssize_t, SyscallError> {
452        // on Linux x86-64, an `unsigned long` is 64 bits, so we can ignore `offset_h`
453        static_assertions::assert_eq_size!(libc::c_ulong, libc::off_t);
454        let offset = offset_l as libc::off_t;
455
456        // if we were previously blocked, get the active file from the last syscall handler
457        // invocation since it may no longer exist in the descriptor table
458        let file = ctx
459            .objs
460            .thread
461            .syscall_condition()
462            // if this was for a C descriptor, then there won't be an active file object
463            .and_then(|x| x.active_file().cloned());
464
465        let file = match file {
466            // we were previously blocked, so re-use the file from the previous syscall invocation
467            Some(x) => x,
468            // get the file from the descriptor table, or return early if it doesn't exist
469            None => {
470                let desc_table = ctx.objs.thread.descriptor_table_borrow(ctx.objs.host);
471                match Self::get_descriptor(&desc_table, fd)?.file() {
472                    CompatFile::New(file) => file.clone(),
473                    // if it's a legacy file, use the C syscall handler instead
474                    CompatFile::Legacy(_) => {
475                        drop(desc_table);
476                        return Self::legacy_syscall(c::syscallhandler_pwritev2, ctx);
477                    }
478                }
479            }
480        };
481
482        // readv(2): "Unlike preadv() and pwritev(), if the offset argument is -1, then the current
483        // file offset is used and updated."
484        let offset = (offset != -1).then_some(offset);
485
486        // if the offset is set, make sure it's not negative
487        if let Some(offset) = offset
488            && offset < 0
489        {
490            return Err(Errno::EINVAL.into());
491        }
492
493        let iov_count = iov_count.try_into().or(Err(Errno::EINVAL))?;
494
495        let iovs = {
496            let mem = ctx.objs.process.memory_borrow_mut();
497            io::read_iovecs(&mem, iov_ptr, iov_count)?
498        };
499        assert_eq!(iovs.len(), iov_count);
500
501        let mut result = Self::writev_helper(ctx, file.inner_file(), &iovs, offset, flags);
502
503        // if the syscall will block, keep the file open until the syscall restarts
504        if let Some(err) = result.as_mut().err()
505            && let Some(cond) = err.blocked_condition()
506        {
507            cond.set_active_file(file);
508        }
509
510        let bytes_written = result?;
511        Ok(bytes_written)
512    }
513
514    pub fn writev_helper(
515        ctx: &mut SyscallContext,
516        file: &File,
517        iovs: &[IoVec],
518        offset: Option<libc::off_t>,
519        flags: std::ffi::c_int,
520    ) -> Result<libc::ssize_t, SyscallError> {
521        let mut mem = ctx.objs.process.memory_borrow_mut();
522        let mut rng = ctx.objs.host.random_mut();
523        let net_ns = ctx.objs.host.network_namespace_borrow();
524
525        // if it's a socket, call sendmsg_helper() instead
526        if let File::Socket(socket) = file {
527            if offset.is_some() {
528                // sockets don't support offsets
529                return Err(Errno::ESPIPE.into());
530            }
531
532            let args = SendmsgArgs {
533                addr: None,
534                iovs,
535                control_ptr: ForeignArrayPtr::new(ForeignPtr::null(), 0),
536                flags: 0,
537            };
538
539            // call the socket's sendmsg(), and run any resulting events
540            let bytes_written = CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
541                Socket::sendmsg(socket, args, &mut mem, &net_ns, &mut *rng, cb_queue)
542            })?;
543
544            return Ok(bytes_written);
545        }
546
547        let file_status = file.borrow().status();
548
549        let result =
550            // call the file's write(), and run any resulting events
551            CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
552                file.borrow_mut().writev(
553                    iovs,
554                    offset,
555                    flags,
556                    &mut mem,
557                    cb_queue,
558                )
559            });
560
561        // if the syscall would block and it's a blocking descriptor
562        if result == Err(Errno::EWOULDBLOCK.into()) && !file_status.contains(FileStatus::O_NONBLOCK)
563        {
564            // TODO: should we block on the WRITABLE and HUP states?
565            // https://github.com/shadow/shadow/issues/2181
566            let wait_for = FileState::WRITABLE;
567
568            // check that we're not already in the state that we're going to wait for
569            debug_assert!(!file.borrow().state().intersects(wait_for));
570
571            return Err(SyscallError::new_blocked_on_file(
572                file.clone(),
573                wait_for,
574                file.borrow().supports_sa_restart(),
575            ));
576        }
577
578        result
579    }
580}