char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
f4a0f5b
main
1use super :: paths::{ resolve, tree}; 2use super :: * ; 3 4// Link authorization and rebasing must not race another managed link mutation. 5pub ( super ) static MUTATIONS : Mutex <()> =Mutex :: new (()); 6 7pub ( super ) fn authorize_target ( 8caps : & Capabilities , 9link : & PathTarget , 10text : & Path , 11label : & str , 12) -> io:: Result <()> { 13if caps. unrestricted_read () && caps. unrestricted_write () { 14return Ok (()); 15} 16let denied_target = ||{ 17 io:: Error :: new ( 18 io:: ErrorKind :: PermissionDenied , 19format! ( "symlink target for {label}" ), 20) 21}; 22let path = link 23. as_path () 24. parent () 25. ok_or_else ( denied_target) ? 26. join ( text); 27let target =PathTarget :: for_creation ( path). map_err ( |_|denied_target ()) ?; 28tree ( caps, & target, label, false ). map_err ( |_|denied_target ()) ?; 29tree ( caps, & target, label, true ). map_err ( |_|denied_target ()) ?; 30 31let mut root =PathBuf :: new (); 32let mut names =Vec :: new (); 33for componentin target. as_path (). components () { 34match component{ 35Component :: Prefix ( _) |Component :: RootDir => root. push ( component. as_os_str ()), 36Component :: Normal ( name) => names. push ( name), 37 _ =>return Err ( denied_target ()), 38} 39} 40let mut directory = 41Dir :: open_ambient_dir ( root, cap_std:: ambient_authority ()). map_err ( |_|denied_target ()) ?; 42for ( index, name) in names. iter (). enumerate () { 43match directory. symlink_metadata ( name) { 44Ok ( metadata) if metadata. is_symlink () =>return Err ( denied_target ()), 45Ok ( _) if index +1 == names. len () =>return Ok (()), 46Ok ( _) =>{ 47 directory = directory 48. open_dir_nofollow ( name) 49. map_err ( |_|denied_target ()) ?; 50} 51Err ( error) if error. kind () == io:: ErrorKind :: NotFound =>return Ok (()), 52Err ( _) =>return Err ( denied_target ()), 53} 54} 55Ok (()) 56} 57 58fn validate ( target : & Path , kind : Option < & str >) -> io:: Result <()> { 59if target. as_os_str (). is_empty () 60 || target. as_os_str (). as_encoded_bytes (). contains ( & 0 ) 61 || kind. is_some_and ( |kind| kind !="file" && kind !="dir" ) 62{ 63return Err ( io:: Error :: new ( 64 io:: ErrorKind :: InvalidInput , 65"Invalid symbolic link target or type" , 66)); 67} 68Ok (()) 69} 70 71pub ( super ) fn create ( directory : & Dir , name : & std:: ffi:: OsStr , target : & Path ) -> io:: Result <()> { 72# [ cfg ( not ( windows ))] 73{ 74 directory. symlink_contents ( target, name) 75} 76# [ cfg ( windows )] 77{ 78let _ =( directory, name); 79Err ( io:: Error :: new ( 80 io:: ErrorKind :: Unsupported , 81"not yet implemented: capability-safe Windows symlink creation" , 82)) 83} 84} 85 86pub ( super ) fn contains_links ( directory : Dir ) -> io:: Result < bool > { 87use cap_fs_ext:: MetadataExt ; 88let metadata = directory. dir_metadata () ?; 89let mut seen = std:: collections:: HashSet :: from ([( metadata. dev (), metadata. ino ())]); 90let mut stack =vec! [( directory. entries () ?, directory)]; 91while let Some (( entries, directory)) = stack. last_mut () { 92let Some ( entry) = entries. next () else { 93 stack. pop (); 94continue ; 95}; 96let entry = entry?; 97let kind = entry. file_type () ?; 98if kind. is_symlink () { 99return Ok ( true ); 100} 101# [ cfg ( windows )] 102{ 103use cap_std:: fs:: MetadataExt ; 104if directory 105. symlink_metadata ( entry. file_name ()) ? 106. file_attributes () 107& 0x400 108 !=0 109{ 110return Ok ( true ); 111} 112} 113if kind. is_dir () { 114let child = directory. open_dir_nofollow ( entry. file_name ()) ?; 115let metadata = child. dir_metadata () ?; 116if seen. insert (( metadata. dev (), metadata. ino ())) { 117 stack. push (( child. entries () ?, child)); 118} 119} 120} 121Ok ( false ) 122} 123 124pub ( super ) fn text ( path : PathBuf ) -> io:: Result < String > { 125 path. into_os_string (). into_string (). map_err ( |_|{ 126 io:: Error :: new ( 127 io:: ErrorKind :: Unsupported , 128"not yet implemented: non-UTF-8 link paths" , 129) 130}) 131} 132 133pub ( super ) fn ops ( files : Arc < Files >) ->Vec < Op > { 134let ops = files. clone (); 135let symlink = files. clone (); 136let read_link = files. clone (); 137let copy_link = files. clone (); 138vec! [ 139 ops. io_ops ( 140"op_file_copy_link" , 141"op_file_copy_link_sync" , 142move |( source, destination): ( String , String ) |{ 143let caps = copy_link. caps . clone (); 144let pending = copy_link. pool . run ( move ||{ 145let _guard =MUTATIONS 146. lock () 147. unwrap_or_else ( std:: sync:: PoisonError :: into_inner); 148let from =resolve ( & caps, & source, true , false ) ?; 149let to =resolve ( & caps, & destination, true , true ) ?; 150let ( from_dir, from_name) =parent ( from. as_path ()) ?; 151let metadata = from_dir. symlink_metadata ( & from_name) ?; 152if !metadata. is_symlink () { 153return Err ( io:: Error :: new ( 154 io:: ErrorKind :: InvalidInput , 155"Source is not a symbolic link" , 156)); 157} 158let contents = from_dir. read_link_contents ( from_name) ?; 159validate ( & contents, None ) ?; 160# [ cfg ( windows )] 161{ 162let _ =( to, contents); 163return Err ::< bool , _ >( io:: Error :: new ( 164 io:: ErrorKind :: Unsupported , 165"not yet implemented: capability-safe Windows symlink creation" , 166)); 167} 168# [ cfg ( not ( windows ))] 169{ 170use cap_fs_ext:: MetadataExt ; 171let ( to_dir, to_name) =super :: paths:: mutable_entry ( & to) ?; 172authorize_target ( & caps, & to, & contents, & destination) ?; 173match to_dir. symlink_metadata ( & to_name) { 174Ok ( existing) 175if existing. dev () == metadata. dev () 176 && existing. ino () == metadata. ino () => 177{ 178return Err ( io:: Error :: new ( 179 io:: ErrorKind :: InvalidInput , 180"Source and destination are the same link" , 181)); 182} 183Ok ( existing) if existing. is_dir () =>{ 184return Err ( io:: Error :: new ( 185 io:: ErrorKind :: IsADirectory , 186"Link destination is a directory" , 187)); 188} 189Ok ( _) => to_dir. remove_file_or_symlink ( & to_name) ?, 190Err ( error) if error. kind () == io:: ErrorKind :: NotFound =>(), 191Err ( error) =>return Err ( error), 192} 193create ( & to_dir, & to_name, & contents) ?; 194Ok ( true ) 195} 196}); 197async move { Ok ::< _ , Infallible >( receive ( pending). await ) } 198}, 199), 200 ops. io_ops ( 201"op_file_symlink" , 202"op_file_symlink_sync" , 203move |( target, link, kind): ( String , String , Option < String >) |{ 204let caps = symlink. caps . clone (); 205let pending = symlink. pool . run ( move ||{ 206validate ( Path :: new ( & target), kind. as_deref ()) ?; 207let _guard =MUTATIONS 208. lock () 209. unwrap_or_else ( std:: sync:: PoisonError :: into_inner); 210let entry =resolve ( & caps, & link, true , true ) ?; 211let ( directory, name) =super :: paths:: mutable_entry ( & entry) ?; 212authorize_target ( & caps, & entry, Path :: new ( & target), & link) ?; 213create ( & directory, & name, Path :: new ( & target)) ?; 214Ok ( true ) 215}); 216async move { Ok ::< _ , Infallible >( receive ( pending). await ) } 217}, 218), 219 ops. io_ops ( 220"op_file_read_link" , 221"op_file_read_link_sync" , 222move |path : String |{ 223let caps = read_link. caps . clone (); 224let pending = read_link. pool . run ( move ||{ 225let entry =resolve ( & caps, & path, true , false ) ?; 226let ( directory, name) =parent ( entry. as_path ()) ?; 227text ( directory. read_link_contents ( name) ?) 228}); 229async move { Ok ::< _ , Infallible >( receive ( pending). await ) } 230}, 231), 232 ops. io_ops ( 233"op_file_real_path" , 234"op_file_real_path_sync" , 235move |path : String |{ 236let caps = files. caps . clone (); 237let pending = files. pool . run ( move ||{ 238let target =resolve ( & caps, & path, false , false ) ?; 239let ( directory, name) =parent ( target. as_path ()) ?; 240if directory. symlink_metadata ( name) ?. is_symlink () { 241return Err ( io:: Error :: new ( 242 io:: ErrorKind :: NotFound , 243"Symbolic link changed or has a missing target" , 244)); 245} 246text ( target. into_path ()) 247}); 248async move { Ok ::< _ , Infallible >( receive ( pending). await ) } 249}, 250), 251] 252. into_iter () 253. flatten () 254. collect () 255}