Skip to content

Commit f5c2686

Browse files
authored
fix: copy/rename return error if source is nonexistent (#5528)
* copy() return error if `from` is nonexistent * Check `from` in loop to avoid TOCTOU race
1 parent 96c4c0b commit f5c2686

File tree

2 files changed

+40
-3
lines changed

2 files changed

+40
-3
lines changed

object_store/src/lib.rs

+27
Original file line numberDiff line numberDiff line change
@@ -2127,6 +2127,33 @@ mod tests {
21272127
storage.delete(&path2).await.unwrap();
21282128
}
21292129

2130+
pub(crate) async fn copy_rename_nonexistent_object(storage: &DynObjectStore) {
2131+
// Create empty source object
2132+
let path1 = Path::from("test1");
2133+
2134+
// Create destination object
2135+
let path2 = Path::from("test2");
2136+
storage.put(&path2, Bytes::from("hello")).await.unwrap();
2137+
2138+
// copy() errors if source does not exist
2139+
let result = storage.copy(&path1, &path2).await;
2140+
assert!(result.is_err());
2141+
assert!(matches!(result.unwrap_err(), crate::Error::NotFound { .. }));
2142+
2143+
// rename() errors if source does not exist
2144+
let result = storage.rename(&path1, &path2).await;
2145+
assert!(result.is_err());
2146+
assert!(matches!(result.unwrap_err(), crate::Error::NotFound { .. }));
2147+
2148+
// copy_if_not_exists() errors if source does not exist
2149+
let result = storage.copy_if_not_exists(&path1, &path2).await;
2150+
assert!(result.is_err());
2151+
assert!(matches!(result.unwrap_err(), crate::Error::NotFound { .. }));
2152+
2153+
// Clean up
2154+
storage.delete(&path2).await.unwrap();
2155+
}
2156+
21302157
pub(crate) async fn multipart(storage: &dyn ObjectStore, multipart: &dyn MultipartStore) {
21312158
let path = Path::from("test_multipart");
21322159
let chunk_size = 5 * 1024 * 1024;

object_store/src/local.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,10 @@ impl ObjectStore for LocalFileSystem {
598598
}
599599
Err(source) => match source.kind() {
600600
ErrorKind::AlreadyExists => id += 1,
601-
ErrorKind::NotFound => create_parent_dirs(&to, source)?,
601+
ErrorKind::NotFound => match from.exists() {
602+
true => create_parent_dirs(&to, source)?,
603+
false => return Err(Error::NotFound { path: from, source }.into()),
604+
},
602605
_ => return Err(Error::UnableToCopyFile { from, to, source }.into()),
603606
},
604607
}
@@ -613,7 +616,10 @@ impl ObjectStore for LocalFileSystem {
613616
match std::fs::rename(&from, &to) {
614617
Ok(_) => return Ok(()),
615618
Err(source) => match source.kind() {
616-
ErrorKind::NotFound => create_parent_dirs(&to, source)?,
619+
ErrorKind::NotFound => match from.exists() {
620+
true => create_parent_dirs(&to, source)?,
621+
false => return Err(Error::NotFound { path: from, source }.into()),
622+
},
617623
_ => return Err(Error::UnableToCopyFile { from, to, source }.into()),
618624
},
619625
}
@@ -636,7 +642,10 @@ impl ObjectStore for LocalFileSystem {
636642
}
637643
.into())
638644
}
639-
ErrorKind::NotFound => create_parent_dirs(&to, source)?,
645+
ErrorKind::NotFound => match from.exists() {
646+
true => create_parent_dirs(&to, source)?,
647+
false => return Err(Error::NotFound { path: from, source }.into()),
648+
},
640649
_ => return Err(Error::UnableToCopyFile { from, to, source }.into()),
641650
},
642651
}
@@ -990,6 +999,7 @@ mod tests {
990999
list_with_delimiter(&integration).await;
9911000
rename_and_copy(&integration).await;
9921001
copy_if_not_exists(&integration).await;
1002+
copy_rename_nonexistent_object(&integration).await;
9931003
stream_get(&integration).await;
9941004
put_opts(&integration, false).await;
9951005
}

0 commit comments

Comments
 (0)