Skip to content

Commit 13096ba

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 13096ba

File tree

2 files changed

+114
-14
lines changed

2 files changed

+114
-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: 111 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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

626723
impl LocalFileSystem {

0 commit comments

Comments
 (0)