1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
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<c_int> {
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<Vec<u8>, 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<io::Result<()>>,
}
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(())
}
}
|