shadow_rs/host/syscall/handler/
fcntl.rs1use std::ops::Range;
2
3use linux_api::errno::Errno;
4use linux_api::fcntl::{DescriptorFlags, FcntlCommand, FlockType, FlockWhence, OFlag, flock};
5use linux_api::unistd::LSeekWhence;
6use log::debug;
7use shadow_shim_helper_rs::syscall_types::ForeignPtr;
8
9use crate::cshadow;
10use crate::host::descriptor::{CompatFile, File, FileMode, FileStatus};
11use crate::host::fcntl_lock_table::{self, FileId, LockError, LockOwner};
12use crate::host::process::Process;
13use crate::host::syscall::handler::{SyscallContext, SyscallHandler};
14use crate::host::syscall::type_formatting::SyscallNonDeterministicArg;
15use crate::host::syscall::types::SyscallError;
16
17#[derive(Debug, Clone)]
18struct CommonFlockParams {
19 file_id: fcntl_lock_table::FileId,
20 range: Range<usize>,
23 owner: LockOwner,
24 access: FlockType,
25}
26
27fn flock_params(
29 file: &CompatFile,
30 process: &Process,
31 flock: &linux_api::fcntl::flock,
32) -> Result<CommonFlockParams, SyscallError> {
33 let requested_access = FlockType::try_from(flock.l_type).map_err(|_| Errno::EINVAL)?;
34 let access_compat = match requested_access {
35 FlockType::F_RDLCK => file.mode().contains(FileMode::READ),
36 FlockType::F_WRLCK => file.mode().contains(FileMode::WRITE),
37 FlockType::F_UNLCK => true,
38 };
39 if !access_compat {
40 log::debug!(
41 "requested access {requested_access:?} incompatible with file mode {:?}",
42 file.mode()
43 );
44 return Err(Errno::EBADF.into());
45 }
46 let file_id = FileId::from(&file.stat()?);
47 let whence = FlockWhence::try_from(flock.l_whence).map_err(|_| Errno::EINVAL)?;
48 let origin = match whence {
49 FlockWhence::SEEK_SET => 0,
50 FlockWhence::SEEK_CUR => file.lseek(0, LSeekWhence::SEEK_CUR)?,
51 FlockWhence::SEEK_END => file.stat()?.lst_size,
52 };
53 let Some(signed_start) = origin.checked_add(flock.l_start) else {
54 return Err(Errno::EINVAL.into());
55 };
56 let (start, open_end) = if flock.l_len == 0 {
57 let start = usize::try_from(signed_start).map_err(|_| Errno::EINVAL)?;
61 (start, FLOCK_LENGTH_0_OPEN_END)
62 } else if flock.l_len < 0 {
63 let start = signed_start
66 .checked_add(flock.l_len)
67 .ok_or(Errno::EINVAL)
68 .and_then(|x| usize::try_from(x).map_err(|_| Errno::EINVAL))?;
69 let open_end = usize::try_from(signed_start).map_err(|_| Errno::EINVAL)?;
70 (start, open_end)
71 } else {
72 let start = usize::try_from(signed_start).map_err(|_| Errno::EINVAL)?;
73 let len =
75 usize::try_from(flock.l_len).expect("Negative length should have been caught earlier");
76 let Some(open_end) = start.checked_add(len) else {
77 return Err(Errno::EOVERFLOW.into());
78 };
79 if open_end > FLOCK_MAX_VISIBLE_RANGE_OPEN_END {
80 return Err(Errno::EOVERFLOW.into());
81 }
82 (start, open_end)
83 };
84 if open_end <= start {
85 return Err(Errno::EINVAL.into());
86 }
87 let requested_owner = LockOwner::Process(process.id());
88 Ok(CommonFlockParams {
89 file_id,
90 range: start..open_end,
91 owner: requested_owner,
92 access: requested_access,
93 })
94}
95
96const FLOCK_MAX_VISIBLE_RANGE_OPEN_END: usize = (i64::MAX as usize) + 1;
102const FLOCK_LENGTH_0_OPEN_END: usize = usize::MAX;
113const _: () = const {
114 assert!(FLOCK_LENGTH_0_OPEN_END > FLOCK_MAX_VISIBLE_RANGE_OPEN_END);
115};
116
117impl SyscallHandler {
118 log_syscall!(
119 fcntl,
120 std::ffi::c_long,
121 std::ffi::c_uint,
122 FcntlCommand,
123 SyscallNonDeterministicArg<std::ffi::c_ulong>,
124 );
125 pub fn fcntl(
126 ctx: &mut SyscallContext,
127 fd: std::ffi::c_uint,
128 cmd: std::ffi::c_uint,
129 arg: std::ffi::c_ulong,
130 ) -> Result<std::ffi::c_long, SyscallError> {
131 let legacy_syscall_fn =
136 |ctx: &mut SyscallContext| Self::legacy_syscall(cshadow::syscallhandler_fcntl, ctx);
137
138 let mut desc_table = ctx.objs.thread.descriptor_table_borrow_mut(ctx.objs.host);
140 let desc = Self::get_descriptor_mut(&mut desc_table, fd)?;
141
142 let Ok(cmd) = FcntlCommand::try_from(cmd) else {
143 debug!("Bad fcntl command: {cmd}");
144 return Err(Errno::EINVAL.into());
145 };
146
147 Ok(match cmd {
148 FcntlCommand::F_SETLK | FcntlCommand::F_SETLKW => {
149 let flock_ptr = ForeignPtr::<()>::from(arg).cast::<flock>();
150 let flock = ctx.objs.process.memory_borrow().read(flock_ptr)?;
151 let file = desc.file();
152 let params = flock_params(file, ctx.objs.process, &flock)?;
153 let mut lock_table = ctx.objs.host.fcntl_lock_table_borrow_mut();
154 let res = lock_table.set_lock(
155 params.file_id,
156 params.range.clone(),
157 ¶ms.owner,
158 params.access,
159 );
160 log::trace!("setlk[w] {params:?} -> {res:?}");
161 if let Err(e) = res {
162 match e {
163 LockError::ConflictingLock => {
164 let errno = Errno::EACCES;
165 if cmd == FcntlCommand::F_SETLKW {
166 log::warn!(
169 "SETLKW({params:?}) should block, but blocking is unimplemented. Returning {errno:?}"
170 );
171 }
172 return Err(errno.into());
173 }
174 }
175 }
176 0i64
177 }
178 FcntlCommand::F_GETLK => {
179 let flock_ptr = ForeignPtr::<()>::from(arg).cast::<flock>();
180 let flock = ctx.objs.process.memory_borrow().read(flock_ptr)?;
181 let file = desc.file();
182 let params = flock_params(file, ctx.objs.process, &flock)?;
183 let lock_table = ctx.objs.host.fcntl_lock_table_borrow();
184 let out_flock = match lock_table.get_coalesced_conflicting_lock(
185 params.file_id,
186 params.range.clone(),
187 ¶ms.owner,
188 params.access,
189 ) {
190 Some((conflict_owner, conflict_range, conflict_access)) => {
191 let start = i64::try_from(conflict_range.start).unwrap_or_else(|_err| {
192 panic!("Current lock range {conflict_range:?} start is out of range")
193 });
194 let length = i64::try_from(conflict_range.len()).unwrap_or(0);
200 flock {
201 l_type: conflict_access.into(),
202 l_whence: FlockWhence::SEEK_SET.into(),
203 l_start: start,
204 l_len: length,
205 l_pid: match conflict_owner {
206 LockOwner::Process(process_id) => process_id.into(),
207 },
208 }
209 }
210 None => flock {
211 l_type: FlockType::F_UNLCK.into(),
212 ..flock
213 },
214 };
215 ctx.objs
216 .process
217 .memory_borrow_mut()
218 .write(flock_ptr, &out_flock)?;
219 log::trace!("getlk {params:?} -> {out_flock:?}");
220 0i64
221 }
222 FcntlCommand::F_OFD_SETLK | FcntlCommand::F_OFD_SETLKW | FcntlCommand::F_OFD_GETLK => {
223 match desc.file() {
224 CompatFile::New(_) => {
225 warn_once_then_debug!("fcntl({cmd:?}) unimplemented for {:?}", desc.file());
226 return Err(Errno::ENOSYS.into());
227 }
228 CompatFile::Legacy(_) => {
229 warn_once_then_debug!(
230 "Using fcntl({cmd:?}) implementation that assumes no lock contention. \
231 See https://github.com/shadow/shadow/issues/2258"
232 );
233 drop(desc_table);
234 return legacy_syscall_fn(ctx);
235 }
236 };
237 }
238 FcntlCommand::F_GETFL => {
239 let file = desc.file();
240 let flags = file.status().as_o_flags() | file.mode().as_o_flags();
242 flags.bits().into()
243 }
244 FcntlCommand::F_SETFL => {
245 let status = i32::try_from(arg).or(Err(Errno::EINVAL))?;
246 let mut status = OFlag::from_bits(status).ok_or(Errno::EINVAL)?;
247
248 status.remove(OFlag::O_RDONLY | OFlag::O_WRONLY | OFlag::O_RDWR | OFlag::O_PATH);
250 status.remove(
252 OFlag::O_CLOEXEC
253 | OFlag::O_CREAT
254 | OFlag::O_DIRECTORY
255 | OFlag::O_EXCL
256 | OFlag::O_NOCTTY
257 | OFlag::O_NOFOLLOW
258 | OFlag::O_TMPFILE
259 | OFlag::O_TRUNC,
260 );
261
262 let old_flags = desc.file().status().as_o_flags();
263
264 let update_mask = OFlag::O_APPEND
267 | OFlag::O_ASYNC
268 | OFlag::O_DIRECT
269 | OFlag::O_NOATIME
270 | OFlag::O_NONBLOCK;
271
272 let status = (old_flags & !update_mask) | (status & update_mask);
287
288 let (status, remaining) = FileStatus::from_o_flags(status);
289
290 if !remaining.is_empty() {
292 return Err(Errno::EINVAL.into());
293 }
294
295 desc.file().set_status(status);
296 0
297 }
298 FcntlCommand::F_GETFD => desc.flags().bits().into(),
299 FcntlCommand::F_SETFD => {
300 let flags = i32::try_from(arg).or(Err(Errno::EINVAL))?;
301 let flags = DescriptorFlags::from_bits(flags).ok_or(Errno::EINVAL)?;
302 desc.set_flags(flags);
303 0
304 }
305 FcntlCommand::F_DUPFD => {
306 let min_fd = arg.try_into().or(Err(Errno::EINVAL))?;
307
308 let new_desc = desc.dup(DescriptorFlags::empty());
309 let new_fd = desc_table
310 .register_descriptor_with_min_fd(new_desc, min_fd)
311 .or(Err(Errno::EINVAL))?;
312 new_fd.into()
313 }
314 FcntlCommand::F_DUPFD_CLOEXEC => {
315 let min_fd = arg.try_into().or(Err(Errno::EINVAL))?;
316
317 let new_desc = desc.dup(DescriptorFlags::FD_CLOEXEC);
318 let new_fd = desc_table
319 .register_descriptor_with_min_fd(new_desc, min_fd)
320 .or(Err(Errno::EINVAL))?;
321 new_fd.into()
322 }
323 FcntlCommand::F_GETPIPE_SZ => {
324 let file = match desc.file() {
325 CompatFile::New(d) => d,
326 CompatFile::Legacy(_) => {
328 return legacy_syscall_fn(ctx);
329 }
330 };
331
332 if let File::Pipe(pipe) = file.inner_file() {
333 pipe.borrow().max_size().try_into().unwrap()
334 } else {
335 return Err(Errno::EINVAL.into());
336 }
337 }
338 cmd => {
339 warn_once_then_debug!("Unhandled fcntl command: {cmd:?}");
340 return Err(Errno::EINVAL.into());
341 }
342 })
343 }
344}