From bfcd43bbf560216a3fd6448cf1a78cc087a8be2d Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sun, 17 Mar 2024 22:13:14 +0000 Subject: [PATCH] feat: support imports from fifo on unix --- --- a/crates/jrsonnet-evaluator/src/import.rs +++ b/crates/jrsonnet-evaluator/src/import.rs @@ -9,7 +9,8 @@ use fs::File; use jrsonnet_gcmodule::Trace; -use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath}; +use jrsonnet_interner::IBytes; +use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath, SourceFifo}; use crate::{ bail, @@ -82,6 +83,37 @@ } } +/// Create `SourcePath` from path, handling directories/Fifo files (on unix)/etc +fn check_path(path: &Path) -> Result> { + let meta = match fs::metadata(path) { + Ok(v) => v, + Err(e) if e.kind() == ErrorKind::NotFound => { + return Ok(None); + } + Err(e) => bail!(ImportIo(e.to_string())), + }; + let ty = meta.file_type(); + if ty.is_file() { + return Ok(Some(SourcePath::new(SourceFile::new( + path.canonicalize().map_err(|e| ImportIo(e.to_string()))?, + )))); + } + let ty = meta.file_type(); + #[cfg(unix)] + { + use std::os::unix::fs::FileTypeExt; + if ty.is_fifo() { + let file = fs::read(path).map_err(|e| ImportIo(format!("FIFO read failed: {e}")))?; + return Ok(Some(SourcePath::new(SourceFifo( + format!("{}", path.display()), + IBytes::from(file.as_slice()), + )))); + } + } + // Block device/some other magic thing. + Err(RuntimeError("special file can't be imported".into()).into()) +} + impl ImportResolver for FileImportResolver { fn resolve_from(&self, from: &SourcePath, path: &str) -> Result { let mut direct = if let Some(f) = from.downcast_ref::() { @@ -95,50 +127,34 @@ } else { unreachable!("resolver can't return this path") }; + direct.push(path); - if direct.is_file() { - Ok(SourcePath::new(SourceFile::new( - direct.canonicalize().map_err(|e| ImportIo(e.to_string()))?, - ))) - } else { - for library_path in self.library_paths.borrow().iter() { - let mut cloned = library_path.clone(); - cloned.push(path); - if cloned.exists() { - return Ok(SourcePath::new(SourceFile::new( - cloned.canonicalize().map_err(|e| ImportIo(e.to_string()))?, - ))); - } + if let Some(direct) = check_path(&direct)? { + return Ok(direct); + } + for library_path in self.library_paths.borrow().iter() { + let mut cloned = library_path.clone(); + cloned.push(path); + if let Some(cloned) = check_path(&cloned)? { + return Ok(cloned); } - bail!(ImportFileNotFound(from.clone(), path.to_owned())) } + bail!(ImportFileNotFound(from.clone(), path.to_owned())) } fn resolve(&self, path: &Path) -> Result { - let meta = match fs::metadata(path) { - Ok(v) => v, - Err(e) if e.kind() == ErrorKind::NotFound => { - bail!(AbsoluteImportFileNotFound(path.to_owned())) - } - Err(e) => bail!(ImportIo(e.to_string())), + let Some(source) = check_path(path)? else { + bail!(AbsoluteImportFileNotFound(path.to_owned())) }; - if meta.is_file() { - Ok(SourcePath::new(SourceFile::new( - path.canonicalize().map_err(|e| ImportIo(e.to_string()))?, - ))) - } else if meta.is_dir() { - Ok(SourcePath::new(SourceDirectory::new( - path.canonicalize().map_err(|e| ImportIo(e.to_string()))?, - ))) - } else { - unreachable!("this can't be a symlink") - } + Ok(source) } fn load_file_contents(&self, id: &SourcePath) -> Result> { let path = if let Some(f) = id.downcast_ref::() { f.path() - } else if id.downcast_ref::().is_some() || id.is_default() { + } else if id.downcast_ref::().is_some() { bail!(ImportIsADirectory(id.clone())) + } else if let Some(f) = id.downcast_ref::() { + return Ok(f.1.to_vec()); } else { unreachable!("other types are not supported in resolve"); }; --- a/crates/jrsonnet-parser/src/lib.rs +++ b/crates/jrsonnet-parser/src/lib.rs @@ -11,7 +11,9 @@ mod source; mod unescape; pub use location::CodeLocation; -pub use source::{Source, SourceDirectory, SourceFile, SourcePath, SourcePathT, SourceVirtual}; +pub use source::{ + Source, SourceDirectory, SourceFifo, SourceFile, SourcePath, SourcePathT, SourceVirtual, +}; pub struct ParserSettings { pub source: Source, --- a/crates/jrsonnet-parser/src/source.rs +++ b/crates/jrsonnet-parser/src/source.rs @@ -7,7 +7,7 @@ }; use jrsonnet_gcmodule::{Trace, Tracer}; -use jrsonnet_interner::IStr; +use jrsonnet_interner::{IBytes, IStr}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[cfg(feature = "structdump")] @@ -75,6 +75,7 @@ /// - [`SourceFile`] - for any file /// - [`SourceDirectory`] - for resolution from CWD /// - [`SourceVirtual`] - for stdlib/ext-str +/// - [`SourceFifo`] - for /dev/fd/X (This path may appear with `jrsonnet <(command_that_produces_jsonnet)`) /// /// From all of those, only [`SourceVirtual`] may be constructed manually, any other path kind should be only obtained /// from assigned `ImportResolver` @@ -254,6 +255,37 @@ any_ext_impl!(SourcePathT); } +/// Represents resolved FIFO file, those files may only be read once, and this type is only used for +/// unix, where user might want to do `jrsonnet <(command_that_produces_jsonnet_source)` +/// In most cases, user most probably want to use `jrsonnet -` instead of `jrsonnet /dev/stdin` +/// for better cross-platform support. +// PartialEq is limited to ptr equality +#[allow(clippy::derived_hash_with_manual_eq)] +#[derive(Trace, Debug, Hash)] +pub struct SourceFifo(pub String, pub IBytes); +impl PartialEq for SourceFifo { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self, other) + } +} +impl fmt::Display for SourceFifo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "fifo({:?})", self.0) + } +} +impl SourcePathT for SourceFifo { + fn is_default(&self) -> bool { + // In case of FD input, user won't expect relative paths to be resolved from /dev/fd/ + true + } + + fn path(&self) -> Option<&Path> { + None + } + + any_ext_impl!(SourcePathT); +} + /// Either real file, or virtual /// Hash of FileName always have same value as raw Path, to make it possible to use with raw_entry_mut #[cfg_attr(feature = "structdump", derive(Codegen))] -- gitstuff