cli: Support opening anonymous file descriptors via the cli on MacOS and Linux (#26744)

Ben Kunkle created

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

Change summary

crates/cli/src/main.rs | 103 ++++++++++++++++++++++++++++++++++++-------
1 file changed, 85 insertions(+), 18 deletions(-)

Detailed changes

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<fs::File> = 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<anyhow::Result<()>> = 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<JoinHandle<anyhow::Result<()>>> =
+        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<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 {
         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<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"))]
 mod linux {
     use std::{