Skip to content

Commit 2923fbb

Browse files
committed
refactor(hm-util): convert fs helpers to async tokio operations
create_dir_with_mode and write_file_with_mode now use tokio::fs instead of std::fs. This eliminates the last spawn_blocking from write_atomic_restricted — all I/O goes through tokio's async API.
1 parent 61aaf74 commit 2923fbb

1 file changed

Lines changed: 50 additions & 57 deletions

File tree

  • crates/hm-util/src/os

crates/hm-util/src/os/fs.rs

Lines changed: 50 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ use std::path::Path;
1515
/// Write `contents` to `path` atomically with `file_mode`, ensuring the
1616
/// parent directory exists and is set to `dir_mode`.
1717
///
18-
/// Internally offloads blocking I/O to [`tokio::task::spawn_blocking`].
19-
///
2018
/// # Errors
2119
///
2220
/// Returns an error if `path` has no parent or no file-name component,
@@ -29,41 +27,37 @@ pub async fn write_atomic_restricted(
2927
file_mode: u32,
3028
dir_mode: u32,
3129
) -> io::Result<()> {
32-
let dest = path.as_ref().to_owned();
30+
let path = path.as_ref().to_owned();
3331
let contents = contents.as_ref().to_vec();
34-
let path = dest.clone();
3532

36-
let tmp_path = tokio::task::spawn_blocking(move || {
37-
let parent = path.parent().ok_or_else(|| {
33+
let parent = path
34+
.parent()
35+
.ok_or_else(|| {
3836
io::Error::new(
3937
io::ErrorKind::InvalidInput,
4038
format!("{} has no parent directory", path.display()),
4139
)
42-
})?;
43-
44-
create_dir_with_mode(parent, dir_mode)?;
45-
46-
let file_name = path
47-
.file_name()
48-
.ok_or_else(|| {
49-
io::Error::new(
50-
io::ErrorKind::InvalidInput,
51-
format!("{} has no file name", path.display()),
52-
)
53-
})?
54-
.to_os_string();
55-
let mut tmp_name = file_name;
56-
tmp_name.push(format!(".tmp.{}", std::process::id()));
57-
let tmp_path = parent.join(&tmp_name);
58-
59-
write_file_with_mode(&tmp_path, &contents, file_mode)?;
60-
61-
io::Result::Ok(tmp_path)
62-
})
63-
.await
64-
.map_err(io::Error::other)??;
65-
66-
let rename_result = atomic_rename_over(&tmp_path, &dest).await;
40+
})?
41+
.to_owned();
42+
43+
create_dir_with_mode(&parent, dir_mode).await?;
44+
45+
let file_name = path
46+
.file_name()
47+
.ok_or_else(|| {
48+
io::Error::new(
49+
io::ErrorKind::InvalidInput,
50+
format!("{} has no file name", path.display()),
51+
)
52+
})?
53+
.to_os_string();
54+
let mut tmp_name = file_name;
55+
tmp_name.push(format!(".tmp.{}", std::process::id()));
56+
let tmp_path = parent.join(&tmp_name);
57+
58+
write_file_with_mode(&tmp_path, &contents, file_mode).await?;
59+
60+
let rename_result = atomic_rename_over(&tmp_path, &path).await;
6761
if rename_result.is_err() {
6862
let _ = tokio::fs::remove_file(&tmp_path).await;
6963
}
@@ -118,45 +112,44 @@ pub async fn remove_file_if_exists(path: impl AsRef<Path>) -> io::Result<()> {
118112
// ---------------------------------------------------------------------------
119113

120114
#[cfg(unix)]
121-
fn create_dir_with_mode(dir: &Path, mode: u32) -> io::Result<()> {
122-
use std::os::unix::fs::{DirBuilderExt, PermissionsExt};
123-
if dir.exists() {
124-
let current = std::fs::metadata(dir)?.permissions().mode() & 0o777;
125-
if current != mode {
126-
std::fs::set_permissions(dir, std::fs::Permissions::from_mode(mode))?;
115+
async fn create_dir_with_mode(dir: &Path, mode: u32) -> io::Result<()> {
116+
use std::os::unix::fs::PermissionsExt;
117+
match tokio::fs::metadata(dir).await {
118+
Ok(meta) => {
119+
let current = meta.permissions().mode() & 0o777;
120+
if current != mode {
121+
tokio::fs::set_permissions(dir, std::fs::Permissions::from_mode(mode)).await?;
122+
}
123+
}
124+
Err(e) if e.kind() == io::ErrorKind::NotFound => {
125+
let mut builder = tokio::fs::DirBuilder::new();
126+
builder.recursive(true).mode(mode);
127+
builder.create(dir).await?;
127128
}
128-
} else {
129-
std::fs::DirBuilder::new()
130-
.recursive(true)
131-
.mode(mode)
132-
.create(dir)?;
129+
Err(e) => return Err(e),
133130
}
134131
Ok(())
135132
}
136133

137134
#[cfg(not(unix))]
138-
fn create_dir_with_mode(dir: &Path, _mode: u32) -> io::Result<()> {
139-
std::fs::create_dir_all(dir)
135+
async fn create_dir_with_mode(dir: &Path, _mode: u32) -> io::Result<()> {
136+
tokio::fs::create_dir_all(dir).await
140137
}
141138

142139
#[cfg(unix)]
143-
fn write_file_with_mode(path: &Path, contents: &[u8], mode: u32) -> io::Result<()> {
144-
use std::io::Write;
145-
use std::os::unix::fs::OpenOptionsExt;
146-
let mut f = std::fs::OpenOptions::new()
147-
.write(true)
148-
.create(true)
149-
.truncate(true)
150-
.mode(mode)
151-
.open(path)?;
152-
f.write_all(contents)?;
153-
f.sync_all()?;
140+
async fn write_file_with_mode(path: &Path, contents: &[u8], mode: u32) -> io::Result<()> {
141+
use tokio::io::AsyncWriteExt;
142+
let mut opts = tokio::fs::OpenOptions::new();
143+
opts.write(true).create(true).truncate(true).mode(mode);
144+
let mut f = opts.open(path).await?;
145+
f.write_all(contents).await?;
146+
f.sync_all().await?;
154147
Ok(())
155148
}
156149

157150
#[cfg(not(unix))]
158-
fn write_file_with_mode(path: &Path, contents: &[u8], _mode: u32) -> io::Result<()> {
159-
std::fs::write(path, contents)
151+
async fn write_file_with_mode(path: &Path, contents: &[u8], _mode: u32) -> io::Result<()> {
152+
tokio::fs::write(path, contents).await
160153
}
161154

162155
#[cfg(windows)]

0 commit comments

Comments
 (0)