git.delta.rocks / fleet / refs/commits / 992c7b63e98f

difftreelog

feat postgres secret generator

xmvqpnskYaroslav Bolyukin2026-05-20parent: #7fa1963.patch.diff

10 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1045,7 +1045,10 @@
  "ed25519-dalek",
  "fleet-shared",
  "hex",
+ "hmac",
+ "pbkdf2",
  "rand 0.10.1",
+ "sha2",
  "x25519-dalek",
 ]
 
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -31,6 +31,7 @@
 futures = "0.3.31"
 futures-util = { version = "0.3.31", features = ["sink"] }
 hex = "0.4.3"
+hmac = "0.12"
 hostname = "0.4.1"
 human-repr = "1.1"
 hyper = "1.8.1"
@@ -40,11 +41,12 @@
 linked-hash-map = "0.5.6"
 nix = { version = "0.31.2", features = ["fs", "user"] }
 nom = "8.0.0"
+openssh = "0.11.5"
 opentelemetry = "0.31.0"
+opentelemetry-appender-tracing = "0.31.1"
 opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "gzip-tonic", "http-json", "reqwest-rustls"] }
 opentelemetry_sdk = "0.31.0"
-opentelemetry-appender-tracing = "0.31.1"
-openssh = "0.11.5"
+pbkdf2 = "0.12"
 peg = "0.8.5"
 pkg-config = "0.3.30"
 rand = "0.10.0"
@@ -52,6 +54,7 @@
 serde = { version = "1.0", features = ["derive"] }
 serde-transcode = "1.1.1"
 serde_json = "1.0"
+sha2 = "0.10"
 shlex = "1.3"
 tabled = "0.20.0"
 tempfile = "3.20"
modifiedcmds/fleet/Cargo.tomldiffbeforeafterboth
--- a/cmds/fleet/Cargo.toml
+++ b/cmds/fleet/Cargo.toml
@@ -44,12 +44,12 @@
 indicatif = { workspace = true, optional = true }
 nom.workspace = true
 opentelemetry.workspace = true
+opentelemetry-appender-tracing.workspace = true
+opentelemetry-exporter-env.workspace = true
 opentelemetry_sdk.workspace = true
 thiserror.workspace = true
 tracing-indicatif = { workspace = true, optional = true }
 tracing-opentelemetry.workspace = true
-opentelemetry-exporter-env.workspace = true
-opentelemetry-appender-tracing.workspace = true
 
 [features]
 default = ["indicatif"]
modifiedcmds/generator-helper/Cargo.tomldiffbeforeafterboth
--- a/cmds/generator-helper/Cargo.toml
+++ b/cmds/generator-helper/Cargo.toml
@@ -13,5 +13,8 @@
 base64.workspace = true
 ed25519-dalek.workspace = true
 hex.workspace = true
+hmac.workspace = true
+pbkdf2.workspace = true
 rand.workspace = true
+sha2.workspace = true
 x25519-dalek.workspace = true
modifiedcmds/generator-helper/src/main.rsdiffbeforeafterboth
--- a/cmds/generator-helper/src/main.rs
+++ b/cmds/generator-helper/src/main.rs
@@ -10,14 +10,30 @@
 	ssh::{ParseRecipientKeyError, Recipient as SshRecipient},
 };
 use anyhow::{Context, Result, anyhow, bail, ensure};
+use base64::{Engine as _, engine::general_purpose::STANDARD, write::EncoderWriter};
 use clap::{Parser, ValueEnum};
 use ed25519_dalek::SecretKey;
 use fleet_shared::SecretData;
+use hmac::Mac as _;
 use rand::{
 	Rng as _,
 	distr::{Alphanumeric, Distribution, SampleString, Uniform},
 	rng,
 };
+use sha2::Digest as _;
+
+fn gen_password(rng: &mut impl rand::Rng, size: usize, no_symbols: bool) -> String {
+	if no_symbols {
+		Alphanumeric.sample_string(rng, size)
+	} else {
+		const GEN_ASCII_SYMBOLS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
+		let uniform = Uniform::new(0, GEN_ASCII_SYMBOLS.len()).expect("range is valid");
+		(0..size)
+			.map(|_| uniform.sample(rng))
+			.map(|i| GEN_ASCII_SYMBOLS[i] as char)
+			.collect::<String>()
+	}
+}
 
 fn write_output_file(out: &str) -> Result<File> {
 	let file = OpenOptions::new()
@@ -105,8 +121,6 @@
 	match encoding {
 		OutputEncoding::Raw => coerce(w),
 		OutputEncoding::Base64 => {
-			use base64::{engine::general_purpose::STANDARD, write::EncoderWriter};
-
 			let writer = EncoderWriter::new(w, &STANDARD);
 			coerce(writer)
 		}
@@ -177,6 +191,18 @@
 		#[arg(long, short = 'e', value_enum, default_value_t)]
 		encoding: OutputEncoding,
 	},
+	PostgresPassword {
+		#[arg(long, short = 's')]
+		secret: String,
+		#[arg(long, short = 'H')]
+		hash: String,
+		#[arg(long, default_value_t = 24)]
+		size: usize,
+		#[arg(long, default_value_t = 4096)]
+		iterations: u32,
+		#[arg(long, short = 'n')]
+		no_symbols: bool,
+	},
 	Bytes {
 		#[arg(long, short = 'o')]
 		output: String,
@@ -286,20 +312,62 @@
 						"misconfiguration? password is shorter than 6 chars"
 					);
 					let recipients = load_identities()?;
-					let out = if no_symbols {
-						Alphanumeric.sample_string(&mut rng, size)
-					} else {
-						// Alphabet of Alphanumberic + symbols
-						const GEN_ASCII_SYMBOLS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
-						let uniform =
-							Uniform::new(0, GEN_ASCII_SYMBOLS.len()).expect("range is valid");
-						(0..size)
-							.map(|_| uniform.sample(&mut rng))
-							.map(|i| GEN_ASCII_SYMBOLS[i] as char)
-							.collect::<String>()
-					};
+					let out = gen_password(&mut rng, size, no_symbols);
 					write_private(&recipients, &output, out.as_bytes(), encoding)?;
 				}
