mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-14 19:27:01 -04:00
commit
3f9b3b20cb
3 files changed
+45
-2
No files matched your search
@@ -9,6 +9,7 @@
|
||||
//! (`<2 hex>/<32 hex>.jpg` and the `<32 hex>.tmp…` temp files of [`DiskCache::put`], see
|
||||
//! [`is_cache_file`]): the directory may be shared with other files (e.g. a library opened on a
|
||||
//! photo folder that already has a `thumbs/` folder), and those are never touched (issue #98).
|
||||
//! A shard directory that is a symlink or junction is not followed (issue #1504).
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
@@ -65,7 +66,7 @@ impl DiskCache {
|
||||
|
||||
pub fn get(&self, key: Hash128) -> Option<Rgba8> {
|
||||
let p = self.path(key);
|
||||
let Ok(bytes) = std::fs::read(&p) else {
|
||||
let Some(bytes) = p.parent().filter(|d| is_real_dir(d)).and_then(|_| std::fs::read(&p).ok()) else {
|
||||
self.misses.fetch_add(1, Ordering::Relaxed);
|
||||
return None;
|
||||
};
|
||||
@@ -91,7 +92,7 @@ impl DiskCache {
|
||||
let Some(bytes) = encode_jpeg(img) else { return };
|
||||
let p = self.path(key);
|
||||
let Some(parent) = p.parent() else { return };
|
||||
if std::fs::create_dir_all(parent).is_err() {
|
||||
if std::fs::create_dir_all(parent).is_err() || !is_real_dir(parent) {
|
||||
return;
|
||||
}
|
||||
// Serialize the file replacement and its size accounting. A repeated render of
|
||||
@@ -124,6 +125,10 @@ impl DiskCache {
|
||||
if !is_shard_name(sub_name) {
|
||||
continue;
|
||||
}
|
||||
// a shard that is a symlink (or junction) leads outside the cache: never walk it
|
||||
if !is_real_dir(&sub.path()) {
|
||||
continue;
|
||||
}
|
||||
let Ok(files) = std::fs::read_dir(sub.path()) else { continue };
|
||||
for f in files.flatten() {
|
||||
let name = f.file_name();
|
||||
@@ -173,6 +178,11 @@ impl DiskCache {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `p` is a directory itself, not a symlink or Windows reparse point to one.
|
||||
fn is_real_dir(p: &Path) -> bool {
|
||||
std::fs::symlink_metadata(p).is_ok_and(|m| m.is_dir())
|
||||
}
|
||||
|
||||
fn is_lower_hex(s: &str) -> bool {
|
||||
s.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
|
||||
}
|
||||
|
||||
@@ -380,6 +380,33 @@ fn disk_cache_never_touches_foreign_files() {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// A shard directory that is a symlink to a folder outside the cache is neither listed, cleared,
|
||||
/// pruned, read nor written (issue #1504).
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn disk_cache_ignores_symlinked_shard_directories() {
|
||||
let dir = temp_dir("symlink-shard");
|
||||
let outside = temp_dir("symlink-outside");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
std::fs::create_dir_all(&outside).unwrap();
|
||||
let key = hash_bytes(&[9, 9]);
|
||||
let hex = key.to_string();
|
||||
let sentinel = outside.join(format!("{hex}.jpg"));
|
||||
std::fs::write(&sentinel, vec![7u8; 50_000]).unwrap();
|
||||
std::os::unix::fs::symlink(&outside, dir.join(&hex[..2])).unwrap();
|
||||
let d = DiskCache::new(&dir, 10);
|
||||
assert_eq!(d.size(), 0, "files behind the link are not the cache's");
|
||||
d.prune();
|
||||
assert!(sentinel.exists(), "prune followed the link");
|
||||
assert!(d.get(key).is_none(), "get read through the link");
|
||||
d.put(key, &gradient(16, 16, 1));
|
||||
assert_eq!(std::fs::metadata(&sentinel).unwrap().len(), 50_000, "put wrote through the link");
|
||||
d.clear();
|
||||
assert!(sentinel.exists(), "clear followed the link");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let _ = std::fs::remove_dir_all(&outside);
|
||||
}
|
||||
|
||||
/// [`JobPool::wait`] returns once a job finishes, which [`JobPool::try_recv`] then hands out, or
|
||||
/// after the timeout.
|
||||
#[test]
|
||||
|
||||
@@ -42,6 +42,12 @@
|
||||
with Z lenses and with F-mount lenses on the FTZ adapter, follows the in-body stabilisation's shift of the optical
|
||||
centre, and can be turned off under Lens Corrections. Vignetting correction from the file comes later.
|
||||
|
||||
### Thumbnail cache stays in its folder
|
||||
- The thumbnail cache no longer follows a shard folder (`thumbs/ab/`) that is a symlink or junction to somewhere
|
||||
else (issue #1504): trimming or clearing the cache can't delete files outside it, and thumbnails are never read
|
||||
from or written through such a link.
|
||||
## 2026-10-13
|
||||
|
||||
### Slider drags on large raws
|
||||
- Dragging a slider no longer re-renders the edited photo's filmstrip thumbnail on every frame; it follows once you
|
||||
let go. On a 64 MP raw the first of those renders decoded the whole file again, which held up the loupe's
|
||||
|
||||
Reference in new issue
Block a user