cli: Support opening anonymous file descriptors via the cli on MacOS and Linux (#26744)
Closes #4770 (really closes issue described in [this comment](https://github.com/zed-industries/zed/issues/4770#issuecomment-2258728884) on #4770) Only implemented for MacOS and Linux for now as I have no way to test on Windows or BSD. PRs welcome! Release Notes: - Added support for reading from anonymous file descriptors (e.g. created as part of process substitution) on MacOS and Linux
This commit is contained in:
parent
22ad7b17c5
commit
daa16bcf42
1 changed files with 85 additions and 18 deletions
|
@ -198,6 +198,8 @@ fn main() -> Result<()> {
|
||||||
let mut paths = vec![];
|
let mut paths = vec![];
|
||||||
let mut urls = vec![];
|
let mut urls = vec![];
|
||||||
let mut stdin_tmp_file: Option<fs::File> = None;
|
let mut stdin_tmp_file: Option<fs::File> = None;
|
||||||
|
let mut anonymous_fd_tmp_files = vec![];
|
||||||
|
|
||||||
for path in args.paths_with_position.iter() {
|
for path in args.paths_with_position.iter() {
|
||||||
if path.starts_with("zed://")
|
if path.starts_with("zed://")
|
||||||
|| path.starts_with("http://")
|
|| path.starts_with("http://")
|
||||||
|
@ -211,6 +213,11 @@ fn main() -> Result<()> {
|
||||||
paths.push(file.path().to_string_lossy().to_string());
|
paths.push(file.path().to_string_lossy().to_string());
|
||||||
let (file, _) = file.keep()?;
|
let (file, _) = file.keep()?;
|
||||||
stdin_tmp_file = Some(file);
|
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 {
|
} else {
|
||||||
paths.push(parse_path_with_position(path)?)
|
paths.push(parse_path_with_position(path)?)
|
||||||
}
|
}
|
||||||
|
@ -252,31 +259,33 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let pipe_handle: JoinHandle<anyhow::Result<()>> = thread::spawn(move || {
|
let stdin_pipe_handle: Option<JoinHandle<anyhow::Result<()>>> =
|
||||||
if let Some(mut tmp_file) = stdin_tmp_file {
|
stdin_tmp_file.map(|tmp_file| {
|
||||||
let mut stdin = std::io::stdin().lock();
|
thread::spawn(move || {
|
||||||
if io::IsTerminal::is_terminal(&stdin) {
|
let stdin = std::io::stdin().lock();
|
||||||
return Ok(());
|
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;
|
|
||||||
}
|
}
|
||||||
io::Write::write(&mut tmp_file, &buffer[..bytes_read])?;
|
return pipe_to_tmp(stdin, tmp_file);
|
||||||
}
|
})
|
||||||
io::Write::flush(&mut tmp_file)?;
|
});
|
||||||
}
|
|
||||||
Ok(())
|
let anonymous_fd_pipe_handles: Vec<JoinHandle<anyhow::Result<()>>> = anonymous_fd_tmp_files
|
||||||
});
|
.into_iter()
|
||||||
|
.map(|(file, tmp_file)| thread::spawn(move || pipe_to_tmp(file, tmp_file)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
if args.foreground {
|
if args.foreground {
|
||||||
app.run_foreground(url)?;
|
app.run_foreground(url)?;
|
||||||
} else {
|
} else {
|
||||||
app.launch(url)?;
|
app.launch(url)?;
|
||||||
sender.join().unwrap()?;
|
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() {
|
if let Some(exit_status) = exit_status.lock().take() {
|
||||||
|
@ -285,6 +294,64 @@ fn main() -> Result<()> {
|
||||||
Ok(())
|
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<fs::File> {
|
||||||
|
#[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"))]
|
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||||
mod linux {
|
mod linux {
|
||||||
use std::{
|
use std::{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue