From 94a23a6839f3d4632c5abed1cb8846e4d295383e Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 17 Aug 2021 19:22:05 +0000 Subject: Proof of concept --- src/filter.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/filter.rs (limited to 'src/filter.rs') diff --git a/src/filter.rs b/src/filter.rs new file mode 100644 index 0000000..3352b16 --- /dev/null +++ b/src/filter.rs @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: EUPL-1.2 +// SPDX-FileCopyrightText: 2021 Alyssa Ross + +use std::ffi::{CStr, OsString}; +use std::fmt::{Display, Formatter}; +use std::io::{self, ErrorKind, Write}; +use std::os::raw::{c_char, c_int}; +use std::path::Path; +use std::process::{Child, ChildStdout, Command, Stdio}; +use std::thread::{spawn, JoinHandle}; + +use git2::{Repository, Tree}; +use libc::{SIGPIPE, WEXITSTATUS, WIFEXITED, WIFSIGNALED, WTERMSIG}; + +extern "C" { + fn sigdescr_np(sig: c_int) -> *const c_char; +} + +unsafe fn wait(pid: u32) -> io::Result { + let mut wstatus: c_int = 0; + if libc::waitpid(pid as i32, &mut wstatus, 0) == -1 { + return Err(io::Error::last_os_error()); + } + Ok(wstatus) +} + +fn filter_output(argv: &[OsString]) -> Child { + Command::new(argv.get(0).unwrap()) + .args(&argv[1..]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("spawn") +} + +fn tree_path_content(repo: &Repository, tree: &Tree, path: &Path) -> Result, git2::Error> { + let blob = tree.get_path(path)?.to_object(&repo)?.peel_to_blob()?; + Ok(blob.content().to_vec()) +} + +#[derive(Debug)] +pub enum FilterError { + Write(io::Error), + Exit(c_int), + Signal(c_int), +} + +impl Display for FilterError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use FilterError::*; + match self { + Write(e) => write!(f, "writing to filter stdin: {}", e), + Exit(code) => write!(f, "filter command failed with code {}", code), + Signal(signo) => { + let sigdescr = unsafe { CStr::from_ptr(sigdescr_np(*signo)) }.to_string_lossy(); + write!(f, "filter command killed by signal: {}", sigdescr) + } + } + } +} + +impl std::error::Error for FilterError {} + +pub struct Filter { + pid: u32, + writer: JoinHandle>, +} + +impl Filter { + pub fn new( + repo: &Repository, + tree: &Tree, + path: &Path, + args: &[OsString], + ) -> (Self, ChildStdout) { + let blob = tree_path_content(repo, tree, path).unwrap(); + + let child = filter_output(args); + let pid = child.id(); + + // Destructure here to make sure there are no + // remaining references to the Child structs after + // this. This makes sure nothing else will wait for + // our filter processes, and we don't have to worry + // about PID reuse. + let Child { stdin, stdout, .. } = child; + + let mut stdin = stdin.unwrap(); + let stdout = stdout.unwrap(); + + let writer = spawn(move || stdin.write_all(&blob)); + + (Self { pid, writer }, stdout) + } + + pub fn wait(self) -> Result<(), FilterError> { + if let Err(e) = self.writer.join().unwrap() { + if e.kind() != ErrorKind::BrokenPipe { + return Err(FilterError::Write(e)); + } + } + + let wstatus = unsafe { wait(self.pid).unwrap() }; + if WIFEXITED(wstatus) { + let status = WEXITSTATUS(wstatus); + if status != 0 { + return Err(FilterError::Exit(status)); + } + } else if WIFSIGNALED(wstatus) { + let signal = WTERMSIG(wstatus); + if signal != SIGPIPE { + return Err(FilterError::Signal(signal)); + } + } else { + unreachable!() + } + + Ok(()) + } +} -- cgit 1.4.1