use std::io::{self, Read}; use std::path::{Component, Path}; use crate::authority::SourceAuthority; use crate::caps::PathTarget; use cap_fs_ext::{DirExt, FollowSymlinks, OpenOptionsFollowExt, OpenOptionsSyncExt}; pub fn open_directory(path: &Path) -> io::Result { let mut root = std::path::PathBuf::new(); let mut names = Vec::new(); for component in path.components() { match component { Component::Prefix(_) | Component::RootDir => root.push(component.as_os_str()), Component::Normal(name) => names.push(name), _ => return Err(io::Error::other("Expected a canonical path")), } } let mut directory = cap_std::fs::Dir::open_ambient_dir(root, cap_std::ambient_authority())?; // Canonicalization alone races with symlink replacement. Walk the resolved path // through directory handles without following any newly introduced symlinks. for name in names { directory = directory.open_dir_nofollow(name)?; } Ok(directory) } pub fn read_text( target: &PathTarget, authority: &SourceAuthority, ) -> Result> { authority.check_local(target)?; let path = target.as_path(); let directory = open_directory(path.parent().ok_or("Expected a module directory")?)?; let mut file = open_file(&directory, target, authority)?; let mut source = String::new(); file.read_to_string(&mut source)?; Ok(source) } pub fn open_file( directory: &cap_std::fs::Dir, target: &PathTarget, authority: &SourceAuthority, ) -> Result> { authority.check_local(target)?; let file_name = target .as_path() .file_name() .ok_or("Expected a module file")?; let file = open_regular(directory, file_name)?; authority.check_local(target)?; Ok(file) } /// Opens a regular file without following symlinks or blocking on special files. pub fn open_regular( directory: &cap_std::fs::Dir, file_name: impl AsRef, ) -> Result> { let mut options = cap_std::fs::OpenOptions::new(); options.read(true).follow(FollowSymlinks::No).nonblock(true); let file = directory.open_with(file_name, &options)?; if !file.metadata()?.is_file() { return Err("Modules must be regular files".into()); } Ok(file) }