1use std::cell::OnceCell;
1use std::collections::HashMap;2use std::collections::{BTreeMap, HashMap};
2use std::sync::{Arc, Mutex};3use std::sync::{Arc, Mutex, OnceLock};
34
5use anyhow::{Context, bail};
6use itertools::Itertools;
4use nix_eval::{NativeFn, Value};7use nix_eval::{NativeFn, Value, nix_go, nix_go_json};
8use serde::Deserialize;
5use tracing::info;9use tracing::{info, warn};
610
7use crate::fleetdata::{FleetData, FleetSecrets};11use crate::fleetdata::{FleetData, FleetSecrets};
12use crate::host::Config;
813
9#[derive(thiserror::Error, Debug)]14#[derive(thiserror::Error, Debug)]
10enum Error {}15enum Error {}
2328
24struct FsSecretsBackend {}29struct FsSecretsBackend {}
30
31pub static PRIMOPS_DATA: OnceLock<Config> = OnceLock::new();
32
33#[derive(Deserialize, Debug)]
34struct GeneratorPart {
35 encrypted: bool,
36}
2537
26pub fn init_primops(secrets: Arc<Mutex<FleetData>>) {38pub fn init_primops() {
27 info!("initializing primops");39 info!("initializing primops");
28 NativeFn::new(40 NativeFn::new(
29 c"__fleetEnsureHostSecret",41 c"__fleetEnsureHostSecret",
30 c"Ensure secret existence for a host, regenerating it in case of some mismatch",42 c"Ensure secret existence for a host, regenerating it in case of some mismatch",
31 [c"host", c"secret", c"generator"],43 [c"host", c"secret", c"generator"],
32 |[host, secret, generator]| {44 |es, [host, secret, generator]| {
45 info!("get host");
46 let host = host.to_string()?;
33 todo!("ensure secret");47 info!("get secret");
48 let secret = secret.to_string()?;
49
50 info!("get config");
51 let config = PRIMOPS_DATA
52 .get()
53 .expect("primops data should be set on init");
54
55 info!("get pkgs");
56 let nixpkgs = &config.nixpkgs;
57 let default_pkgs = &config.default_pkgs;
58 let default_mk_secret_generators = nix_go!(default_pkgs.mkSecretGenerators);
59 let generators = nix_go!(default_mk_secret_generators(Obj {
60 recipients: <Vec<String>>::new(),
61 }));
62 let pkgs_and_generators = default_pkgs.clone().attrs_update(generators)?;
63
64 info!("call package");
65 let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));
66 let default_generator = call_package
67 .call(generator.clone())
68 .context("calling callPackage with generator")?
69 .call(Value::new_attrs(HashMap::new()))
70 .context("providing extra callPackage args")?;
71
72 info!("get parts");
73 let mut parts: BTreeMap<String, GeneratorPart> = nix_go_json!(default_generator.parts);
74 info!("got parts: {parts:?}");
75
76 let Some(existing) = config
77 .host_secret(&host, &secret) else {
78 bail!("missing secret {secret} for host {host}; secret needs regeneration")
79 };
80
81 info!("got existing: {existing:?}");
82
83 let mut out = HashMap::new();
84
85 for (part_name, part) in &existing.secret.parts {
86 let Some(definition) = parts.remove(part_name) else {
87 warn!("secret {secret} part {part_name} is stored, but not defined in nixos config, it will not be passed to nix");
88 continue;
89 };
90 if definition.encrypted != part.raw.encrypted {
91 bail!("secret {secret} part {part_name} is supposed to be {}, but it is {}; secret needs regeneration", if definition.encrypted {"encrypted"} else {"unencrypted"}, if part.raw.encrypted {"encrypted"} else {"unencrypted"});
92 }
34 Ok(Value::new_attrs(HashMap::from_iter([(93 out.insert(part_name.as_str(), Value::new_attrs(HashMap::from_iter([("raw", Value::new_str(&part.raw.to_string()))])));
35 "raw",94 }
36 Value::new_str("rawData"),95 if !parts.is_empty(){
37 )])))96 let defs = parts.keys().collect_vec();
97 bail!("secret parts are defined, but not stored: {defs:?}, secret needs regeneration")
98 }
99
100 Ok(Value::new_attrs(out))
38 },101 },
39 )102 )
40 .register();103 .register();
--- a/crates/nix-eval/src/lib.rs
+++ b/crates/nix-eval/src/lib.rs
@@ -12,7 +12,7 @@
use serde::de::DeserializeOwned;
pub use anyhow::Result;
-use tracing::instrument;
+use tracing::{info, instrument, warn};
use self::logging::{ErrorInfoBuilder, nix_logging_cxx};
use self::nix_cxx::set_fetcher_setting;
@@ -22,7 +22,8 @@
GC_thread_is_registered, GC_unregister_my_thread, ListBuilder as c_list_builder, PrimOp,
PrimOpFun, Store as c_store, StorePath as c_store_path, alloc_primop, alloc_value,
bindings_builder_free, bindings_builder_insert, c_context, c_context_create, c_context_free,
- clear_err, copy_value, err_code, err_info_msg, err_msg, eval_state_build,
+ clear_err, copy_value, err_NIX_ERR_KEY, err_NIX_ERR_NIX_ERROR, err_NIX_ERR_OVERFLOW,
+ err_NIX_ERR_UNKNOWN, err_code, err_info_msg, err_msg, eval_state_build,
eval_state_builder_load, eval_state_builder_new, eval_state_builder_set_eval_setting,
expr_eval_from_string, fetchers_settings, fetchers_settings_free, fetchers_settings_new,
flake_lock, flake_lock_flags, flake_lock_flags_free, flake_lock_flags_new, flake_reference,
@@ -36,9 +37,8 @@
make_attrs, make_bindings_builder, make_list, make_list_builder, realised_string,
realised_string_free, realised_string_get_buffer_size, realised_string_get_buffer_start,
realised_string_get_store_path, realised_string_get_store_path_count, register_primop,
- set_err_msg, setting_set, state_free, store_copy_closure, store_get_fs_closure, store_open,
- store_parse_path, store_path_free, store_path_name, string_realise, value, value_call,
- value_decref, value_incref,
+ set_err_msg, setting_set, state_free, store_open, store_parse_path, store_path_free,
+ store_path_name, string_realise, value, value_call, value_decref, value_force, value_incref,
};
// Contains macros helpers
@@ -62,6 +62,7 @@
type nix_fetchers_settings;
include!("nix-eval/src/lib.hh");
+ #[allow(clippy::missing_safety_doc)]
unsafe fn set_fetcher_setting(
settings: *mut nix_fetchers_settings,
setting: *const c_char,
@@ -111,19 +112,19 @@
#[derive(Debug)]
#[repr(i32)]
pub enum NixErrorKind {
- Unknown = 1,
- Overflow = 2,
- Key = 3,
- Generic = 4,
+ Unknown = err_NIX_ERR_UNKNOWN,
+ Overflow = err_NIX_ERR_OVERFLOW,
+ Key = err_NIX_ERR_KEY,
+ Generic = err_NIX_ERR_NIX_ERROR,
}
impl NixErrorKind {
fn from_int(v: c_int) -> Option<Self> {
Some(match v {
0 => return None,
- -1 => Self::Unknown,
- -2 => Self::Overflow,
- -3 => Self::Key,
- -4 => Self::Generic,
+ nix_raw::err_NIX_ERR_UNKNOWN => Self::Unknown,
+ nix_raw::err_NIX_ERR_OVERFLOW => Self::Overflow,
+ nix_raw::err_NIX_ERR_KEY => Self::Key,
+ nix_raw::err_NIX_ERR_NIX_ERROR => Self::Generic,
_ => {
debug_assert!(false, "unexpected nix error kind: {v}");
Self::Unknown
@@ -298,8 +299,10 @@
}
}
-static GLOBAL_STATE: LazyLock<GlobalState> =
- LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));
+static GLOBAL_STATE: LazyLock<GlobalState> = LazyLock::new(|| {
+ info!("initializing nix global state");
+ GlobalState::new().expect("global state init shouldn't fail")
+});
thread_local! {
static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));
@@ -424,7 +427,8 @@
}
}
-struct EvalState(*mut c_eval_state);
+#[repr(transparent)]
+pub struct EvalState(*mut c_eval_state);
unsafe impl Send for EvalState {}
unsafe impl Sync for EvalState {}
@@ -697,7 +701,15 @@
let builtin = Self::eval("builtins.toString")?;
builtin.call(self.clone())
}
+ fn force(&mut self, s: *mut nix_raw::EvalState) -> Result<()> {
+ with_default_context(|c, _| unsafe { value_force(c, s, self.0) })?;
+ Ok(())
+ }
pub fn to_string(&self) -> Result<String> {
+ let ty = self.type_of();
+ if !matches!(ty, NixType::String) {
+ bail!("unexpected type: {ty:?}, expected string");
+ }
let mut str_out = String::new();
with_default_context(|c, _| unsafe {
get_string(c, self.0, Some(copy_nix_str), (&raw mut str_out).cast())
@@ -931,17 +943,39 @@
ret: *mut value,
) {
let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };
+ let mut e = None;
let args: [&Value; N] = array::from_fn(|i| {
- let v: &Value = unsafe { &*args.add(i).cast_const().cast() };
- v
+ let v: &mut Value = unsafe { &mut *args.add(i).cast() };
+
+ info!("forcing arg");
+ if matches!(v.type_of(), NixType::Thunk)
+ && let Err(err) = v.force(state)
+ {
+ e = Some(err);
+ };
+ v as &Value
});
+ info!("args forced");
let ctx: &mut NixContext = unsafe { &mut *context.cast() };
- match user_closure(args) {
+ if let Some(e) = e {
+ warn!("set err = {e}");
+ unsafe { init_int(context, ret, 0) };
+ return ctx.set_err(
+ NixErrorKind::Unknown,
+ &CString::new(e.to_string()).expect("forcing argument value failed"),
+ );
+ }
+
+ let state: &EvalState = unsafe { std::mem::transmute(&state) };
+
+ match user_closure(state, args) {
Ok(v) => {
unsafe { copy_value(context, ret, v.0) };
}
Err(e) => {
+ unsafe { init_int(context, ret, 0) };
+ warn!("set err = {e:#?}");
ctx.set_err(
NixErrorKind::Unknown,
&CString::new(e.to_string()).expect("error should not contain internal nuls"),
@@ -950,7 +984,7 @@
}
}
-type UserClosure<const N: usize> = Box<dyn Fn([&Value; N]) -> Result<Value>>;
+type UserClosure<const N: usize> = Box<dyn Fn(&EvalState, [&Value; N]) -> Result<Value>>;
pub struct NativeFn(*mut PrimOp);
impl NativeFn {
@@ -958,7 +992,7 @@
name: &'static CStr,
doc: &'static CStr,
args: [&'static CStr; N],
- f: impl Fn([&Value; N]) -> Result<Value> + 'static,
+ f: impl Fn(&EvalState, [&Value; N]) -> Result<Value> + 'static,
) -> Self {
// Double-boxing to make it thin pointer, as vtable gets outside of first Box
let closure: Box<UserClosure<N>> = Box::new(Box::new(f));
@@ -1003,7 +1037,7 @@
c"__uppercaseSuffix2",
c"make string uppercase and add suffix",
[c"str", c"suffix"],
- |[str, suffix]: [&Value; 2]| {
+ |_, [str, suffix]: [&Value; 2]| {
let str = str.to_string()?;
let suffix = suffix.to_string()?;
Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))
@@ -1038,7 +1072,7 @@
c"uppercase_suffix",
c"make string uppercase and add suffix",
[c"str", c"suffix"],
- |[str, suffix]: [&Value; 2]| {
+ |es, [str, suffix]: [&Value; 2]| {
let str = str.to_string()?;
let suffix = suffix.to_string()?;
Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))