+				Generate::PostgresPassword {
+					secret,
+					hash,
+					size,
+					iterations,
+					no_symbols,
+				} => {
+					ensure!(
+						size >= 6,
+						"misconfiguration? password is shorter than 6 chars"
+					);
+					let recipients = load_identities()?;
+					let password = gen_password(&mut rng, size, no_symbols);
+
+					let mut salt = [0u8; 16];
+					rng.fill_bytes(&mut salt);
+					let salted = pbkdf2::pbkdf2_hmac_array::<sha2::Sha256, 32>(
+						password.as_bytes(),
+						&salt,
+						iterations,
+					);
+
+					type HmacSha256 = hmac::Hmac<sha2::Sha256>;
+					let mut mac = <HmacSha256 as hmac::Mac>::new_from_slice(&salted)
+						.expect("HMAC accepts any key length");
+					mac.update(b"Client Key");
+					let client_key = mac.finalize().into_bytes();
+
+					let mut hasher = sha2::Sha256::new();
+					hasher.update(client_key);
+					let stored_key = hasher.finalize();
+
+					let mut mac = <HmacSha256 as hmac::Mac>::new_from_slice(&salted)
+						.expect("HMAC accepts any key length");
+					mac.update(b"Server Key");
+					let server_key = mac.finalize().into_bytes();
+
+					let hash_str = format!(
+						"SCRAM-SHA-256${}:{}${}:{}",
+						iterations,
+						STANDARD.encode(salt),
+						STANDARD.encode(stored_key),
+						STANDARD.encode(server_key),
+					);
+
+					write_private(
+						&recipients,
+						&secret,
+						password.as_bytes(),
+						OutputEncoding::Raw,
+					)?;
+					write_public(&hash, hash_str.as_bytes(), OutputEncoding::Raw)?;
+				}
 				Generate::Bytes {
 					output,
 					count,
modifiedcrates/nix-eval/src/lib.rsdiffbeforeafterboth
before · crates/nix-eval/src/lib.rs
1use std::borrow::Cow;2use std::cell::RefCell;3use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};4use std::ptr::{null, null_mut};5use std::sync::{Arc, LazyLock, OnceLock};6use std::{array, fmt, slice};7use std::{collections::HashMap, path::PathBuf};89use anyhow::{Context, anyhow, bail};10use itertools::Itertools;11use serde::Serialize;12use serde::de::DeserializeOwned;13use std::mem::transmute;1415pub use anyhow::Result;16use tracing::{Span, instrument, warn};1718use self::logging::{ErrorInfoBuilder, nix_logging_cxx};19use self::nix_cxx::set_fetcher_setting;20use self::nix_raw::{21	BindingsBuilder as c_bindings_builder, EvalState as c_eval_state, GC_SUCCESS,22	GC_allow_register_threads, GC_get_stack_base, GC_register_my_thread, GC_stack_base,23	GC_thread_is_registered, GC_unregister_my_thread, ListBuilder as c_list_builder, PrimOp,24	PrimOpFun, Store as c_store, StorePath as c_store_path, alloc_primop, alloc_value,25	bindings_builder_free, bindings_builder_insert, c_context, c_context_create, c_context_free,26	clear_err, copy_value, err_NIX_ERR_KEY, err_NIX_ERR_NIX_ERROR, err_NIX_ERR_OVERFLOW,27	err_NIX_ERR_UNKNOWN, err_code, err_info_msg, err_msg, eval_state_build,28	eval_state_builder_load, eval_state_builder_new, eval_state_builder_set_eval_setting,29	expr_eval_from_string, fetchers_settings,30	fetchers_settings_free, fetchers_settings_new, flake_lock, flake_lock_flags,31	flake_lock_flags_free, flake_lock_flags_new, flake_reference,32	flake_reference_and_fragment_from_string, flake_reference_parse_flags,33	flake_reference_parse_flags_free, flake_reference_parse_flags_new,34	flake_reference_parse_flags_set_base_directory, flake_settings, flake_settings_free,35	flake_settings_new, gc_now as gc_now_raw, get_attr_byname, get_attr_name_byidx, get_attrs_size,36	get_list_byidx, get_list_size, get_string, get_type, has_attr_byname, init_bool, init_int,37	init_primop, init_string, libexpr_init, libstore_init, libutil_init, list_builder_free,38	list_builder_insert, locked_flake, locked_flake_free, locked_flake_get_output_attrs,39	make_attrs, make_bindings_builder, make_list, make_list_builder, realised_string,40	realised_string_free, realised_string_get_buffer_size, realised_string_get_buffer_start,41	realised_string_get_store_path, realised_string_get_store_path_count, register_primop,42	set_err_msg, setting_set, state_free, store_open, store_parse_path, store_path_free,43	store_path_name, string_realise, value, value_call, value_decref, value_force, value_incref,44};4546// Contains macros helpers47pub mod drv;48pub mod logging;49#[doc(hidden)]50pub mod macros;5152#[doc(hidden)]53pub mod __macro_support {54	pub use std::collections::hash_map::HashMap;5556	pub use anyhow::Context;57	pub use tokio::task::block_in_place;58}59pub mod util;6061#[allow(62	non_upper_case_globals,63	non_camel_case_types,64	non_snake_case,65	dead_code66)]67mod nix_raw {68	include!(concat!(env!("OUT_DIR"), "/bindings.rs"));69}70#[cxx::bridge]71pub mod nix_cxx {72	unsafe extern "C++" {73		type nix_fetchers_settings;74		include!("nix-eval/src/lib.hh");7576		#[allow(clippy::missing_safety_doc)]77		unsafe fn set_fetcher_setting(78			settings: *mut nix_fetchers_settings,79			setting: *const c_char,80			value: *const c_char,81		);82	}83}8485#[derive(Debug, PartialEq, Eq)]86pub enum NixType {87	Thunk,88	Int,89	Float,90	Bool,91	String,92	Path,93	Null,94	Attrs,95	List,96	Function,97	External,98}99impl NixType {100	fn from_int(c: c_uint) -> Self {101		match c {102			0 => Self::Thunk,103			1 => Self::Int,104			2 => Self::Float,105			3 => Self::Bool,106			4 => Self::String,107			5 => Self::Path,108			6 => Self::Null,109			7 => Self::Attrs,110			8 => Self::List,111			9 => Self::Function,112			10 => Self::External,113			_ => unreachable!("unknown nix type: {c}"),114		}115	}116}117118enum FunctorKind {119	Function,120	Functor,121}122123#[derive(Debug)]124#[repr(i32)]125pub enum NixErrorKind {126	Unknown = err_NIX_ERR_UNKNOWN,127	Overflow = err_NIX_ERR_OVERFLOW,128	Key = err_NIX_ERR_KEY,129	Generic = err_NIX_ERR_NIX_ERROR,130}131impl NixErrorKind {132	fn from_int(v: c_int) -> Option<Self> {133		Some(match v {134			0 => return None,135			nix_raw::err_NIX_ERR_UNKNOWN => Self::Unknown,136			nix_raw::err_NIX_ERR_OVERFLOW => Self::Overflow,137			nix_raw::err_NIX_ERR_KEY => Self::Key,138			nix_raw::err_NIX_ERR_NIX_ERROR => Self::Generic,139			_ => {140				debug_assert!(false, "unexpected nix error kind: {v}");141				Self::Unknown142			}143		})144	}145}146147pub fn gc_now() {148	unsafe { gc_now_raw() };149}150151pub fn gc_register_my_thread() {152	assert_eq!(unsafe { GC_thread_is_registered() }, 0);153154	let mut sb = GC_stack_base {155		mem_base: null_mut(),156	};157	let r = unsafe { GC_get_stack_base(&mut sb) };158	if r as u32 != GC_SUCCESS {159		panic!("failed to get thread stack base");160	}161	unsafe { GC_register_my_thread(&sb) };162}163pub fn gc_unregister_my_thread() {164	assert_eq!(unsafe { GC_thread_is_registered() }, 1);165166	unsafe { GC_unregister_my_thread() };167}168169pub struct ThreadRegisterGuard {}170impl ThreadRegisterGuard {171	#[allow(clippy::new_without_default)]172	pub fn new() -> Self {173		gc_register_my_thread();174		Self {}175	}176}177impl Drop for ThreadRegisterGuard {178	fn drop(&mut self) {179		gc_unregister_my_thread();180	}181}182183#[repr(transparent)]184pub struct NixContext(*mut c_context);185impl NixContext {186	pub fn set_err_raw(&mut self, err: NixErrorKind, msg: &CStr) {187		unsafe { set_err_msg(self.0, err as c_int, msg.as_ptr()) };188	}189	pub fn set_err(&mut self, err: anyhow::Error) {190		let mut fmt = format!("{err:?}").replace("\0", "\\0");191		self.set_err_raw(192			NixErrorKind::Generic,193			&CString::new(fmt).expect("NUL bytes were just replaced"),194		);195	}196	pub fn new() -> Self {197		let ctx = unsafe { c_context_create() };198		Self(ctx)199	}200	fn error_kind(&self) -> Option<NixErrorKind> {201		let code = unsafe { err_code(self.0) };202		NixErrorKind::from_int(code)203	}204	fn error<'t>(&self) -> Option<(Cow<'t, str>, Option<Box<ErrorInfoBuilder>>)> {205		if let NixErrorKind::Generic = self.error_kind()? {206			let ei = unsafe { logging::nix_logging_cxx::extract_error_info(self.0) };207			let mut err_out = String::new();208			unsafe {209				err_info_msg(210					null_mut(),211					self.0,212					Some(copy_nix_str),213					(&raw mut err_out).cast(),214				)215			};216			return Some((Cow::Owned(err_out), Some(ei)));217		};218219		// TODO: Can throw error (resulting in panic) if unable to retrieve error. Should be able to resolve by passing context as a first argument,220		// but it looks ugly221		let str = unsafe { err_msg(null_mut(), self.0, null_mut()) };222		Some((unsafe { CStr::from_ptr(str) }.to_string_lossy(), None))223	}224	fn clean_err(&mut self) {225		unsafe {226			clear_err(self.0);227		}228	}229230	fn bail_if_error(&self) -> Result<()> {231		if let Some((err, stack)) = self.error() {232			let mut e = Err(anyhow!("{err}"));233			if let Some(stack) = stack {234				for ele in stack.stack_frames {235					e = e.with_context(|| {236						if ele.pos.is_empty() {237							ele.msg238						} else {239							format!("{} at {}", ele.msg, ele.pos)240						}241					})242				}243			}244			return e.context("<nix frames>");245		};246		Ok(())247	}248249	fn run_in_context<T>(&mut self, f: impl FnOnce(*mut c_context) -> T) -> Result<T> {250		self.clean_err();251		let o = f(self.0);252		self.bail_if_error()?;253		self.clean_err();254		Ok(o)255	}256}257258impl Default for NixContext {259	fn default() -> Self {260		Self::new()261	}262}263impl Drop for NixContext {264	fn drop(&mut self) {265		unsafe {266			c_context_free(self.0);267		}268	}269}270struct GlobalState {271	// Store should be valid as long as EvalState is valid272	#[allow(dead_code)]273	store: Store,274	state: EvalState,275}276impl GlobalState {277	fn new() -> Result<Self> {278		let mut ctx = NixContext::new();279		let store = ctx280			.run_in_context(|c| unsafe { store_open(c, c"auto".as_ptr(), null_mut()) })281			.map(Store)?;282283		let builder = ctx.run_in_context(|c| unsafe { eval_state_builder_new(c, store.0) })?;284		ctx.run_in_context(|c| unsafe { eval_state_builder_load(c, builder) })?;285		ctx.run_in_context(|c| unsafe {286			eval_state_builder_set_eval_setting(287				c,288				builder,289				c"lazy-trees".as_ptr(),290				c"true".as_ptr(),291			)292		})?;293		ctx.run_in_context(|c| unsafe {294			eval_state_builder_set_eval_setting(295				c,296				builder,297				c"lazy-locks".as_ptr(),298				c"true".as_ptr(),299			)300		})?;301		let state = ctx302			.run_in_context(|c| unsafe { eval_state_build(c, builder) })303			.map(EvalState)?;304305		Ok(Self { store, state })306	}307}308309struct ThreadState {310	ctx: NixContext,311}312impl ThreadState {313	fn new() -> Result<Self> {314		let ctx = NixContext::new();315316		Ok(Self { ctx })317	}318}319320static GLOBAL_STATE: LazyLock<GlobalState> =321	LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));322323thread_local! {324	static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));325}326pub(crate) fn with_default_context<T>(f: impl FnOnce(*mut c_context, *mut c_eval_state) -> T) -> Result<T> {327	let global = &GLOBAL_STATE.state;328	let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));329	let mut ctx = NixContext(ctx);330	let v = ctx.run_in_context(|c| f(c, state));331	// It is reused for thread332	std::mem::forget(ctx);333	v334}335336/// Same as with_default_context, but also passes store...337/// Yep, this code is garbage and needs to be refactored.338pub(crate) fn with_store_context<T>(339	f: impl FnOnce(*mut c_context, *mut c_store, *mut c_eval_state) -> T,340) -> Result<T> {341	let global = &GLOBAL_STATE;342	let (ctx, store, state) =343		THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.store.0, global.state.0));344	let mut ctx = NixContext(ctx);345	let v = ctx.run_in_context(|c| f(c, store, state));346	std::mem::forget(ctx);347	v348}349350pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {351	with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())352}353354pub struct FetchSettings(*mut fetchers_settings);355impl FetchSettings {356	pub fn new() -> Self {357		Self::try_new().expect("allocation should not fail")358	}359	fn try_new() -> Result<Self> {360		with_default_context(|c, _| unsafe { fetchers_settings_new(c) }).map(Self)361	}362	pub fn set(&mut self, setting: &CStr, value: &CStr) {363		unsafe {364			set_fetcher_setting(self.0.cast(), setting.as_ptr(), value.as_ptr());365		};366	}367}368unsafe impl Send for FetchSettings {}369unsafe impl Sync for FetchSettings {}370371impl Default for FetchSettings {372	fn default() -> Self {373		Self::new()374	}375}376377impl Drop for FetchSettings {378	fn drop(&mut self) {379		unsafe { fetchers_settings_free(self.0) };380	}381}382pub struct FlakeSettings(*mut flake_settings);383impl FlakeSettings {384	pub fn new() -> Result<Self> {385		with_default_context(|c, _| unsafe { flake_settings_new(c) }).map(Self)386	}387}388unsafe impl Send for FlakeSettings {}389unsafe impl Sync for FlakeSettings {}390impl Drop for FlakeSettings {391	fn drop(&mut self) {392		unsafe {393			flake_settings_free(self.0);394		}395	}396}397398pub struct FlakeReferenceParseFlags(*mut flake_reference_parse_flags);399impl FlakeReferenceParseFlags {400	pub fn new(settings: &FlakeSettings) -> Result<Self> {401		with_default_context(|c, _| unsafe { flake_reference_parse_flags_new(c, settings.0) })402			.map(Self)403	}404	pub fn set_base_dir(&mut self, dir: &str) -> Result<()> {405		with_default_context(|c, _| {406			unsafe {407				flake_reference_parse_flags_set_base_directory(408					c,409					self.0,410					dir.as_ptr().cast(),411					dir.len(),412				)413			};414		})415	}416}417impl Drop for FlakeReferenceParseFlags {418	fn drop(&mut self) {419		unsafe {420			flake_reference_parse_flags_free(self.0);421		}422	}423}424pub struct FlakeLockFlags(*mut flake_lock_flags);425impl FlakeLockFlags {426	pub fn new(settings: &FlakeSettings) -> Result<Self> {427		let o = with_default_context(|c, _| unsafe { flake_lock_flags_new(c, settings.0) })428			.map(Self)?;429		// with_default_context(|c, _| unsafe { flake_lock_flags_set_mode_virtual(c, o.0) })?;430431		Ok(o)432	}433}434impl Drop for FlakeLockFlags {435	fn drop(&mut self) {436		unsafe {437			flake_lock_flags_free(self.0);438		}439	}440}441442pub(crate) unsafe extern "C" fn copy_nix_str(start: *const c_char, n: c_uint, user_data: *mut c_void) {443	let s = unsafe { slice::from_raw_parts(start.cast::<u8>(), n as usize) };444	let s = std::str::from_utf8(s).expect("c string has invalid utf-8");445	unsafe { *user_data.cast::<String>() = s.to_owned() };446}447448struct Store(*mut c_store);449unsafe impl Send for Store {}450unsafe impl Sync for Store {}451452impl Store {453	fn parse_path(&self, path: &CStr) -> Result<StorePath> {454		with_default_context(|c, _| {455			StorePath(unsafe { store_parse_path(c, self.0, path.as_ptr()) })456		})457	}458}459460#[repr(transparent)]461pub struct EvalState(*mut c_eval_state);462unsafe impl Send for EvalState {}463unsafe impl Sync for EvalState {}464465impl Drop for EvalState {466	fn drop(&mut self) {467		unsafe {468			state_free(self.0);469		}470	}471}472473pub struct FlakeReference(*mut flake_reference);474impl FlakeReference {475	#[instrument(name = "new-flake-reference", skip(flake, parse, fetch))]476	pub fn new(477		s: &str,478		flake: &FlakeSettings,479		parse: &FlakeReferenceParseFlags,480		fetch: &FetchSettings,481	) -> Result<(Self, String)> {482		let mut out = null_mut();483		let mut fragment = String::new();484		// let fetch_settings = fetcher_settings;485		with_default_context(|c, _| unsafe {486			flake_reference_and_fragment_from_string(487				c,488				fetch.0,489				flake.0,490				parse.0,491				s.as_ptr().cast(),492				s.len(),493				&mut out,494				Some(copy_nix_str),495				(&raw mut fragment).cast(),496			)497		})?;498		assert!(!out.is_null());499500		Ok((Self(out), fragment))501	}502	#[instrument(name = "lock-flake", skip(self, fetch, flake, lock))]503	pub fn lock(504		&mut self,505		fetch: &FetchSettings,506		flake: &FlakeSettings,507		lock: &FlakeLockFlags,508	) -> Result<LockedFlake> {509		with_default_context(|c, es| unsafe { flake_lock(c, fetch.0, flake.0, es, lock.0, self.0) })510			.map(LockedFlake)511	}512}513unsafe impl Send for FlakeReference {}514unsafe impl Sync for FlakeReference {}515516pub struct LockedFlake(*mut locked_flake);517impl LockedFlake {518	pub fn get_attrs(&self, settings: &mut FlakeSettings) -> Result<Value> {519		with_default_context(|c, es| unsafe {520			locked_flake_get_output_attrs(c, settings.0, es, self.0)521		})522		.map(Value)523	}524}525unsafe impl Send for LockedFlake {}526unsafe impl Sync for LockedFlake {}527impl Drop for LockedFlake {528	fn drop(&mut self) {529		unsafe {530			locked_flake_free(self.0);531		};532	}533}534535type FieldName = [u8; 64];536fn init_field_name(v: &str) -> FieldName {537	let mut f = [0; 64];538	assert!(v.len() < 64, "max field name is 63 chars");539	assert!(540		v.bytes().all(|v| v != 0),541		"nul bytes are unsupported in field name"542	);543	f[0..v.len()].copy_from_slice(v.as_bytes());544	f545}546547pub struct RealisedString(*mut realised_string);548impl fmt::Debug for RealisedString {549	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {550		self.as_str().fmt(f)551	}552}553554impl RealisedString {555	pub fn as_str(&self) -> &str {556		let len = unsafe { realised_string_get_buffer_size(self.0) };557		let data: *const u8 = unsafe { realised_string_get_buffer_start(self.0) }.cast();558		let data = unsafe { slice::from_raw_parts(data, len) };559		std::str::from_utf8(data).expect("non-utf8 strings not supported")560	}561	pub fn path_count(&self) -> usize {562		unsafe { realised_string_get_store_path_count(self.0) }563	}564	pub fn path(&self, i: usize) -> String {565		assert!(i < self.path_count());566		let path = unsafe { realised_string_get_store_path(self.0, i) };567		let mut err_out = String::new();568		unsafe { store_path_name(path, Some(copy_nix_str), (&raw mut err_out).cast()) };569		err_out570	}571}572573unsafe impl Send for RealisedString {}574impl Drop for RealisedString {575	fn drop(&mut self) {576		unsafe { realised_string_free(self.0) }577	}578}579580#[repr(transparent)]581pub struct Value(*mut value);582583unsafe impl Send for Value {}584unsafe impl Sync for Value {}585586pub trait AsFieldName {587	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T>;588	fn to_field_name(&self) -> Result<String>;589}590impl AsFieldName for Value {591	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {592		let f = self.to_string()?;593		v(init_field_name(&f))594	}595	fn to_field_name(&self) -> Result<String> {596		self.to_string()597	}598}599impl<E> AsFieldName for E600where601	E: AsRef<str>,602{603	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {604		let f = self.as_ref();605		v(init_field_name(f))606	}607	fn to_field_name(&self) -> Result<String> {608		Ok(self.as_ref().to_owned())609	}610}611612struct AttrsBuilder(*mut c_bindings_builder);613impl AttrsBuilder {614	fn new(capacity: usize) -> Self {615		with_default_context(|c, es| unsafe { make_bindings_builder(c, es, capacity) })616			.map(Self)617			.expect("alloc should not fail")618	}619	fn insert(&mut self, k: &impl AsFieldName, v: Value) {620		k.as_field_name(|name| {621			with_default_context(|c, _| unsafe {622				bindings_builder_insert(c, self.0, name.as_ptr().cast(), v.0);623				// bindings_builder_insert doesn't do incref624			})625		})626		.expect("builder insert shouldn't fail");627	}628}629impl Drop for AttrsBuilder {630	fn drop(&mut self) {631		unsafe { bindings_builder_free(self.0) };632	}633}634635struct ListBuilder(*mut c_list_builder, c_uint);636impl ListBuilder {637	fn new(capacity: usize) -> Self {638		with_default_context(|c, es| unsafe { make_list_builder(c, es, capacity) })639			.map(|l| Self(l, 0))640			.expect("alloc should not fail")641	}642}643impl ListBuilder {644	fn push(&mut self, v: Value) {645		with_default_context(|c, _| unsafe {646			list_builder_insert(647				c,648				self.0,649				{650					let v = self.1;651					self.1 += 1;652					v653				},654				v.0,655			)656		})657		.expect("list insert shouldn't fail");658	}659}660impl Drop for ListBuilder {661	fn drop(&mut self) {662		unsafe { list_builder_free(self.0) };663	}664}665666impl Value {667	pub fn new_primop(v: NativeFn) -> Self {668		let out = Self::new_uninit();669		with_default_context(|c, _| unsafe { init_primop(c, out.0, v.0) })670			.expect("primop initialization should not fail");671		out672	}673	pub fn new_attrs(v: HashMap<&str, Value>) -> Self {674		let out = Self::new_uninit();675		let mut b = AttrsBuilder::new(v.len());676		for (k, v) in v {677			b.insert(&k, v);678		}679		with_default_context(|c, _| unsafe { make_attrs(c, out.0, b.0) })680			.expect("attrs initialization should not fail");681682		out683	}684	fn new_list<T: Into<Self>>(v: Vec<T>) -> Self {685		let out = Self::new_uninit();686		let mut b = ListBuilder::new(v.len());687		for v in v {688			b.push(v.into());689		}690		with_default_context(|c, _| unsafe { make_list(c, b.0, out.0) })691			.expect("list initialization should not fail");692693		out694	}695	fn new_uninit() -> Self {696		let out = with_default_context(|c, es| unsafe { alloc_value(c, es) })697			.expect("value allocation should not fail");698		Self(out)699	}700	pub fn new_str(v: &str) -> Self {701		let s = CString::new(v).expect("string should not contain NULs");702		let out = Self::new_uninit();703		// String is copied, `s` is free to be dropped704		with_default_context(|c, _| unsafe { init_string(c, out.0, s.as_ptr()) })705			.expect("string initialization should not fail");706		out707	}708	pub fn new_int(i: i64) -> Self {709		let out = Self::new_uninit();710		with_default_context(|c, _| unsafe { init_int(c, out.0, i) })711			.expect("int initialization should not fail");712		out713	}714	pub fn new_bool(v: bool) -> Self {715		let out = Self::new_uninit();716		with_default_context(|c, _| unsafe { init_bool(c, out.0, v) })717			.expect("bool initialization should not fail");718		out719	}720	// TODO: As far as I can see, there is no way to get Thunks from nix public C api, so this function is useless721	// fn force(&mut self, st: &mut EvalState) -> Result<()> {722	// 	with_default_context(|c, _| unsafe { value_force(c, st.0, self.0) })?;723	// 	Ok(())724	// }725	pub fn type_of(&self) -> NixType {726		let ty = with_default_context(|c, _| unsafe { get_type(c, self.0) })727			.expect("get_type should not fail");728		NixType::from_int(ty)729	}730	fn builtin_to_string(&self) -> Result<Self> {731		let builtin = Self::eval("builtins.toString")?;732		builtin.call(self.clone())733	}734	fn force(&mut self, s: *mut nix_raw::EvalState) -> Result<()> {735		with_default_context(|c, _| unsafe { value_force(c, s, self.0) })?;736		Ok(())737	}738	pub fn to_string(&self) -> Result<String> {739		let ty = self.type_of();740		if !matches!(ty, NixType::String) {741			bail!("unexpected type: {ty:?}, expected string");742		}743		let mut str_out = String::new();744		with_default_context(|c, _| unsafe {745			get_string(c, self.0, Some(copy_nix_str), (&raw mut str_out).cast())746		})?;747748		Ok(str_out)749	}750	pub fn to_realised_string(&self) -> Result<RealisedString> {751		with_default_context(|c, es| unsafe { string_realise(c, es, self.0, false) })752			.map(RealisedString)753754		// let store_paths = unsafe { nix_raw::realised_string_get_store_path_count(str) };755		// for i in 0..store_paths {756		// 	let store_path = unsafe { nix_raw::realised_string_get_store_path(str, i) };757		// 	nix_raw::store_path_name(store_path, callback, user_data);758		// }759		// dbg!(store_paths);760		// todo!();761	}762763	pub fn has_field(&self, field: &str) -> Result<bool> {764		if !matches!(self.type_of(), NixType::Attrs) {765			bail!("invalid type: expected attrs");766		}767768		let f = init_field_name(field);769		with_default_context(|c, es| unsafe { has_attr_byname(c, self.0, es, f.as_ptr().cast()) })770	}771	// pub fn derivation_path(&self) {772	// 	nix_raw::real773	// }774	pub fn list_fields(&self) -> Result<Vec<String>> {775		if !matches!(self.type_of(), NixType::Attrs) {776			bail!("invalid type: expected attrs");777		}778779		let len = with_default_context(|c, _| unsafe { get_attrs_size(c, self.0) })?;780		let mut out = Vec::with_capacity(len as usize);781782		for i in 0..len {783			let name =784				with_default_context(|c, es| unsafe { get_attr_name_byidx(c, self.0, es, i) })?;785			let c = unsafe { CStr::from_ptr(name) };786			out.push(c.to_str().expect("nix field names are utf-8").to_owned());787		}788		Ok(out)789	}790	pub fn get_elem(&self, v: usize) -> Result<Self> {791		if !matches!(self.type_of(), NixType::List) {792			bail!("invalid type: expected list");793		}794		let len = with_default_context(|c, _| unsafe { get_list_size(c, self.0) })? as usize;795		if v >= len {796			bail!("oob list get: {v} >= {len}");797		}798799		with_default_context(|c, es| unsafe { get_list_byidx(c, self.0, es, v as u32) }).map(Self)800	}801	pub fn attrs_update(self, other: Value /*, ignore_errors: bool*/) -> Result<Self> {802		let attrs_update_fn = Self::eval("a: b: a // b")?;803804		attrs_update_fn805			.call(self)?806			.call(other)807			.context("attrs update")808	}809	pub fn get_field(&self, name: impl AsFieldName) -> Result<Self> {810		if !matches!(self.type_of(), NixType::Attrs) {811			bail!("invalid type: expected attrs");812		}813814		name.as_field_name(|name| {815			with_default_context(|c, es| unsafe {816				get_attr_byname(c, self.0, es, name.as_ptr().cast())817			})818			.map(Self)819		})820		.with_context(|| format!("getting field {:?}", name.to_field_name()))821	}822	pub fn call(&self, v: Value) -> Result<Self> {823		let kind = self824			.functor_kind()825			.ok_or_else(|| anyhow!("can only call function or functor"))?;826827		let function = match kind {828			FunctorKind::Function => self.clone(),829			FunctorKind::Functor => {830				let f = self831					.get_field("__functor")832					.context("getting functor value")?;833				assert_eq!(834					f.type_of(),835					NixType::Function,836					"invalid functor encountered"837				);838				f839			}840		};841842		let out = Value::new_uninit();843		with_default_context(|c, es| unsafe { value_call(c, es, function.0, v.0, out.0) })?;844845		Ok(out)846	}847	pub fn eval(v: &str) -> Result<Self> {848		let s = CString::new(v).expect("expression shouldn't have internal NULs");849		let out = Self::new_uninit();850		with_default_context(|c, es| unsafe {851			expr_eval_from_string(c, es, s.as_ptr(), c"/root".as_ptr(), out.0)852		})?;853		Ok(out)854	}855	#[instrument(name = "build", skip(self), fields(output))]856	pub fn build(&self, output: &str) -> Result<PathBuf> {857		if !self.is_derivation() {858			bail!("expected derivation to build")859		}860		let output_name = self861			.get_field("outputName")862			.context("getting output name field")?863			.to_string()?;864		let v = if output_name != output {865			let out = self.get_field(output).context("getting target output")?;866			if !out.is_derivation() {867				bail!("unknown output: {output}");868			}869			out870		} else {871			self.clone()872		};873874		let drv_path = v875			.get_field("drvPath")876			.context("getting drvPath")?877			.to_string()?;878		let graph = drv::DrvGraph::resolve(&drv_path)?;879		let _guard = logging::register_build_graph(&Span::current(), &graph);880881		// to_string here blocks until the path is built882		let s = v.builtin_to_string()?;883		let rs = s.to_realised_string()?;884		let out_path = rs.as_str().to_owned();885		Ok(PathBuf::from(out_path))886	}887	pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {888		let to_json = Self::eval("builtins.toJSON")?;889		let s = to_json.call(self.clone())?.to_string()?;890		Ok(serde_json::from_str(&s)?)891	}892	pub fn serialized<T: Serialize>(v: &T) -> Result<Self> {893		Self::eval(&nixlike::serialize(v)?)894	}895896	// Convert to string/evaluate derivations/etc897	// fn to_string_weak(&self) -> Result<String> {898	// 	// TODO: For now, it works exactly like to_string, see the comment for fn force()899	// 	self.to_string()900	// }901902	fn is_derivation(&self) -> bool {903		if !matches!(self.type_of(), NixType::Attrs) {904			return false;905		}906		let Some(ty) = self.get_field("type").ok() else {907			return false;908		};909		matches!(ty.to_string().as_deref(), Ok("derivation"))910	}911	fn functor_kind(&self) -> Option<FunctorKind> {912		match self.type_of() {913			NixType::Attrs => self914				.has_field("__functor")915				.expect("has_field shouldn't fail for attrs")916				.then_some(FunctorKind::Functor),917			NixType::Function => Some(FunctorKind::Function),918			_ => None,919		}920	}921	pub fn is_function(&self) -> bool {922		self.functor_kind().is_some()923	}924	pub fn is_null(&self) -> bool {925		matches!(self.type_of(), NixType::Null)926	}927	pub fn is_string(&self) -> bool {928		matches!(self.type_of(), NixType::String)929	}930	pub fn is_attrs(&self) -> bool {931		matches!(self.type_of(), NixType::Attrs)932	}933}934935impl From<String> for Value {936	fn from(value: String) -> Self {937		Value::new_str(&value)938	}939}940impl From<bool> for Value {941	fn from(value: bool) -> Self {942		Value::new_bool(value)943	}944}945impl From<&str> for Value {946	fn from(value: &str) -> Self {947		Value::new_str(value)948	}949}950impl<T> From<Vec<T>> for Value951where952	T: Into<Value>,953{954	fn from(value: Vec<T>) -> Self {955		Value::new_list(value)956	}957}958959impl Clone for Value {960	fn clone(&self) -> Self {961		with_default_context(|c, _| unsafe { value_incref(c, self.0) })962			.expect("value incref should not fail");963		Self(self.0)964	}965}966impl Drop for Value {967	fn drop(&mut self) {968		with_default_context(|c, _| unsafe { value_decref(c, self.0) })969			.expect("value drop should not fail");970	}971}972973static TOKIO_FOR_NIX: OnceLock<Arc<tokio::runtime::Runtime>> = OnceLock::new();974975pub fn init_libraries() {976	unsafe { GC_allow_register_threads() };977978	let mut ctx = NixContext::new();979	ctx.run_in_context(|c| unsafe { libutil_init(c) })980		.expect("util init should not fail");981	ctx.run_in_context(|c| unsafe { libstore_init(c) })982		.expect("store init should not fail");983	ctx.run_in_context(|c| unsafe { libexpr_init(c) })984		.expect("expr init should not fail");985986	nix_logging_cxx::apply_tracing_logger();987}988989pub fn init_tokio_for_nix(tokio: Arc<tokio::runtime::Runtime>) {990	TOKIO_FOR_NIX991		.set(tokio)992		.expect("tokio for nix should only be initialized once");993}994995pub fn await_in_nix<F: Send + 'static>(f: impl Future<Output = F> + Send + 'static) -> F {996	// It should be possible to do Handle::current(), but some of the planned features don't work well with that997	let runtime = TOKIO_FOR_NIX998		.get()999		.expect("init_tokio_for_nix was not called");1000	std::thread::spawn(move || runtime.block_on(f))1001		.join()1002		.expect("await_in_nix inner thread panicked")1003}10041005unsafe extern "C" fn nix_primop_closure_adapter<const N: usize>(1006	user_data: *mut c_void,1007	mut context: *mut c_context,1008	state: *mut nix_raw::EvalState,1009	args: *mut *mut value,1010	ret: *mut value,1011) {1012	let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };1013	let args: [&Value; N] = array::from_fn(|i| {1014		let v: &mut Value = unsafe { &mut *args.add(i).cast() };1015		v as &Value1016	});1017	let ctx: &mut NixContext = unsafe { transmute(&mut context) };10181019	let state: &EvalState = unsafe { std::mem::transmute(&state) };10201021	match user_closure(state, args) {1022		Ok(v) => {1023			unsafe { copy_value(context, ret, v.0) };1024		}1025		Err(e) => {1026			ctx.set_err(e);1027		}1028	}1029}10301031type UserClosure<const N: usize> = Box<dyn Fn(&EvalState, [&Value; N]) -> Result<Value>>;10321033pub struct NativeFn(*mut PrimOp);1034impl NativeFn {1035	pub fn new<const N: usize>(1036		name: &'static CStr,1037		doc: &'static CStr,1038		args: [&'static CStr; N],1039		f: impl Fn(&EvalState, [&Value; N]) -> Result<Value> + 'static,1040	) -> Self {1041		// Double-boxing to make it thin pointer, as vtable gets outside of first Box1042		let closure: Box<UserClosure<N>> = Box::new(Box::new(f));1043		let f: PrimOpFun = Some(nix_primop_closure_adapter::<N>);1044		let mut args = args.into_iter().map(|v| v.as_ptr()).collect_vec();1045		args.push(null());1046		let args = args.as_mut_ptr();1047		let primop = unsafe {1048			alloc_primop(1049				null_mut(),1050				f,1051				N as i32,1052				name.as_ptr(),1053				args,1054				doc.as_ptr(),1055				Box::into_raw(closure).cast(),1056			)1057		};10581059		assert!(!primop.is_null(), "primop allocation should not fail");10601061		Self(primop)1062	}1063	pub fn register(self) {1064		unsafe { register_primop(null_mut(), self.0) };1065	}1066}10671068struct StorePath(*mut c_store_path);1069impl StorePath {}10701071impl Drop for StorePath {1072	fn drop(&mut self) {1073		unsafe { store_path_free(self.0) }1074	}1075}10761077#[test_log::test]1078fn test_native() -> Result<()> {1079	init_libraries();1080	NativeFn::new(1081		c"__uppercaseSuffix2",1082		c"make string uppercase and add suffix",1083		[c"str", c"suffix"],1084		|_, [str, suffix]: [&Value; 2]| {1085			let str = str.to_string()?;1086			let suffix = suffix.to_string()?;1087			Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1088		},1089	)1090	.register();10911092	let mut fetch_settings = FetchSettings::new();1093	fetch_settings.set(c"warn-dirty", c"false");10941095	let manifest = format!("git+file://{}/../../", env!("CARGO_MANIFEST_DIR"));1096	let flake = FlakeSettings::new()?;1097	let parse = FlakeReferenceParseFlags::new(&flake)?;1098	let (mut r, _) = FlakeReference::new(&manifest, &flake, &parse, &fetch_settings)?;1099	let lock = FlakeLockFlags::new(&flake)?;1100	let locked = r.lock(&fetch_settings, &flake, &lock)?;1101	let attrs = locked.get_attrs(&mut FlakeSettings::new()?)?;11021103	let builtins = Value::eval("builtins")?;1104	assert_eq!(builtins.type_of(), NixType::Attrs);11051106	assert_eq!(attrs.type_of(), NixType::Attrs);1107	let test_data = nix_go!(attrs.testData);11081109	let test_string: String = nix_go_json!(test_data.testString);1110	assert_eq!(test_string, "hello");11111112	let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);1113	let s = CString::new(s.to_string()?).expect("path str is cstring");11141115	let uppercase_suffix = Value::new_primop(NativeFn::new(1116		c"uppercase_suffix",1117		c"make string uppercase and add suffix",1118		[c"str", c"suffix"],1119		|es, [str, suffix]: [&Value; 2]| {1120			let str = str.to_string()?;1121			let suffix = suffix.to_string()?;1122			Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1123		},1124	));11251126	let test_result: String = nix_go_json!(test_data.testPrimop(uppercase_suffix));1127	assert_eq!(test_result, "PREFIX_BODY_SUFFIX");1128	let test_result: String = nix_go_json!(builtins.uppercaseSuffix2("test")("suffix"));1129	assert_eq!(test_result, "TESTsuffix");11301131	let drv_path = nix_go!(attrs.packages["x86_64-linux"]["fleet-install-secrets"].drvPath)1132		.to_string()?;1133	let graph = drv::DrvGraph::resolve(&drv_path)?;1134	eprintln!(1135		"fleet-install-secrets dependency graph: {} nodes",1136		graph.nodes.len()1137	);1138	for (path, node) in &graph.nodes {1139		if !node.input_drvs.is_empty() {1140			eprintln!("  {} ({} deps)", node.name, node.input_drvs.len());1141		}1142	}11431144	Ok(())1145}11461147// pub struct GcAlloc;1148// unsafe impl GlobalAlloc for GcAlloc {1149// 	unsafe fn alloc(&self, l: Layout) -> *mut u8 {1150// 		let ptr = unsafe { GC_malloc(l.size()) };1151// 		ptr.cast()1152// 	}1153// 	unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) {1154// 		// unsafe { GC_free(ptr.cast()) };1155// 	}1156//1157// 	unsafe fn realloc(&self, ptr: *mut u8, _: Layout, new_size: usize) -> *mut u8 {1158// 		let ptr = unsafe { GC_realloc(ptr.cast(), new_size) };1159// 		ptr.cast()1160// 	}1161// }1162//1163// #[global_allocator]1164// static GC: GcAlloc = GcAlloc;
after · crates/nix-eval/src/lib.rs
1use std::borrow::Cow;2use std::cell::RefCell;3use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};4use std::ptr::{null, null_mut};5use std::sync::{Arc, LazyLock, OnceLock};6use std::{array, fmt, slice};7use std::{collections::HashMap, path::PathBuf};89use anyhow::{Context, anyhow, bail};10use itertools::Itertools;11use serde::Serialize;12use serde::de::DeserializeOwned;13use std::mem::transmute;1415pub use anyhow::Result;16use tracing::{Span, instrument, warn};1718use self::logging::{ErrorInfoBuilder, nix_logging_cxx};19use self::nix_cxx::set_fetcher_setting;20use self::nix_raw::{21	BindingsBuilder as c_bindings_builder, EvalState as c_eval_state, GC_SUCCESS,22	GC_allow_register_threads, GC_get_stack_base, GC_register_my_thread, GC_stack_base,23	GC_thread_is_registered, GC_unregister_my_thread, ListBuilder as c_list_builder, PrimOp,24	PrimOpFun, Store as c_store, StorePath as c_store_path, alloc_primop, alloc_value,25	bindings_builder_free, bindings_builder_insert, c_context, c_context_create, c_context_free,26	clear_err, copy_value, err_NIX_ERR_KEY, err_NIX_ERR_NIX_ERROR, err_NIX_ERR_OVERFLOW,27	err_NIX_ERR_UNKNOWN, err_code, err_info_msg, err_msg, eval_state_build,28	eval_state_builder_load, eval_state_builder_new, eval_state_builder_set_eval_setting,29	expr_eval_from_string, fetchers_settings, fetchers_settings_free, fetchers_settings_new,30	flake_lock, flake_lock_flags, flake_lock_flags_free, flake_lock_flags_new, flake_reference,31	flake_reference_and_fragment_from_string, flake_reference_parse_flags,32	flake_reference_parse_flags_free, flake_reference_parse_flags_new,33	flake_reference_parse_flags_set_base_directory, flake_settings, flake_settings_free,34	flake_settings_new, gc_now as gc_now_raw, get_attr_byname, get_attr_name_byidx, get_attrs_size,35	get_list_byidx, get_list_size, get_string, get_type, has_attr_byname, init_bool, init_int,36	init_primop, init_string, libexpr_init, libstore_init, libutil_init, list_builder_free,37	list_builder_insert, locked_flake, locked_flake_free, locked_flake_get_output_attrs,38	make_attrs, make_bindings_builder, make_list, make_list_builder, realised_string,39	realised_string_free, realised_string_get_buffer_size, realised_string_get_buffer_start,40	realised_string_get_store_path, realised_string_get_store_path_count, register_primop,41	set_err_msg, setting_set, state_free, store_open, store_parse_path, store_path_free,42	store_path_name, string_realise, value, value_call, value_decref, value_force, value_incref,43};4445// Contains macros helpers46pub mod drv;47pub mod logging;48#[doc(hidden)]49pub mod macros;5051#[doc(hidden)]52pub mod __macro_support {53	pub use std::collections::hash_map::HashMap;5455	pub use anyhow::Context;56	pub use tokio::task::block_in_place;57}58pub mod util;5960#[allow(61	non_upper_case_globals,62	non_camel_case_types,63	non_snake_case,64	dead_code65)]66mod nix_raw {67	include!(concat!(env!("OUT_DIR"), "/bindings.rs"));68}69#[cxx::bridge]70pub mod nix_cxx {71	unsafe extern "C++" {72		type nix_fetchers_settings;73		include!("nix-eval/src/lib.hh");7475		#[allow(clippy::missing_safety_doc)]76		unsafe fn set_fetcher_setting(77			settings: *mut nix_fetchers_settings,78			setting: *const c_char,79			value: *const c_char,80		);81	}82}8384#[derive(Debug, PartialEq, Eq)]85pub enum NixType {86	Thunk,87	Int,88	Float,89	Bool,90	String,91	Path,92	Null,93	Attrs,94	List,95	Function,96	External,97}98impl NixType {99	fn from_int(c: c_uint) -> Self {100		match c {101			0 => Self::Thunk,102			1 => Self::Int,103			2 => Self::Float,104			3 => Self::Bool,105			4 => Self::String,106			5 => Self::Path,107			6 => Self::Null,108			7 => Self::Attrs,109			8 => Self::List,110			9 => Self::Function,111			10 => Self::External,112			_ => unreachable!("unknown nix type: {c}"),113		}114	}115}116117enum FunctorKind {118	Function,119	Functor,120}121122#[derive(Debug)]123#[repr(i32)]124pub enum NixErrorKind {125	Unknown = err_NIX_ERR_UNKNOWN,126	Overflow = err_NIX_ERR_OVERFLOW,127	Key = err_NIX_ERR_KEY,128	Generic = err_NIX_ERR_NIX_ERROR,129}130impl NixErrorKind {131	fn from_int(v: c_int) -> Option<Self> {132		Some(match v {133			0 => return None,134			nix_raw::err_NIX_ERR_UNKNOWN => Self::Unknown,135			nix_raw::err_NIX_ERR_OVERFLOW => Self::Overflow,136			nix_raw::err_NIX_ERR_KEY => Self::Key,137			nix_raw::err_NIX_ERR_NIX_ERROR => Self::Generic,138			_ => {139				debug_assert!(false, "unexpected nix error kind: {v}");140				Self::Unknown141			}142		})143	}144}145146pub fn gc_now() {147	unsafe { gc_now_raw() };148}149150pub fn gc_register_my_thread() {151	assert_eq!(unsafe { GC_thread_is_registered() }, 0);152153	let mut sb = GC_stack_base {154		mem_base: null_mut(),155	};156	let r = unsafe { GC_get_stack_base(&mut sb) };157	if r as u32 != GC_SUCCESS {158		panic!("failed to get thread stack base");159	}160	unsafe { GC_register_my_thread(&sb) };161}162pub fn gc_unregister_my_thread() {163	assert_eq!(unsafe { GC_thread_is_registered() }, 1);164165	unsafe { GC_unregister_my_thread() };166}167168pub struct ThreadRegisterGuard {}169impl ThreadRegisterGuard {170	#[allow(clippy::new_without_default)]171	pub fn new() -> Self {172		gc_register_my_thread();173		Self {}174	}175}176impl Drop for ThreadRegisterGuard {177	fn drop(&mut self) {178		gc_unregister_my_thread();179	}180}181182#[repr(transparent)]183pub struct NixContext(*mut c_context);184impl NixContext {185	pub fn set_err_raw(&mut self, err: NixErrorKind, msg: &CStr) {186		unsafe { set_err_msg(self.0, err as c_int, msg.as_ptr()) };187	}188	pub fn set_err(&mut self, err: anyhow::Error) {189		let mut fmt = format!("{err:?}").replace("\0", "\\0");190		self.set_err_raw(191			NixErrorKind::Generic,192			&CString::new(fmt).expect("NUL bytes were just replaced"),193		);194	}195	pub fn new() -> Self {196		let ctx = unsafe { c_context_create() };197		Self(ctx)198	}199	fn error_kind(&self) -> Option<NixErrorKind> {200		let code = unsafe { err_code(self.0) };201		NixErrorKind::from_int(code)202	}203	fn error<'t>(&self) -> Option<(Cow<'t, str>, Option<Box<ErrorInfoBuilder>>)> {204		if let NixErrorKind::Generic = self.error_kind()? {205			let ei = unsafe { logging::nix_logging_cxx::extract_error_info(self.0) };206			let mut err_out = String::new();207			unsafe {208				err_info_msg(209					null_mut(),210					self.0,211					Some(copy_nix_str),212					(&raw mut err_out).cast(),213				)214			};215			return Some((Cow::Owned(err_out), Some(ei)));216		};217218		// TODO: Can throw error (resulting in panic) if unable to retrieve error. Should be able to resolve by passing context as a first argument,219		// but it looks ugly220		let str = unsafe { err_msg(null_mut(), self.0, null_mut()) };221		Some((unsafe { CStr::from_ptr(str) }.to_string_lossy(), None))222	}223	fn clean_err(&mut self) {224		unsafe {225			clear_err(self.0);226		}227	}228229	fn bail_if_error(&self) -> Result<()> {230		if let Some((err, stack)) = self.error() {231			let mut e = Err(anyhow!("{err}"));232			if let Some(stack) = stack {233				for ele in stack.stack_frames {234					e = e.with_context(|| {235						if ele.pos.is_empty() {236							ele.msg237						} else {238							format!("{} at {}", ele.msg, ele.pos)239						}240					})241				}242			}243			return e.context("<nix frames>");244		};245		Ok(())246	}247248	fn run_in_context<T>(&mut self, f: impl FnOnce(*mut c_context) -> T) -> Result<T> {249		self.clean_err();250		let o = f(self.0);251		self.bail_if_error()?;252		self.clean_err();253		Ok(o)254	}255}256257impl Default for NixContext {258	fn default() -> Self {259		Self::new()260	}261}262impl Drop for NixContext {263	fn drop(&mut self) {264		unsafe {265			c_context_free(self.0);266		}267	}268}269struct GlobalState {270	// Store should be valid as long as EvalState is valid271	#[allow(dead_code)]272	store: Store,273	state: EvalState,274}275impl GlobalState {276	fn new() -> Result<Self> {277		let mut ctx = NixContext::new();278		let store = ctx279			.run_in_context(|c| unsafe { store_open(c, c"auto".as_ptr(), null_mut()) })280			.map(Store)?;281282		let builder = ctx.run_in_context(|c| unsafe { eval_state_builder_new(c, store.0) })?;283		ctx.run_in_context(|c| unsafe { eval_state_builder_load(c, builder) })?;284		ctx.run_in_context(|c| unsafe {285			eval_state_builder_set_eval_setting(286				c,287				builder,288				c"lazy-trees".as_ptr(),289				c"true".as_ptr(),290			)291		})?;292		ctx.run_in_context(|c| unsafe {293			eval_state_builder_set_eval_setting(294				c,295				builder,296				c"lazy-locks".as_ptr(),297				c"true".as_ptr(),298			)299		})?;300		let state = ctx301			.run_in_context(|c| unsafe { eval_state_build(c, builder) })302			.map(EvalState)?;303304		Ok(Self { store, state })305	}306}307308struct ThreadState {309	ctx: NixContext,310}311impl ThreadState {312	fn new() -> Result<Self> {313		let ctx = NixContext::new();314315		Ok(Self { ctx })316	}317}318319static GLOBAL_STATE: LazyLock<GlobalState> =320	LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));321322thread_local! {323	static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));324}325pub(crate) fn with_default_context<T>(326	f: impl FnOnce(*mut c_context, *mut c_eval_state) -> T,327) -> Result<T> {328	let global = &GLOBAL_STATE.state;329	let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));330	let mut ctx = NixContext(ctx);331	let v = ctx.run_in_context(|c| f(c, state));332	// It is reused for thread333	std::mem::forget(ctx);334	v335}336337/// Same as with_default_context, but also passes store...338/// Yep, this code is garbage and needs to be refactored.339pub(crate) fn with_store_context<T>(340	f: impl FnOnce(*mut c_context, *mut c_store, *mut c_eval_state) -> T,341) -> Result<T> {342	let global = &GLOBAL_STATE;343	let (ctx, store, state) =344		THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.store.0, global.state.0));345	let mut ctx = NixContext(ctx);346	let v = ctx.run_in_context(|c| f(c, store, state));347	std::mem::forget(ctx);348	v349}350351pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {352	with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())353}354355pub struct FetchSettings(*mut fetchers_settings);356impl FetchSettings {357	pub fn new() -> Self {358		Self::try_new().expect("allocation should not fail")359	}360	fn try_new() -> Result<Self> {361		with_default_context(|c, _| unsafe { fetchers_settings_new(c) }).map(Self)362	}363	pub fn set(&mut self, setting: &CStr, value: &CStr) {364		unsafe {365			set_fetcher_setting(self.0.cast(), setting.as_ptr(), value.as_ptr());366		};367	}368}369unsafe impl Send for FetchSettings {}370unsafe impl Sync for FetchSettings {}371372impl Default for FetchSettings {373	fn default() -> Self {374		Self::new()375	}376}377378impl Drop for FetchSettings {379	fn drop(&mut self) {380		unsafe { fetchers_settings_free(self.0) };381	}382}383pub struct FlakeSettings(*mut flake_settings);384impl FlakeSettings {385	pub fn new() -> Result<Self> {386		with_default_context(|c, _| unsafe { flake_settings_new(c) }).map(Self)387	}388}389unsafe impl Send for FlakeSettings {}390unsafe impl Sync for FlakeSettings {}391impl Drop for FlakeSettings {392	fn drop(&mut self) {393		unsafe {394			flake_settings_free(self.0);395		}396	}397}398399pub struct FlakeReferenceParseFlags(*mut flake_reference_parse_flags);400impl FlakeReferenceParseFlags {401	pub fn new(settings: &FlakeSettings) -> Result<Self> {402		with_default_context(|c, _| unsafe { flake_reference_parse_flags_new(c, settings.0) })403			.map(Self)404	}405	pub fn set_base_dir(&mut self, dir: &str) -> Result<()> {406		with_default_context(|c, _| {407			unsafe {408				flake_reference_parse_flags_set_base_directory(409					c,410					self.0,411					dir.as_ptr().cast(),412					dir.len(),413				)414			};415		})416	}417}418impl Drop for FlakeReferenceParseFlags {419	fn drop(&mut self) {420		unsafe {421			flake_reference_parse_flags_free(self.0);422		}423	}424}425pub struct FlakeLockFlags(*mut flake_lock_flags);426impl FlakeLockFlags {427	pub fn new(settings: &FlakeSettings) -> Result<Self> {428		let o = with_default_context(|c, _| unsafe { flake_lock_flags_new(c, settings.0) })429			.map(Self)?;430		// with_default_context(|c, _| unsafe { flake_lock_flags_set_mode_virtual(c, o.0) })?;431432		Ok(o)433	}434}435impl Drop for FlakeLockFlags {436	fn drop(&mut self) {437		unsafe {438			flake_lock_flags_free(self.0);439		}440	}441}442443pub(crate) unsafe extern "C" fn copy_nix_str(444	start: *const c_char,445	n: c_uint,446	user_data: *mut c_void,447) {448	let s = unsafe { slice::from_raw_parts(start.cast::<u8>(), n as usize) };449	let s = std::str::from_utf8(s).expect("c string has invalid utf-8");450	unsafe { *user_data.cast::<String>() = s.to_owned() };451}452453struct Store(*mut c_store);454unsafe impl Send for Store {}455unsafe impl Sync for Store {}456457impl Store {458	fn parse_path(&self, path: &CStr) -> Result<StorePath> {459		with_default_context(|c, _| {460			StorePath(unsafe { store_parse_path(c, self.0, path.as_ptr()) })461		})462	}463}464465#[repr(transparent)]466pub struct EvalState(*mut c_eval_state);467unsafe impl Send for EvalState {}468unsafe impl Sync for EvalState {}469470impl Drop for EvalState {471	fn drop(&mut self) {472		unsafe {473			state_free(self.0);474		}475	}476}477478pub struct FlakeReference(*mut flake_reference);479impl FlakeReference {480	#[instrument(name = "new-flake-reference", skip(flake, parse, fetch))]481	pub fn new(482		s: &str,483		flake: &FlakeSettings,484		parse: &FlakeReferenceParseFlags,485		fetch: &FetchSettings,486	) -> Result<(Self, String)> {487		let mut out = null_mut();488		let mut fragment = String::new();489		// let fetch_settings = fetcher_settings;490		with_default_context(|c, _| unsafe {491			flake_reference_and_fragment_from_string(492				c,493				fetch.0,494				flake.0,495				parse.0,496				s.as_ptr().cast(),497				s.len(),498				&mut out,499				Some(copy_nix_str),500				(&raw mut fragment).cast(),501			)502		})?;503		assert!(!out.is_null());504505		Ok((Self(out), fragment))506	}507	#[instrument(name = "lock-flake", skip(self, fetch, flake, lock))]508	pub fn lock(509		&mut self,510		fetch: &FetchSettings,511		flake: &FlakeSettings,512		lock: &FlakeLockFlags,513	) -> Result<LockedFlake> {514		with_default_context(|c, es| unsafe { flake_lock(c, fetch.0, flake.0, es, lock.0, self.0) })515			.map(LockedFlake)516	}517}518unsafe impl Send for FlakeReference {}519unsafe impl Sync for FlakeReference {}520521pub struct LockedFlake(*mut locked_flake);522impl LockedFlake {523	pub fn get_attrs(&self, settings: &mut FlakeSettings) -> Result<Value> {524		with_default_context(|c, es| unsafe {525			locked_flake_get_output_attrs(c, settings.0, es, self.0)526		})527		.map(Value)528	}529}530unsafe impl Send for LockedFlake {}531unsafe impl Sync for LockedFlake {}532impl Drop for LockedFlake {533	fn drop(&mut self) {534		unsafe {535			locked_flake_free(self.0);536		};537	}538}539540type FieldName = [u8; 64];541fn init_field_name(v: &str) -> FieldName {542	let mut f = [0; 64];543	assert!(v.len() < 64, "max field name is 63 chars");544	assert!(545		v.bytes().all(|v| v != 0),546		"nul bytes are unsupported in field name"547	);548	f[0..v.len()].copy_from_slice(v.as_bytes());549	f550}551552pub struct RealisedString(*mut realised_string);553impl fmt::Debug for RealisedString {554	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {555		self.as_str().fmt(f)556	}557}558559impl RealisedString {560	pub fn as_str(&self) -> &str {561		let len = unsafe { realised_string_get_buffer_size(self.0) };562		let data: *const u8 = unsafe { realised_string_get_buffer_start(self.0) }.cast();563		let data = unsafe { slice::from_raw_parts(data, len) };564		std::str::from_utf8(data).expect("non-utf8 strings not supported")565	}566	pub fn path_count(&self) -> usize {567		unsafe { realised_string_get_store_path_count(self.0) }568	}569	pub fn path(&self, i: usize) -> String {570		assert!(i < self.path_count());571		let path = unsafe { realised_string_get_store_path(self.0, i) };572		let mut err_out = String::new();573		unsafe { store_path_name(path, Some(copy_nix_str), (&raw mut err_out).cast()) };574		err_out575	}576}577578unsafe impl Send for RealisedString {}579impl Drop for RealisedString {580	fn drop(&mut self) {581		unsafe { realised_string_free(self.0) }582	}583}584585#[repr(transparent)]586pub struct Value(*mut value);587588unsafe impl Send for Value {}589unsafe impl Sync for Value {}590591pub trait AsFieldName {592	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T>;593	fn to_field_name(&self) -> Result<String>;594}595impl AsFieldName for Value {596	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {597		let f = self.to_string()?;598		v(init_field_name(&f))599	}600	fn to_field_name(&self) -> Result<String> {601		self.to_string()602	}603}604impl<E> AsFieldName for E605where606	E: AsRef<str>,607{608	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {609		let f = self.as_ref();610		v(init_field_name(f))611	}612	fn to_field_name(&self) -> Result<String> {613		Ok(self.as_ref().to_owned())614	}615}616617struct AttrsBuilder(*mut c_bindings_builder);618impl AttrsBuilder {619	fn new(capacity: usize) -> Self {620		with_default_context(|c, es| unsafe { make_bindings_builder(c, es, capacity) })621			.map(Self)622			.expect("alloc should not fail")623	}624	fn insert(&mut self, k: &impl AsFieldName, v: Value) {625		k.as_field_name(|name| {626			with_default_context(|c, _| unsafe {627				bindings_builder_insert(c, self.0, name.as_ptr().cast(), v.0);628				// bindings_builder_insert doesn't do incref629			})630		})631		.expect("builder insert shouldn't fail");632	}633}634impl Drop for AttrsBuilder {635	fn drop(&mut self) {636		unsafe { bindings_builder_free(self.0) };637	}638}639640struct ListBuilder(*mut c_list_builder, c_uint);641impl ListBuilder {642	fn new(capacity: usize) -> Self {643		with_default_context(|c, es| unsafe { make_list_builder(c, es, capacity) })644			.map(|l| Self(l, 0))645			.expect("alloc should not fail")646	}647}648impl ListBuilder {649	fn push(&mut self, v: Value) {650		with_default_context(|c, _| unsafe {651			list_builder_insert(652				c,653				self.0,654				{655					let v = self.1;656					self.1 += 1;657					v658				},659				v.0,660			)661		})662		.expect("list insert shouldn't fail");663	}664}665impl Drop for ListBuilder {666	fn drop(&mut self) {667		unsafe { list_builder_free(self.0) };668	}669}670671impl Value {672	pub fn new_primop(v: NativeFn) -> Self {673		let out = Self::new_uninit();674		with_default_context(|c, _| unsafe { init_primop(c, out.0, v.0) })675			.expect("primop initialization should not fail");676		out677	}678	pub fn new_attrs(v: HashMap<&str, Value>) -> Self {679		let out = Self::new_uninit();680		let mut b = AttrsBuilder::new(v.len());681		for (k, v) in v {682			b.insert(&k, v);683		}684		with_default_context(|c, _| unsafe { make_attrs(c, out.0, b.0) })685			.expect("attrs initialization should not fail");686687		out688	}689	fn new_list<T: Into<Self>>(v: Vec<T>) -> Self {690		let out = Self::new_uninit();691		let mut b = ListBuilder::new(v.len());692		for v in v {693			b.push(v.into());694		}695		with_default_context(|c, _| unsafe { make_list(c, b.0, out.0) })696			.expect("list initialization should not fail");697698		out699	}700	fn new_uninit() -> Self {701		let out = with_default_context(|c, es| unsafe { alloc_value(c, es) })702			.expect("value allocation should not fail");703		Self(out)704	}705	pub fn new_str(v: &str) -> Self {706		let s = CString::new(v).expect("string should not contain NULs");707		let out = Self::new_uninit();708		// String is copied, `s` is free to be dropped709		with_default_context(|c, _| unsafe { init_string(c, out.0, s.as_ptr()) })710			.expect("string initialization should not fail");711		out712	}713	pub fn new_int(i: i64) -> Self {714		let out = Self::new_uninit();715		with_default_context(|c, _| unsafe { init_int(c, out.0, i) })716			.expect("int initialization should not fail");717		out718	}719	pub fn new_bool(v: bool) -> Self {720		let out = Self::new_uninit();721		with_default_context(|c, _| unsafe { init_bool(c, out.0, v) })722			.expect("bool initialization should not fail");723		out724	}725	// TODO: As far as I can see, there is no way to get Thunks from nix public C api, so this function is useless726	// fn force(&mut self, st: &mut EvalState) -> Result<()> {727	// 	with_default_context(|c, _| unsafe { value_force(c, st.0, self.0) })?;728	// 	Ok(())729	// }730	pub fn type_of(&self) -> NixType {731		let ty = with_default_context(|c, _| unsafe { get_type(c, self.0) })732			.expect("get_type should not fail");733		NixType::from_int(ty)734	}735	fn builtin_to_string(&self) -> Result<Self> {736		let builtin = Self::eval("builtins.toString")?;737		builtin.call(self.clone())738	}739	fn force(&mut self, s: *mut nix_raw::EvalState) -> Result<()> {740		with_default_context(|c, _| unsafe { value_force(c, s, self.0) })?;741		Ok(())742	}743	pub fn to_string(&self) -> Result<String> {744		let ty = self.type_of();745		if !matches!(ty, NixType::String) {746			bail!("unexpected type: {ty:?}, expected string");747		}748		let mut str_out = String::new();749		with_default_context(|c, _| unsafe {750			get_string(c, self.0, Some(copy_nix_str), (&raw mut str_out).cast())751		})?;752753		Ok(str_out)754	}755	pub fn to_realised_string(&self) -> Result<RealisedString> {756		with_default_context(|c, es| unsafe { string_realise(c, es, self.0, false) })757			.map(RealisedString)758759		// let store_paths = unsafe { nix_raw::realised_string_get_store_path_count(str) };760		// for i in 0..store_paths {761		// 	let store_path = unsafe { nix_raw::realised_string_get_store_path(str, i) };762		// 	nix_raw::store_path_name(store_path, callback, user_data);763		// }764		// dbg!(store_paths);765		// todo!();766	}767768	pub fn has_field(&self, field: &str) -> Result<bool> {769		if !matches!(self.type_of(), NixType::Attrs) {770			bail!("invalid type: expected attrs");771		}772773		let f = init_field_name(field);774		with_default_context(|c, es| unsafe { has_attr_byname(c, self.0, es, f.as_ptr().cast()) })775	}776	// pub fn derivation_path(&self) {777	// 	nix_raw::real778	// }779	pub fn list_fields(&self) -> Result<Vec<String>> {780		if !matches!(self.type_of(), NixType::Attrs) {781			bail!("invalid type: expected attrs");782		}783784		let len = with_default_context(|c, _| unsafe { get_attrs_size(c, self.0) })?;785		let mut out = Vec::with_capacity(len as usize);786787		for i in 0..len {788			let name =789				with_default_context(|c, es| unsafe { get_attr_name_byidx(c, self.0, es, i) })?;790			let c = unsafe { CStr::from_ptr(name) };791			out.push(c.to_str().expect("nix field names are utf-8").to_owned());792		}793		Ok(out)794	}795	pub fn get_elem(&self, v: usize) -> Result<Self> {796		if !matches!(self.type_of(), NixType::List) {797			bail!("invalid type: expected list");798		}799		let len = with_default_context(|c, _| unsafe { get_list_size(c, self.0) })? as usize;800		if v >= len {801			bail!("oob list get: {v} >= {len}");802		}803804		with_default_context(|c, es| unsafe { get_list_byidx(c, self.0, es, v as u32) }).map(Self)805	}806	pub fn attrs_update(self, other: Value /*, ignore_errors: bool*/) -> Result<Self> {807		let attrs_update_fn = Self::eval("a: b: a // b")?;808809		attrs_update_fn810			.call(self)?811			.call(other)812			.context("attrs update")813	}814	pub fn get_field(&self, name: impl AsFieldName) -> Result<Self> {815		if !matches!(self.type_of(), NixType::Attrs) {816			bail!("invalid type: expected attrs");817		}818819		name.as_field_name(|name| {820			with_default_context(|c, es| unsafe {821				get_attr_byname(c, self.0, es, name.as_ptr().cast())822			})823			.map(Self)824		})825		.with_context(|| format!("getting field {:?}", name.to_field_name()))826	}827	pub fn call(&self, v: Value) -> Result<Self> {828		let kind = self829			.functor_kind()830			.ok_or_else(|| anyhow!("can only call function or functor"))?;831832		let function = match kind {833			FunctorKind::Function => self.clone(),834			FunctorKind::Functor => {835				let f = self836					.get_field("__functor")837					.context("getting functor value")?;838				assert_eq!(839					f.type_of(),840					NixType::Function,841					"invalid functor encountered"842				);843				f844			}845		};846847		let out = Value::new_uninit();848		with_default_context(|c, es| unsafe { value_call(c, es, function.0, v.0, out.0) })?;849850		Ok(out)851	}852	pub fn eval(v: &str) -> Result<Self> {853		let s = CString::new(v).expect("expression shouldn't have internal NULs");854		let out = Self::new_uninit();855		with_default_context(|c, es| unsafe {856			expr_eval_from_string(c, es, s.as_ptr(), c"/root".as_ptr(), out.0)857		})?;858		Ok(out)859	}860	#[instrument(name = "build", skip(self), fields(output))]861	pub fn build(&self, output: &str) -> Result<PathBuf> {862		if !self.is_derivation() {863			bail!("expected derivation to build")864		}865		let output_name = self866			.get_field("outputName")867			.context("getting output name field")?868			.to_string()?;869		let v = if output_name != output {870			let out = self.get_field(output).context("getting target output")?;871			if !out.is_derivation() {872				bail!("unknown output: {output}");873			}874			out875		} else {876			self.clone()877		};878879		let drv_path = v880			.get_field("drvPath")881			.context("getting drvPath")?882			.to_string()?;883		let graph = drv::DrvGraph::resolve(&drv_path)?;884		let _guard = logging::register_build_graph(&Span::current(), &graph);885886		// to_string here blocks until the path is built887		let s = v.builtin_to_string()?;888		let rs = s.to_realised_string()?;889		let out_path = rs.as_str().to_owned();890		Ok(PathBuf::from(out_path))891	}892	pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {893		let to_json = Self::eval("builtins.toJSON")?;894		let s = to_json.call(self.clone())?.to_string()?;895		Ok(serde_json::from_str(&s)?)896	}897	pub fn serialized<T: Serialize>(v: &T) -> Result<Self> {898		Self::eval(&nixlike::serialize(v)?)899	}900901	// Convert to string/evaluate derivations/etc902	// fn to_string_weak(&self) -> Result<String> {903	// 	// TODO: For now, it works exactly like to_string, see the comment for fn force()904	// 	self.to_string()905	// }906907	fn is_derivation(&self) -> bool {908		if !matches!(self.type_of(), NixType::Attrs) {909			return false;910		}911		let Some(ty) = self.get_field("type").ok() else {912			return false;913		};914		matches!(ty.to_string().as_deref(), Ok("derivation"))915	}916	fn functor_kind(&self) -> Option<FunctorKind> {917		match self.type_of() {918			NixType::Attrs => self919				.has_field("__functor")920				.expect("has_field shouldn't fail for attrs")921				.then_some(FunctorKind::Functor),922			NixType::Function => Some(FunctorKind::Function),923			_ => None,924		}925	}926	pub fn is_function(&self) -> bool {927		self.functor_kind().is_some()928	}929	pub fn is_null(&self) -> bool {930		matches!(self.type_of(), NixType::Null)931	}932	pub fn is_string(&self) -> bool {933		matches!(self.type_of(), NixType::String)934	}935	pub fn is_attrs(&self) -> bool {936		matches!(self.type_of(), NixType::Attrs)937	}938}939940impl From<String> for Value {941	fn from(value: String) -> Self {942		Value::new_str(&value)943	}944}945impl From<bool> for Value {946	fn from(value: bool) -> Self {947		Value::new_bool(value)948	}949}950impl From<&str> for Value {951	fn from(value: &str) -> Self {952		Value::new_str(value)953	}954}955impl<T> From<Vec<T>> for Value956where957	T: Into<Value>,958{959	fn from(value: Vec<T>) -> Self {960		Value::new_list(value)961	}962}963964impl Clone for Value {965	fn clone(&self) -> Self {966		with_default_context(|c, _| unsafe { value_incref(c, self.0) })967			.expect("value incref should not fail");968		Self(self.0)969	}970}971impl Drop for Value {972	fn drop(&mut self) {973		with_default_context(|c, _| unsafe { value_decref(c, self.0) })974			.expect("value drop should not fail");975	}976}977978static TOKIO_FOR_NIX: OnceLock<Arc<tokio::runtime::Runtime>> = OnceLock::new();979980pub fn init_libraries() {981	unsafe { GC_allow_register_threads() };982983	let mut ctx = NixContext::new();984	ctx.run_in_context(|c| unsafe { libutil_init(c) })985		.expect("util init should not fail");986	ctx.run_in_context(|c| unsafe { libstore_init(c) })987		.expect("store init should not fail");988	ctx.run_in_context(|c| unsafe { libexpr_init(c) })989		.expect("expr init should not fail");990991	nix_logging_cxx::apply_tracing_logger();992}993994pub fn init_tokio_for_nix(tokio: Arc<tokio::runtime::Runtime>) {995	TOKIO_FOR_NIX996		.set(tokio)997		.expect("tokio for nix should only be initialized once");998}9991000pub fn await_in_nix<F: Send + 'static>(f: impl Future<Output = F> + Send + 'static) -> F {1001	// It should be possible to do Handle::current(), but some of the planned features don't work well with that1002	let runtime = TOKIO_FOR_NIX1003		.get()1004		.expect("init_tokio_for_nix was not called");1005	std::thread::spawn(move || runtime.block_on(f))1006		.join()1007		.expect("await_in_nix inner thread panicked")1008}10091010unsafe extern "C" fn nix_primop_closure_adapter<const N: usize>(1011	user_data: *mut c_void,1012	mut context: *mut c_context,1013	state: *mut nix_raw::EvalState,1014	args: *mut *mut value,1015	ret: *mut value,1016) {1017	let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };1018	let args: [&Value; N] = array::from_fn(|i| {1019		let v: &mut Value = unsafe { &mut *args.add(i).cast() };1020		v as &Value1021	});1022	let ctx: &mut NixContext = unsafe { transmute(&mut context) };10231024	let state: &EvalState = unsafe { std::mem::transmute(&state) };10251026	match user_closure(state, args) {1027		Ok(v) => {1028			unsafe { copy_value(context, ret, v.0) };1029		}1030		Err(e) => {1031			ctx.set_err(e);1032		}1033	}1034}10351036type UserClosure<const N: usize> = Box<dyn Fn(&EvalState, [&Value; N]) -> Result<Value>>;10371038pub struct NativeFn(*mut PrimOp);1039impl NativeFn {1040	pub fn new<const N: usize>(1041		name: &'static CStr,1042		doc: &'static CStr,1043		args: [&'static CStr; N],1044		f: impl Fn(&EvalState, [&Value; N]) -> Result<Value> + 'static,1045	) -> Self {1046		// Double-boxing to make it thin pointer, as vtable gets outside of first Box1047		let closure: Box<UserClosure<N>> = Box::new(Box::new(f));1048		let f: PrimOpFun = Some(nix_primop_closure_adapter::<N>);1049		let mut args = args.into_iter().map(|v| v.as_ptr()).collect_vec();1050		args.push(null());1051		let args = args.as_mut_ptr();1052		let primop = unsafe {1053			alloc_primop(1054				null_mut(),1055				f,1056				N as i32,1057				name.as_ptr(),1058				args,1059				doc.as_ptr(),1060				Box::into_raw(closure).cast(),1061			)1062		};10631064		assert!(!primop.is_null(), "primop allocation should not fail");10651066		Self(primop)1067	}1068	pub fn register(self) {1069		unsafe { register_primop(null_mut(), self.0) };1070	}1071}10721073struct StorePath(*mut c_store_path);1074impl StorePath {}10751076impl Drop for StorePath {1077	fn drop(&mut self) {1078		unsafe { store_path_free(self.0) }1079	}1080}10811082#[test_log::test]1083fn test_native() -> Result<()> {1084	init_libraries();1085	NativeFn::new(1086		c"__uppercaseSuffix2",1087		c"make string uppercase and add suffix",1088		[c"str", c"suffix"],1089		|_, [str, suffix]: [&Value; 2]| {1090			let str = str.to_string()?;1091			let suffix = suffix.to_string()?;1092			Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1093		},1094	)1095	.register();10961097	let mut fetch_settings = FetchSettings::new();1098	fetch_settings.set(c"warn-dirty", c"false");10991100	let manifest = format!("git+file://{}/../../", env!("CARGO_MANIFEST_DIR"));1101	let flake = FlakeSettings::new()?;1102	let parse = FlakeReferenceParseFlags::new(&flake)?;1103	let (mut r, _) = FlakeReference::new(&manifest, &flake, &parse, &fetch_settings)?;1104	let lock = FlakeLockFlags::new(&flake)?;1105	let locked = r.lock(&fetch_settings, &flake, &lock)?;1106	let attrs = locked.get_attrs(&mut FlakeSettings::new()?)?;11071108	let builtins = Value::eval("builtins")?;1109	assert_eq!(builtins.type_of(), NixType::Attrs);11101111	assert_eq!(attrs.type_of(), NixType::Attrs);1112	let test_data = nix_go!(attrs.testData);11131114	let test_string: String = nix_go_json!(test_data.testString);1115	assert_eq!(test_string, "hello");11161117	let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);1118	let s = CString::new(s.to_string()?).expect("path str is cstring");11191120	let uppercase_suffix = Value::new_primop(NativeFn::new(1121		c"uppercase_suffix",1122		c"make string uppercase and add suffix",1123		[c"str", c"suffix"],1124		|es, [str, suffix]: [&Value; 2]| {1125			let str = str.to_string()?;1126			let suffix = suffix.to_string()?;1127			Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1128		},1129	));11301131	let test_result: String = nix_go_json!(test_data.testPrimop(uppercase_suffix));1132	assert_eq!(test_result, "PREFIX_BODY_SUFFIX");1133	let test_result: String = nix_go_json!(builtins.uppercaseSuffix2("test")("suffix"));1134	assert_eq!(test_result, "TESTsuffix");11351136	let drv_path =1137		nix_go!(attrs.packages["x86_64-linux"]["fleet-install-secrets"].drvPath).to_string()?;1138	let graph = drv::DrvGraph::resolve(&drv_path)?;1139	eprintln!(1140		"fleet-install-secrets dependency graph: {} nodes",1141		graph.nodes.len()1142	);1143	for (path, node) in &graph.nodes {1144		if !node.input_drvs.is_empty() {1145			eprintln!("  {} ({} deps)", node.name, node.input_drvs.len());1146		}1147	}11481149	Ok(())1150}11511152// pub struct GcAlloc;1153// unsafe impl GlobalAlloc for GcAlloc {1154// 	unsafe fn alloc(&self, l: Layout) -> *mut u8 {1155// 		let ptr = unsafe { GC_malloc(l.size()) };1156// 		ptr.cast()1157// 	}1158// 	unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) {1159// 		// unsafe { GC_free(ptr.cast()) };1160// 	}1161//1162// 	unsafe fn realloc(&self, ptr: *mut u8, _: Layout, new_size: usize) -> *mut u8 {1163// 		let ptr = unsafe { GC_realloc(ptr.cast(), new_size) };1164// 		ptr.cast()1165// 	}1166// }1167//1168// #[global_allocator]1169// static GC: GcAlloc = GcAlloc;
modifiedcrates/nix-eval/src/logging.rsdiffbeforeafterboth
--- a/crates/nix-eval/src/logging.rs
+++ b/crates/nix-eval/src/logging.rs
@@ -351,12 +351,15 @@
 			if let Some(entry) = drv_graph.get_mut(dep_path) {
 				entry.refcount += 1;
 			} else {
-				drv_graph.insert(dep_path.clone(), DrvGraphEntry {
-					name: dep_node.name.clone(),
-					parent: Some(path.clone()),
-					span: None,
-					refcount: 1,
-				});
+				drv_graph.insert(
+					dep_path.clone(),
+					DrvGraphEntry {
+						name: dep_node.name.clone(),
+						parent: Some(path.clone()),
+						span: None,
+						refcount: 1,
+					},
+				);
 			}
 			paths.push(dep_path.clone());
 			queue.push_back(dep_path.clone());
