diff --git a/Cargo.lock b/Cargo.lock index 24a8508..28413c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,7 +261,7 @@ dependencies = [ [[package]] name = "l-s" -version = "0.5.1" +version = "0.5.2" dependencies = [ "anyhow", "clap", diff --git a/src/main.rs b/src/main.rs index 26203e8..25b4551 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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!( diff --git a/src/meta/file.rs b/src/meta/file.rs index 6eb41f3..abd11d7 100644 --- a/src/meta/file.rs +++ b/src/meta/file.rs @@ -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 { .with_context(|| format!("无法打开文件: {}", path.display())) } -#[cfg(not(unix))] +#[cfg(windows)] +fn open_file_nofollow(path: &Path) -> Result { + 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 { let info = fs::symlink_metadata(path) .with_context(|| format!("无法读取文件信息: {}", path.display()))?; diff --git a/src/meta/tree.rs b/src/meta/tree.rs index 350854f..66e1bc6 100644 --- a/src/meta/tree.rs +++ b/src/meta/tree.rs @@ -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 + )); } } }