Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-implement get_buildlog in rust #443

Merged
merged 3 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions harmonia/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ percent-encoding = "2.3.1"
anyhow = "1.0.91"
tempfile = "3.10.1"
url = "2.4.1"
async-compression = { version = "0.4.17", features = ["tokio", "bzip2"] }
tokio-util = "0.7.12"


libnixstore = { path = "../libnixstore" }
Expand Down
97 changes: 88 additions & 9 deletions harmonia/src/buildlog.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,100 @@
use std::error::Error;

use actix_web::{http, web, HttpResponse};
use actix_files::NamedFile;
use actix_web::http::header::HeaderValue;
use actix_web::Responder;
use actix_web::{http, web, HttpRequest, HttpResponse};
use anyhow::Context;
use async_compression::tokio::bufread::BzDecoder;
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::path::PathBuf;
use tokio::io::BufReader;
use tokio_util::io::ReaderStream;

use crate::config::Config;
use crate::{cache_control_max_age_1y, cache_control_no_store, nixhash, some_or_404};

fn query_drv_path(drv: &str) -> Option<String> {
nixhash(if drv.len() > 32 { &drv[0..32] } else { drv })
}

pub(crate) async fn get(drv: web::Path<String>) -> Result<HttpResponse, Box<dyn Error>> {
pub fn get_build_log(store: &Path, drv_path: &Path) -> Option<PathBuf> {
let drv_name = drv_path.file_name()?.as_bytes();
let log_path = match store.parent().map(|p| {
p.join("var")
.join("log")
.join("nix")
.join("drvs")
.join(OsStr::from_bytes(&drv_name[0..2]))
.join(OsStr::from_bytes(&drv_name[2..]))
}) {
Some(log_path) => log_path,
None => return None,
};
if log_path.exists() {
return Some(log_path);
}
// check if compressed log exists
let log_path = log_path.with_extension("drv.bz2");
if log_path.exists() {
Some(log_path)
} else {
None
}
}

pub(crate) async fn get(
drv: web::Path<String>,
req: HttpRequest,
settings: web::Data<Config>,
) -> Result<HttpResponse, Box<dyn std::error::Error>> {
let drv_path = some_or_404!(query_drv_path(&drv));
if libnixstore::is_valid_path(&drv_path) {
let build_log = some_or_404!(libnixstore::get_build_log(&drv_path));
return Ok(HttpResponse::Ok()
.insert_header(http::header::ContentType(mime::TEXT_PLAIN_UTF_8))
.insert_header(cache_control_max_age_1y())
.body(build_log));
let build_log = some_or_404!(get_build_log(
settings.store.real_store(),
&PathBuf::from(drv_path)
));
if let Some(ext) = build_log.extension() {
let accept_encoding = req
.headers()
.get(http::header::ACCEPT_ENCODING)
.and_then(|value| value.to_str().ok())
.unwrap_or("");

if ext == "bz2" && !accept_encoding.contains("bzip2") {
// Decompress the bz2 file and serve the decompressed content
let file = tokio::fs::File::open(&build_log).await.with_context(|| {
format!("Failed to open build log: {:?}", build_log.display())
})?;
let reader = BufReader::new(file);
let decompressed_stream = BzDecoder::new(reader);
let stream = ReaderStream::new(decompressed_stream);
let body = actix_web::body::BodyStream::new(stream);

return Ok(HttpResponse::Ok()
.insert_header(cache_control_max_age_1y())
.insert_header(http::header::ContentType(mime::TEXT_PLAIN_UTF_8))
.body(body));
} else {
// Serve the file as-is with the appropriate Content-Encoding header
let encoding = if ext == "bz2" {
HeaderValue::from_static("bzip2")
} else {
HeaderValue::from_static("identity")
};

let log = NamedFile::open_async(&build_log)
.await
.with_context(|| {
format!("Failed to open build log: {:?}", build_log.display())
})?
.customize()
.insert_header(cache_control_max_age_1y())
.insert_header(("Content-Encoding", encoding));

return Ok(log.respond_to(&req).map_into_boxed_body());
}
}
}
Ok(HttpResponse::NotFound()
.insert_header(cache_control_no_store())
Expand Down
7 changes: 4 additions & 3 deletions harmonia/src/nar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ pub(crate) async fn get(
.insert_header(crate::cache_control_no_store())
.body("hash mismatch detected"));
}
let store_path = PathBuf::from(&store_path);

let mut rlength = info.size;
let offset;
Expand Down Expand Up @@ -394,7 +395,7 @@ pub(crate) async fn get(

let err = dump_path(settings.store.get_real_path(&store_path), &tx2).await;
if let Err(err) = err {
log::error!("Error dumping path {}: {:?}", store_path, err);
log::error!("Error dumping path {}: {:?}", store_path.display(), err);
}
});
// we keep this closure extra to avoid unaligned copies in the non-range request case.
Expand Down Expand Up @@ -434,7 +435,7 @@ pub(crate) async fn get(
task::spawn(async move {
let err = dump_path(settings.store.get_real_path(&store_path), &tx).await;
if let Err(err) = err {
log::error!("Error dumping path {}: {:?}", store_path, err);
log::error!("Error dumping path {}: {:?}", store_path.display(), err);
}
});
};
Expand All @@ -456,7 +457,7 @@ mod test {
let store = Store::new();
let (tx, mut rx) = tokio::sync::mpsc::channel::<Result<Bytes, ThreadSafeError>>(1000);
task::spawn(async move {
let e = dump_path(store.get_real_path(&path), &tx).await;
let e = dump_path(store.get_real_path(&PathBuf::from(&path)), &tx).await;
if let Err(e) = e {
eprintln!("Error dumping path: {:?}", e);
}
Expand Down
2 changes: 1 addition & 1 deletion harmonia/src/narlist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ pub(crate) async fn get(
hash: web::Path<String>,
settings: web::Data<Config>,
) -> Result<HttpResponse, Box<dyn Error>> {
let store_path = some_or_404!(nixhash(&hash));
let store_path = PathBuf::from(some_or_404!(nixhash(&hash)));

let nar_list = get_nar_list(settings.store.get_real_path(&store_path)).await?;
Ok(HttpResponse::Ok()
Expand Down
6 changes: 4 additions & 2 deletions harmonia/src/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ fn file_size(bytes: u64) -> String {
pub(crate) fn directory_listing(
url_prefix: &Path,
fs_path: &Path,
real_store: &str,
real_store: &Path,
) -> ServerResult {
let path_without_store = fs_path.strip_prefix(real_store).unwrap_or(fs_path);
let index_of = format!(
Expand Down Expand Up @@ -135,7 +135,9 @@ pub(crate) async fn get(
let (hash, dir) = path.into_inner();
let dir = dir.strip_prefix("/").unwrap_or(&dir);

let store_path = settings.store.get_real_path(&some_or_404!(nixhash(&hash)));
let store_path = settings
.store
.get_real_path(&PathBuf::from(&some_or_404!(nixhash(&hash))));
let full_path = if dir == Path::new("") {
store_path.clone()
} else {
Expand Down
26 changes: 11 additions & 15 deletions harmonia/src/store.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::path::Path;
use std::path::PathBuf;

#[derive(Default, Debug)]
pub struct Store {
virtual_store: String,
real_store: Option<String>,
virtual_store: PathBuf,
real_store: Option<PathBuf>,
}

impl Store {
Expand All @@ -13,30 +14,25 @@ impl Store {

if virtual_store == real_store {
return Self {
virtual_store,
virtual_store: PathBuf::from(virtual_store),
real_store: None,
};
}
Self {
virtual_store,
real_store: Some(real_store),
virtual_store: PathBuf::from(virtual_store),
real_store: Some(PathBuf::from(real_store)),
}
}
pub fn get_real_path(&self, virtual_path: &str) -> PathBuf {
pub fn get_real_path(&self, virtual_path: &Path) -> PathBuf {
if let Some(real_store) = &self.real_store {
if virtual_path.starts_with(&self.virtual_store) {
return PathBuf::from(format!(
"{}{}",
real_store,
&virtual_path[self.virtual_store.len()..]
));
return real_store.join(virtual_path.strip_prefix(&self.virtual_store).unwrap());
}
}
PathBuf::from(virtual_path)
}
pub fn real_store(&self) -> &str {
self.real_store
.as_ref()
.map_or(&self.virtual_store, |s| s.as_str())

pub fn real_store(&self) -> &Path {
self.real_store.as_ref().unwrap_or(&self.virtual_store)
}
}
11 changes: 0 additions & 11 deletions libnixstore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ mod ffi {
fn query_path_from_hash_part(hash_part: &str) -> Result<String>;
fn get_store_dir() -> String;
fn get_real_store_dir() -> String;
fn get_build_log(derivation_path: &str) -> Result<String>;
}
}

Expand Down Expand Up @@ -141,13 +140,3 @@ pub fn get_store_dir() -> String {
pub fn get_real_store_dir() -> String {
ffi::get_real_store_dir()
}

#[inline]
#[must_use]
/// Return the build log of the specified store path, if available, or null otherwise.
pub fn get_build_log(derivation_path: &str) -> Option<String> {
match ffi::get_build_log(derivation_path) {
Ok(v) => string_to_opt(v),
Err(_) => None,
}
}
Loading