Implement fail-closed ignored workspace routing
This commit is contained in:
parent
ba6a075839
commit
95104fa4e7
2 changed files with 6024 additions and 6 deletions
|
|
@ -0,0 +1,30 @@
|
|||
use std::env;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use lazaret_ignored_workspace::{GitSentry, GitSentryConfiguration};
|
||||
|
||||
fn main() {
|
||||
if let Err(error) = run() {
|
||||
eprintln!("lazaret Git Sentry refused: {error}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> io::Result<()> {
|
||||
let arguments = env::args_os().skip(1).collect::<Vec<_>>();
|
||||
if arguments.len() != 3 {
|
||||
return Err(io::Error::other(
|
||||
"usage: lazaret-git-sentry MOUNTED_WORKSPACE HOST_GIT_COMMON_DIRECTORY TRANSIENT_HOOKS",
|
||||
));
|
||||
}
|
||||
let sentry = GitSentry::install(&GitSentryConfiguration {
|
||||
mounted_workspace: PathBuf::from(&arguments[0]),
|
||||
host_git_common_directory: PathBuf::from(&arguments[1]),
|
||||
transient_hooks: PathBuf::from(&arguments[2]),
|
||||
})?;
|
||||
println!("ready");
|
||||
io::stdout().flush()?;
|
||||
io::stdin().read_to_end(&mut Vec::new())?;
|
||||
sentry.unmount()
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::io::{self, Read};
|
||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||
use std::os::unix::fs::{FileTypeExt, MetadataExt};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rustix::fs::{AtFlags, StatxFlags, statx};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
const FIXTURE_CONTEXT: &[u8] = b"lazaret-ignored-workspace-fixture-v1\0";
|
||||
|
||||
fn main() {
|
||||
if let Err(error) = run() {
|
||||
eprintln!("lazaret workspace benchmark refused: {error}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> io::Result<()> {
|
||||
let arguments = env::args_os().skip(1).collect::<Vec<_>>();
|
||||
let root = parse_metadata_arguments(&arguments)?;
|
||||
let result = measure_metadata(&root)?;
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::json!({
|
||||
"content_bytes": result.content_bytes,
|
||||
"fixture_identity": hex(&result.fixture_identity),
|
||||
"regular_files": result.regular_files,
|
||||
})
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_metadata_arguments(arguments: &[OsString]) -> io::Result<PathBuf> {
|
||||
if arguments.len() != 6
|
||||
|| arguments[0] != "metadata"
|
||||
|| arguments[1] != "--root"
|
||||
|| arguments[3] != "--bytewise-order"
|
||||
|| arguments[4] != "--statx-mask"
|
||||
|| arguments[5] != "basic-stats"
|
||||
{
|
||||
return Err(io::Error::other(
|
||||
"usage: lazaret-workspace-benchmark metadata --root ROOT --bytewise-order --statx-mask basic-stats",
|
||||
));
|
||||
}
|
||||
Ok(PathBuf::from(&arguments[2]))
|
||||
}
|
||||
|
||||
fn measure_metadata(root: &Path) -> io::Result<MetadataResult> {
|
||||
let mut entries = Vec::new();
|
||||
collect_entries(root, Path::new(""), &mut entries)?;
|
||||
entries.sort_by(|left, right| {
|
||||
left.relative
|
||||
.as_os_str()
|
||||
.as_bytes()
|
||||
.cmp(right.relative.as_os_str().as_bytes())
|
||||
});
|
||||
|
||||
let mut stream = Vec::from(FIXTURE_CONTEXT);
|
||||
stream.extend_from_slice(
|
||||
&u32::try_from(entries.len())
|
||||
.map_err(|_| io::Error::other("fixture contains too many entries"))?
|
||||
.to_be_bytes(),
|
||||
);
|
||||
let mut regular_files = 0_u64;
|
||||
let mut content_bytes = 0_u64;
|
||||
for entry in entries {
|
||||
let path_bytes = entry.relative.as_os_str().as_bytes();
|
||||
stream.extend_from_slice(
|
||||
&u32::try_from(path_bytes.len())
|
||||
.map_err(|_| io::Error::other("fixture path is too long"))?
|
||||
.to_be_bytes(),
|
||||
);
|
||||
stream.extend_from_slice(path_bytes);
|
||||
stream.push(entry.kind);
|
||||
stream.extend_from_slice(&(entry.metadata.mode() & 0o7777).to_be_bytes());
|
||||
let payload_length = u64::try_from(entry.payload.len())
|
||||
.map_err(|_| io::Error::other("fixture payload is too large"))?;
|
||||
stream.extend_from_slice(&payload_length.to_be_bytes());
|
||||
stream.extend_from_slice(&Sha256::digest(&entry.payload));
|
||||
if entry.kind == 2 {
|
||||
regular_files = regular_files.saturating_add(1);
|
||||
content_bytes = content_bytes.saturating_add(payload_length);
|
||||
}
|
||||
}
|
||||
Ok(MetadataResult {
|
||||
regular_files,
|
||||
content_bytes,
|
||||
fixture_identity: Sha256::digest(stream).into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn collect_entries(
|
||||
root: &Path,
|
||||
relative: &Path,
|
||||
entries: &mut Vec<FixtureEntry>,
|
||||
) -> io::Result<()> {
|
||||
let mut children = fs::read_dir(root.join(relative))?
|
||||
.map(|entry| entry.map(|entry| entry.file_name()))
|
||||
.collect::<io::Result<Vec<_>>>()?;
|
||||
children.sort_by(|left, right| left.as_bytes().cmp(right.as_bytes()));
|
||||
for name in children {
|
||||
let child_relative = relative.join(name);
|
||||
let path = root.join(&child_relative);
|
||||
let metadata = fs::symlink_metadata(&path)?;
|
||||
statx(
|
||||
rustix::fs::CWD,
|
||||
&path,
|
||||
AtFlags::SYMLINK_NOFOLLOW,
|
||||
StatxFlags::BASIC_STATS,
|
||||
)?;
|
||||
let file_type = metadata.file_type();
|
||||
let (kind, payload) = if file_type.is_dir() {
|
||||
(1, Vec::new())
|
||||
} else if file_type.is_file() {
|
||||
let mut payload = Vec::new();
|
||||
fs::File::open(&path)?.read_to_end(&mut payload)?;
|
||||
(2, payload)
|
||||
} else if file_type.is_symlink() {
|
||||
(3, fs::read_link(&path)?.into_os_string().into_vec())
|
||||
} else if file_type.is_socket()
|
||||
|| file_type.is_fifo()
|
||||
|| file_type.is_block_device()
|
||||
|| file_type.is_char_device()
|
||||
{
|
||||
return Err(io::Error::other(
|
||||
"fixture contains an unsupported entry kind",
|
||||
));
|
||||
} else {
|
||||
return Err(io::Error::other("fixture entry kind is unknown"));
|
||||
};
|
||||
entries.push(FixtureEntry {
|
||||
relative: child_relative.clone(),
|
||||
metadata,
|
||||
kind,
|
||||
payload,
|
||||
});
|
||||
if kind == 1 {
|
||||
collect_entries(root, &child_relative, entries)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hex(bytes: &[u8]) -> String {
|
||||
const DIGITS: &[u8; 16] = b"0123456789abcdef";
|
||||
let mut encoded = String::with_capacity(bytes.len() * 2);
|
||||
for byte in bytes {
|
||||
encoded.push(char::from(DIGITS[usize::from(byte >> 4)]));
|
||||
encoded.push(char::from(DIGITS[usize::from(byte & 0x0f)]));
|
||||
}
|
||||
encoded
|
||||
}
|
||||
|
||||
struct FixtureEntry {
|
||||
relative: PathBuf,
|
||||
metadata: fs::Metadata,
|
||||
kind: u8,
|
||||
payload: Vec<u8>,
|
||||
}
|
||||
|
||||
struct MetadataResult {
|
||||
regular_files: u64,
|
||||
content_bytes: u64,
|
||||
fixture_identity: [u8; 32],
|
||||
}
|
||||
Loading…
Reference in a new issue