diff options
author | Matt Mastracci <matthew@mastracci.com> | 2024-04-19 18:12:03 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-19 18:12:03 -0600 |
commit | 472a37064071c66cd1311cdea2e78de8d2bc0641 (patch) | |
tree | 94459f249eee0429480e2cea6ac37319e27de41d /ext/fs | |
parent | 365e1f48f7059f94d4eeb8f5ba8b3949b686b355 (diff) |
feat(runtime): Allow embedders to perform additional access checks on file open (#23208)
Embedders may have special requirements around file opening, so we add a
new `check_open` permission check that is called as part of the file
open process.
Diffstat (limited to 'ext/fs')
-rw-r--r-- | ext/fs/in_memory_fs.rs | 32 | ||||
-rw-r--r-- | ext/fs/interface.rs | 67 | ||||
-rw-r--r-- | ext/fs/lib.rs | 34 | ||||
-rw-r--r-- | ext/fs/ops.rs | 153 | ||||
-rw-r--r-- | ext/fs/std_fs.rs | 134 |
5 files changed, 313 insertions, 107 deletions
diff --git a/ext/fs/in_memory_fs.rs b/ext/fs/in_memory_fs.rs index fdd0ad7e7..153327ff6 100644 --- a/ext/fs/in_memory_fs.rs +++ b/ext/fs/in_memory_fs.rs @@ -19,6 +19,7 @@ use deno_io::fs::FsError; use deno_io::fs::FsResult; use deno_io::fs::FsStat; +use crate::interface::AccessCheckCb; use crate::interface::FsDirEntry; use crate::interface::FsFileType; use crate::FileSystem; @@ -48,6 +49,7 @@ impl InMemoryFs { .write_file_sync( &path, OpenOptions::write(true, false, false, None), + None, &text.into_bytes(), ) .unwrap(); @@ -82,15 +84,17 @@ impl FileSystem for InMemoryFs { &self, _path: &Path, _options: OpenOptions, + _access_check: Option<AccessCheckCb>, ) -> FsResult<Rc<dyn File>> { Err(FsError::NotSupported) } - async fn open_async( - &self, + async fn open_async<'a>( + &'a self, path: PathBuf, options: OpenOptions, + access_check: Option<AccessCheckCb<'a>>, ) -> FsResult<Rc<dyn File>> { - self.open_sync(&path, options) + self.open_sync(&path, options, access_check) } fn mkdir_sync( @@ -350,6 +354,7 @@ impl FileSystem for InMemoryFs { &self, path: &Path, options: OpenOptions, + _access_check: Option<AccessCheckCb>, data: &[u8], ) -> FsResult<()> { let path = normalize_path(path); @@ -397,16 +402,21 @@ impl FileSystem for InMemoryFs { } } - async fn write_file_async( - &self, + async fn write_file_async<'a>( + &'a self, path: PathBuf, options: OpenOptions, + access_check: Option<AccessCheckCb<'a>>, data: Vec<u8>, ) -> FsResult<()> { - self.write_file_sync(&path, options, &data) + self.write_file_sync(&path, options, access_check, &data) } - fn read_file_sync(&self, path: &Path) -> FsResult<Vec<u8>> { + fn read_file_sync( + &self, + path: &Path, + _access_check: Option<AccessCheckCb>, + ) -> FsResult<Vec<u8>> { let entry = self.get_entry(path); match entry { Some(entry) => match &*entry { @@ -419,7 +429,11 @@ impl FileSystem for InMemoryFs { None => Err(FsError::Io(Error::new(ErrorKind::NotFound, "Not found"))), } } - async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> { - self.read_file_sync(&path) + async fn read_file_async<'a>( + &'a self, + path: PathBuf, + access_check: Option<AccessCheckCb<'a>>, + ) -> FsResult<Vec<u8>> { + self.read_file_sync(&path, access_check) } } diff --git a/ext/fs/interface.rs b/ext/fs/interface.rs index c5a348eb1..70f9fdf63 100644 --- a/ext/fs/interface.rs +++ b/ext/fs/interface.rs @@ -80,6 +80,25 @@ pub struct FsDirEntry { #[allow(clippy::disallowed_types)] pub type FileSystemRc = crate::sync::MaybeArc<dyn FileSystem>; +pub trait AccessCheckFn: + for<'a> FnMut( + bool, + &'a Path, + &'a OpenOptions, +) -> FsResult<std::borrow::Cow<'a, Path>> +{ +} +impl<T> AccessCheckFn for T where + T: for<'a> FnMut( + bool, + &'a Path, + &'a OpenOptions, + ) -> FsResult<std::borrow::Cow<'a, Path>> +{ +} + +pub type AccessCheckCb<'a> = &'a mut (dyn AccessCheckFn + 'a); + #[async_trait::async_trait(?Send)] pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync { fn cwd(&self) -> FsResult<PathBuf>; @@ -91,11 +110,13 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync { &self, path: &Path, options: OpenOptions, + access_check: Option<AccessCheckCb>, ) -> FsResult<Rc<dyn File>>; - async fn open_async( - &self, + async fn open_async<'a>( + &'a self, path: PathBuf, options: OpenOptions, + access_check: Option<AccessCheckCb<'a>>, ) -> FsResult<Rc<dyn File>>; fn mkdir_sync(&self, path: &Path, recursive: bool, mode: u32) @@ -202,22 +223,24 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync { &self, path: &Path, options: OpenOptions, + access_check: Option<AccessCheckCb>, data: &[u8], ) -> FsResult<()> { - let file = self.open_sync(path, options)?; + let file = self.open_sync(path, options, access_check)?; if let Some(mode) = options.mode { file.clone().chmod_sync(mode)?; } file.write_all_sync(data)?; Ok(()) } - async fn write_file_async( - &self, + async fn write_file_async<'a>( + &'a self, path: PathBuf, options: OpenOptions, + access_check: Option<AccessCheckCb<'a>>, data: Vec<u8>, ) -> FsResult<()> { - let file = self.open_async(path, options).await?; + let file = self.open_async(path, options, access_check).await?; if let Some(mode) = options.mode { file.clone().chmod_async(mode).await?; } @@ -225,15 +248,23 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync { Ok(()) } - fn read_file_sync(&self, path: &Path) -> FsResult<Vec<u8>> { + fn read_file_sync( + &self, + path: &Path, + access_check: Option<AccessCheckCb>, + ) -> FsResult<Vec<u8>> { let options = OpenOptions::read(); - let file = self.open_sync(path, options)?; + let file = self.open_sync(path, options, access_check)?; let buf = file.read_all_sync()?; Ok(buf) } - async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> { + async fn read_file_async<'a>( + &'a self, + path: PathBuf, + access_check: Option<AccessCheckCb<'a>>, + ) -> FsResult<Vec<u8>> { let options = OpenOptions::read(); - let file = self.open_async(path, options).await?; + let file = self.open_async(path, options, access_check).await?; let buf = file.read_all_async().await?; Ok(buf) } @@ -253,14 +284,22 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync { self.stat_sync(path).is_ok() } - fn read_text_file_sync(&self, path: &Path) -> FsResult<String> { - let buf = self.read_file_sync(path)?; + fn read_text_file_sync( + &self, + path: &Path, + access_check: Option<AccessCheckCb>, + ) -> FsResult<String> { + let buf = self.read_file_sync(path, access_check)?; String::from_utf8(buf).map_err(|err| { std::io::Error::new(std::io::ErrorKind::InvalidData, err).into() }) } - async fn read_text_file_async(&self, path: PathBuf) -> FsResult<String> { - let buf = self.read_file_async(path).await?; + async fn read_text_file_async<'a>( + &'a self, + path: PathBuf, + access_check: Option<AccessCheckCb<'a>>, + ) -> FsResult<String> { + let buf = self.read_file_async(path, access_check).await?; String::from_utf8(buf).map_err(|err| { std::io::Error::new(std::io::ErrorKind::InvalidData, err).into() }) diff --git a/ext/fs/lib.rs b/ext/fs/lib.rs index 05b119e2e..d4e79b75f 100644 --- a/ext/fs/lib.rs +++ b/ext/fs/lib.rs @@ -7,6 +7,8 @@ mod std_fs; pub mod sync; pub use crate::in_memory_fs::InMemoryFs; +pub use crate::interface::AccessCheckCb; +pub use crate::interface::AccessCheckFn; pub use crate::interface::FileSystem; pub use crate::interface::FileSystemRc; pub use crate::interface::FsDirEntry; @@ -20,9 +22,18 @@ use crate::ops::*; use deno_core::error::AnyError; use deno_core::OpState; +use deno_io::fs::FsError; use std::path::Path; -pub trait FsPermissions { +pub trait FsPermissions: Send + Sync { + fn check_open<'a>( + &mut self, + resolved: bool, + read: bool, + write: bool, + path: &'a Path, + api_name: &str, + ) -> Result<std::borrow::Cow<'a, Path>, FsError>; fn check_read(&mut self, path: &Path, api_name: &str) -> Result<(), AnyError>; fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>; @@ -50,19 +61,20 @@ pub trait FsPermissions { api_name: &str, ) -> Result<(), AnyError>; - fn check( + fn check<'a>( &mut self, + resolved: bool, open_options: &OpenOptions, - path: &Path, + path: &'a Path, api_name: &str, - ) -> Result<(), AnyError> { - if open_options.read { - self.check_read(path, api_name)?; - } - if open_options.write || open_options.append { - self.check_write(path, api_name)?; - } - Ok(()) + ) -> Result<std::borrow::Cow<'a, Path>, FsError> { + self.check_open( + resolved, + open_options.read, + open_options.write || open_options.append, + path, + api_name, + ) } } diff --git a/ext/fs/ops.rs b/ext/fs/ops.rs index d688f619e..3ff32bd8f 100644 --- a/ext/fs/ops.rs +++ b/ext/fs/ops.rs @@ -28,12 +28,55 @@ use rand::Rng; use serde::Serialize; use crate::check_unstable; +use crate::interface::AccessCheckFn; use crate::interface::FileSystemRc; use crate::interface::FsDirEntry; use crate::interface::FsFileType; use crate::FsPermissions; use crate::OpenOptions; +fn sync_permission_check<'a, P: FsPermissions + 'static>( + permissions: &'a mut P, + api_name: &'static str, +) -> impl AccessCheckFn + 'a { + move |resolved, path, options| { + permissions.check(resolved, options, path, api_name) + } +} + +fn async_permission_check<P: FsPermissions + 'static>( + state: Rc<RefCell<OpState>>, + api_name: &'static str, +) -> impl AccessCheckFn { + move |resolved, path, options| { + let mut state = state.borrow_mut(); + let permissions = state.borrow_mut::<P>(); + permissions.check(resolved, options, path, api_name) + } +} + +fn map_permission_error( + operation: &'static str, + error: FsError, + path: &Path, +) -> AnyError { + match error { + FsError::PermissionDenied(err) => { + let path = format!("{path:?}"); + let (path, truncated) = if path.len() > 1024 { + (&path[0..1024], "...(truncated)") + } else { + (path.as_str(), "") + }; + custom_error("PermissionDenied", format!("Requires {err} access to {path}{truncated}, run again with the --allow-{err} flag")) + } + err => Err::<(), _>(err) + .context_path(operation, path) + .err() + .unwrap(), + } +} + #[op2] #[string] pub fn op_fs_cwd<P>(state: &mut OpState) -> Result<String, AnyError> @@ -89,12 +132,14 @@ where let path = PathBuf::from(path); let options = options.unwrap_or_else(OpenOptions::read); - let permissions = state.borrow_mut::<P>(); - permissions.check(&options, &path, "Deno.openSync()")?; - - let fs = state.borrow::<FileSystemRc>(); - let file = fs.open_sync(&path, options).context_path("open", &path)?; + let fs = state.borrow::<FileSystemRc>().clone(); + let mut access_check = + sync_permission_check::<P>(state.borrow_mut(), "Deno.openSync()"); + let file = fs + .open_sync(&path, options, Some(&mut access_check)) + .map_err(|error| map_permission_error("open", error, &path))?; + drop(access_check); let rid = state .resource_table .add(FileResource::new(file, "fsFile".to_string())); @@ -114,16 +159,13 @@ where let path = PathBuf::from(path); let options = options.unwrap_or_else(OpenOptions::read); - let fs = { - let mut state = state.borrow_mut(); - let permissions = state.borrow_mut::<P>(); - permissions.check(&options, &path, "Deno.open()")?; - state.borrow::<FileSystemRc>().clone() - }; + let mut access_check = + async_permission_check::<P>(state.clone(), "Deno.open()"); + let fs = state.borrow().borrow::<FileSystemRc>().clone(); let file = fs - .open_async(path.clone(), options) + .open_async(path.clone(), options, Some(&mut access_check)) .await - .context_path("open", &path)?; + .map_err(|error| map_permission_error("open", error, &path))?; let rid = state .borrow_mut() @@ -961,11 +1003,10 @@ where }; let mut rng = thread_rng(); - const MAX_TRIES: u32 = 10; for _ in 0..MAX_TRIES { let path = tmp_name(&mut rng, &dir, prefix.as_deref(), suffix.as_deref())?; - match fs.open_sync(&path, open_opts) { + match fs.open_sync(&path, open_opts, None) { Ok(_) => return path_into_string(path.into_os_string()), Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => { continue; @@ -1007,7 +1048,7 @@ where const MAX_TRIES: u32 = 10; for _ in 0..MAX_TRIES { let path = tmp_name(&mut rng, &dir, prefix.as_deref(), suffix.as_deref())?; - match fs.clone().open_async(path.clone(), open_opts).await { + match fs.clone().open_async(path.clone(), open_opts, None).await { Ok(_) => return path_into_string(path.into_os_string()), Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => { continue; @@ -1150,14 +1191,13 @@ where { let path = PathBuf::from(path); - let permissions = state.borrow_mut::<P>(); let options = OpenOptions::write(create, append, create_new, mode); - permissions.check(&options, &path, "Deno.writeFileSync()")?; - - let fs = state.borrow::<FileSystemRc>(); + let fs = state.borrow::<FileSystemRc>().clone(); + let mut access_check = + sync_permission_check::<P>(state.borrow_mut(), "Deno.writeFileSync()"); - fs.write_file_sync(&path, options, &data) - .context_path("writefile", &path)?; + fs.write_file_sync(&path, options, Some(&mut access_check), &data) + .map_err(|error| map_permission_error("writefile", error, &path))?; Ok(()) } @@ -1181,16 +1221,21 @@ where let options = OpenOptions::write(create, append, create_new, mode); + let mut access_check = + async_permission_check::<P>(state.clone(), "Deno.writeFile()"); let (fs, cancel_handle) = { - let mut state = state.borrow_mut(); - let permissions = state.borrow_mut::<P>(); - permissions.check(&options, &path, "Deno.writeFile()")?; + let state = state.borrow_mut(); let cancel_handle = cancel_rid .and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok()); (state.borrow::<FileSystemRc>().clone(), cancel_handle) }; - let fut = fs.write_file_async(path.clone(), options, data.to_vec()); + let fut = fs.write_file_async( + path.clone(), + options, + Some(&mut access_check), + data.to_vec(), + ); if let Some(cancel_handle) = cancel_handle { let res = fut.or_cancel(cancel_handle).await; @@ -1201,9 +1246,11 @@ where } }; - res?.context_path("writefile", &path)?; + res?.map_err(|error| map_permission_error("writefile", error, &path))?; } else { - fut.await.context_path("writefile", &path)?; + fut + .await + .map_err(|error| map_permission_error("writefile", error, &path))?; } Ok(()) @@ -1220,11 +1267,12 @@ where { let path = PathBuf::from(path); - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&path, "Deno.readFileSync()")?; - - let fs = state.borrow::<FileSystemRc>(); - let buf = fs.read_file_sync(&path).context_path("readfile", &path)?; + let fs = state.borrow::<FileSystemRc>().clone(); + let mut access_check = + sync_permission_check::<P>(state.borrow_mut(), "Deno.readFileSync()"); + let buf = fs + .read_file_sync(&path, Some(&mut access_check)) + .map_err(|error| map_permission_error("readfile", error, &path))?; Ok(buf.into()) } @@ -1241,16 +1289,16 @@ where { let path = PathBuf::from(path); + let mut access_check = + async_permission_check::<P>(state.clone(), "Deno.readFile()"); let (fs, cancel_handle) = { - let mut state = state.borrow_mut(); - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&path, "Deno.readFile()")?; + let state = state.borrow(); let cancel_handle = cancel_rid .and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok()); (state.borrow::<FileSystemRc>().clone(), cancel_handle) }; - let fut = fs.read_file_async(path.clone()); + let fut = fs.read_file_async(path.clone(), Some(&mut access_check)); let buf = if let Some(cancel_handle) = cancel_handle { let res = fut.or_cancel(cancel_handle).await; @@ -1261,9 +1309,11 @@ where } }; - res?.context_path("readfile", &path)? + res?.map_err(|error| map_permission_error("readfile", error, &path))? } else { - fut.await.context_path("readfile", &path)? + fut + .await + .map_err(|error| map_permission_error("readfile", error, &path))? }; Ok(buf.into()) @@ -1280,11 +1330,12 @@ where { let path = PathBuf::from(path); - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&path, "Deno.readFileSync()")?; - - let fs = state.borrow::<FileSystemRc>(); - let buf = fs.read_file_sync(&path).context_path("readfile", &path)?; + let fs = state.borrow::<FileSystemRc>().clone(); + let mut access_check = + sync_permission_check::<P>(state.borrow_mut(), "Deno.readFileSync()"); + let buf = fs + .read_file_sync(&path, Some(&mut access_check)) + .map_err(|error| map_permission_error("readfile", error, &path))?; Ok(string_from_utf8_lossy(buf)) } @@ -1301,16 +1352,16 @@ where { let path = PathBuf::from(path); + let mut access_check = + async_permission_check::<P>(state.clone(), "Deno.readFile()"); let (fs, cancel_handle) = { - let mut state = state.borrow_mut(); - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&path, "Deno.readFile()")?; + let state = state.borrow_mut(); let cancel_handle = cancel_rid .and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok()); (state.borrow::<FileSystemRc>().clone(), cancel_handle) }; - let fut = fs.read_file_async(path.clone()); + let fut = fs.read_file_async(path.clone(), Some(&mut access_check)); let buf = if let Some(cancel_handle) = cancel_handle { let res = fut.or_cancel(cancel_handle).await; @@ -1321,9 +1372,11 @@ where } }; - res?.context_path("readfile", &path)? + res?.map_err(|error| map_permission_error("readfile", error, &path))? } else { - fut.await.context_path("readfile", &path)? + fut + .await + .map_err(|error| map_permission_error("readfile", error, &path))? }; Ok(string_from_utf8_lossy(buf)) diff --git a/ext/fs/std_fs.rs b/ext/fs/std_fs.rs index 332866e45..1176697de 100644 --- a/ext/fs/std_fs.rs +++ b/ext/fs/std_fs.rs @@ -2,27 +2,29 @@ #![allow(clippy::disallowed_methods)] +use std::env::current_dir; use std::fs; use std::io; +use std::io::Read; use std::io::Write; use std::path::Path; use std::path::PathBuf; use std::rc::Rc; +use deno_core::normalize_path; use deno_core::unsync::spawn_blocking; use deno_io::fs::File; +use deno_io::fs::FsError; use deno_io::fs::FsResult; use deno_io::fs::FsStat; use deno_io::StdFileResourceInner; +use crate::interface::AccessCheckCb; use crate::interface::FsDirEntry; use crate::interface::FsFileType; use crate::FileSystem; use crate::OpenOptions; -#[cfg(not(unix))] -use deno_io::fs::FsError; - #[derive(Debug, Clone)] pub struct RealFs; @@ -80,18 +82,18 @@ impl FileSystem for RealFs { &self, path: &Path, options: OpenOptions, + access_check: Option<AccessCheckCb>, ) -> FsResult<Rc<dyn File>> { - let opts = open_options(options); - let std_file = opts.open(path)?; + let std_file = open_with_access_check(options, path, access_check)?; Ok(Rc::new(StdFileResourceInner::file(std_file))) } - async fn open_async( - &self, + async fn open_async<'a>( + &'a self, path: PathBuf, options: OpenOptions, + access_check: Option<AccessCheckCb<'a>>, ) -> FsResult<Rc<dyn File>> { - let opts = open_options(options); - let std_file = spawn_blocking(move || opts.open(path)).await??; + let std_file = open_with_access_check(options, &path, access_check)?; Ok(Rc::new(StdFileResourceInner::file(std_file))) } @@ -276,10 +278,10 @@ impl FileSystem for RealFs { &self, path: &Path, options: OpenOptions, + access_check: Option<AccessCheckCb>, data: &[u8], ) -> FsResult<()> { - let opts = open_options(options); - let mut file = opts.open(path)?; + let mut file = open_with_access_check(options, path, access_check)?; #[cfg(unix)] if let Some(mode) = options.mode { use std::os::unix::fs::PermissionsExt; @@ -289,15 +291,15 @@ impl FileSystem for RealFs { Ok(()) } - async fn write_file_async( - &self, + async fn write_file_async<'a>( + &'a self, path: PathBuf, options: OpenOptions, + access_check: Option<AccessCheckCb<'a>>, data: Vec<u8>, ) -> FsResult<()> { + let mut file = open_with_access_check(options, &path, access_check)?; spawn_blocking(move || { - let opts = open_options(options); - let mut file = opts.open(path)?; #[cfg(unix)] if let Some(mode) = options.mode { use std::os::unix::fs::PermissionsExt; @@ -309,13 +311,43 @@ impl FileSystem for RealFs { .await? } - fn read_file_sync(&self, path: &Path) -> FsResult<Vec<u8>> { - fs::read(path).map_err(Into::into) - } - async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> { - spawn_blocking(move || fs::read(path)) - .await? - .map_err(Into::into) + fn read_file_sync( + &self, + path: &Path, + access_check: Option<AccessCheckCb>, + ) -> FsResult<Vec<u8>> { + let mut file = open_with_access_check( + OpenOptions { + read: true, + ..Default::default() + }, + path, + access_check, + )?; + let mut buf = Vec::new(); + file.read_to_end(&mut buf)?; + Ok(buf) + } + async fn read_file_async<'a>( + &'a self, + path: PathBuf, + access_check: Option<AccessCheckCb<'a>>, + ) -> FsResult<Vec<u8>> { + let mut file = open_with_access_check( + OpenOptions { + read: true, + ..Default::default() + }, + &path, + access_check, + )?; + spawn_blocking(move || { + let mut buf = Vec::new(); + file.read_to_end(&mut buf)?; + Ok::<_, FsError>(buf) + }) + .await? + .map_err(Into::into) } } @@ -410,7 +442,6 @@ fn copy_file(from: &Path, to: &Path) -> FsResult<()> { use libc::stat; use libc::unlink; use std::ffi::CString; - use std::io::Read; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::fs::PermissionsExt; @@ -845,3 +876,60 @@ fn open_options(options: OpenOptions) -> fs::OpenOptions { open_options.create_new(options.create_new); open_options } + +#[inline(always)] +fn open_with_access_check( + options: OpenOptions, + path: &Path, + access_check: Option<AccessCheckCb>, +) -> FsResult<std::fs::File> { + if let Some(access_check) = access_check { + let path = if path.is_absolute() { + normalize_path(path) + } else { + let cwd = current_dir()?; + normalize_path(cwd.join(path)) + }; + (*access_check)(false, &path, &options)?; + // On Linux, /proc may contain magic links that we don't want to resolve + let needs_canonicalization = + !cfg!(target_os = "linux") || path.starts_with("/proc"); + let path = if needs_canonicalization { + match path.canonicalize() { + Ok(path) => path, + Err(_) => { + if let (Some(parent), Some(filename)) = + (path.parent(), path.file_name()) + { + parent.canonicalize()?.join(filename) + } else { + return Err(std::io::ErrorKind::NotFound.into()); + } + } + } + } else { + path + }; + + (*access_check)(true, &path, &options)?; + + // For windows + #[allow(unused_mut)] + let mut opts: fs::OpenOptions = open_options(options); + #[cfg(unix)] + { + // Don't follow symlinks on open -- we must always pass fully-resolved files + // with the exception of /proc/ which is too special, and /dev/std* which might point to + // proc. + use std::os::unix::fs::OpenOptionsExt; + if needs_canonicalization { + opts.custom_flags(libc::O_NOFOLLOW); + } + } + + Ok(opts.open(&path)?) + } else { + let opts = open_options(options); + Ok(opts.open(path)?) + } +} |