Skip to content

Commit 2d1fcea

Browse files
committed
Add support for Android
Android doesn't support hard_link so provide an alternate path for performing the requested operation. This also fixes a bug where failed put might result in the staged file being left around.
1 parent 94c25d2 commit 2d1fcea

File tree

2 files changed

+89
-14
lines changed

2 files changed

+89
-14
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ serde_json = { version = "1.0", default-features = false, features = ["std"], op
6161
serde_urlencoded = { version = "0.7", optional = true }
6262
tokio = { version = "1.29.0", features = ["sync", "macros", "rt", "time", "io-util"] }
6363

64+
[target.'cfg(target_os="android")'.dependencies]
65+
nix = { version = "0.30.0", features = ["fs"] }
66+
6467
[target.'cfg(target_family="unix")'.dev-dependencies]
6568
nix = { version = "0.30.0", features = ["fs"] }
6669

src/local.rs

Lines changed: 86 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -351,22 +351,38 @@ impl ObjectStore for LocalFileSystem {
351351
std::mem::drop(file);
352352
match std::fs::rename(&staging_path, &path) {
353353
Ok(_) => None,
354-
Err(source) => Some(Error::UnableToRenameFile { source }),
354+
Err(source) => {
355+
let _ = std::fs::remove_file(&staging_path);
356+
Some(Error::UnableToRenameFile { source })
357+
},
355358
}
356359
}
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
360+
PutMode::Create => {
361+
#[cfg(not(target_os = "android"))]
362+
let create_result = std::fs::hard_link(&staging_path, &path);
363+
364+
#[cfg(target_os = "android")]
365+
let create_result = nix::fcntl::renameat2(
366+
nix::fcntl::AtFlags::AT_FDCWD,
367+
&staged,
368+
nix::fcntl::AtFlags::AT_FDCWD,
369+
&path,
370+
nix::fcntl::RenameFlags::RENAME_NOREPLACE,
371+
);
372+
373+
let _ = std::fs::remove_file(&staging_path); // Attempt to cleanup
374+
375+
match create_result {
376+
Ok(_) => None,
377+
Err(source) => match source.kind() {
378+
ErrorKind::AlreadyExists => Some(Error::AlreadyExists {
379+
path: path.to_str().unwrap().to_string(),
380+
source,
381+
}),
382+
_ => Some(Error::UnableToRenameFile { source }),
383+
},
361384
}
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-
},
385+
}
370386
PutMode::Update(_) => unreachable!(),
371387
}
372388
}
@@ -558,7 +574,14 @@ impl ObjectStore for LocalFileSystem {
558574
// This is necessary because hard_link returns an error if the destination already exists
559575
maybe_spawn_blocking(move || loop {
560576
let staged = staged_upload_path(&to, &id.to_string());
561-
match std::fs::hard_link(&from, &staged) {
577+
578+
#[cfg(not(target_os = "android"))]
579+
let stage_result = std::fs::hard_link(&from, &staged);
580+
581+
#[cfg(target_os = "android")]
582+
let stage_result = std::fs::copy(&from, &staged);
583+
584+
match stage_result {
562585
Ok(_) => {
563586
return std::fs::rename(&staged, &to).map_err(|source| {
564587
let _ = std::fs::remove_file(&staged); // Attempt to clean up
@@ -596,6 +619,7 @@ impl ObjectStore for LocalFileSystem {
596619
.await
597620
}
598621

622+
#[cfg(not(target_os = "android"))]
599623
async fn copy_if_not_exists(&self, from: &Path, to: &Path) -> Result<()> {
600624
let from = self.path_to_filesystem(from)?;
601625
let to = self.path_to_filesystem(to)?;
@@ -621,6 +645,54 @@ impl ObjectStore for LocalFileSystem {
621645
})
622646
.await
623647
}
648+
649+
#[cfg(target_os = "android")]
650+
async fn copy_if_not_exists(&self, from: &Path, to: &Path) -> Result<()> {
651+
let from = self.path_to_filesystem(from)?;
652+
let to = self.path_to_filesystem(to)?;
653+
let mut id = 0;
654+
// In order to make this atomic we:
655+
//
656+
// - stage to a temporary file
657+
// - atomically rename this temporary file into place only if to does not exist
658+
//
659+
// This is necessary because hard_link is EACCESS on Android.
660+
maybe_spawn_blocking(move || loop {
661+
let staged = staged_upload_path(&to, &id.to_string());
662+
663+
match std::fs::copy(&from, &staged) {
664+
Ok(_) => {
665+
let rename_result = nix::fcntl::renameat2(
666+
nix::fcntl::AtFlags::AT_FDCWD,
667+
&staged,
668+
nix::fcntl::AtFlags::AT_FDCWD,
669+
&to,
670+
nix::fcntl::RenameFlags::RENAME_NOREPLACE,
671+
);
672+
let _ = std::fs::remove_file(&staged); // Attempt to clean up
673+
return rename_result.map_err(|source| {
674+
if source.kind() == ErrorKind::NotFound {
675+
Error::AlreadyExists {
676+
path: to.to_str().unwrap().to_string(),
677+
source,
678+
}
679+
} else {
680+
Error::UnableToCopyFile { from, to, source }
681+
}
682+
});
683+
}
684+
Err(source) => match source.kind() {
685+
ErrorKind::AlreadyExists => id += 1,
686+
ErrorKind::NotFound => match from.exists() {
687+
true => create_parent_dirs(&to, source)?,
688+
false => return Err(Error::NotFound { path: from, source }.into()),
689+
},
690+
_ => return Err(Error::UnableToCopyFile { from, to, source }.into()),
691+
},
692+
}
693+
})
694+
.await
695+
}
624696
}
625697

626698
impl LocalFileSystem {

0 commit comments

Comments
 (0)