use super::super::super::fs::compute_oflags;
#[cfg(racy_asserts)]
use crate::fs::is_same_file;
use crate::fs::{errors, manually, OpenOptions};
use io_lifetimes::FromFd;
use rustix::fs::{openat2, Mode, OFlags, RawMode, ResolveFlags};
use rustix::path::Arg;
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::Relaxed;
use std::{fs, io};
pub(crate) fn open_impl(
start: &fs::File,
path: &Path,
options: &OpenOptions,
) -> io::Result<fs::File> {
let result = open_beneath(start, path, options);
if let Err(err) = &result {
if Some(rustix::io::Errno::NOSYS.raw_os_error()) == err.raw_os_error() {
return manually::open(start, path, options);
}
}
result
}
pub(crate) fn open_beneath(
start: &fs::File,
path: &Path,
options: &OpenOptions,
) -> io::Result<fs::File> {
static INVALID: AtomicBool = AtomicBool::new(false);
if INVALID.load(Relaxed) {
return Err(rustix::io::Errno::NOSYS.into());
}
let oflags = compute_oflags(options)?;
let mode = if oflags.contains(OFlags::CREATE) || oflags.contains(OFlags::TMPFILE) {
Mode::from_bits((options.ext.mode & 0o7777) as RawMode).unwrap()
} else {
Mode::empty()
};
#[cfg(target_os = "android")]
{
static CHECKED: AtomicBool = AtomicBool::new(false);
if !CHECKED.load(Relaxed) {
if !openat2_supported() {
INVALID.store(true, Relaxed);
return Err(rustix::io::Errno::NOSYS.into());
}
CHECKED.store(true, Relaxed);
}
}
path.into_with_c_str(|path_c_str| {
for _ in 0..4 {
match openat2(
start,
path_c_str,
oflags,
mode,
ResolveFlags::BENEATH | ResolveFlags::NO_MAGICLINKS,
) {
Ok(file) => {
let file = fs::File::from_into_fd(file);
#[cfg(racy_asserts)]
check_open(start, path, options, &file);
return Ok(file);
}
Err(err) => match err {
rustix::io::Errno::AGAIN => continue,
rustix::io::Errno::PERM => break,
rustix::io::Errno::NOSYS => {
INVALID.store(true, Relaxed);
break;
}
_ => return Err(err),
},
}
}
Err(rustix::io::Errno::NOSYS)
})
.map_err(|err| match err {
rustix::io::Errno::XDEV => errors::escape_attempt(),
err => err.into(),
})
}
#[cfg(target_os = "android")]
fn openat2_supported() -> bool {
let uname = rustix::process::uname();
let release = uname.release().to_bytes();
if let Some((major, minor)) = linux_major_minor(release) {
if major >= 6 || (major == 5 && minor >= 6) {
return true;
}
}
false
}
#[cfg(target_os = "android")]
fn linux_major_minor(release: &[u8]) -> Option<(u32, u32)> {
let mut parts = release.split(|b| *b == b'.');
if let Some(major) = parts.next() {
if let Ok(major) = std::str::from_utf8(major) {
if let Ok(major) = major.parse::<u32>() {
if let Some(minor) = parts.next() {
if let Ok(minor) = std::str::from_utf8(minor) {
if let Ok(minor) = minor.parse::<u32>() {
return Some((major, minor));
}
}
}
}
}
}
None
}
#[cfg(target_os = "android")]
#[test]
fn test_linux_major_minor() {
assert_eq!(linux_major_minor(b"5.11.0-5489-something"), Some((5, 11)));
assert_eq!(linux_major_minor(b"5.10.0-9-whatever"), Some((5, 10)));
assert_eq!(linux_major_minor(b"5.6.0"), Some((5, 6)));
assert_eq!(linux_major_minor(b"2.6.34"), Some((2, 6)));
assert_eq!(linux_major_minor(b""), None);
assert_eq!(linux_major_minor(b"linux-2.6.32"), None);
}
#[cfg(racy_asserts)]
fn check_open(start: &fs::File, path: &Path, options: &OpenOptions, file: &fs::File) {
let check = manually::open(
start,
path,
options
.clone()
.create(false)
.create_new(false)
.truncate(false),
)
.expect("manually::open failed when open_openat2 succeeded");
assert!(
is_same_file(file, &check).unwrap(),
"manually::open should open the same inode as open_openat2"
);
}