Skip to main content

wireshift_fallback/ops/
open.rs

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}