Skip to content

Commit c7fcbb9

Browse files
fix: delete file on write failure and preserve parent directory (#27)
1 parent f7624d2 commit c7fcbb9

File tree

1 file changed

+66
-18
lines changed

1 file changed

+66
-18
lines changed

pre-compute/src/compute/utils/file_utils.rs

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ pub fn write_file(content: &[u8], file_path: &Path, context: &str) -> Result<(),
4545
"Failed to write file [{context}, path:{}]",
4646
file_path.display()
4747
);
48+
if file_path.exists() {
49+
match fs::remove_file(file_path) {
50+
Ok(_) => {
51+
info!("File deleted [path:{}]", file_path.display());
52+
}
53+
Err(e) => {
54+
error!(
55+
"Failed to delete file [path:{}, error:{e}]",
56+
file_path.display()
57+
);
58+
}
59+
}
60+
}
4861
Err(e)
4962
}
5063
}
@@ -54,7 +67,7 @@ pub fn write_file(content: &[u8], file_path: &Path, context: &str) -> Result<(),
5467
///
5568
/// If the download or any file operation fails, the function logs an appropriate error
5669
/// and returns `None`. It also ensures the parent directory exists, creating it if necessary.
57-
/// If the directory is newly created but the file write fails, it is cleaned up (deleted).
70+
/// If the file write fails and a partial file was created, it will be cleaned up (deleted).
5871
///
5972
/// # Arguments
6073
///
@@ -115,23 +128,9 @@ pub fn download_file(url: &str, parent_dir: &str, filename: &str) -> Option<Path
115128

116129
let file_path = parent_path.join(filename);
117130

118-
if write_file(&bytes, &file_path, &format!("url:{url}")).is_ok() {
119-
Some(file_path)
120-
} else {
121-
if !parent_existed {
122-
match fs::remove_dir_all(parent_path) {
123-
Ok(_) => {
124-
info!("Folder deleted [path:{}]", parent_path.display());
125-
}
126-
Err(_) => {
127-
error!(
128-
"Folder does not exist, nothing to delete [path:{}]",
129-
parent_path.display()
130-
);
131-
}
132-
}
133-
}
134-
None
131+
match write_file(&bytes, &file_path, &format!("url:{url}")) {
132+
Ok(_) => Some(file_path),
133+
Err(_) => None,
135134
}
136135
}
137136

@@ -312,6 +311,55 @@ mod tests {
312311
assert!(result.is_none());
313312
}
314313

314+
#[test]
315+
#[cfg(unix)]
316+
fn download_from_url_fails_and_preserves_parent_dir_when_file_write_error() {
317+
use std::os::unix::fs::PermissionsExt;
318+
let (_container, container_url) = start_container();
319+
320+
let temp_dir = TempDir::new().unwrap();
321+
let nested_path = temp_dir.path().join("new_dir");
322+
323+
// Create the directory and make it read-only to prevent file creation
324+
fs::create_dir_all(&nested_path).unwrap();
325+
let mut perms = fs::metadata(&nested_path).unwrap().permissions();
326+
perms.set_mode(0o555);
327+
fs::set_permissions(&nested_path, perms).unwrap();
328+
329+
let result = download_file(&container_url, nested_path.to_str().unwrap(), "test.json");
330+
331+
// Download should fail due to inability to write file. Parent directory should still exist
332+
assert!(result.is_none());
333+
assert!(nested_path.exists());
334+
}
335+
336+
#[test]
337+
#[cfg(unix)]
338+
fn download_from_url_fails_and_file_deleted_when_write_partialy_fails() {
339+
use std::os::unix::fs::PermissionsExt;
340+
let (_container, container_url) = start_container();
341+
342+
let temp_dir = TempDir::new().unwrap();
343+
let parent_path = temp_dir.path();
344+
let file_path = parent_path.join("test.json");
345+
346+
// Pre-create a read-only file to force write failure
347+
fs::write(&file_path, b"old content").unwrap();
348+
let mut perms = fs::metadata(&file_path).unwrap().permissions();
349+
perms.set_mode(0o444);
350+
fs::set_permissions(&file_path, perms).unwrap();
351+
352+
let result = download_file(&container_url, parent_path.to_str().unwrap(), "test.json");
353+
assert!(result.is_none());
354+
355+
// Parent directory should still exist. File should be deleted on cleanup
356+
assert!(parent_path.exists());
357+
assert!(
358+
!file_path.exists(),
359+
"File should have been cleaned up after write failure"
360+
);
361+
}
362+
315363
#[test]
316364
fn test_download_from_url_with_server_error() {
317365
let rt = tokio::runtime::Runtime::new().unwrap();

0 commit comments

Comments
 (0)