diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index bf87ec92d3..c4451739dd 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -198,6 +198,8 @@ fn main() -> Result<()> { let mut paths = vec![]; let mut urls = vec![]; let mut stdin_tmp_file: Option = None; + let mut anonymous_fd_tmp_files = vec![]; + for path in args.paths_with_position.iter() { if path.starts_with("zed://") || path.starts_with("http://") @@ -211,6 +213,11 @@ fn main() -> Result<()> { paths.push(file.path().to_string_lossy().to_string()); let (file, _) = file.keep()?; stdin_tmp_file = Some(file); + } else if let Some(file) = anonymous_fd(path) { + let tmp_file = NamedTempFile::new()?; + paths.push(tmp_file.path().to_string_lossy().to_string()); + let (tmp_file, _) = tmp_file.keep()?; + anonymous_fd_tmp_files.push((file, tmp_file)); } else { paths.push(parse_path_with_position(path)?) } @@ -252,31 +259,33 @@ fn main() -> Result<()> { } }); - let pipe_handle: JoinHandle> = thread::spawn(move || { - if let Some(mut tmp_file) = stdin_tmp_file { - let mut stdin = std::io::stdin().lock(); - if io::IsTerminal::is_terminal(&stdin) { - return Ok(()); - } - let mut buffer = [0; 8 * 1024]; - loop { - let bytes_read = io::Read::read(&mut stdin, &mut buffer)?; - if bytes_read == 0 { - break; + let stdin_pipe_handle: Option>> = + stdin_tmp_file.map(|tmp_file| { + thread::spawn(move || { + let stdin = std::io::stdin().lock(); + if io::IsTerminal::is_terminal(&stdin) { + return Ok(()); } - io::Write::write(&mut tmp_file, &buffer[..bytes_read])?; - } - io::Write::flush(&mut tmp_file)?; - } - Ok(()) - }); + return pipe_to_tmp(stdin, tmp_file); + }) + }); + + let anonymous_fd_pipe_handles: Vec>> = anonymous_fd_tmp_files + .into_iter() + .map(|(file, tmp_file)| thread::spawn(move || pipe_to_tmp(file, tmp_file))) + .collect(); if args.foreground { app.run_foreground(url)?; } else { app.launch(url)?; sender.join().unwrap()?; - pipe_handle.join().unwrap()?; + if let Some(handle) = stdin_pipe_handle { + handle.join().unwrap()?; + } + for handle in anonymous_fd_pipe_handles { + handle.join().unwrap()?; + } } if let Some(exit_status) = exit_status.lock().take() { @@ -285,6 +294,64 @@ fn main() -> Result<()> { Ok(()) } +fn pipe_to_tmp(mut src: impl io::Read, mut dest: fs::File) -> Result<()> { + let mut buffer = [0; 8 * 1024]; + loop { + let bytes_read = match src.read(&mut buffer) { + Err(err) if err.kind() == io::ErrorKind::Interrupted => continue, + res => res?, + }; + if bytes_read == 0 { + break; + } + io::Write::write_all(&mut dest, &buffer[..bytes_read])?; + } + io::Write::flush(&mut dest)?; + Ok(()) +} + +fn anonymous_fd(path: &str) -> Option { + #[cfg(target_os = "linux")] + { + use std::os::fd::{self, FromRawFd}; + + let fd_str = path.strip_prefix("/proc/self/fd/")?; + + let link = fs::read_link(path).ok()?; + if !link.starts_with("memfd:") { + return None; + } + + let fd: fd::RawFd = fd_str.parse().ok()?; + let file = unsafe { fs::File::from_raw_fd(fd) }; + return Some(file); + } + #[cfg(target_os = "macos")] + { + use std::os::{ + fd::{self, FromRawFd}, + unix::fs::FileTypeExt, + }; + + let fd_str = path.strip_prefix("/dev/fd/")?; + + let metadata = fs::metadata(path).ok()?; + let file_type = metadata.file_type(); + if !file_type.is_fifo() && !file_type.is_socket() { + return None; + } + let fd: fd::RawFd = fd_str.parse().ok()?; + let file = unsafe { fs::File::from_raw_fd(fd) }; + return Some(file); + } + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + _ = path; + // not implemented for bsd, windows. Could be, but isn't yet + return None; + } +} + #[cfg(any(target_os = "linux", target_os = "freebsd"))] mod linux { use std::{