Skip to content

Commit 6156595

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 6156595

File tree

2 files changed

+113
-14
lines changed

2 files changed

+113
-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" }
66+
6467
[target.'cfg(target_family="unix")'.dev-dependencies]
6568
nix = { version = "0.30.0", features = ["fs"] }
6669

src/local.rs

Lines changed: 110 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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

626722
impl LocalFileSystem {

0 commit comments

Comments
 (0)