modifiedcrates/opentelemetry-exporter-env/src/lib.rsdiffbeforeafterboth
--- a/crates/opentelemetry-exporter-env/src/lib.rs
+++ b/crates/opentelemetry-exporter-env/src/lib.rs
@@ -11,10 +11,7 @@
 #[derive(thiserror::Error, Debug)]
 pub enum Error {
 	#[error("environment variable {env} contains invalid UTF-8: {value:?}")]
-	InvalidUtf8 {
-		env: &'static str,
-		value: OsString,
-	},
+	InvalidUtf8 { env: &'static str, value: OsString },
 	#[error("environment variable {env}={value:?}: {error}")]
 	EnvParse {
 		env: &'static str,
@@ -103,13 +100,30 @@
 #[cfg_attr(feature = "clap", derive(clap::Parser))]
 pub struct SignalExporterSettings {
 	/// Traces exporter to be used.
-	#[cfg_attr(feature = "clap", arg(long = "otel-traces-exporter", env = "OTEL_TRACES_EXPORTER", value_enum))]
+	#[cfg_attr(
+		feature = "clap",
+		arg(
+			long = "otel-traces-exporter",
+			env = "OTEL_TRACES_EXPORTER",
+			value_enum
+		)
+	)]
 	pub traces: Option<ExporterKind>,
 	/// Metrics exporter to be used.
-	#[cfg_attr(feature = "clap", arg(long = "otel-metrics-exporter", env = "OTEL_METRICS_EXPORTER", value_enum))]
+	#[cfg_attr(
+		feature = "clap",
+		arg(
+			long = "otel-metrics-exporter",
+			env = "OTEL_METRICS_EXPORTER",
+			value_enum
+		)
+	)]
 	pub metrics: Option<ExporterKind>,
 	/// Logs exporter to be used.
