@@ -133,6 +133,37 @@ impl From<Error> for super::Error {
133133 }
134134}
135135
136+ #[ cfg( target_os = "android" ) ]
137+ fn rename_noreplace <
138+ P1 : ?Sized + nix:: NixPath ,
139+ P2 : ?Sized + nix:: NixPath ,
140+ > (
141+ old_path : & P1 ,
142+ new_path : & P2 ,
143+ ) -> std:: result:: Result < ( ) , std:: io:: Error > {
144+ use nix:: errno:: Errno ;
145+ use nix:: libc:: AT_FDCWD ;
146+
147+ const RENAME_NOREPLACE : std:: ffi:: c_uint = 1 ;
148+
149+ let res = old_path. with_nix_path ( |old_cstr| {
150+ new_path. with_nix_path ( |new_cstr| unsafe {
151+ nix:: libc:: syscall (
152+ nix:: libc:: SYS_renameat2 ,
153+ AT_FDCWD ,
154+ old_cstr. as_ptr ( ) ,
155+ AT_FDCWD ,
156+ new_cstr. as_ptr ( ) ,
157+ RENAME_NOREPLACE ,
158+ )
159+ } )
160+ } ) ??;
161+
162+ Errno :: result ( res)
163+ . map ( std:: mem:: drop)
164+ . map_err ( |e| std:: io:: Error :: from_raw_os_error ( e as i32 ) )
165+ }
166+
136167/// Local filesystem storage providing an [`ObjectStore`] interface to files on
137168/// local disk. Can optionally be created with a directory prefix
138169///
@@ -351,22 +382,35 @@ impl ObjectStore for LocalFileSystem {
351382 std:: mem:: drop ( file) ;
352383 match std:: fs:: rename ( & staging_path, & path) {
353384 Ok ( _) => None ,
354- Err ( source) => Some ( Error :: UnableToRenameFile { source } ) ,
385+ Err ( source) => {
386+ let _ = std:: fs:: remove_file ( & staging_path) ;
387+ Some ( Error :: UnableToRenameFile { source } )
388+ }
355389 }
356390 }
357- PutMode :: Create => match std:: fs:: hard_link ( & staging_path, & path) {
358- Ok ( _) => {
359- let _ = std:: fs:: remove_file ( & staging_path) ; // Attempt to cleanup
360- None
391+ PutMode :: Create => {
392+ #[ cfg( not( target_os = "android" ) ) ]
393+ let create_result = std:: fs:: hard_link ( & staging_path, & path) ;
394+
395+ #[ cfg( target_os = "android" ) ]
396+ let create_result = rename_noreplace (
397+ & staging_path,
398+ & path,
399+ ) ;
400+
401+ let _ = std:: fs:: remove_file ( & staging_path) ; // Attempt to cleanup
402+
403+ match create_result {
404+ Ok ( _) => None ,
405+ Err ( source) => match source. kind ( ) {
406+ ErrorKind :: AlreadyExists => Some ( Error :: AlreadyExists {
407+ path : path. to_str ( ) . unwrap ( ) . to_string ( ) ,
408+ source,
409+ } ) ,
410+ _ => Some ( Error :: UnableToRenameFile { source } ) ,
411+ } ,
361412 }
362- Err ( source) => match source. kind ( ) {
363- ErrorKind :: AlreadyExists => Some ( Error :: AlreadyExists {
364- path : path. to_str ( ) . unwrap ( ) . to_string ( ) ,
365- source,
366- } ) ,
367- _ => Some ( Error :: UnableToRenameFile { source } ) ,
368- } ,
369- } ,
413+ }
370414 PutMode :: Update ( _) => unreachable ! ( ) ,
371415 }
372416 }
@@ -558,7 +602,14 @@ impl ObjectStore for LocalFileSystem {
558602 // This is necessary because hard_link returns an error if the destination already exists
559603 maybe_spawn_blocking ( move || loop {
560604 let staged = staged_upload_path ( & to, & id. to_string ( ) ) ;
561- match std:: fs:: hard_link ( & from, & staged) {
605+
606+ #[ cfg( not( target_os = "android" ) ) ]
607+ let stage_result = std:: fs:: hard_link ( & from, & staged) ;
608+
609+ #[ cfg( target_os = "android" ) ]
610+ let stage_result = std:: fs:: copy ( & from, & staged) ;
611+
612+ match stage_result {
562613 Ok ( _) => {
563614 return std:: fs:: rename ( & staged, & to) . map_err ( |source| {
564615 let _ = std:: fs:: remove_file ( & staged) ; // Attempt to clean up
@@ -596,6 +647,7 @@ impl ObjectStore for LocalFileSystem {
596647 . await
597648 }
598649
650+ #[ cfg( not( target_os = "android" ) ) ]
599651 async fn copy_if_not_exists ( & self , from : & Path , to : & Path ) -> Result < ( ) > {
600652 let from = self . path_to_filesystem ( from) ?;
601653 let to = self . path_to_filesystem ( to) ?;
@@ -621,6 +673,51 @@ impl ObjectStore for LocalFileSystem {
621673 } )
622674 . await
623675 }
676+
677+ #[ cfg( target_os = "android" ) ]
678+ async fn copy_if_not_exists ( & self , from : & Path , to : & Path ) -> Result < ( ) , super :: Error > {
679+ let from = self . path_to_filesystem ( from) ?;
680+ let to = self . path_to_filesystem ( to) ?;
681+ let mut id = 0 ;
682+ // In order to make this atomic we:
683+ //
684+ // - stage to a temporary file
685+ // - atomically rename this temporary file into place only if to does not exist
686+ //
687+ // This is necessary because hard_link is EACCESS on Android.
688+ maybe_spawn_blocking ( move || loop {
689+ let staged = staged_upload_path ( & to, & id. to_string ( ) ) ;
690+
691+ match std:: fs:: copy ( & from, & staged) {
692+ Ok ( _) => {
693+ let rename_result = rename_noreplace (
694+ & staged,
695+ & to,
696+ ) ;
697+ let _ = std:: fs:: remove_file ( & staged) ; // Attempt to clean up
698+ return rename_result. map_err ( |source| {
699+ if source. kind ( ) == ErrorKind :: NotFound {
700+ Error :: AlreadyExists {
701+ path : to. to_str ( ) . unwrap ( ) . to_string ( ) ,
702+ source,
703+ }
704+ } else {
705+ Error :: UnableToCopyFile { from, to, source }
706+ } . into ( )
707+ } ) ;
708+ }
709+ Err ( source) => match source. kind ( ) {
710+ ErrorKind :: AlreadyExists => id += 1 ,
711+ ErrorKind :: NotFound => match from. exists ( ) {
712+ true => create_parent_dirs ( & to, source) ?,
713+ false => return Err ( Error :: NotFound { path : from, source } . into ( ) ) ,
714+ } ,
715+ _ => return Err ( Error :: UnableToCopyFile { from, to, source } . into ( ) ) ,
716+ } ,
717+ }
718+ } )
719+ . await
720+ }
624721}
625722
626723impl LocalFileSystem {
0 commit comments