bring code from attic
This commit is contained in:
parent
39792cdd40
commit
c956d6741a
3 changed files with 539 additions and 0 deletions
270
src/bindings/mod.rs
Normal file
270
src/bindings/mod.rs
Normal file
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
Copyright 2022 Zhaofeng Li and the Attic contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
//! `libnixstore` Bindings
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use futures::stream::{Stream, StreamExt};
|
||||
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||
|
||||
use crate::{AtticError, AtticResult};
|
||||
|
||||
// The C++ implementation takes care of concurrency
|
||||
#[repr(transparent)]
|
||||
pub struct FfiNixStore(UnsafeCell<cxx::UniquePtr<ffi::CNixStore>>);
|
||||
|
||||
unsafe impl Send for FfiNixStore {}
|
||||
unsafe impl Sync for FfiNixStore {}
|
||||
|
||||
impl FfiNixStore {
|
||||
pub fn store(&self) -> Pin<&mut ffi::CNixStore> {
|
||||
unsafe {
|
||||
let ptr = self.0.get().as_mut().unwrap();
|
||||
ptr.pin_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain a handle to the Nix store.
|
||||
pub unsafe fn open_nix_store() -> AtticResult<FfiNixStore> {
|
||||
match ffi::open_nix_store() {
|
||||
Ok(ptr) => {
|
||||
let cell = UnsafeCell::new(ptr);
|
||||
Ok(FfiNixStore(cell))
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Benchmark different implementations
|
||||
// (tokio, crossbeam, flume)
|
||||
mod mpsc {
|
||||
// Tokio
|
||||
pub use tokio::sync::mpsc::{
|
||||
UnboundedReceiver, UnboundedSender, error::SendError, unbounded_channel,
|
||||
};
|
||||
}
|
||||
|
||||
/// Async write request.
|
||||
#[derive(Debug)]
|
||||
enum AsyncWriteMessage {
|
||||
Data(Vec<u8>),
|
||||
Error(String),
|
||||
Eof,
|
||||
}
|
||||
|
||||
/// Async write request sender.
|
||||
#[derive(Clone)]
|
||||
pub struct AsyncWriteSender {
|
||||
sender: mpsc::UnboundedSender<AsyncWriteMessage>,
|
||||
}
|
||||
|
||||
impl AsyncWriteSender {
|
||||
fn send(&mut self, data: &[u8]) -> Result<(), mpsc::SendError<AsyncWriteMessage>> {
|
||||
let message = AsyncWriteMessage::Data(Vec::from(data));
|
||||
self.sender.send(message)
|
||||
}
|
||||
|
||||
fn eof(&mut self) -> Result<(), mpsc::SendError<AsyncWriteMessage>> {
|
||||
let message = AsyncWriteMessage::Eof;
|
||||
self.sender.send(message)
|
||||
}
|
||||
|
||||
pub(crate) fn rust_error(
|
||||
&mut self,
|
||||
error: impl std::error::Error,
|
||||
) -> Result<(), impl std::error::Error> {
|
||||
let message = AsyncWriteMessage::Error(error.to_string());
|
||||
self.sender.send(message)
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper of the `AsyncWrite` trait for the synchronous Nix C++ land.
|
||||
pub struct AsyncWriteAdapter {
|
||||
receiver: mpsc::UnboundedReceiver<AsyncWriteMessage>,
|
||||
eof: bool,
|
||||
}
|
||||
|
||||
impl AsyncWriteAdapter {
|
||||
pub fn new() -> (Self, Box<AsyncWriteSender>) {
|
||||
let (sender, receiver) = mpsc::unbounded_channel();
|
||||
|
||||
let r = Self {
|
||||
receiver,
|
||||
eof: false,
|
||||
};
|
||||
let sender = Box::new(AsyncWriteSender { sender });
|
||||
|
||||
(r, sender)
|
||||
}
|
||||
|
||||
/// Write everything the sender sends to us.
|
||||
pub async fn write_all(mut self, mut writer: Box<dyn AsyncWrite + Unpin>) -> AtticResult<()> {
|
||||
let writer = writer.as_mut();
|
||||
|
||||
while let Some(data) = self.next().await {
|
||||
match data {
|
||||
Ok(v) => {
|
||||
writer.write_all(&v).await?;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !self.eof {
|
||||
Err(io::Error::from(io::ErrorKind::BrokenPipe).into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for AsyncWriteAdapter {
|
||||
type Item = AtticResult<Vec<u8>>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match self.receiver.poll_recv(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(Some(message)) => {
|
||||
use AsyncWriteMessage::*;
|
||||
match message {
|
||||
Data(v) => Poll::Ready(Some(Ok(v))),
|
||||
Error(exception) => {
|
||||
let error = AtticError::CxxError { exception };
|
||||
Poll::Ready(Some(Err(error)))
|
||||
}
|
||||
Eof => {
|
||||
self.eof = true;
|
||||
Poll::Ready(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
Poll::Ready(None) => {
|
||||
if !self.eof {
|
||||
Poll::Ready(Some(Err(io::Error::from(io::ErrorKind::BrokenPipe).into())))
|
||||
} else {
|
||||
Poll::Ready(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cxx::bridge]
|
||||
/// Generated by `cxx.rs`.
|
||||
///
|
||||
/// Mid-level wrapper of `libnixstore` implemented in C++.
|
||||
mod ffi {
|
||||
extern "Rust" {
|
||||
type AsyncWriteSender;
|
||||
fn send(self: &mut AsyncWriteSender, data: &[u8]) -> Result<()>;
|
||||
fn eof(self: &mut AsyncWriteSender) -> Result<()>;
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("attic/src/nix_store/bindings/nix.hpp");
|
||||
|
||||
// =========
|
||||
// CNixStore
|
||||
// =========
|
||||
|
||||
/// Mid-level wrapper for the Unix Domain Socket Nix Store.
|
||||
type CNixStore;
|
||||
|
||||
/// Returns the path of the Nix store itself.
|
||||
fn store_dir(self: Pin<&mut CNixStore>) -> String;
|
||||
|
||||
/*
|
||||
/// Verifies that a path is indeed in the Nix store, then return the base store path.
|
||||
///
|
||||
/// Use parse_store_path instead.
|
||||
fn to_store_path(self: Pin<&mut CNixStore>, path: &str) -> Result<String>;
|
||||
*/
|
||||
|
||||
/// Queries information about a valid path.
|
||||
fn query_path_info(
|
||||
self: Pin<&mut CNixStore>,
|
||||
store_path: &[u8],
|
||||
) -> Result<UniquePtr<CPathInfo>>;
|
||||
|
||||
/// Computes the closure of a valid path.
|
||||
///
|
||||
/// If `flip_directions` is true, the set of paths that can reach `store_path` is
|
||||
/// returned.
|
||||
fn compute_fs_closure(
|
||||
self: Pin<&mut CNixStore>,
|
||||
store_path: &[u8],
|
||||
flip_direction: bool,
|
||||
include_outputs: bool,
|
||||
include_derivers: bool,
|
||||
) -> Result<UniquePtr<CxxVector<CxxString>>>;
|
||||
|
||||
/// Computes the closure of a list of valid paths.
|
||||
///
|
||||
/// This is the multi-path variant of `compute_fs_closure`.
|
||||
/// If `flip_directions` is true, the set of paths that can reach `store_path` is
|
||||
/// returned.
|
||||
///
|
||||
/// It's easier and more efficient to just pass a vector of slices
|
||||
/// instead of wrangling with concrete "extern rust" / "extern C++"
|
||||
/// types.
|
||||
fn compute_fs_closure_multi(
|
||||
self: Pin<&mut CNixStore>,
|
||||
base_names: &[&[u8]],
|
||||
flip_direction: bool,
|
||||
include_outputs: bool,
|
||||
include_derivers: bool,
|
||||
) -> Result<UniquePtr<CxxVector<CxxString>>>;
|
||||
|
||||
/// Creates a NAR dump from a path.
|
||||
fn nar_from_path(
|
||||
self: Pin<&mut CNixStore>,
|
||||
base_name: Vec<u8>,
|
||||
sender: Box<AsyncWriteSender>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Obtains a handle to the Nix store.
|
||||
fn open_nix_store() -> Result<UniquePtr<CNixStore>>;
|
||||
|
||||
// =========
|
||||
// CPathInfo
|
||||
// =========
|
||||
|
||||
/// Mid-level wrapper for the `nix::ValidPathInfo` struct.
|
||||
type CPathInfo;
|
||||
|
||||
/// Returns the SHA-256 hash of the store path.
|
||||
fn nar_sha256_hash(self: Pin<&mut CPathInfo>) -> &[u8];
|
||||
|
||||
/// Returns the size of the NAR.
|
||||
fn nar_size(self: Pin<&mut CPathInfo>) -> u64;
|
||||
|
||||
/// Returns the references of the store path.
|
||||
fn references(self: Pin<&mut CPathInfo>) -> UniquePtr<CxxVector<CxxString>>;
|
||||
|
||||
/// Returns the possibly invalid signatures attached to the store path.
|
||||
fn sigs(self: Pin<&mut CPathInfo>) -> UniquePtr<CxxVector<CxxString>>;
|
||||
|
||||
/// Returns the CA field of the store path.
|
||||
fn ca(self: Pin<&mut CPathInfo>) -> String;
|
||||
}
|
||||
}
|
173
src/bindings/nix.cpp
Normal file
173
src/bindings/nix.cpp
Normal file
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
Copyright 2022 Zhaofeng Li and the Attic contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// C++ side of the libnixstore glue.
|
||||
//
|
||||
// We implement a mid-level wrapper of the Nix Store interface,
|
||||
// which is then wrapped again in the Rust side to enable full
|
||||
// async-await operation.
|
||||
//
|
||||
// Here we stick with the naming conventions of Rust and handle
|
||||
// Rust types directly where possible, so that the interfaces are
|
||||
// satisfying to use from the Rust side via cxx.rs.
|
||||
|
||||
#include "attic/src/nix_store/bindings/nix.hpp"
|
||||
|
||||
static std::mutex g_init_nix_mutex;
|
||||
static bool g_init_nix_done = false;
|
||||
|
||||
static nix::StorePath store_path_from_rust(RBasePathSlice base_name) {
|
||||
std::string_view sv((const char *)base_name.data(), base_name.size());
|
||||
return nix::StorePath(sv);
|
||||
}
|
||||
|
||||
static bool hash_is_sha256(const nix::Hash &hash) {
|
||||
#ifdef ATTIC_NIX_2_20
|
||||
return hash.algo == nix::HashAlgorithm::SHA256;
|
||||
#else
|
||||
return hash.type == nix::htSHA256;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ========
|
||||
// RustSink
|
||||
// ========
|
||||
|
||||
RustSink::RustSink(RBox<AsyncWriteSender> sender) : sender(std::move(sender)) {}
|
||||
|
||||
void RustSink::operator () (std::string_view data) {
|
||||
RBasePathSlice s((const unsigned char *)data.data(), data.size());
|
||||
|
||||
this->sender->send(s);
|
||||
}
|
||||
|
||||
void RustSink::eof() {
|
||||
this->sender->eof();
|
||||
}
|
||||
|
||||
|
||||
// =========
|
||||
// CPathInfo
|
||||
// =========
|
||||
|
||||
CPathInfo::CPathInfo(nix::ref<const nix::ValidPathInfo> pi) : pi(pi) {}
|
||||
|
||||
RHashSlice CPathInfo::nar_sha256_hash() {
|
||||
auto &hash = this->pi->narHash;
|
||||
|
||||
if (!hash_is_sha256(hash)) {
|
||||
throw nix::Error("Only SHA-256 hashes are supported at the moment");
|
||||
}
|
||||
|
||||
return RHashSlice(hash.hash, hash.hashSize);
|
||||
}
|
||||
|
||||
uint64_t CPathInfo::nar_size() {
|
||||
return this->pi->narSize;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<std::string>> CPathInfo::sigs() {
|
||||
std::vector<std::string> result;
|
||||
for (auto&& elem : this->pi->sigs) {
|
||||
result.push_back(std::string(elem));
|
||||
}
|
||||
return std::make_unique<std::vector<std::string>>(result);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<std::string>> CPathInfo::references() {
|
||||
std::vector<std::string> result;
|
||||
for (auto&& elem : this->pi->references) {
|
||||
result.push_back(std::string(elem.to_string()));
|
||||
}
|
||||
return std::make_unique<std::vector<std::string>>(result);
|
||||
}
|
||||
|
||||
RString CPathInfo::ca() {
|
||||
if (this->pi->ca) {
|
||||
return RString(nix::renderContentAddress(this->pi->ca));
|
||||
} else {
|
||||
return RString("");
|
||||
}
|
||||
}
|
||||
|
||||
// =========
|
||||
// CNixStore
|
||||
// =========
|
||||
|
||||
CNixStore::CNixStore() {
|
||||
std::map<std::string, std::string> params;
|
||||
std::lock_guard<std::mutex> lock(g_init_nix_mutex);
|
||||
|
||||
if (!g_init_nix_done) {
|
||||
nix::initNix();
|
||||
g_init_nix_done = true;
|
||||
}
|
||||
|
||||
this->store = nix::openStore(nix::settings.storeUri.get(), params);
|
||||
}
|
||||
|
||||
RString CNixStore::store_dir() {
|
||||
return RString(this->store->storeDir);
|
||||
}
|
||||
|
||||
std::unique_ptr<CPathInfo> CNixStore::query_path_info(RBasePathSlice base_name) {
|
||||
auto store_path = store_path_from_rust(base_name);
|
||||
|
||||
auto r = this->store->queryPathInfo(store_path);
|
||||
return std::make_unique<CPathInfo>(r);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<std::string>> CNixStore::compute_fs_closure(RBasePathSlice base_name, bool flip_direction, bool include_outputs, bool include_derivers) {
|
||||
std::set<nix::StorePath> out;
|
||||
|
||||
this->store->computeFSClosure(store_path_from_rust(base_name), out, flip_direction, include_outputs, include_derivers);
|
||||
|
||||
std::vector<std::string> result;
|
||||
for (auto&& elem : out) {
|
||||
result.push_back(std::string(elem.to_string()));
|
||||
}
|
||||
return std::make_unique<std::vector<std::string>>(result);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<std::string>> CNixStore::compute_fs_closure_multi(RSlice<const RBasePathSlice> base_names, bool flip_direction, bool include_outputs, bool include_derivers) {
|
||||
std::set<nix::StorePath> path_set, out;
|
||||
for (auto&& base_name : base_names) {
|
||||
path_set.insert(store_path_from_rust(base_name));
|
||||
}
|
||||
|
||||
this->store->computeFSClosure(path_set, out, flip_direction, include_outputs, include_derivers);
|
||||
|
||||
std::vector<std::string> result;
|
||||
for (auto&& elem : out) {
|
||||
result.push_back(std::string(elem.to_string()));
|
||||
}
|
||||
return std::make_unique<std::vector<std::string>>(result);
|
||||
}
|
||||
|
||||
void CNixStore::nar_from_path(RVec<unsigned char> base_name, RBox<AsyncWriteSender> sender) {
|
||||
RustSink sink(std::move(sender));
|
||||
|
||||
std::string_view sv((const char *)base_name.data(), base_name.size());
|
||||
nix::StorePath store_path(sv);
|
||||
|
||||
// exceptions will be thrown into Rust
|
||||
this->store->narFromPath(store_path, sink);
|
||||
sink.eof();
|
||||
}
|
||||
|
||||
std::unique_ptr<CNixStore> open_nix_store() {
|
||||
return std::make_unique<CNixStore>();
|
||||
}
|
96
src/bindings/nix.hpp
Normal file
96
src/bindings/nix.hpp
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
Copyright 2022 Zhaofeng Li and the Attic contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// C++ side of the libnixstore glue.
|
||||
//
|
||||
// We implement a mid-level wrapper of the Nix Store interface,
|
||||
// which is then wrapped again in the Rust side to enable full
|
||||
// async-await operation.
|
||||
//
|
||||
// Here we stick with the naming conventions of Rust and handle
|
||||
// Rust types directly where possible, so that the interfaces are
|
||||
// satisfying to use from the Rust side via cxx.rs.
|
||||
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <nix/store-api.hh>
|
||||
#include <nix/local-store.hh>
|
||||
#include <nix/remote-store.hh>
|
||||
#include <nix/uds-remote-store.hh>
|
||||
#include <nix/hash.hh>
|
||||
#include <nix/path.hh>
|
||||
#include <nix/serialise.hh>
|
||||
#include <nix/shared.hh>
|
||||
#include <rust/cxx.h>
|
||||
|
||||
template<class T> using RVec = rust::Vec<T>;
|
||||
template<class T> using RBox = rust::Box<T>;
|
||||
template<class T> using RSlice = rust::Slice<T>;
|
||||
using RString = rust::String;
|
||||
using RStr = rust::Str;
|
||||
using RBasePathSlice = RSlice<const unsigned char>;
|
||||
using RHashSlice = RSlice<const unsigned char>;
|
||||
|
||||
struct AsyncWriteSender;
|
||||
|
||||
struct RustSink : nix::Sink
|
||||
{
|
||||
RBox<AsyncWriteSender> sender;
|
||||
public:
|
||||
RustSink(RBox<AsyncWriteSender> sender);
|
||||
void operator () (std::string_view data) override;
|
||||
void eof();
|
||||
};
|
||||
|
||||
// Opaque wrapper for nix::ValidPathInfo
|
||||
class CPathInfo {
|
||||
nix::ref<const nix::ValidPathInfo> pi;
|
||||
public:
|
||||
CPathInfo(nix::ref<const nix::ValidPathInfo> pi);
|
||||
RHashSlice nar_sha256_hash();
|
||||
uint64_t nar_size();
|
||||
std::unique_ptr<std::vector<std::string>> sigs();
|
||||
std::unique_ptr<std::vector<std::string>> references();
|
||||
RString ca();
|
||||
};
|
||||
|
||||
class CNixStore {
|
||||
std::shared_ptr<nix::Store> store;
|
||||
public:
|
||||
CNixStore();
|
||||
|
||||
RString store_dir();
|
||||
std::unique_ptr<CPathInfo> query_path_info(RBasePathSlice base_name);
|
||||
std::unique_ptr<std::vector<std::string>> compute_fs_closure(
|
||||
RBasePathSlice base_name,
|
||||
bool flip_direction,
|
||||
bool include_outputs,
|
||||
bool include_derivers);
|
||||
std::unique_ptr<std::vector<std::string>> compute_fs_closure_multi(
|
||||
RSlice<const RBasePathSlice> base_names,
|
||||
bool flip_direction,
|
||||
bool include_outputs,
|
||||
bool include_derivers);
|
||||
void nar_from_path(RVec<unsigned char> base_name, RBox<AsyncWriteSender> sender);
|
||||
};
|
||||
|
||||
std::unique_ptr<CNixStore> open_nix_store();
|
||||
|
||||
// Relies on our definitions
|
||||
#include "attic/src/nix_store/bindings/mod.rs.h"
|
Loading…
Add table
Add a link
Reference in a new issue