@@ -133,6 +133,36 @@ 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:: renameat2 (
152+ AT_FDCWD ,
153+ old_cstr. as_ptr ( ) ,
154+ AT_FDCWD ,
155+ new_cstr. as_ptr ( ) ,
156+ RENAME_NOREPLACE ,
157+ )
158+ } )
159+ } ) ??;
160+
161+ Errno :: result ( res)
162+ . map ( std:: mem:: drop)
163+ . map_err ( |e| std:: io:: Error :: from_raw_os_error ( e as i32 ) )
164+ }
165+
136166/// Local filesystem storage providing an [`ObjectStore`] interface to files on
137167/// local disk. Can optionally be created with a directory prefix
138168///
@@ -351,22 +381,35 @@ impl ObjectStore for LocalFileSystem {
351381 std:: mem:: drop ( file) ;
352382 match std:: fs:: rename ( & staging_path, & path) {
353383 Ok ( _) => None ,
354- Err ( source) => Some ( Error :: UnableToRenameFile { source } ) ,
384+ Err ( source) => {
385+ let _ = std:: fs:: remove_file ( & staging_path) ;
386+ Some ( Error :: UnableToRenameFile { source } )
387+ }
355388 }
356389 }
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
390+ PutMode :: Create => {
391+ #[ cfg( not( target_os = "android" ) ) ]
392+ let create_result = std:: fs:: hard_link ( & staging_path, & path) ;
393+
394+ #[ cfg( target_os = "android" ) ]
395+ let create_result = rename_noreplace (
396+ & staging_path,
397+ & path,
398+ ) ;
399+
400+ let _ = std:: fs:: remove_file ( & staging_path) ; // Attempt to cleanup
401+
402+ match create_result {
403+ Ok ( _) => None ,
404+ Err ( source) => match source. kind ( ) {
405+ ErrorKind :: AlreadyExists => Some ( Error :: AlreadyExists {
406+ path : path. to_str ( ) . unwrap ( ) . to_string ( ) ,
407+ source,
408+ } ) ,
409+ _ => Some ( Error :: UnableToRenameFile { source } ) ,
410+ } ,
361411 }
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- } ,
412+ }
370413 PutMode :: Update ( _) => unreachable ! ( ) ,
371414 }
372415 }
@@ -558,7 +601,14 @@ impl ObjectStore for LocalFileSystem {
558601 // This is necessary because hard_link returns an error if the destination already exists
559602 maybe_spawn_blocking ( move || loop {
560603 let staged = staged_upload_path ( & to, & id. to_string ( ) ) ;
561- match std:: fs:: hard_link ( & from, & staged) {
604+
605+ #[ cfg( not( target_os = "android" ) ) ]
606+ let stage_result = std:: fs:: hard_link ( & from, & staged) ;
607+
608+ #[ cfg( target_os = "android" ) ]
609+ let stage_result = std:: fs:: copy ( & from, & staged) ;
610+
611+ match stage_result {
562612 Ok ( _) => {
563613 return std:: fs:: rename ( & staged, & to) . map_err ( |source| {
564614 let _ = std:: fs:: remove_file ( & staged) ; // Attempt to clean up
@@ -596,6 +646,7 @@ impl ObjectStore for LocalFileSystem {
596646 . await
597647 }
598648
649+ #[ cfg( not( target_os = "android" ) ) ]
599650 async fn copy_if_not_exists ( & self , from : & Path , to : & Path ) -> Result < ( ) > {
600651 let from = self . path_to_filesystem ( from) ?;
601652 let to = self . path_to_filesystem ( to) ?;
@@ -621,6 +672,51 @@ impl ObjectStore for LocalFileSystem {
621672 } )
622673 . await
623674 }
675+
676+ #[ cfg( target_os = "android" ) ]
677+ async fn copy_if_not_exists ( & self , from : & Path , to : & Path ) -> Result < ( ) , super :: Error > {
678+ let from = self . path_to_filesystem ( from) ?;
679+ let to = self . path_to_filesystem ( to) ?;
680+ let mut id = 0 ;
681+ // In order to make this atomic we:
682+ //
683+ // - stage to a temporary file
684+ // - atomically rename this temporary file into place only if to does not exist
685+ //
686+ // This is necessary because hard_link is EACCESS on Android.
687+ maybe_spawn_blocking ( move || loop {
688+ let staged = staged_upload_path ( & to, & id. to_string ( ) ) ;
689+
690+ match std:: fs:: copy ( & from, & staged) {
691+ Ok ( _) => {
692+ let rename_result = rename_noreplace (
693+ & staged,
694+ & to,
695+ ) ;
696+ let _ = std:: fs:: remove_file ( & staged) ; // Attempt to clean up
697+ return rename_result. map_err ( |source| {
698+ if source. kind ( ) == ErrorKind :: NotFound {
699+ Error :: AlreadyExists {
700+ path : to. to_str ( ) . unwrap ( ) . to_string ( ) ,
701+ source,
702+ }
703+ } else {
704+ Error :: UnableToCopyFile { from, to, source }
705+ } . into ( )
706+ } ) ;
707+ }
708+ Err ( source) => match source. kind ( ) {
709+ ErrorKind :: AlreadyExists => id += 1 ,
710+ ErrorKind :: NotFound => match from. exists ( ) {
711+ true => create_parent_dirs ( & to, source) ?,
712+ false => return Err ( Error :: NotFound { path : from, source } . into ( ) ) ,
713+ } ,
714+ _ => return Err ( Error :: UnableToCopyFile { from, to, source } . into ( ) ) ,
715+ } ,
716+ }
717+ } )
718+ . await
719+ }
624720}
625721
626722impl LocalFileSystem {
0 commit comments