-	#[cfg_attr(feature = "clap", arg(long = "otel-logs-exporter", env = "OTEL_LOGS_EXPORTER", value_enum))]
+	#[cfg_attr(
+		feature = "clap",
+		arg(long = "otel-logs-exporter", env = "OTEL_LOGS_EXPORTER", value_enum)
+	)]
 	pub logs: Option<ExporterKind>,
 }
 
modifiedcrates/opentelemetry-exporter-env/src/otlp.rsdiffbeforeafterboth
--- a/crates/opentelemetry-exporter-env/src/otlp.rs
+++ b/crates/opentelemetry-exporter-env/src/otlp.rs
@@ -55,15 +55,13 @@
 				}
 				builder.build()
 			}
-			OtlpProtocol::HttpProtobuf | OtlpProtocol::HttpJson => {
-				<$exporter>::builder()
-					.with_http()
-					.with_endpoint(&s.endpoint)
-					.with_headers(to_hashmap(s.headers.as_deref()))
-					.with_protocol(s.protocol.into())
-					.with_timeout(s.timeout)
-					.build()
-			}
+			OtlpProtocol::HttpProtobuf | OtlpProtocol::HttpJson => <$exporter>::builder()
+				.with_http()
+				.with_endpoint(&s.endpoint)
+				.with_headers(to_hashmap(s.headers.as_deref()))
+				.with_protocol(s.protocol.into())
+				.with_timeout(s.timeout)
+				.build(),
 		}
 	}};
 }
