fix(security): [#1981] 修复原子写入fsync、优化错误提示及平台兼容

This commit is contained in:
2026-05-10 02:20:03 +08:00
parent e3afbbe3be
commit fdf99c7184
4 changed files with 50 additions and 19 deletions
Generated
+1 -1
View File
@@ -261,7 +261,7 @@ dependencies = [
[[package]]
name = "l-s"
version = "0.5.1"
version = "0.5.2"
dependencies = [
"anyhow",
"clap",
+4 -4
View File
@@ -206,16 +206,16 @@ fn write_atomic(path: &Path, contents: &str) -> Result<()> {
let tmp_path = parent.join(format!(".l-s-tmp-{}-{nanos}", std::process::id()));
let result = (|| -> Result<()> {
let mut file = OpenOptions::new()
let mut temp = OpenOptions::new()
.write(true)
.create_new(true)
.open(&tmp_path)
.with_context(|| format!("无法创建临时文件: {}", tmp_path.display()))?;
file.write_all(contents.as_bytes())
temp.write_all(contents.as_bytes())
.with_context(|| format!("无法写入临时文件: {}", tmp_path.display()))?;
file.sync_all()
temp.sync_all()
.with_context(|| format!("无法同步临时文件: {}", tmp_path.display()))?;
drop(file);
drop(temp);
fs::rename(&tmp_path, path).with_context(|| {
format!(
+29 -10
View File
@@ -16,10 +16,12 @@ use crate::constants::{DEFAULT_BUFFER_SIZE, HEAD_115_BYTES, HEAD_BAIDU_BYTES};
use crate::head_hash::{calc_head_115, calc_head_baidu, HeadChunk};
use crate::utils::{basename, friendly_size, hex_upper};
#[cfg(not(unix))]
#[cfg(all(not(unix), not(windows)))]
use std::fs;
#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;
#[cfg(windows)]
use std::os::windows::fs::{MetadataExt, OpenOptionsExt};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileMeta {
@@ -166,18 +168,13 @@ where
F1: FnMut(u64),
F2: FnMut(),
{
let info = file
.metadata()
.with_context(|| format!("无法读取文件信息: {}", path.display()))?;
if !info.is_file() {
return Err(anyhow!("{} 不是文件", path.display()));
}
let mut buffer = vec![0u8; DEFAULT_BUFFER_SIZE];
let mut hasher = Xxh3::new();
loop {
let read_len = file.read(&mut buffer)?;
let read_len = file
.read(&mut buffer)
.with_context(|| format!("无法读取文件: {}", path.display()))?;
if read_len == 0 {
break;
}
@@ -210,7 +207,29 @@ fn open_file_nofollow(path: &Path) -> Result<File> {
.with_context(|| format!("无法打开文件: {}", path.display()))
}
#[cfg(not(unix))]
#[cfg(windows)]
fn open_file_nofollow(path: &Path) -> Result<File> {
const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
const FILE_FLAG_OPEN_REPARSE_POINT: u32 = 0x0020_0000;
let mut options = OpenOptions::new();
options.read(true);
options.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT);
let file = options
.open(path)
.with_context(|| format!("无法打开文件: {}", path.display()))?;
let info = file
.metadata()
.with_context(|| format!("无法读取文件信息: {}", path.display()))?;
if info.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
return Err(anyhow!("不支持扫描符号链接或重解析点: {}", path.display()));
}
Ok(file)
}
#[cfg(all(not(unix), not(windows)))]
fn open_file_nofollow(path: &Path) -> Result<File> {
let info = fs::symlink_metadata(path)
.with_context(|| format!("无法读取文件信息: {}", path.display()))?;
+16 -4
View File
@@ -454,7 +454,7 @@ mod unix_walk {
.metadata()
.with_context(|| format!("无法读取文件信息: {}", path.display()))?;
if !info.is_file() {
return Err(anyhow!("{} 不是文件", path.display()));
return Err(anyhow!("{} 打开后不是普通文件", path.display()));
}
if !stat_matches(&info, &entry.stat) {
return Err(anyhow!("扫描期间文件被替换: {}", path.display()));
@@ -545,7 +545,11 @@ mod unix_walk {
}
EntryKind::Other => {
if !should_skip_file(&name) {
return Err(anyhow!("{} 不是文件", full_path.display()));
return Err(anyhow!(
"不支持的特殊文件: {} (mode {:o})",
full_path.display(),
entry.stat.st_mode
));
}
}
}
@@ -638,7 +642,11 @@ mod unix_walk {
}
EntryKind::Other => {
if !should_skip_file(&name) {
return Err(anyhow!("{} 不是文件", full_path.display()));
return Err(anyhow!(
"不支持的特殊文件: {} (mode {:o})",
full_path.display(),
entry.stat.st_mode
));
}
}
}
@@ -685,7 +693,11 @@ mod unix_walk {
}
EntryKind::Other => {
if !should_skip_file(&name) {
return Err(anyhow!("{} 不是文件", full_path.display()));
return Err(anyhow!(
"不支持的特殊文件: {} (mode {:o})",
full_path.display(),
entry.stat.st_mode
));
}
}
}