char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
78f4748
main
1use std:: collections:: HashMap ; 2use std:: io; 3use std:: path:: PathBuf ; 4use std:: sync:: atomic::{ AtomicU64 , Ordering }; 5use std:: sync::{ Arc , Mutex , MutexGuard }; 6 7use avril_caps::{ Capabilities , PathTarget }; 8use avril_kv::{ Access , Store }; 9 10fn denied ( label : & str , write : bool ) -> io:: Error { 11 io:: Error :: new ( 12 io:: ErrorKind :: PermissionDenied , 13format! ( "{} {label}" , if write{ "write" } else { "read" }), 14) 15} 16 17fn authorize ( caps : & Capabilities , target : & PathTarget , label : & str , write : bool ) -> io:: Result <()> { 18if write{ 19 caps. check_write ( target) 20} else { 21 caps. check_read ( target) 22} 23. map_err ( |_|denied ( label, write)) 24} 25 26fn absolute ( caps : & Capabilities , label : & str ) -> io:: Result <( PathBuf , PathTarget )> { 27if label. is_empty () { 28return Err ( io:: Error :: new ( 29 io:: ErrorKind :: NotFound , 30"Empty kv database path" , 31)); 32} 33let path = caps. cwd (). join ( label); 34// Resolve the existing prefix like ext:file does; a missing tail is 35// expected when creating, so refusal to resolve only denies when the 36// grant could not have covered the path anyway. 37let target =match PathTarget :: for_creation ( & path) { 38Ok ( target) => target, 39Err ( _) if !caps. unrestricted_read () && !caps. unrestricted_write () =>{ 40return Err ( denied ( label, false )); 41} 42Err ( error) =>return Err ( error), 43}; 44Ok (( path, target)) 45} 46 47struct Auth { 48caps : Arc < Capabilities >, 49target : PathTarget , 50label : String , 51} 52 53pub struct Entry { 54state : Mutex < EntryState >, 55} 56 57struct EntryState { 58store : Option < Store >, 59auth : Auth , 60} 61 62pub struct Registry { 63next : AtomicU64 , 64entries : Mutex < HashMap < u64 , Arc < Entry >>>, 65} 66 67impl Default for Registry { 68fn default () ->Self { 69Self :: new () 70} 71} 72 73fn closed_handle () -> io:: Error { 74 io:: Error :: new ( io:: ErrorKind :: NotConnected , "kv database handle is closed" ) 75} 76 77impl Registry { 78pub fn new () ->Self { 79Self { 80next : AtomicU64 :: new ( 1 ), 81entries : Mutex :: new ( HashMap :: new ()), 82} 83} 84 85fn lock_entries ( & self ) ->MutexGuard < ' _ , HashMap < u64 , Arc < Entry >>> { 86self . entries 87. lock () 88. unwrap_or_else ( |error| error. into_inner ()) 89} 90 91fn entry ( & self , id : u64 ) -> io:: Result < Arc < Entry >> { 92self . lock_entries (). get ( & id). cloned (). ok_or_else ( ||{ 93 io:: Error :: new ( 94 io:: ErrorKind :: NotConnected , 95"kv database handle is closed or unknown" , 96) 97}) 98} 99 100pub fn with_entry < T >( 101& self , 102id : u64 , 103write : bool , 104work : impl FnOnce ( & Store ) -> io:: Result < T >, 105) -> io:: Result < T > { 106let entry =self . entry ( id) ?; 107let state = entry 108. state 109. lock () 110. unwrap_or_else ( |error| error. into_inner ()); 111let Some ( store) = state. store . as_ref () else { 112return Err ( closed_handle ()); 113}; 114// Re-authorize against the live Capabilities on every operation so a 115// pledge() narrowing applies to already-open handles. Writes need 116// read access too: a write transaction reads internally. 117let auth =& state. auth ; 118authorize ( & auth. caps , & auth. target , & auth. label , false ) ?; 119if write{ 120authorize ( & auth. caps , & auth. target , & auth. label , true ) ?; 121} 122// The entry mutex stays held for the whole operation: close() takes 123// the store under the same mutex, so nothing is in flight when the 124// last Arc drops and the database file is physically released. 125work ( store) 126} 127 128pub fn open ( 129& self , 130caps : & Arc < Capabilities >, 131label : & str , 132access : & str , 133create : bool , 134) -> io:: Result < u64 > { 135let access =Access :: parse ( access) ?; 136let ( path, target) =absolute ( caps, label) ?; 137authorize ( caps, & target, label, false ) ?; 138if access. writes () { 139authorize ( caps, & target, label, true ) ?; 140} 141let store =Store :: open ( & path, access, create) ?; 142let id =self . next . fetch_add ( 1 , Ordering :: Relaxed ); 143self . lock_entries (). insert ( 144 id, 145Arc :: new ( Entry { 146state : Mutex :: new ( EntryState { 147store : Some ( store), 148auth : Auth { 149caps : caps. clone (), 150 target, 151label : label. to_owned (), 152}, 153}), 154}), 155); 156Ok ( id) 157} 158 159// Idempotent: closing an unknown or already-closed id succeeds. Taking 160// the store under the entry mutex physically releases the database: 161// with_entry holds that mutex for its whole operation, so nothing is in 162// flight when close returns. 163pub fn close ( & self , id : u64 ) -> io:: Result <()> { 164let entry =self . lock_entries (). remove ( & id); 165if let Some ( entry) = entry{ 166let mut state = entry 167. state 168. lock () 169. unwrap_or_else ( |error| error. into_inner ()); 170 state. store . take (); 171drop ( state); 172} 173Ok (()) 174} 175}