fix(security): [#1981] 修复原子写入fsync、优化错误提示及平台兼容
This commit is contained in:
Generated
+1
-1
@@ -261,7 +261,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "l-s"
|
name = "l-s"
|
||||||
version = "0.5.1"
|
version = "0.5.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
+4
-4
@@ -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 tmp_path = parent.join(format!(".l-s-tmp-{}-{nanos}", std::process::id()));
|
||||||
|
|
||||||
let result = (|| -> Result<()> {
|
let result = (|| -> Result<()> {
|
||||||
let mut file = OpenOptions::new()
|
let mut temp = OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.create_new(true)
|
.create_new(true)
|
||||||
.open(&tmp_path)
|
.open(&tmp_path)
|
||||||
.with_context(|| format!("无法创建临时文件: {}", tmp_path.display()))?;
|
.with_context(|| format!("无法创建临时文件: {}", tmp_path.display()))?;
|
||||||
file.write_all(contents.as_bytes())
|
temp.write_all(contents.as_bytes())
|
||||||
.with_context(|| format!("无法写入临时文件: {}", tmp_path.display()))?;
|
.with_context(|| format!("无法写入临时文件: {}", tmp_path.display()))?;
|
||||||
file.sync_all()
|
temp.sync_all()
|
||||||
.with_context(|| format!("无法同步临时文件: {}", tmp_path.display()))?;
|
.with_context(|| format!("无法同步临时文件: {}", tmp_path.display()))?;
|
||||||
drop(file);
|
drop(temp);
|
||||||
|
|
||||||
fs::rename(&tmp_path, path).with_context(|| {
|
fs::rename(&tmp_path, path).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
|
|||||||
+29
-10
@@ -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::head_hash::{calc_head_115, calc_head_baidu, HeadChunk};
|
||||||
use crate::utils::{basename, friendly_size, hex_upper};
|
use crate::utils::{basename, friendly_size, hex_upper};
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(all(not(unix), not(windows)))]
|
||||||
use std::fs;
|
use std::fs;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use std::os::windows::fs::{MetadataExt, OpenOptionsExt};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct FileMeta {
|
pub struct FileMeta {
|
||||||
@@ -166,18 +168,13 @@ where
|
|||||||
F1: FnMut(u64),
|
F1: FnMut(u64),
|
||||||
F2: FnMut(),
|
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 buffer = vec![0u8; DEFAULT_BUFFER_SIZE];
|
||||||
let mut hasher = Xxh3::new();
|
let mut hasher = Xxh3::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let read_len = file.read(&mut buffer)?;
|
let read_len = file
|
||||||
|
.read(&mut buffer)
|
||||||
|
.with_context(|| format!("无法读取文件: {}", path.display()))?;
|
||||||
if read_len == 0 {
|
if read_len == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -210,7 +207,29 @@ fn open_file_nofollow(path: &Path) -> Result<File> {
|
|||||||
.with_context(|| format!("无法打开文件: {}", path.display()))
|
.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> {
|
fn open_file_nofollow(path: &Path) -> Result<File> {
|
||||||
let info = fs::symlink_metadata(path)
|
let info = fs::symlink_metadata(path)
|
||||||
.with_context(|| format!("无法读取文件信息: {}", path.display()))?;
|
.with_context(|| format!("无法读取文件信息: {}", path.display()))?;
|
||||||
|
|||||||
+16
-4
@@ -454,7 +454,7 @@ mod unix_walk {
|
|||||||
.metadata()
|
.metadata()
|
||||||
.with_context(|| format!("无法读取文件信息: {}", path.display()))?;
|
.with_context(|| format!("无法读取文件信息: {}", path.display()))?;
|
||||||
if !info.is_file() {
|
if !info.is_file() {
|
||||||
return Err(anyhow!("{} 不是文件", path.display()));
|
return Err(anyhow!("{} 打开后不是普通文件", path.display()));
|
||||||
}
|
}
|
||||||
if !stat_matches(&info, &entry.stat) {
|
if !stat_matches(&info, &entry.stat) {
|
||||||
return Err(anyhow!("扫描期间文件被替换: {}", path.display()));
|
return Err(anyhow!("扫描期间文件被替换: {}", path.display()));
|
||||||
@@ -545,7 +545,11 @@ mod unix_walk {
|
|||||||
}
|
}
|
||||||
EntryKind::Other => {
|
EntryKind::Other => {
|
||||||
if !should_skip_file(&name) {
|
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 => {
|
EntryKind::Other => {
|
||||||
if !should_skip_file(&name) {
|
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 => {
|
EntryKind::Other => {
|
||||||
if !should_skip_file(&name) {
|
if !should_skip_file(&name) {
|
||||||
return Err(anyhow!("{} 不是文件", full_path.display()));
|
return Err(anyhow!(
|
||||||
|
"不支持的特殊文件: {} (mode {:o})",
|
||||||
|
full_path.display(),
|
||||||
|
entry.stat.st_mode
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user