shadow_rs/host/syscall/handler/
fcntl.rs

1use linux_api::errno::Errno;
2use linux_api::fcntl::{DescriptorFlags, FcntlCommand, OFlag};
3use log::debug;
4
5use crate::cshadow;
6use crate::host::descriptor::{CompatFile, File, FileStatus};
7use crate::host::syscall::handler::{SyscallContext, SyscallHandler};
8use crate::host::syscall::type_formatting::SyscallNonDeterministicArg;
9use crate::host::syscall::types::SyscallError;
10
11impl SyscallHandler {
12    log_syscall!(
13        fcntl,
14        /* rv */ std::ffi::c_long,
15        /* fd */ std::ffi::c_uint,
16        /* cmd */ std::ffi::c_uint,
17        /* arg */ SyscallNonDeterministicArg<std::ffi::c_ulong>,
18    );
19    pub fn fcntl(
20        ctx: &mut SyscallContext,
21        fd: std::ffi::c_uint,
22        cmd: std::ffi::c_uint,
23        arg: std::ffi::c_ulong,
24    ) -> Result<std::ffi::c_long, SyscallError> {
25        // NOTE: this function should *not* run the C syscall handler if the cmd modifies the
26        // descriptor
27
28        // helper function to run the C syscall handler
29        let legacy_syscall_fn =
30            |ctx: &mut SyscallContext| Self::legacy_syscall(cshadow::syscallhandler_fcntl, ctx);
31
32        // get the descriptor, or return early if it doesn't exist
33        let mut desc_table = ctx.objs.thread.descriptor_table_borrow_mut(ctx.objs.host);
34        let desc = Self::get_descriptor_mut(&mut desc_table, fd)?;
35
36        let Ok(cmd) = FcntlCommand::try_from(cmd) else {
37            debug!("Bad fcntl command: {cmd}");
38            return Err(Errno::EINVAL.into());
39        };
40
41        Ok(match cmd {
42            FcntlCommand::F_SETLK
43            | FcntlCommand::F_SETLKW
44            | FcntlCommand::F_OFD_SETLKW
45            | FcntlCommand::F_GETLK
46            | FcntlCommand::F_OFD_GETLK => {
47                match desc.file() {
48                    CompatFile::New(_) => {
49                        warn_once_then_debug!("fcntl({cmd:?}) unimplemented for {:?}", desc.file());
50                        return Err(Errno::ENOSYS.into());
51                    }
52                    CompatFile::Legacy(_) => {
53                        warn_once_then_debug!(
54                            "Using fcntl({cmd:?}) implementation that assumes no lock contention. \
55                            See https://github.com/shadow/shadow/issues/2258"
56                        );
57                        drop(desc_table);
58                        return legacy_syscall_fn(ctx);
59                    }
60                };
61            }
62            FcntlCommand::F_GETFL => {
63                let file = match desc.file() {
64                    CompatFile::New(d) => d,
65                    // if it's a legacy file, use the C syscall handler instead
66                    CompatFile::Legacy(_) => {
67                        drop(desc_table);
68                        return legacy_syscall_fn(ctx);
69                    }
70                };
71
72                let file = file.inner_file().borrow();
73                // combine the file status and access mode flags
74                let flags = file.status().as_o_flags() | file.mode().as_o_flags();
75                flags.bits().into()
76            }
77            FcntlCommand::F_SETFL => {
78                let file = match desc.file() {
79                    CompatFile::New(d) => d,
80                    // if it's a legacy file, use the C syscall handler instead
81                    CompatFile::Legacy(_) => {
82                        drop(desc_table);
83                        return legacy_syscall_fn(ctx);
84                    }
85                };
86
87                let status = i32::try_from(arg).or(Err(Errno::EINVAL))?;
88                let mut status = OFlag::from_bits(status).ok_or(Errno::EINVAL)?;
89                // remove access mode flags
90                status.remove(OFlag::O_RDONLY | OFlag::O_WRONLY | OFlag::O_RDWR | OFlag::O_PATH);
91                // remove file creation flags
92                status.remove(
93                    OFlag::O_CLOEXEC
94                        | OFlag::O_CREAT
95                        | OFlag::O_DIRECTORY
96                        | OFlag::O_EXCL
97                        | OFlag::O_NOCTTY
98                        | OFlag::O_NOFOLLOW
99                        | OFlag::O_TMPFILE
100                        | OFlag::O_TRUNC,
101                );
102
103                let mut file = file.inner_file().borrow_mut();
104                let old_flags = file.status().as_o_flags();
105
106                // fcntl(2): "On Linux, this command can change only the O_APPEND, O_ASYNC, O_DIRECT,
107                // O_NOATIME, and O_NONBLOCK flags"
108                let update_mask = OFlag::O_APPEND
109                    | OFlag::O_ASYNC
110                    | OFlag::O_DIRECT
111                    | OFlag::O_NOATIME
112                    | OFlag::O_NONBLOCK;
113
114                // The proper way for the process to update its flags is to:
115                //   int flags = fcntl(fd, F_GETFL);
116                //   flags = flags | O_NONBLOCK; // add O_NONBLOCK
117                //   fcntl(fd, F_SETFL, flags);
118                // So if there are flags that we can't update, we should assume they are leftover
119                // from the F_GETFL and we shouldn't return an error. This includes `O_DSYNC` and
120                // `O_SYNC`, which fcntl(2) says:
121                //   "It is not possible to use F_SETFL to change the state of the O_DSYNC and O_SYNC
122                //   flags. Attempts to change the state of these flags are silently ignored."
123                // In other words, the following code should always be valid:
124                //   int flags = fcntl(fd, F_GETFL);
125                //   fcntl(fd, F_SETFL, flags); // set to the current existing flags
126
127                // keep the old flags that we can't change, and use the new flags that we can change
128                let status = (old_flags & !update_mask) | (status & update_mask);
129
130                let (status, remaining) = FileStatus::from_o_flags(status);
131
132                // check if there are flags that we don't support but Linux does
133                if !remaining.is_empty() {
134                    return Err(Errno::EINVAL.into());
135                }
136
137                file.set_status(status);
138                0
139            }
140            FcntlCommand::F_GETFD => desc.flags().bits().into(),
141            FcntlCommand::F_SETFD => {
142                let flags = i32::try_from(arg).or(Err(Errno::EINVAL))?;
143                let flags = DescriptorFlags::from_bits(flags).ok_or(Errno::EINVAL)?;
144                desc.set_flags(flags);
145                0
146            }
147            FcntlCommand::F_DUPFD => {
148                let min_fd = arg.try_into().or(Err(Errno::EINVAL))?;
149
150                let new_desc = desc.dup(DescriptorFlags::empty());
151                let new_fd = desc_table
152                    .register_descriptor_with_min_fd(new_desc, min_fd)
153                    .or(Err(Errno::EINVAL))?;
154                new_fd.into()
155            }
156            FcntlCommand::F_DUPFD_CLOEXEC => {
157                let min_fd = arg.try_into().or(Err(Errno::EINVAL))?;
158
159                let new_desc = desc.dup(DescriptorFlags::FD_CLOEXEC);
160                let new_fd = desc_table
161                    .register_descriptor_with_min_fd(new_desc, min_fd)
162                    .or(Err(Errno::EINVAL))?;
163                new_fd.into()
164            }
165            FcntlCommand::F_GETPIPE_SZ => {
166                let file = match desc.file() {
167                    CompatFile::New(d) => d,
168                    // if it's a legacy file, use the C syscall handler instead
169                    CompatFile::Legacy(_) => {
170                        return legacy_syscall_fn(ctx);
171                    }
172                };
173
174                if let File::Pipe(pipe) = file.inner_file() {
175                    pipe.borrow().max_size().try_into().unwrap()
176                } else {
177                    return Err(Errno::EINVAL.into());
178                }
179            }
180            cmd => {
181                warn_once_then_debug!("Unhandled fcntl command: {cmd:?}");
182                return Err(Errno::EINVAL.into());
183            }
184        })
185    }
186}