modifiedlib/default.nixdiffbeforeafterboth
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -89,6 +89,44 @@
       );
 
     /**
+      Generate a random password suitable for PostgreSQL role authentication.
+
+      Options:
+        size: generated password length in ascii characters (bytes).
+        iterations: PBKDF2 iteration count (PG default: 4096).
+        noSymbols: by default, character set includes various special characters ($ , ! + * : ~), and might
+                   not be accepted in some contexts, this option switches charset to just [A-Za-z0-9].
+
+      Output:
+        secret: encrypted password.
+        hash: SCRAM-SHA-256 hash of the password to be used by postgres.
+    */
+    # It is not possible to extract it into a scram-sha-256 generic generator because there is no estabilished format
+    # for it, and mongodb for example encodes it differently.
+    mkPostgresPassword =
+      {
+        size ? 32,
+        iterations ? 4096,
+        noSymbols ? false,
+      }:
+      (
+        { mkSecretGenerator }:
+        mkSecretGenerator {
+          script = ''
+            mkdir $out
+            gh generate postgres-password \
+              -s $out/secret \
+              -H $out/hash \
+              --size ${toString size} \
+              --iterations ${toString iterations} \
+              ${optionalString noSymbols "--no-symbols"}
+          '';
+          parts.secret.encrypted = true;
+          parts.hash.encrypted = false;
+        }
+      );
+
+    /**
       Generate a random ed25519 keypair
 
       Options:
@@ -205,8 +243,7 @@
       mkAskFile {
         inherit part;
         header = builtins.concatStringsSep "\n" (
-          (map (l: "# ${l}") (lib.splitString "\n" header))
-          ++ (map (v: "${v}=") variables)
+          (map (l: "# ${l}") (lib.splitString "\n" header)) ++ (map (v: "${v}=") variables)
         );
       };
 
@@ -306,6 +343,7 @@
 
   inherit (secrets)
     mkPassword
+    mkPostgresPassword
     mkEd25519
     mkX25519
     mkRsa