1use std::collections::HashMap;
2use std::path::PathBuf;
3
4use rustix::fs::{openat, Mode, OFlags, CWD};
5use wireshift_core::op::CompletionPayload;
6use wireshift_core::{Error, Result};
7
8pub(crate) fn execute_openat(
9 dir: Option<std::fs::File>,
10 path: PathBuf,
11 flags: u32,
12 read: bool,
13 write: bool,
14 create: bool,
15 truncate: bool,
16) -> Result<CompletionPayload> {
17 Ok(CompletionPayload::File(open_file_at(
18 dir, path, flags, read, write, create, truncate,
19 )?))
20}
21
22#[allow(clippy::too_many_arguments)]
23pub(crate) fn execute_openat_direct(
24 dir: Option<std::fs::File>,
25 path: PathBuf,
26 flags: u32,
27 read: bool,
28 write: bool,
29 create: bool,
30 truncate: bool,
31 slot: u32,
32 fixed_files: &mut HashMap<u32, std::fs::File>,
33) -> Result<CompletionPayload> {
34 let file = open_file_at(dir, path, flags, read, write, create, truncate)?;
35 if fixed_files.insert(slot, file).is_some() {
36 return Err(Error::completion(
37 format!("direct descriptor slot {slot} was reused before close"),
38 "ensure each chained direct descriptor slot is closed exactly once",
39 ));
40 }
41 Ok(CompletionPayload::Unit)
42}
43
44fn open_file_at(
45 dir: Option<std::fs::File>,
46 path: PathBuf,
47 flags: u32,
48 read: bool,
49 write: bool,
50 create: bool,
51 truncate: bool,
52) -> Result<std::fs::File> {
53 if path.as_os_str().is_empty() {
54 return Err(Error::validation(
55 "openat path must not be empty",
56 "provide a non-empty filesystem path",
57 ));
58 }
59 if dir.is_some() && path.is_absolute() {
60 return Err(Error::validation(
61 "openat path must be relative when a directory handle is provided",
62 "pass a relative path or omit the directory file descriptor",
63 ));
64 }
65 let oflags = open_flags(flags, read, write, create, truncate)?;
66 let create_mode = if create {
67 Mode::from_raw_mode(0o600)
68 } else {
69 Mode::empty()
70 };
71 let file = if let Some(dir) = dir {
72 std::fs::File::from(openat(&dir, &path, oflags, create_mode).map_err(|error| {
73 Error::io(
74 "openat failed",
75 error.into(),
76 "ensure the relative path exists under the provided directory and permissions are correct",
77 )
78 })?)
79 } else {
80 std::fs::File::from(openat(CWD, &path, oflags, create_mode).map_err(|error| {
81 Error::io(
82 "openat failed",
83 error.into(),
84 "ensure the path exists and permissions are correct",
85 )
86 })?)
87 };
88 apply_open_hints(&file, flags)?;
89 Ok(file)
90}
91
92fn open_flags(
93 open_flags: u32,
94 read: bool,
95 write: bool,
96 create: bool,
97 truncate: bool,
98) -> Result<OFlags> {
99 let mut flags = match (read, write) {
100 (true, true) => OFlags::RDWR,
101 (true, false) => OFlags::RDONLY,
102 (false, true) => OFlags::WRONLY,
103 (false, false) => {
104 return Err(Error::validation(
105 "openat must request read and/or write access",
106 "enable read_only or create a read-write open request",
107 ));
108 }
109 };
110 if create {
111 flags |= OFlags::CREATE;
112 }
113 if truncate {
114 flags |= OFlags::TRUNC;
115 }
116 flags |= OFlags::from_bits_retain(open_flags & !sequential_hint_bit());
117 flags |= OFlags::CLOEXEC;
118 Ok(flags)
119}
120
121fn apply_open_hints(file: &std::fs::File, flags: u32) -> Result<()> {
122 if flags & sequential_hint_bit() == 0 {
123 return Ok(());
124 }
125 rustix::fs::fadvise(file, 0, None, rustix::fs::Advice::Sequential).map_err(|error| {
126 Error::io(
127 "posix_fadvise(SEQUENTIAL) failed after openat",
128 std::io::Error::from(error),
129 "remove OpenFlags::SEQUENTIAL or ensure the target filesystem supports fadvise",
130 )
131 })?;
132 Ok(())
133}
134
135const fn sequential_hint_bit() -> u32 {
136 1 << 31
137}