char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
7325e8c
main
1mod links; 2mod paths; 3mod posix; 4mod watch; 5mod workflows; 6 7use std:: collections::{ HashMap , VecDeque }; 8use std:: convert:: Infallible ; 9use std:: ffi:: OsString ; 10use std:: io::{ self , Read , Seek , SeekFrom , Write }; 11use std:: path::{ Component , Path , PathBuf }; 12use std:: sync:: atomic::{ AtomicBool , AtomicU64 , Ordering }; 13use std:: sync::{ Arc , Mutex }; 14 15use avril_caps::{ Capabilities , PathTarget }; 16use avril_core:: serde_v8::{ JsBuffer , ToJsBuffer }; 17use avril_core::{ Extension , Op , PublicModule }; 18use avril_host:: io::{ CHUNK , MAX_SLICES , Response , response}; 19use cap_fs_ext::{ DirExt , FollowSymlinks , OpenOptionsFollowExt , OpenOptionsSyncExt }; 20use cap_std:: fs::{ Dir , File , Metadata , OpenOptions }; 21use futures:: channel:: oneshot; 22use tokio:: runtime:: Handle ; 23 24pub const PUBLIC_MODULES : & [ PublicModule ] =& [ 25 avril_core:: public_module!( "avril:fs" , "ext:file" , "fs" ), 26 avril_core:: public_module!( "avril:fs/sync" , "ext:file" , "fs_sync" ), 27]; 28 29const MAX_ID : u64 =9_007_199_254_740_991 ; 30const SAFE_INTEGER : u64 =( 1 <<53 ) -1 ; 31/// Runs against the file and returns the reply's delivery. 32type Operation =Box < dyn FnOnce ( & mut Option < Opened >, bool ) ->Delivery +Send >; 33type Delivery =Box < dyn FnOnce () +Send >; 34type Reply < T > = oneshot:: Receiver < io:: Result < T >>; 35type Options =( bool , bool , bool , bool , bool , bool , Option < u32 >); 36type Info =( 37bool , 38bool , 39bool , 40u64 , 41Option < f64 >, 42Option < f64 >, 43Option < f64 >, 44Option < u32 >, 45Option < u32 >, 46Option < u32 >, 47); 48 49async fn receive < T >( reply : Reply < T >) ->Response < T > { 50response ( 51 reply 52. await 53. unwrap_or_else ( |_|Err ( io:: Error :: other ( "Filesystem worker stopped" ))), 54) 55} 56 57fn failed < T >( error : io:: Error ) ->Reply < T > { 58let ( reply, receive) = oneshot:: channel (); 59let _ = reply. send ( Err ( error)); 60 receive 61} 62 63fn filesystem_path ( caps : & Capabilities , label : & str ) -> io:: Result < PathBuf > { 64# [ cfg ( windows )] 65if !Path :: new ( label). has_root () 66 &&matches! ( 67Path :: new ( label). components (). next (), 68Some ( Component :: Prefix ( _)) 69) 70{ 71return Err ( io:: Error :: new ( 72 io:: ErrorKind :: Unsupported , 73"not yet implemented: drive-relative filesystem paths" , 74)); 75} 76Ok ( caps. cwd (). join ( label)) 77} 78 79fn denied ( label : & str , write : bool ) -> io:: Error { 80 io:: Error :: new ( 81 io:: ErrorKind :: PermissionDenied , 82format! ( "{} {label}" , if write{ "write" } else { "read" }), 83) 84} 85 86fn authorize ( caps : & Capabilities , target : & PathTarget , label : & str , write : bool ) -> io:: Result <()> { 87if write{ 88 caps. check_write ( target) 89} else { 90 caps. check_read ( target) 91} 92. map_err ( |_|denied ( label, write)) 93} 94 95fn bad_resource () -> io:: Error { 96 io:: Error :: new ( 97 io:: ErrorKind :: NotConnected , 98"File handle is closed or does not allow this operation" , 99) 100} 101 102# [ derive ( Clone )] 103struct BlockingPool { 104runtime : Handle , 105} 106 107impl BlockingPool { 108fn run < T : Send +' static >( 109& self , 110work : impl FnOnce () -> io:: Result < T > +Send +' static , 111) ->Reply < T > { 112let ( reply, receive) = oneshot:: channel (); 113self . runtime . spawn_blocking ( move ||{ 114let _ = reply. send ( work ()); 115}); 116 receive 117} 118} 119 120struct Queue { 121operations : VecDeque < Operation >, 122running : bool , 123} 124 125struct Resource { 126closed : Arc < AtomicBool >, 127queue : Mutex < Queue >, 128file : Mutex < Option < Opened >>, 129pool : BlockingPool , 130} 131 132impl Resource { 133fn submit < T : Send +' static >( 134self : & Arc < Self >, 135operation : impl FnOnce ( & mut Opened ) -> io:: Result < T > +Send +' static , 136) ->Reply < T > { 137let ( reply, receive) = oneshot:: channel (); 138let mut queue =self . queue . lock (). unwrap (); 139if self . closed . load ( Ordering :: Acquire ) { 140return failed ( bad_resource ()); 141} 142 queue. operations . push_back ( Box :: new ( move |file, closed|{ 143let result =if closed{ 144Err ( bad_resource ()) 145} else { 146 file. as_mut (). ok_or_else ( bad_resource). and_then ( operation) 147}; 148Box :: new ( move ||{ 149let _ = reply. send ( result); 150}) 151})); 152if !queue. running { 153 queue. running =true ; 154let resource =self . clone (); 155self . pool . runtime . spawn_blocking ( move || resource. run ()); 156} 157 receive 158} 159 160/// Allows multi-batch operations to observe closure without releasing the queue. 161fn submit_observed < T : Send +' static >( 162self : & Arc < Self >, 163operation : impl FnOnce ( & mut Opened , & AtomicBool ) -> io:: Result < T > +Send +' static , 164) ->Reply < T > { 165let closed =self . closed . clone (); 166self . submit ( move |opened|operation ( opened, & closed)) 167} 168 169fn run ( self : Arc < Self >) { 170loop { 171let operation ={ 172let mut queue =self . queue . lock (). unwrap (); 173match queue. operations . pop_front () { 174Some ( operation) => operation, 175None =>{ 176 queue. running =false ; 177drop ( queue); 178return self . finish (); 179} 180} 181}; 182let delivery ={ 183let mut file =self . file . lock (). unwrap (); 184let closed =self . closed . load ( Ordering :: Acquire ); 185 std:: panic:: catch_unwind ( std:: panic:: AssertUnwindSafe ( ||{ 186operation ( & mut file, closed) 187})) 188. inspect_err ( |_|{ 189self . closed . store ( true , Ordering :: Release ); 190 file. take (); 191}) 192. ok () 193}; 194// Settle the queue before waking the caller, so code resuming on 195// the reply (such as lending the handle to a subprocess) finds 196// the handle idle rather than busy. Delivering under the queue 197// lock keeps replies in submission order. 198let idle ={ 199let mut queue =self . queue . lock (). unwrap (); 200 queue. running = !queue. operations . is_empty (); 201if let Some ( delivery) = delivery{ 202delivery (); 203} 204 !queue. running 205}; 206if idle{ 207return self . finish (); 208} 209} 210} 211 212fn finish ( & self ) { 213if self . closed . load ( Ordering :: Acquire ) { 214self . file . lock (). unwrap (). take (); 215} 216} 217 218fn close ( self : & Arc < Self >) { 219self . closed . store ( true , Ordering :: Release ); 220let mut queue =self . queue . lock (). unwrap (); 221if !queue. running { 222 queue. running =true ; 223let resource =self . clone (); 224// Physical closure stays off V8's thread, including finalizer cleanup. 225self . pool . runtime . spawn_blocking ( move || resource. run ()); 226} 227} 228} 229 230struct Opened { 231file : File , 232target : PathTarget , 233label : String , 234caps : Arc < Capabilities >, 235read : bool , 236write : bool , 237} 238 239impl Opened { 240fn check ( & self , write : bool ) -> io:: Result <()> { 241if ( write && !self . write ) ||( !write && !self . read ) { 242return Err ( bad_resource ()); 243} 244authorize ( & self . caps , & self . target , & self . label , write) 245} 246 247fn check_metadata ( & self ) -> io:: Result <()> { 248if self . read { 249let read =self . check ( false ); 250if read. is_ok () || !self . write { 251return read; 252} 253} 254self . check ( true ) 255} 256} 257 258fn open_file ( 259caps : Arc < Capabilities >, 260path : String , 261options : Options , 262non_regular : io:: ErrorKind , 263) -> io:: Result < Opened > { 264if path. is_empty () { 265return Err ( io:: Error :: new ( 266 io:: ErrorKind :: NotFound , 267"Empty filesystem path" , 268)); 269} 270let ( read, write, append, truncate, create, create_new, mode) = options; 271if ( !read && !write && !append) 272 ||(( truncate || create || create_new) && !write && !append) 273 || mode. is_some_and ( |mode| mode >0o7777 ) 274{ 275return Err ( io:: Error :: new ( 276 io:: ErrorKind :: InvalidInput , 277"Invalid open options" , 278)); 279} 280let write = write || append; 281let absolute =filesystem_path ( & caps, & path) ?; 282let target =match if create_new{ 283PathTarget :: for_entry ( absolute) 284} else { 285PathTarget :: for_creation ( absolute) 286} { 287Ok ( target) => target, 288Err ( _) if read && !caps. unrestricted_read () =>{ 289return Err ( denied ( & path, false )); 290} 291Err ( _) if write && !caps. unrestricted_write () =>{ 292return Err ( denied ( & path, true )); 293} 294Err ( error) =>return Err ( error), 295}; 296if read{ 297authorize ( & caps, & target, & path, false ) ?; 298} 299if write{ 300authorize ( & caps, & target, & path, true ) ?; 301} 302let ( directory, name) =parent ( target. as_path ()) ?; 303match directory. symlink_metadata ( & name) { 304Ok ( _) if create_new =>{ 305return Err ( io:: Error :: new ( 306 io:: ErrorKind :: AlreadyExists , 307"File already exists" , 308)); 309} 310Ok ( metadata) if !metadata. is_file () =>{ 311return Err ( io:: Error :: new ( 312 non_regular, 313"not yet implemented: opening non-regular files" , 314)); 315} 316Ok ( _) =>(), 317Err ( error) if error. kind () == io:: ErrorKind :: NotFound =>(), 318Err ( error) =>return Err ( error), 319} 320let mut flags =OpenOptions :: new (); 321 flags 322. read ( read) 323. write ( write) 324. append ( append) 325. create ( create) 326. create_new ( create_new) 327. follow ( FollowSymlinks :: No ) 328. nonblock ( true ); 329# [ cfg ( unix )] 330{ 331use cap_std:: fs:: OpenOptionsExt ; 332if let Some ( mode) = mode{ 333 flags. mode ( mode); 334} 335} 336let file = directory. open_with ( name, & flags) ?; 337if !file. metadata () ?. is_file () { 338return Err ( io:: Error :: new ( 339 non_regular, 340"not yet implemented: opening non-regular files" , 341)); 342} 343let opened =Opened { 344 file, 345 target, 346label : path, 347caps : caps. clone (), 348 read, 349 write, 350}; 351// Truncation must not happen until the opened object is verified and authorized. 352if truncate{ 353 opened. check ( true ) ?; 354 opened. file . set_len ( 0 ) ?; 355} 356Ok ( opened) 357} 358 359# [ cfg_attr ( 360not ( any ( target_os = "linux" , target_os = "macos" , windows )), 361allow ( dead_code ) 362)] 363pub fn process_file ( 364caps : Arc < Capabilities >, 365path : String , 366options : Options , 367) -> io:: Result < std:: fs:: File > { 368Ok ( open_file ( caps, path, options, io:: ErrorKind :: Unsupported ) ? 369. file 370. into_std ()) 371} 372 373pub fn response_file ( 374caps : Arc < Capabilities >, 375path : String , 376) -> io:: Result <( std:: fs:: File , avril_http:: file:: CheckFile )> { 377let opened =open_file ( 378 caps, 379 path, 380( true , false , false , false , false , false , None ), 381 io:: ErrorKind :: NotFound , 382) ?; 383 opened. check ( false ) ?; 384let Opened { file, target, label, caps, ..} = opened; 385let check =Arc :: new ( move ||authorize ( & caps, & target, & label, false )); 386Ok (( file. into_std (), check)) 387} 388 389pub struct Files { 390pool : BlockingPool , 391resources : Mutex < HashMap < u64 , Arc < Resource >>>, 392next : AtomicU64 , 393caps : Arc < Capabilities >, 394interrupted : Arc < AtomicBool >, 395} 396 397impl Drop for Files { 398fn drop ( & mut self ) { 399for resourcein self 400. resources 401. get_mut () 402. unwrap () 403. drain () 404. map ( |( _, resource) | resource) 405{ 406 resource. close (); 407} 408} 409} 410 411impl Files { 412fn io_ops < A , R , F >( 413& self , 414name : &' static str , 415sync_name : &' static str , 416function : impl Fn ( A ) ->F +Clone +' static , 417) ->Vec < Op > 418where 419A : serde:: de:: DeserializeOwned , 420R : serde:: Serialize +' static , 421F : std:: future:: Future < Output =Result < R , Infallible >> +' static , 422{ 423let blocking = function. clone (); 424let interrupted =self . interrupted . clone (); 425vec! [ 426Op :: asynchronous ( name, function), 427Op :: with_scope ( sync_name, move |scope, args|{ 428 avril_host:: wait ( blocking ( args), ||{ 429 avril_host:: interrupted ( scope, & interrupted) 430}) 431. transpose () 432}), 433] 434} 435 436# [ cfg_attr ( 437not ( any ( target_os = "linux" , target_os = "macos" , windows )), 438allow ( dead_code ) 439)] 440pub fn duplicate_for_process ( 441& self , 442id : u64 , 443read : bool , 444write : bool , 445) -> io:: Result < std:: fs:: File > { 446let resource =self 447. resources 448. lock () 449. unwrap () 450. get ( & id) 451. cloned () 452. ok_or_else ( bad_resource) ?; 453if resource. closed . load ( Ordering :: SeqCst ) { 454return Err ( bad_resource ()); 455} 456let queue = resource. queue . lock (). unwrap (); 457if queue. running || !queue. operations . is_empty () { 458return Err ( io:: Error :: new ( 459 io:: ErrorKind :: WouldBlock , 460"File handle is busy" , 461)); 462} 463let file = resource 464. file 465. try_lock () 466. map_err ( |_| io:: Error :: new ( io:: ErrorKind :: WouldBlock , "File handle is busy" )) ?; 467let opened = file. as_ref (). ok_or_else ( bad_resource) ?; 468if read{ 469 opened. check ( false ) ?; 470} 471if write{ 472 opened. check ( true ) ?; 473} 474Ok ( opened. file . try_clone () ?. into_std ()) 475} 476 477pub fn response_handle ( 478& self , 479id : u64 , 480consume : bool , 481) -> io:: Result <( String , std:: fs:: File , avril_http:: file:: CheckFile )> { 482let mut resources =self . resources . lock (). unwrap (); 483let resource = resources. get ( & id). cloned (). ok_or_else ( bad_resource) ?; 484let queue = resource. queue . lock (). unwrap (); 485if resource. closed . load ( Ordering :: Acquire ) { 486return Err ( bad_resource ()); 487} 488if queue. running || !queue. operations . is_empty () { 489return Err ( io:: Error :: new ( 490 io:: ErrorKind :: WouldBlock , 491"File handle is busy" , 492)); 493} 494let mut file = resource 495. file 496. try_lock () 497. map_err ( |_| io:: Error :: new ( io:: ErrorKind :: WouldBlock , "File handle is busy" )) ?; 498let opened = file. as_ref (). ok_or_else ( bad_resource) ?; 499 opened. check ( false ) ?; 500let name = opened. label . clone (); 501let target = opened. target . clone (); 502let caps = opened. caps . clone (); 503let label = name. clone (); 504let check =Arc :: new ( move ||authorize ( & caps, & target, & label, false )); 505let file =if consume{ 506 resource. closed . store ( true , Ordering :: Release ); 507let opened = file. take (). unwrap (); 508 resources. remove ( & id); 509 opened. file . into_std () 510} else { 511# [ cfg ( unix )] 512let duplicate = opened. file . try_clone () ?; 513# [ cfg ( windows )] 514let duplicate ={ 515use cap_fs_ext:: Reopen ; 516// Windows positional reads still change a duplicated handle's shared cursor. 517 opened. file . reopen ( OpenOptions :: new (). read ( true )) ? 518}; 519# [ cfg ( not ( any ( unix , windows )))] 520return Err ( io:: Error :: new ( 521 io:: ErrorKind :: Unsupported , 522"Retaining file response handles is not supported on this platform" , 523)); 524# [ cfg ( any ( unix , windows ))] 525 duplicate. into_std () 526}; 527Ok (( name, file, check)) 528} 529 530fn submit < T : Send +' static >( 531& self , 532id : u64 , 533operation : impl FnOnce ( & mut Opened ) -> io:: Result < T > +Send +' static , 534) ->Reply < T > { 535let resource =self . resources . lock (). unwrap (). get ( & id). cloned (); 536match resource{ 537Some ( resource) => resource. submit ( operation), 538None =>failed ( bad_resource ()), 539} 540} 541 542fn submit_observed < T : Send +' static >( 543& self , 544id : u64 , 545operation : impl FnOnce ( & mut Opened , & AtomicBool ) -> io:: Result < T > +Send +' static , 546) ->Reply < T > { 547let resource =self . resources . lock (). unwrap (). get ( & id). cloned (); 548match resource{ 549Some ( resource) => resource. submit_observed ( operation), 550None =>failed ( bad_resource ()), 551} 552} 553 554fn open ( self : & Arc < Self >, path : String , options : Options ) ->Reply < u64 > { 555let state =self . clone (); 556self . pool . run ( move ||{ 557let opened =open_file ( 558 state. caps . clone (), 559 path, 560 options, 561 io:: ErrorKind :: Unsupported , 562) ?; 563let id = state 564. next 565. fetch_update ( Ordering :: AcqRel , Ordering :: Acquire , |id|{ 566( id <=MAX_ID ). then_some ( id +1 ) 567}) 568. map_err ( |_| io:: Error :: other ( "File handle identifiers exhausted" )) ?; 569 state. resources . lock (). unwrap (). insert ( 570 id, 571Arc :: new ( Resource { 572closed : Arc :: new ( AtomicBool :: new ( false )), 573queue : Mutex :: new ( Queue { 574operations : VecDeque :: new (), 575running : false , 576}), 577file : Mutex :: new ( Some ( opened)), 578pool : state. pool . clone (), 579}), 580); 581Ok ( id) 582}) 583} 584} 585 586pub ( crate ) use avril_caps:: fs:: parent; 587 588fn info ( metadata : Metadata ) ->Info { 589fn time ( time : io:: Result < cap_std:: time:: SystemTime >) ->Option < f64 > { 590// Integer milliseconds stay exact through Date's float conversion; 591// float seconds would smear sub-millisecond remainders into the 592// millisecond field. Float arithmetic also cannot overflow: times 593// past the Date range read back as Invalid Date, as before. 594fn millis ( duration : std:: time:: Duration ) ->f64 { 595 duration. as_secs () as f64 * 1000.0 + f64:: from ( duration. subsec_millis ()) 596} 597let time = time. ok () ?. into_std (); 598Some ( match time. duration_since ( std:: time:: UNIX_EPOCH ) { 599Ok ( duration) =>millis ( duration), 600Err ( error) => -millis ( error. duration ()), 601}) 602} 603# [ cfg ( unix )] 604let ( mode, uid, gid) ={ 605use cap_std:: fs:: MetadataExt ; 606( 607Some ( metadata. mode ()), 608Some ( metadata. uid ()), 609Some ( metadata. gid ()), 610) 611}; 612# [ cfg ( not ( unix ))] 613let ( mode, uid, gid) =( None , None , None ); 614( 615 metadata. is_file (), 616 metadata. is_dir (), 617 metadata. is_symlink (), 618 metadata. len (), 619time ( metadata. modified ()), 620time ( metadata. accessed ()), 621time ( metadata. created ()), 622 mode, 623 uid, 624 gid, 625) 626} 627 628pub fn init ( 629runtime : Handle , 630caps : Arc < Capabilities >, 631interrupted : Arc < AtomicBool >, 632) ->( Extension , Arc < Files >) { 633let state =Arc :: new ( Files { 634pool : BlockingPool { runtime}, 635resources : Mutex :: new ( HashMap :: new ()), 636next : AtomicU64 :: new ( 1 ), 637 caps, 638 interrupted, 639}); 640let ops = state. clone (); 641let shared = state. clone (); 642let open = state. clone (); 643let read = state. clone (); 644let write = state. clone (); 645let writev = state. clone (); 646let seek = state. clone (); 647let truncate = state. clone (); 648let sync = state. clone (); 649let stat = state. clone (); 650let path_stat = state. clone (); 651let link_ops = links:: ops ( state. clone ()); 652let path_ops = paths:: ops ( state. clone ()); 653let posix_ops = posix:: ops ( state. clone ()); 654let workflow_ops = workflows:: ops ( state. clone ()); 655let watch_ops = watch:: ops ( state. clone ()); 656let mut extension =Extension { 657name : "ext:file" , 658dependencies : & [ "ext:url" , "ext:web" , "ext:webstreams" ], 659ops : vec! [ 660vec! [ Op :: new ( "op_file_url_path" , |value : String |{ 661let parsed =( ||{ 662let url = url:: Url :: parse ( & value). map_err ( io:: Error :: other) ?; 663if url. scheme () !="file" || url. query (). is_some () || url. fragment (). is_some () { 664return Err ( io:: Error :: new ( 665 io:: ErrorKind :: InvalidInput , 666"Expected a file URL without query or fragment" , 667)); 668} 669 url. to_file_path () 670. map_err ( |_|{ 671 io:: Error :: new ( io:: ErrorKind :: InvalidInput , "Invalid file URL" ) 672}) ? 673. into_os_string () 674. into_string () 675. map_err ( |_|{ 676 io:: Error :: new ( io:: ErrorKind :: InvalidInput , "Non-UTF-8 file URL" ) 677}) 678})(); 679Ok ::< _ , Infallible >( response ( parsed)) 680})], 681 ops. io_ops ( 682"op_file_open" , 683"op_file_open_sync" , 684move |( path, options): ( String , Options ) |{ 685let pending = open. open ( path, options); 686async move { Ok ::< _ , Infallible >( receive ( pending). await ) } 687}, 688), 689 ops. io_ops ( 690"op_file_read" , 691"op_file_read_sync" , 692move |( id, limit): ( u64 , usize ) |{ 693let pending = read. submit ( id, move |opened|{ 694 opened. check ( false ) ?; 695let mut bytes =vec! [ 0 ; limit. min ( CHUNK )]; 696if limit ==0 { 697return Ok ( Some ( ToJsBuffer :: from ( bytes))); 698} 699let count =loop { 700match opened. file . read ( & mut bytes) { 701Err ( error) if error. kind () == io:: ErrorKind :: Interrupted =>{ 702continue ; 703} 704 result =>break result?, 705} 706}; 707 bytes. truncate ( count); 708Ok ( if count ==0 { 709None 710} else { 711Some ( ToJsBuffer :: from ( bytes)) 712}) 713}); 714async move { Ok ::< _ , Infallible >( receive ( pending). await ) } 715}, 716), 717 ops. io_ops ( 718"op_file_write" , 719"op_file_write_sync" , 720move |( id, bytes): ( u64 , JsBuffer ) |{ 721let bytes = bytes[ ..bytes. len (). min ( CHUNK )]. to_vec (); 722let pending = write. submit ( id, move |opened|{ 723 opened. check ( true ) ?; 724loop { 725match opened. file . write ( & bytes) { 726Err ( error) if error. kind () == io:: ErrorKind :: Interrupted =>{ 727continue ; 728} 729 result =>return result, 730} 731} 732}); 733async move { Ok ::< _ , Infallible >( receive ( pending). await ) } 734}, 735), 736 ops. io_ops ( 737"op_file_writev" , 738"op_file_writev_sync" , 739move |( id, buffers): ( u64 , Vec < JsBuffer >) |{ 740let pending = writev. submit_observed ( id, move |opened, closed|{ 741 opened. check ( true ) ?; 742let mut total =0 ; 743let mut elem =0 ; 744let mut inner =0 ; 745while elem < buffers. len () { 746while elem < buffers. len () && inner >= buffers[ elem]. len () { 747 elem +=1 ; 748 inner =0 ; 749} 750if elem >= buffers. len () { 751break ; 752} 753if closed. load ( Ordering :: Acquire ) { 754return Err ( bad_resource ()); 755} 756 opened. check ( true ) ?; 757let mut slices =Vec :: new (); 758let mut budget =CHUNK ; 759let mut scan = elem; 760let mut scan_inner = inner; 761while scan < buffers. len () && slices. len () <MAX_SLICES && budget >0 { 762let bytes: & [ u8 ] =& buffers[ scan]; 763let available = bytes. len (). saturating_sub ( scan_inner); 764if available ==0 { 765 scan +=1 ; 766 scan_inner =0 ; 767continue ; 768} 769let take = available. min ( budget); 770 slices 771. push ( io:: IoSlice :: new ( & bytes[ scan_inner..scan_inner + take])); 772 budget -= take; 773 scan_inner += take; 774if scan_inner >= bytes. len () { 775 scan +=1 ; 776 scan_inner =0 ; 777} 778} 779if slices. is_empty () { 780break ; 781} 782let count =loop { 783match opened. file . write_vectored ( & slices) { 784Err ( error) if error. kind () == io:: ErrorKind :: Interrupted =>{ 785continue ; 786} 787 result =>break result?, 788} 789}; 790if count ==0 { 791return Err ( io:: Error :: new ( 792 io:: ErrorKind :: WriteZero , 793"File write made no progress" , 794)); 795} 796 total += count; 797let mut skip = count; 798while skip >0 { 799let remaining = buffers[ elem]. len () - inner; 800if remaining ==0 { 801 elem +=1 ; 802 inner =0 ; 803continue ; 804} 805if skip < remaining{ 806 inner += skip; 807break ; 808} 809 skip -= remaining; 810 elem +=1 ; 811 inner =0 ; 812} 813} 814Ok ( total) 815}); 816async move { Ok ::< _ , Infallible >( receive ( pending). await ) } 817}, 818), 819 ops. io_ops ( 820"op_file_seek" , 821"op_file_seek_sync" , 822move |( id, offset, whence): ( u64 , i64 , u8 ) |{ 823let pending = seek. submit ( id, move |opened|{ 824 opened. check_metadata () ?; 825if offset. unsigned_abs () >SAFE_INTEGER { 826return Err ( io:: Error :: new ( 827 io:: ErrorKind :: InvalidInput , 828"Unsafe seek offset" , 829)); 830} 831let origin =match whence{ 8320 =>0 , 8331 => opened. file . stream_position () ?, 8342 => opened. file . metadata () ?. len (), 835 _ =>{ 836return Err ( io:: Error :: new ( 837 io:: ErrorKind :: InvalidInput , 838"Invalid seek mode" , 839)); 840} 841}; 842let position = origin 843. checked_add_signed ( offset) 844. filter ( |position|* position <=SAFE_INTEGER ) 845. ok_or_else ( ||{ 846 io:: Error :: new ( io:: ErrorKind :: InvalidInput , "Unsafe seek position" ) 847}) ?; 848let position = opened. file . seek ( SeekFrom :: Start ( position)) ?; 849Ok ( position) 850}); 851async move { Ok ::< _ , Infallible >( receive ( pending). await ) } 852}, 853), 854 ops. io_ops ( 855"op_file_truncate" , 856"op_file_truncate_sync" , 857move |( id, length): ( u64 , u64 ) |{ 858let pending = truncate. submit ( id, move |opened|{ 859 opened. check ( true ) ?; 860if length >SAFE_INTEGER { 861return Err ( io:: Error :: new ( 862 io:: ErrorKind :: InvalidInput , 863"Unsafe truncate length" , 864)); 865} 866 opened. file . set_len ( length). map ( |_|true ) 867}); 868async move { Ok ::< _ , Infallible >( receive ( pending). await ) } 869}, 870), 871 ops. io_ops ( 872"op_file_sync" , 873"op_file_sync_sync" , 874move |( id, data): ( u64 , bool ) |{ 875let pending = sync. submit ( id, move |opened|{ 876 opened. check ( true ) ?; 877if data{ 878 opened. file . sync_data () ?; 879} else { 880 opened. file . sync_all () ?; 881} 882Ok ( true ) 883}); 884async move { Ok ::< _ , Infallible >( receive ( pending). await ) } 885}, 886), 887 ops. io_ops ( "op_file_stat" , "op_file_stat_sync" , move |id : u64 |{ 888let pending = stat. submit ( id, |opened|{ 889 opened. check_metadata () ?; 890 opened. file . metadata (). map ( info) 891}); 892async move { Ok ::< _ , Infallible >( receive ( pending). await ) } 893}), 894 ops. io_ops ( 895"op_file_path_stat" , 896"op_file_path_stat_sync" , 897move |( path, follow): ( String , bool ) |{ 898let caps = path_stat. caps . clone (); 899let pending = path_stat. pool . run ( move ||{ 900if path. is_empty () { 901return Err ( io:: Error :: new ( 902 io:: ErrorKind :: NotFound , 903"Empty filesystem path" , 904)); 905} 906let follow = follow || paths:: follows_link ( & path); 907let absolute =filesystem_path ( & caps, & path) ?; 908let target =if follow{ 909PathTarget :: for_creation ( & absolute) 910} else { 911PathTarget :: for_entry ( & absolute) 912}; 913let target =match target{ 914Ok ( target) => target, 915Err ( _) if !caps. unrestricted_read () =>{ 916return Err ( denied ( & path, false )); 917} 918Err ( error) =>return Err ( error), 919}; 920authorize ( & caps, & target, & path, false ) ?; 921let ( directory, name) =parent ( target. as_path ()) ?; 922let metadata = directory. symlink_metadata ( name) ?; 923if follow && metadata. is_symlink () { 924return Err ( io:: Error :: new ( 925 io:: ErrorKind :: NotFound , 926"Symbolic link changed or has a missing target" , 927)); 928} 929Ok ( info ( metadata)) 930}); 931async move { Ok ::< _ , Infallible >( receive ( pending). await ) } 932}, 933), 934vec! [ Op :: new ( "op_file_close" , move |( id, quiet): ( u64 , bool ) |{ 935let resource = state. resources . lock (). unwrap (). remove ( & id); 936let result =match resource{ 937Some ( resource) =>{ 938 resource. close (); 939Ok ( true ) 940} 941None if quiet =>Ok ( true ), 942None =>Err ( bad_resource ()), 943}; 944Ok ::< _ , Infallible >( response ( result)) 945})], 946] 947. into_iter () 948. flatten () 949. collect (), 950modules : const { 951& [ 952( 953"ext:file/main.js" , 954include_str! ( concat! ( env! ( "OUT_DIR" ), "/main.js" )), 955), 956( 957"ext:file/errors.js" , 958include_str! ( concat! ( env! ( "OUT_DIR" ), "/errors.js" )), 959), 960( 961"ext:file/sync.js" , 962include_str! ( concat! ( env! ( "OUT_DIR" ), "/sync.js" )), 963), 964PUBLIC_MODULES [ 0 ]. module , 965PUBLIC_MODULES [ 1 ]. module , 966] 967}, 968entry_point : None , 969}; 970 extension. ops . extend ( link_ops); 971 extension. ops . extend ( path_ops); 972 extension. ops . extend ( posix_ops); 973 extension. ops . extend ( workflow_ops); 974 extension. ops . extend ( watch_ops); 975( extension, shared) 976} 977 978# [ cfg ( test )] 979mod tests{ 980use super :: * ; 981 982# [ tokio :: test ] 983async fn response_handles_reject_busy_files_without_consuming_them () { 984let root = tempfile:: tempdir (). unwrap (); 985 std:: fs:: write ( root. path (). join ( "file.txt" ), b"content" ). unwrap (); 986let caps =Capabilities :: new ( avril_caps:: Initial :: new ( false ), root. path (). to_owned ()); 987let ( _extension, files) =init ( Handle :: current (), caps, Arc :: new ( AtomicBool :: new ( false ))); 988let id = files 989. open ( 990"file.txt" . into (), 991( true , false , false , false , false , false , None ), 992) 993. await 994. unwrap () 995. unwrap (); 996let ( started, ready) = oneshot:: channel (); 997let ( release, wait) = std:: sync:: mpsc:: channel (); 998let operation = files. submit ( id, move |_|{ 999 started. send (()). unwrap (); 1000 wait. recv (). unwrap (); 1001Ok (()) 1002}); 1003 ready. await . unwrap (); 1004for consumein [ false , true ] { 1005assert_eq! ( 1006 files. response_handle ( id, consume). err (). unwrap (). kind (), 1007 io:: ErrorKind :: WouldBlock , 1008); 1009} 1010 release. send (()). unwrap (); 1011 operation. await . unwrap (). unwrap (); 1012let ( name, file, check) = files. response_handle ( id, true ). unwrap (); 1013assert_eq! ( name, "file.txt" ); 1014check (). unwrap (); 1015assert_eq! ( file. metadata (). unwrap (). len (), 7 ); 1016assert_eq! ( 1017 files. response_handle ( id, false ). err (). unwrap (). kind (), 1018 io:: ErrorKind :: NotConnected , 1019); 1020} 1021}