Handle pre-epoch file timestamps consistently and record Rust audit
This commit is contained in:
1 parent
59b2fc39b2
commit
14adbbc616
7 files changed
+262
-46
No files matched your search
@@ -21,7 +21,6 @@ use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
time::UNIX_EPOCH,
|
||||
};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
@@ -657,12 +656,7 @@ impl SearchIndex {
|
||||
report.discovered += 1;
|
||||
let metadata = entry.metadata()?;
|
||||
let size = i64::try_from(metadata.len()).context("file exceeds supported size")?;
|
||||
let modified = metadata
|
||||
.modified()?
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.context("file timestamp predates Unix epoch")?;
|
||||
let mtime = i64::try_from(modified.as_nanos())
|
||||
.context("file timestamp exceeds supported range")?;
|
||||
let mtime = searchi_media::unix_time_ns(metadata.modified()?)?;
|
||||
let extension = entry
|
||||
.path()
|
||||
.extension()
|
||||
@@ -1257,7 +1251,7 @@ impl SearchIndex {
|
||||
pub fn file_signature(&self, path: &str) -> Result<Option<(u64, i64)>> {
|
||||
self.connection
|
||||
.query_row(
|
||||
"SELECT size,mtime FROM files WHERE path=?1 AND size>=0 AND mtime>=0",
|
||||
"SELECT size,mtime FROM files WHERE path=?1 AND size>=0",
|
||||
[path],
|
||||
|row| Ok((row.get(0)?, row.get(1)?)),
|
||||
)
|
||||
@@ -1294,9 +1288,8 @@ impl SearchIndex {
|
||||
Ok(value) if value.is_file() => value,
|
||||
_ => return Ok(None),
|
||||
};
|
||||
let mtime = metadata.modified()?.duration_since(UNIX_EPOCH)?;
|
||||
let size = i64::try_from(metadata.len())?;
|
||||
let modified = i64::try_from(mtime.as_nanos())?;
|
||||
let modified = searchi_media::unix_time_ns(metadata.modified()?)?;
|
||||
let Some(expected_hash) = self.extracted_source_hash(path)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
@@ -90,3 +90,40 @@ fn oversized_text_and_unsupported_files_remain_filename_only() -> Result<()> {
|
||||
assert_eq!(index.search("large.txt", 10)?.len(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_epoch_source_remains_searchable_and_verifiable() -> Result<()> {
|
||||
let directory = tempfile::tempdir()?;
|
||||
let path = directory.path().join("archived-notes.txt");
|
||||
let content = "Historical supplier cancellation notice\n";
|
||||
fs::write(&path, content)?;
|
||||
fs::File::options().write(true).open(&path)?.set_times(
|
||||
FileTimes::new().set_modified(std::time::UNIX_EPOCH - std::time::Duration::from_secs(1)),
|
||||
)?;
|
||||
let mut index = SearchIndex::open(":memory:")?;
|
||||
let report = index.index_folder(directory.path())?;
|
||||
assert_eq!(report.indexed, 1);
|
||||
assert_eq!(report.failures, 0);
|
||||
assert_eq!(index.search("archived-notes.txt", 10)?.len(), 1);
|
||||
assert_eq!(index.search("cancellation", 10)?.len(), 1);
|
||||
let passage = index.passage_batch(0, 1)?.remove(0);
|
||||
assert_eq!(
|
||||
index.file_signature(&passage.path)?,
|
||||
Some((content.len() as u64, -1_000_000_000))
|
||||
);
|
||||
assert_eq!(
|
||||
index.extracted_source_hash(&passage.path)?,
|
||||
Some(format!("{:x}", Sha256::digest(content.as_bytes())))
|
||||
);
|
||||
assert_eq!(
|
||||
index
|
||||
.source_passage(&passage.path, &passage.source, 8192)?
|
||||
.unwrap()
|
||||
.text,
|
||||
content
|
||||
);
|
||||
let rescan = index.index_folder(directory.path())?;
|
||||
assert_eq!(rescan.unchanged, 1);
|
||||
assert_eq!(rescan.indexed, 0);
|
||||
Ok(())
|
||||
}
|
||||
Reference in new issue
Block a user