2024-11-27 14:13:54 +01:00
use anyhow ::{ bail , Error , Result } ;
2024-02-22 22:32:50 +01:00
use lazy_static ::lazy_static ;
use nix_c_raw as raw ;
use nix_util ::context ::Context ;
2024-05-22 14:45:12 +02:00
use nix_util ::string_return ::{ callback_get_result_string , callback_get_result_string_data } ;
2024-06-15 14:51:26 +02:00
use nix_util ::{ check_call , result_string_init } ;
2024-11-27 14:13:54 +01:00
use std ::collections ::HashMap ;
2024-05-24 00:16:10 +02:00
use std ::ffi ::{ c_char , CString } ;
2024-02-22 22:32:50 +01:00
use std ::ptr ::null_mut ;
use std ::ptr ::NonNull ;
2024-11-27 14:13:54 +01:00
use std ::sync ::{ Arc , Mutex , Weak } ;
2024-02-22 22:32:50 +01:00
2024-12-17 09:54:28 +01:00
use crate ::path ::StorePath ;
2024-02-22 22:32:50 +01:00
/* TODO make Nix itself thread safe */
lazy_static! {
2024-06-15 14:51:26 +02:00
static ref INIT : Result < ( ) > = unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::libstore_init ( & mut Context ::new ( ) ) ) ? ;
2024-06-15 14:51:26 +02:00
Ok ( ( ) )
2024-02-22 22:32:50 +01:00
} ;
}
struct StoreRef {
inner : NonNull < raw ::Store > ,
}
impl StoreRef {
2024-12-17 10:40:03 +01:00
/// # Safety
///
/// The returned pointer is only valid as long as the `StoreRef` is alive.
pub unsafe fn ptr ( & self ) -> * mut raw ::Store {
2024-02-22 22:32:50 +01:00
self . inner . as_ptr ( )
}
}
impl Drop for StoreRef {
fn drop ( & mut self ) {
unsafe {
2024-05-09 12:29:32 -04:00
raw ::store_free ( self . inner . as_ptr ( ) ) ;
2024-02-22 22:32:50 +01:00
}
}
}
2024-10-02 14:50:47 +02:00
unsafe impl Send for StoreRef { }
2024-11-27 14:13:54 +01:00
/// Unlike pointers in general, operations on raw::Store are thread safe and it is therefore safe to share them between threads.
unsafe impl Sync for StoreRef { }
2024-02-22 22:32:50 +01:00
2024-06-27 15:57:43 +02:00
/// A [Weak] reference to a store.
pub struct StoreWeak {
inner : Weak < StoreRef > ,
}
impl StoreWeak {
/// Upgrade the weak reference to a proper [Store].
2024-06-27 16:01:40 +02:00
///
/// If no normal reference to the [Store] is around anymore elsewhere, this fails by returning `None`.
2024-06-27 15:57:43 +02:00
pub fn upgrade ( & self ) -> Option < Store > {
self . inner . upgrade ( ) . map ( | inner | Store {
inner ,
context : Context ::new ( ) ,
} )
}
}
2024-11-27 14:13:54 +01:00
/// Protects against https://github.com/NixOS/nix/issues/11979 (unless different parameters are passed, in which case it's up to luck, but you do get your own parameters as you asked for).
type StoreCacheMap = HashMap < ( String , Vec < ( String , String ) > ) , StoreWeak > ;
lazy_static! {
static ref STORE_CACHE : Arc < Mutex < StoreCacheMap > > = Arc ::new ( Mutex ::new ( HashMap ::new ( ) ) ) ;
}
2024-02-22 22:32:50 +01:00
pub struct Store {
2024-06-26 21:00:22 +02:00
inner : Arc < StoreRef > ,
2024-02-22 22:32:50 +01:00
/* An error context to reuse. This way we don't have to allocate them for each store operation. */
context : Context ,
}
impl Store {
2024-05-24 00:16:10 +02:00
pub fn open < ' a , ' b > (
url : & str ,
params : impl IntoIterator < Item = ( & ' a str , & ' b str ) > ,
2024-11-27 14:13:54 +01:00
) -> Result < Self > {
let params = params
. into_iter ( )
. map ( | ( k , v ) | ( k . to_owned ( ) , v . to_owned ( ) ) )
. collect ::< Vec < ( String , String ) > > ( ) ;
let params2 = params . clone ( ) ;
let mut store_cache = STORE_CACHE
. lock ( )
. map_err ( | _ | Error ::msg ( " Failed to lock store cache. This should never happen. " ) ) ? ;
match store_cache . entry ( ( url . to_string ( ) , params ) ) {
std ::collections ::hash_map ::Entry ::Occupied ( mut e ) = > {
if let Some ( store ) = e . get ( ) . upgrade ( ) {
Ok ( store )
} else {
let store = Self ::open_uncached (
url ,
params2 . iter ( ) . map ( | ( k , v ) | ( k . as_str ( ) , v . as_str ( ) ) ) ,
) ? ;
e . insert ( store . weak_ref ( ) ) ;
Ok ( store )
}
}
std ::collections ::hash_map ::Entry ::Vacant ( e ) = > {
let store = Self ::open_uncached (
url ,
params2 . iter ( ) . map ( | ( k , v ) | ( k . as_str ( ) , v . as_str ( ) ) ) ,
) ? ;
e . insert ( store . weak_ref ( ) ) ;
Ok ( store )
}
}
}
fn open_uncached < ' a , ' b > (
url : & str ,
params : impl IntoIterator < Item = ( & ' a str , & ' b str ) > ,
2024-05-24 00:16:10 +02:00
) -> Result < Self > {
2024-02-22 22:32:50 +01:00
let x = INIT . as_ref ( ) ;
match x {
Ok ( _ ) = > { }
Err ( e ) = > {
// Couldn't just clone the error, so we have to print it here.
bail! ( " nix_libstore_init error: {} " , e ) ;
}
}
2024-06-15 12:40:45 +02:00
let mut context : Context = Context ::new ( ) ;
2024-02-22 22:32:50 +01:00
let uri_ptr = CString ::new ( url ) ? ;
2024-05-24 00:16:10 +02:00
// this intermediate value must be here and must not be moved
// because it owns the data the `*const c_char` pointers point to.
let params : Vec < ( CString , CString ) > = params
. into_iter ( )
. map ( | ( k , v ) | Ok ( ( CString ::new ( k ) ? , CString ::new ( v ) ? ) ) ) // to do. context
. collect ::< Result < _ > > ( ) ? ;
// this intermediate value owns the data the `*mut *const c_char` pointer points to.
let mut params : Vec < _ > = params
. iter ( )
. map ( | ( k , v ) | [ k . as_ptr ( ) , v . as_ptr ( ) ] )
. collect ( ) ;
// this intermediate value owns the data the `*mut *mut *const c_char` pointer points to.
let mut params : Vec < * mut * const c_char > = params
. iter_mut ( )
. map ( | t | t . as_mut_ptr ( ) )
. chain ( std ::iter ::once ( null_mut ( ) ) ) // signal the end of the array
. collect ( ) ;
2024-06-15 14:51:26 +02:00
let store = unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::store_open (
& mut context ,
uri_ptr . as_ptr ( ) ,
params . as_mut_ptr ( )
) )
2024-06-15 14:51:26 +02:00
} ? ;
2024-02-22 22:32:50 +01:00
if store . is_null ( ) {
2024-05-09 12:41:34 -04:00
panic! ( " nix_c_store_open returned a null pointer without an error " ) ;
2024-02-22 22:32:50 +01:00
}
let store = Store {
2024-06-26 21:00:22 +02:00
inner : Arc ::new ( StoreRef {
2024-02-22 22:32:50 +01:00
inner : NonNull ::new ( store ) . unwrap ( ) ,
2024-06-26 21:00:22 +02:00
} ) ,
2024-02-22 22:32:50 +01:00
context ,
} ;
Ok ( store )
}
2024-12-17 10:40:03 +01:00
/// # Safety
///
/// The returned pointer is only valid as long as the `Store` is alive.
pub unsafe fn raw_ptr ( & self ) -> * mut raw ::Store {
2024-03-19 14:43:01 +01:00
self . inner . ptr ( )
}
2024-06-15 12:40:45 +02:00
pub fn get_uri ( & mut self ) -> Result < String > {
2024-05-22 14:45:12 +02:00
let mut r = result_string_init! ( ) ;
2024-06-15 14:51:26 +02:00
unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::store_get_uri (
& mut self . context ,
self . inner . ptr ( ) ,
Some ( callback_get_result_string ) ,
callback_get_result_string_data ( & mut r )
) )
2024-12-16 12:52:15 +01:00
} ? ;
r
}
#[ cfg(nix_at_least = " 2.26 " ) ]
#[ doc(alias = " nix_store_get_storedir " ) ]
pub fn get_storedir ( & mut self ) -> Result < String > {
let mut r = result_string_init! ( ) ;
unsafe {
check_call! ( raw ::store_get_storedir (
& mut self . context ,
self . inner . ptr ( ) ,
Some ( callback_get_result_string ) ,
callback_get_result_string_data ( & mut r )
) )
2024-06-15 14:51:26 +02:00
} ? ;
2024-05-22 14:45:12 +02:00
r
2024-02-22 22:32:50 +01:00
}
2024-06-27 15:57:43 +02:00
2024-12-17 09:54:28 +01:00
#[ doc(alias = " nix_store_parse_path " ) ]
pub fn parse_store_path ( & mut self , path : & str ) -> Result < StorePath > {
let path = CString ::new ( path ) ? ;
unsafe {
let store_path = check_call! ( raw ::store_parse_path (
& mut self . context ,
self . inner . ptr ( ) ,
path . as_ptr ( )
) ) ? ;
let store_path =
NonNull ::new ( store_path ) . expect ( " nix_store_parse_path returned a null pointer " ) ;
Ok ( StorePath ::new_raw ( store_path ) )
}
}
2024-06-27 15:57:43 +02:00
pub fn weak_ref ( & self ) -> StoreWeak {
StoreWeak {
inner : Arc ::downgrade ( & self . inner ) ,
}
}
2024-02-22 22:32:50 +01:00
}
2024-06-26 21:00:22 +02:00
impl Clone for Store {
fn clone ( & self ) -> Self {
Store {
inner : self . inner . clone ( ) ,
context : Context ::new ( ) ,
}
}
}
2024-02-22 22:32:50 +01:00
#[ cfg(test) ]
mod tests {
2024-05-24 00:17:01 +02:00
use std ::collections ::HashMap ;
2024-02-22 22:32:50 +01:00
use super ::* ;
#[ test ]
fn auto_works ( ) {
2024-05-24 00:17:01 +02:00
let res = Store ::open ( " auto " , HashMap ::new ( ) ) ;
2024-03-19 18:01:23 +01:00
res . unwrap ( ) ;
2024-02-22 22:32:50 +01:00
}
#[ test ]
fn invalid_uri_fails ( ) {
2024-05-24 00:17:01 +02:00
let res = Store ::open ( " invalid://uri " , HashMap ::new ( ) ) ;
2024-02-22 22:32:50 +01:00
assert! ( res . is_err ( ) ) ;
}
#[ test ]
fn get_uri ( ) {
2024-06-15 12:40:45 +02:00
let mut store = Store ::open ( " auto " , HashMap ::new ( ) ) . unwrap ( ) ;
2024-02-22 22:32:50 +01:00
let uri = store . get_uri ( ) . unwrap ( ) ;
assert! ( ! uri . is_empty ( ) ) ;
// must be ascii
assert! ( uri . is_ascii ( ) ) ;
// usually something like "daemon", but that's not something we can check here.
println! ( " uri: {} " , uri ) ;
}
#[ test ]
2024-03-19 18:02:04 +01:00
#[ ignore ] // Needs network access
2024-02-22 22:32:50 +01:00
fn get_uri_nixos_cache ( ) {
2024-06-15 12:40:45 +02:00
let mut store = Store ::open ( " https://cache.nixos.org/ " , HashMap ::new ( ) ) . unwrap ( ) ;
2024-02-22 22:32:50 +01:00
let uri = store . get_uri ( ) . unwrap ( ) ;
assert_eq! ( uri , " https://cache.nixos.org " ) ;
}
2024-06-27 15:57:43 +02:00
2024-12-17 09:54:28 +01:00
#[ test ]
#[ cfg(nix_at_least = " 2.26 " /* get_storedir */) ]
fn parse_store_path_ok ( ) {
let mut store = crate ::store ::Store ::open ( " dummy:// " , [ ] ) . unwrap ( ) ;
let store_dir = store . get_storedir ( ) . unwrap ( ) ;
let store_path_string =
format! ( " {store_dir} /rdd4pnr4x9rqc9wgbibhngv217w2xvxl-bash-interactive-5.2p26 " ) ;
let store_path = store . parse_store_path ( store_path_string . as_str ( ) ) . unwrap ( ) ;
assert_eq! ( store_path . name ( ) . unwrap ( ) , " bash-interactive-5.2p26 " ) ;
}
#[ test ]
fn parse_store_path_fail ( ) {
let mut store = crate ::store ::Store ::open ( " dummy:// " , [ ] ) . unwrap ( ) ;
let store_path_string = format! ( " bash-interactive-5.2p26 " ) ;
let r = store . parse_store_path ( store_path_string . as_str ( ) ) ;
match r {
Err ( e ) = > {
assert! ( e . to_string ( ) . contains ( " bash-interactive-5.2p26 " ) ) ;
}
_ = > panic! ( " Expected error " ) ,
}
}
2024-06-27 15:57:43 +02:00
#[ test ]
fn weak_ref ( ) {
let mut store = Store ::open ( " auto " , HashMap ::new ( ) ) . unwrap ( ) ;
let uri = store . get_uri ( ) . unwrap ( ) ;
let weak = store . weak_ref ( ) ;
let mut store2 = weak . upgrade ( ) . unwrap ( ) ;
assert_eq! ( store2 . get_uri ( ) . unwrap ( ) , uri ) ;
}
#[ test ]
fn weak_ref_gone ( ) {
let weak = {
2024-11-27 14:13:54 +01:00
// Concurrent tests calling Store::open will keep the weak reference to auto alive,
// so for this test we need to bypass the global cache.
let store = Store ::open_uncached ( " auto " , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-27 15:57:43 +02:00
store . weak_ref ( )
} ;
assert! ( weak . upgrade ( ) . is_none ( ) ) ;
assert! ( weak . inner . upgrade ( ) . is_none ( ) ) ;
}
2024-02-22 22:32:50 +01:00
}