OXYGENHUB

Coverage / crates/oxy-core/src/strata.rs 85.0%

835 of 982 lines run, 156 of 271 functions · run #427 on main · run missed

1
//! Oxygen Strata (docs/stores.md): the storage a repository can be kept
2
//! on, as a trait, so that a bucket, a directory or a hub's disk all look
3
//! the same to the code that pushes to and fetches from them.
4
//!
5
//! A store has two halves. **Objects** are written once and never
6
//! change: packs and their indexes, named by content, which any store
7
//! can hold safely. **References** are the one mutable record (bookmarks,
8
//! tags, the policy), updated only by compare-and-swap on a version the
9
//! store hands out: an ETag on S3, a generation on GCS, a content hash on
10
//! a directory. Two writers can therefore share a store with no lock
11
//! server: the loser of a race reads again, merges, and tries again.
12
//!
13
//! A repository's own objects (`.oxy/objects`, see [`crate::store`]) are
14
//! kept on a store too, a directory one, with the same keys a bucket
15
//! would hold; a remote is a store as well. This module holds the trait,
16
//! a directory store, an in-memory store for tests, and the conformance
17
//! check every store must pass. Cloud stores live in a crate of their
18
//! own, because of what they pull in.
19
20
use std::collections::BTreeMap;
21
use std::fs;
22
use std::io::{self, Read, Seek, SeekFrom, Write};
23
use std::ops::Range;
24
use std::path::{Path, PathBuf};
25
use std::sync::Mutex;
26
use std::time::{Duration, Instant};
27
28
/// The version of a reference record as the store reports it: opaque,
29
/// compared only for equality, and only ever valid for the store that
30
/// issued it. Writing the same bytes again may keep the version (S3's
31
/// ETag and a directory's content hash are both of the content): a
32
/// compare-and-swap guards against losing a *different* record, which is
33
/// all it needs to.
34
#[derive(Clone, Debug, PartialEq, Eq)]
35
pub struct Version(pub String);
36
37
/// One key in a listing.
38
#[derive(Clone, Debug, PartialEq, Eq)]
39
pub struct Entry {
40
    pub key: String,
41
    pub size: u64,
42
    /// When the object was written, in wall milliseconds, where the store
43
    /// knows (garbage collection's grace period reads it).
44
    pub modified_ms: Option<u64>,
45
}
46
47
#[derive(Debug, thiserror::Error)]
48
pub enum Error {
49
    #[error("{0}: not in the store")]
50
    NotFound(String),
51
    #[error("{0}: already in the store")]
52
    Exists(String),
53
    #[error("{0}: changed since it was read")]
54
    Conflict(String),
55
    #[error("bad store key {0:?}: {1}")]
56
    Key(String, &'static str),
57
    #[error("{0}: busy (another writer holds it)")]
58
    Busy(String),
59
    #[error(transparent)]
60
    Io(#[from] io::Error),
61
    #[error("{0}")]
62
    Backend(String),
63
}
64
65
pub type Result<T> = std::result::Result<T, Error>;
66
67
/// Longest key a store accepts; S3 allows 1024 bytes.
68
pub const MAX_KEY_LEN: usize = 1024;
69
70
/// Refuse a key that is not a plain relative path: `/`-separated
71
/// segments of `[A-Za-z0-9._-]`, none empty, none `.` or `..`. The same
72
/// rule on every store, so a key written to one can be read from another,
73
/// and so a key can never escape a directory store's root.
74148846×
pub fn check_key(key: &str) -> Result<()> {
75148846×
    let bad = |why| Error::Key(key.to_string(), why);
76148846×
    if key.is_empty() {
771×
        return Err(bad("empty"));
78148845×
    }
79148845×
    if key.len() > MAX_KEY_LEN {
801×
        return Err(bad("longer than 1024 bytes"));
81148844×
    }
82408226×
    for seg in key.split('/') {
83408226×
        if seg.is_empty() {
844×
            return Err(bad(
854×
                "an empty segment (a leading, trailing or doubled slash)",
864×
            ));
87408222×
        }
88408222×
        if seg == "." || seg == ".." {
894×
            return Err(bad("a . or .. segment"));
90408218×
        }
91408218×
        if !seg
92408218×
            .bytes()
939746316×
            .all(|b| b.is_ascii_alphanumeric() || b == b'.' || b == b'_' || b == b'-')
94
        {
953×
            return Err(bad("a character outside A-Z a-z 0-9 . _ -"));
96408215×
        }
97
    }
98148833×
    Ok(())
99148846×
}
100
101
/// Where a repository's packs and references are kept. Every method is
102
/// synchronous and may be called from several threads at once.
103
pub trait Store: Send + Sync {
104
    /// A name for messages: the URL, or the directory.
105
    fn describe(&self) -> String;
106
107
    // ---- objects: written once, never changed
108
109
    fn has(&self, key: &str) -> Result<bool>;
110
    /// The whole object; `NotFound` when there is none.
111
    fn get(&self, key: &str) -> Result<Vec<u8>>;
112
    /// Bytes `range` of an object; an error when the range runs past its end.
113
    fn get_range(&self, key: &str, range: Range<u64>) -> Result<Vec<u8>>;
114
    /// Write an object that does not exist yet; `Exists` when it does, and
115
    /// then nothing is changed. Readers never see a partial object.
116
    fn put_new(&self, key: &str, bytes: &[u8]) -> Result<()>;
117
    /// Every key under `prefix` (a key prefix, not necessarily a whole
118
    /// segment), sorted, with sizes.
119
    fn list(&self, prefix: &str) -> Result<Vec<Entry>>;
120
    /// Remove an object; fine when it is already gone.
121
    fn delete(&self, key: &str) -> Result<()>;
122
    /// One object's listing entry without listing its neighbours; `None`
123
    /// when there is none.
1248×
    fn stat(&self, key: &str) -> Result<Option<Entry>> {
1258×
        Ok(self.list(key)?.into_iter().find(|e| e.key == key))
1268×
    }
127
    /// Write several objects that do not exist yet; one that does is left
128
    /// as it is, which is not an error (objects are named by content). A
129
    /// store that can batch its syncs does; this does them one at a time.
1302×
    fn put_many_new(&self, objects: &[(String, Vec<u8>)]) -> Result<()> {
1316×
        for (key, bytes) in objects {
1326×
            match self.put_new(key, bytes) {
1336×
                Ok(()) | Err(Error::Exists(_)) => {}
1340×
                Err(e) => return Err(e),
135
            }
136
        }
1372×
        Ok(())
1382×
    }
139
    /// `put_new` of a file's contents: a store on the same disk can move
140
    /// the file into place instead of copying it.
14139×
    fn put_new_file(&self, key: &str, path: &Path) -> Result<()> {
14239×
        self.put_new(key, &fs::read(path)?)
14339×
    }
144
145
    // ---- references: one small record, updated by compare-and-swap
146
147
    /// The record and its current version, or `None` when it was never
148
    /// written.
149
    fn read_ref(&self, key: &str) -> Result<Option<(Vec<u8>, Version)>>;
150
    /// Replace the record only if its version is still `expected`
151
    /// (`None`: only if it does not exist yet). `Conflict` otherwise, and
152
    /// then nothing is changed. Returns the new version.
153
    fn write_ref(&self, key: &str, bytes: &[u8], expected: Option<&Version>) -> Result<Version>;
154
}
155
156
/// A reference record's version on stores that have no native one: the
157
/// hash of its bytes, which two different records never share.
1582099×
fn content_version(bytes: &[u8]) -> Version {
1592099×
    Version(blake3::hash(bytes).to_hex().to_string())
1602099×
}
161
162536×
fn check_expected(key: &str, current: Option<&Version>, expected: Option<&Version>) -> Result<()> {
163536×
    if current == expected {
164428×
        Ok(())
165
    } else {
166108×
        Err(Error::Conflict(key.to_string()))
167
    }
168536×
}
169
170
// ---------------------------------------------------------------- directory
171
172
/// A store in a directory: each key a file under the root. Create-only
173
/// writes go through a hard link, which the file system refuses when the
174
/// name is taken, and a reference is updated under a lock file beside it.
175
/// Several processes on one machine can share it; it is also what the
176
/// tests and `oxy remote check` use.
177
pub struct LocalStore {
178
    root: PathBuf,
179
}
180
181
/// How long a reference's lock may be held before it counts as abandoned
182
/// by a process that died, and how long a writer waits for it.
183
const LOCK_STALE: Duration = Duration::from_secs(60);
184
const LOCK_WAIT: Duration = Duration::from_secs(10);
185
186
impl LocalStore {
187
    /// Open the store at `root`, creating the directory if needed.
1883468×
    pub fn open(root: impl Into<PathBuf>) -> Result<LocalStore> {
1893468×
        let root = root.into();
1903468×
        fs::create_dir_all(&root)?;
1913468×
        Ok(LocalStore { root })
1923468×
    }
193
1941×
    pub fn root(&self) -> &Path {
1951×
        &self.root
1961×
    }
197
198141873×
    fn path(&self, key: &str) -> Result<PathBuf> {
199141873×
        check_key(key)?;
200141871×
        Ok(self.root.join(key))
201141873×
    }
202
20315452×
    fn temp_beside(&self, path: &Path) -> PathBuf {
20415452×
        let mut nonce = [0u8; 6];
20515452×
        rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut nonce);
20615452×
        let name = path
20715452×
            .file_name()
20815452×
            .map(|n| n.to_string_lossy().into_owned())
20915452×
            .unwrap_or_default();
21015452×
        path.with_file_name(format!(".{name}.tmp-{}", hex::encode(nonce)))
21115452×
    }
212
21344210×
    fn list_dir(&self, dir: &Path, rel: &str, prefix: &str, out: &mut Vec<Entry>) -> Result<()> {
21444210×
        let entries = match fs::read_dir(dir) {
21544210×
            Ok(e) => e,
2160×
            Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(()),
2170×
            Err(e) => return Err(e.into()),
218
        };
219111184×
        for entry in entries {
220111184×
            let entry = entry?;
221111184×
            let name = entry.file_name().to_string_lossy().into_owned();
222
            // Temporary and lock files are not objects.
223111184×
            if name.starts_with('.') {
22434×
                continue;
225111150×
            }
226111150×
            let key = if rel.is_empty() {
22743425×
                name.clone()
228
            } else {
22967725×
                format!("{rel}/{name}")
230
            };
231111150×
            let ty = entry.file_type()?;
232111150×
            if ty.is_dir() {
233
                // Descend only where the prefix could still match.
23465605×
                let dir_prefix = format!("{key}/");
23565605×
                if prefix.starts_with(&dir_prefix) || dir_prefix.starts_with(prefix) {
23629572×
                    self.list_dir(&entry.path(), &key, prefix, out)?;
23736033×
                }
23845545×
            } else if ty.is_file() && key.starts_with(prefix) {
23931979×
                let meta = entry.metadata()?;
24031979×
                out.push(Entry {
24131979×
                    key,
24231979×
                    size: meta.len(),
24331979×
                    modified_ms: modified_ms(&meta),
24431979×
                });
24513566×
            }
246
        }
24744210×
        Ok(())
24844210×
    }
249
250
    /// Take `path.lock`, waiting for another writer, and removing a lock
251
    /// a dead process left behind.
252131×
    fn lock(&self, key: &str, path: &Path) -> Result<PathBuf> {
253131×
        let lock = path.with_file_name(format!(
254
            ".{}.lock",
255131×
            path.file_name()
256131×
                .map(|n| n.to_string_lossy().into_owned())
257131×
                .unwrap_or_default()
258
        ));
259131×
        let started = Instant::now();
260
        loop {
261132×
            match fs::OpenOptions::new()
262132×
                .write(true)
263132×
                .create_new(true)
264132×
                .open(&lock)
265
            {
266131×
                Ok(mut f) => {
267131×
                    let _ = writeln!(f, "{}", std::process::id());
268131×
                    return Ok(lock);
269
                }
2701×
                Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
2711×
                    if let Ok(m) = fs::metadata(&lock) {
2721×
                        let age = m.modified().ok().and_then(|t| t.elapsed().ok());
2731×
                        if age.is_some_and(|a| a > LOCK_STALE) {
2741×
                            let _ = fs::remove_file(&lock);
2751×
                            continue;
2760×
                        }
2770×
                    }
2780×
                    if started.elapsed() > LOCK_WAIT {
2790×
                        return Err(Error::Busy(key.to_string()));
2800×
                    }
2810×
                    std::thread::sleep(Duration::from_millis(10));
282
                }
2830×
                Err(e) => return Err(e.into()),
284
            }
285
        }
286131×
    }
287
}
288
289
/// A file's modification time as wall milliseconds.
29031993×
fn modified_ms(meta: &fs::Metadata) -> Option<u64> {
29131993×
    meta.modified()
29231993×
        .ok()
29331993×
        .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
29431993×
        .map(|d| d.as_millis() as u64)
29531993×
}
296
297
/// Put `tmp` at `path` only if `path` is free, as `fs::hard_link` does
298
/// (`tmp` may remain). Android forbids apps hard links in their own data
299
/// (oxygen#141): there a create-only rename does the same, atomically.
30015498×
fn place_new(tmp: &Path, path: &Path) -> io::Result<()> {
30115498×
    match fs::hard_link(tmp, path) {
3020×
        Err(e) if e.kind() == io::ErrorKind::PermissionDenied => rename_new(tmp, path),
30315498×
        r => r,
304
    }
30515498×
}
306
307
#[cfg(any(target_os = "linux", target_os = "android"))]
3082×
fn rename_new(tmp: &Path, path: &Path) -> io::Result<()> {
309
    use std::ffi::CString;
310
    use std::os::unix::ffi::OsStrExt;
3112×
    let from = CString::new(tmp.as_os_str().as_bytes())?;
3122×
    let to = CString::new(path.as_os_str().as_bytes())?;
3132×
    let r = unsafe {
3142×
        libc::renameat2(
315
            libc::AT_FDCWD,
3162×
            from.as_ptr(),
317
            libc::AT_FDCWD,
3182×
            to.as_ptr(),
3192×
            libc::RENAME_NOREPLACE as _,
320
        )
321
    };
3222×
    if r == 0 {
3231×
        Ok(())
324
    } else {
3251×
        Err(io::Error::last_os_error())
326
    }
3272×
}
328
329
#[cfg(not(any(target_os = "linux", target_os = "android")))]
330
fn rename_new(tmp: &Path, path: &Path) -> io::Result<()> {
331
    if path.exists() {
332
        return Err(io::ErrorKind::AlreadyExists.into());
333
    }
334
    fs::rename(tmp, path)
335
}
336
337
impl LocalStore {
338
    /// Link `tmp` into `path`, create-only; the directory is synced and
339
    /// `tmp` removed either way. `Exists` when the name is taken.
3409605×
    fn link_into_place(&self, key: &str, tmp: &Path, path: &Path) -> Result<()> {
3419605×
        let linked = place_new(tmp, path);
3429605×
        let _ = fs::remove_file(tmp);
3430×
        match linked {
344
            Ok(()) => {
3459605×
                if let Some(dir) = path.parent() {
3469605×
                    crate::durable::sync_dir(dir)?;
3470×
                }
3489605×
                Ok(())
349
            }
3500×
            Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
3510×
                Err(Error::Exists(key.to_string()))
352
            }
3530×
            Err(e) => Err(e.into()),
354
        }
3559605×
    }
356
357
    /// Create `path`'s directory, syncing the first directory that was
358
    /// made so its entry is durable too.
35915524×
    fn ensure_dir(&self, path: &Path) -> Result<()> {
36015524×
        let Some(dir) = path.parent() else {
3610×
            return Ok(());
362
        };
36315524×
        if dir.is_dir() {
3642300×
            return Ok(());
36513224×
        }
36613224×
        let mut made = dir.to_path_buf();
36714454×
        while let Some(parent) = made.parent() {
36814454×
            if parent.is_dir() || parent == self.root {
36913224×
                break;
3701230×
            }
3711230×
            made = parent.to_path_buf();
372
        }
37313224×
        fs::create_dir_all(dir)?;
37413224×
        if let Some(parent) = made.parent() {
37513224×
            crate::durable::sync_dir(parent)?;
3760×
        }
37713224×
        Ok(())
37815524×
    }
379
}
380
381
impl Store for LocalStore {
38286×
    fn describe(&self) -> String {
38386×
        self.root.display().to_string()
38486×
    }
385
38625×
    fn stat(&self, key: &str) -> Result<Option<Entry>> {
38725×
        match fs::metadata(self.path(key)?) {
38814×
            Ok(m) if m.is_file() => Ok(Some(Entry {
38914×
                key: key.to_string(),
39014×
                size: m.len(),
39114×
                modified_ms: modified_ms(&m),
39214×
            })),
3930×
            Ok(_) => Ok(None),
39411×
            Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
3950×
            Err(e) => Err(e.into()),
396
        }
39725×
    }
398
399
    /// Every file written and synced (on every core), then each linked
400
    /// into place and each directory synced once: durable when this
401
    /// returns, as one at a time would be, with far fewer waits.
4021612×
    fn put_many_new(&self, objects: &[(String, Vec<u8>)]) -> Result<()> {
403
        use rayon::prelude::*;
4041612×
        let mut todo: Vec<(PathBuf, PathBuf, &[u8])> = Vec::new();
4055847×
        for (key, bytes) in objects {
4065847×
            let path = self.path(key)?;
40711825×
            if path.exists() || todo.iter().any(|(p, ..)| *p == path) {
4080×
                continue;
4095847×
            }
4105847×
            self.ensure_dir(&path)?;
4115847×
            todo.push((path.clone(), self.temp_beside(&path), bytes));
412
        }
4131612×
        if todo.is_empty() {
4140×
            return Ok(());
4151612×
        }
4161612×
        crate::durable::step("write loose objects")?;
4171610×
        todo.par_iter()
4185834×
            .try_for_each(|(_, tmp, bytes)| -> io::Result<()> {
4195834×
                let mut f = fs::File::create(tmp)?;
4205834×
                f.write_all(bytes)?;
4215834×
                f.sync_all()
4225834×
            })?;
4231610×
        crate::durable::step("rename loose objects")?;
4241608×
        let mut dirs: std::collections::BTreeSet<PathBuf> = std::collections::BTreeSet::new();
4255821×
        for (path, tmp, _) in &todo {
4265821×
            let linked = place_new(tmp, path);
4275821×
            let _ = fs::remove_file(tmp);
4280×
            match linked {
429
                Ok(()) => {
4305821×
                    if let Some(d) = path.parent() {
4315821×
                        dirs.insert(d.to_path_buf());
4325821×
                    }
433
                }
4340×
                Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}
4350×
                Err(e) => return Err(e.into()),
436
            }
437
        }
4381608×
        dirs.par_iter()
4395780×
            .try_for_each(|d| crate::durable::sync_dir(d))?;
4401608×
        Ok(())
4411612×
    }
442
443
    /// The file is linked into place when it is on this file system,
444
    /// else copied; it is removed either way.
44572×
    fn put_new_file(&self, key: &str, file: &Path) -> Result<()> {
44672×
        let path = self.path(key)?;
44772×
        if path.exists() {
4480×
            let _ = fs::remove_file(file);
4490×
            return Err(Error::Exists(key.to_string()));
45072×
        }
45172×
        self.ensure_dir(&path)?;
45272×
        match place_new(file, &path) {
453
            Ok(()) => {
45472×
                let _ = fs::remove_file(file);
45572×
                if let Some(dir) = path.parent() {
45672×
                    crate::durable::sync_dir(dir)?;
4570×
                }
45872×
                Ok(())
459
            }
4600×
            Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
4610×
                let _ = fs::remove_file(file);
4620×
                Err(Error::Exists(key.to_string()))
463
            }
464
            Err(_) => {
465
                // Another file system: copy beside the name, then link.
4660×
                let tmp = self.temp_beside(&path);
4670×
                let copied = (|| {
4680×
                    let mut src = fs::File::open(file)?;
4690×
                    let mut dst = fs::File::create(&tmp)?;
4700×
                    io::copy(&mut src, &mut dst)?;
4710×
                    dst.sync_all()
472
                })();
4730×
                let _ = fs::remove_file(file);
4740×
                copied?;
4750×
                self.link_into_place(key, &tmp, &path)
476
            }
477
        }
47872×
    }
479
48034696×
    fn has(&self, key: &str) -> Result<bool> {
48134696×
        Ok(self.path(key)?.is_file())
48234696×
    }
483
48471620×
    fn get(&self, key: &str) -> Result<Vec<u8>> {
48571620×
        let path = self.path(key)?;
48671619×
        match fs::read(&path) {
48763207×
            Ok(b) => Ok(b),
4888412×
            Err(e) if e.kind() == io::ErrorKind::NotFound => Err(Error::NotFound(key.to_string())),
4890×
            Err(e) => Err(e.into()),
490
        }
49171620×
    }
492
49316211×
    fn get_range(&self, key: &str, range: Range<u64>) -> Result<Vec<u8>> {
49416211×
        let path = self.path(key)?;
49516211×
        let mut f = match fs::File::open(&path) {
49616127×
            Ok(f) => f,
49784×
            Err(e) if e.kind() == io::ErrorKind::NotFound => {
49884×
                return Err(Error::NotFound(key.to_string()))
499
            }
5000×
            Err(e) => return Err(e.into()),
501
        };
50216127×
        let len = f.metadata()?.len();
50316127×
        if range.end > len || range.start > range.end {
5045×
            return Err(Error::Backend(format!(
5055×
                "{key}: bytes {}..{} asked of an object of {len} bytes",
5065×
                range.start, range.end
5075×
            )));
50816122×
        }
50916122×
        f.seek(SeekFrom::Start(range.start))?;
51016122×
        let mut out = vec![0u8; (range.end - range.start) as usize];
51116122×
        f.read_exact(&mut out)?;
51216122×
        Ok(out)
51316211×
    }
514
5159628×
    fn put_new(&self, key: &str, bytes: &[u8]) -> Result<()> {
5169628×
        let path = self.path(key)?;
5179627×
        if path.exists() {
51822×
            return Err(Error::Exists(key.to_string()));
5199605×
        }
5209605×
        self.ensure_dir(&path)?;
521
        // Written beside its name, then linked into it: the link fails if
522
        // the name was taken meanwhile, and a reader never sees a part.
5239605×
        let tmp = self.temp_beside(&path);
5249605×
        let written = (|| {
5259605×
            let mut f = fs::File::create(&tmp)?;
5269605×
            f.write_all(bytes)?;
5279605×
            f.sync_all()
528
        })();
5299605×
        if let Err(e) = written {
5300×
            let _ = fs::remove_file(&tmp);
5310×
            return Err(e.into());
5329605×
        }
5339605×
        self.link_into_place(key, &tmp, &path)
5349628×
    }
535
53614638×
    fn list(&self, prefix: &str) -> Result<Vec<Entry>> {
53714638×
        let mut out = Vec::new();
53814638×
        self.list_dir(&self.root, "", prefix, &mut out)?;
539170935×
        out.sort_by(|a, b| a.key.cmp(&b.key));
54014638×
        Ok(out)
54114638×
    }
542
5432782×
    fn delete(&self, key: &str) -> Result<()> {
5442782×
        let path = self.path(key)?;
5452782×
        match fs::remove_file(&path) {
5461770×
            Ok(()) => {}
5471012×
            Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(()),
5480×
            Err(e) => return Err(e.into()),
549
        }
550
        // A directory left empty goes too, up to the root, so the store
551
        // holds nothing but its objects.
5521770×
        let mut dir = path.parent();
5532250×
        while let Some(d) = dir {
5542250×
            if d == self.root || fs::remove_dir(d).is_err() {
5551770×
                break;
556480×
            }
557480×
            dir = d.parent();
558
        }
5591770×
        Ok(())
5602782×
    }
561
562829×
    fn read_ref(&self, key: &str) -> Result<Option<(Vec<u8>, Version)>> {
563829×
        let path = self.path(key)?;
564829×
        match fs::read(&path) {
565749×
            Ok(b) => {
566749×
                let v = content_version(&b);
567749×
                Ok(Some((b, v)))
568
            }
56980×
            Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
5700×
            Err(e) => Err(e.into()),
571
        }
572829×
    }
573
574131×
    fn write_ref(&self, key: &str, bytes: &[u8], expected: Option<&Version>) -> Result<Version> {
575131×
        let path = self.path(key)?;
576131×
        if let Some(dir) = path.parent() {
577131×
            fs::create_dir_all(dir)?;
5780×
        }
579131×
        let lock = self.lock(key, &path)?;
580131×
        let result = (|| {
581131×
            let current = self.read_ref(key)?.map(|(_, v)| v);
582131×
            check_expected(key, current.as_ref(), expected)?;
583115×
            crate::durable::write_atomic(&path, bytes)?;
584115×
            Ok(content_version(bytes))
585
        })();
586131×
        let _ = fs::remove_file(&lock);
587131×
        result
588131×
    }
589
}
590
591
// ---------------------------------------------------------------- memory
592
593
/// A store in memory, for tests: the conditional writes behave as the
594
/// cloud stores' do.
595
#[derive(Default)]
596
pub struct MemoryStore {
597
    objects: Mutex<BTreeMap<String, (Vec<u8>, u64)>>,
598
}
599
6002001×
fn now_ms() -> u64 {
6012001×
    std::time::SystemTime::now()
6022001×
        .duration_since(std::time::UNIX_EPOCH)
6032001×
        .map(|d| d.as_millis() as u64)
6042001×
        .unwrap_or(0)
6052001×
}
606
607
impl MemoryStore {
60824×
    pub fn new() -> MemoryStore {
60924×
        MemoryStore::default()
61024×
    }
611
6127615×
    fn map(&self) -> std::sync::MutexGuard<'_, BTreeMap<String, (Vec<u8>, u64)>> {
6137615×
        self.objects.lock().unwrap_or_else(|p| p.into_inner())
6147615×
    }
615
616
    /// Pretend `key` was written `age` ago (for grace-period tests).
6170×
    pub fn age(&self, key: &str, age: Duration) {
6180×
        if let Some(entry) = self.map().get_mut(key) {
6190×
            entry.1 = now_ms().saturating_sub(age.as_millis() as u64);
6200×
        }
6210×
    }
622
}
623
624
impl Store for MemoryStore {
6251×
    fn describe(&self) -> String {
6261×
        "memory".into()
6271×
    }
628
6291996×
    fn has(&self, key: &str) -> Result<bool> {
6301996×
        check_key(key)?;
6311996×
        Ok(self.map().contains_key(key))
6321996×
    }
633
6342116×
    fn get(&self, key: &str) -> Result<Vec<u8>> {
6352116×
        check_key(key)?;
6362116×
        self.map()
6372116×
            .get(key)
6382116×
            .map(|(b, _)| b.clone())
6392116×
            .ok_or_else(|| Error::NotFound(key.to_string()))
6402116×
    }
641
6421212×
    fn get_range(&self, key: &str, range: Range<u64>) -> Result<Vec<u8>> {
6431212×
        let all = self.get(key)?;
6441131×
        let len = all.len() as u64;
6451131×
        if range.end > len || range.start > range.end {
6461×
            return Err(Error::Backend(format!(
6471×
                "{key}: bytes {}..{} asked of an object of {len} bytes",
6481×
                range.start, range.end
6491×
            )));
6501130×
        }
6511130×
        Ok(all[range.start as usize..range.end as usize].to_vec())
6521212×
    }
653
6541691×
    fn put_new(&self, key: &str, bytes: &[u8]) -> Result<()> {
6551691×
        check_key(key)?;
6561691×
        let mut map = self.map();
6571691×
        if map.contains_key(key) {
6583×
            return Err(Error::Exists(key.to_string()));
6591688×
        }
6601688×
        map.insert(key.to_string(), (bytes.to_vec(), now_ms()));
6611688×
        Ok(())
6621691×
    }
663
664656×
    fn list(&self, prefix: &str) -> Result<Vec<Entry>> {
665656×
        Ok(self
666656×
            .map()
667656×
            .range(prefix.to_string()..)
6683918×
            .take_while(|(k, _)| k.starts_with(prefix))
669656×
            .map(|(k, (v, t))| Entry {
6703566×
                key: k.clone(),
6713566×
                size: v.len() as u64,
6723566×
                modified_ms: Some(*t),
6733566×
            })
674656×
            .collect())
675656×
    }
676
677160×
    fn delete(&self, key: &str) -> Result<()> {
678160×
        check_key(key)?;
679160×
        self.map().remove(key);
680160×
        Ok(())
681160×
    }
682
683591×
    fn read_ref(&self, key: &str) -> Result<Option<(Vec<u8>, Version)>> {
684591×
        check_key(key)?;
685591×
        Ok(self
686591×
            .map()
687591×
            .get(key)
688591×
            .map(|(b, _)| (b.clone(), content_version(b))))
689591×
    }
690
691405×
    fn write_ref(&self, key: &str, bytes: &[u8], expected: Option<&Version>) -> Result<Version> {
692405×
        check_key(key)?;
693405×
        let mut map = self.map();
694405×
        let current = map.get(key).map(|(b, _)| content_version(b));
695405×
        check_expected(key, current.as_ref(), expected)?;
696313×
        map.insert(key.to_string(), (bytes.to_vec(), now_ms()));
697313×
        Ok(content_version(bytes))
698405×
    }
699
}
700
701
// ---------------------------------------------------------------- caching
702
703
/// A store in front of another (oxygen#68): objects are read through a
704
/// local directory and written through it, to the remote first, so the
705
/// remote always holds everything and the cache can be dropped at any
706
/// time. Objects never change, so a cached one is always right (and every
707
/// read is hashed again above this layer). The cache is bounded: past its
708
/// limit the objects used least recently are removed. An object larger
709
/// than an eighth of the limit is not cached; it is read by range from
710
/// the remote. References are never cached: they are read from and
711
/// written to the remote every time, so compare-and-swap is the remote's.
712
pub struct CachingStore {
713
    cache: LocalStore,
714
    remote: std::sync::Arc<dyn Store>,
715
    limit: u64,
716
    /// Bytes in the cache, counted on first need and kept up to date.
717
    used: Mutex<Option<u64>>,
718
}
719
720
/// Where a caching store keeps the ranges it read of objects too large to
721
/// keep whole, and those objects' sizes: apart from every object key.
722
const RANGES: &str = "cached-range";
723
const SIZES: &str = "cached-size";
724
725
/// How stale a cached object's use time may get before a read renews it.
726
const TOUCH_AFTER: Duration = Duration::from_secs(60);
727
728
impl CachingStore {
729
    /// `remote` behind a cache in the directory `dir` of at most `limit`
730
    /// bytes.
7318×
    pub fn open(
7328×
        remote: std::sync::Arc<dyn Store>,
7338×
        dir: impl Into<PathBuf>,
7348×
        limit: u64,
7358×
    ) -> Result<CachingStore> {
736
        Ok(CachingStore {
7378×
            cache: LocalStore::open(dir)?,
7388×
            remote,
7398×
            limit,
7408×
            used: Mutex::new(None),
741
        })
7428×
    }
743
7440×
    pub fn remote(&self) -> &std::sync::Arc<dyn Store> {
7450×
        &self.remote
7460×
    }
747
748
    /// Bytes the cache holds now.
74923×
    pub fn cached_bytes(&self) -> Result<u64> {
75023×
        let mut used = self.used.lock().unwrap_or_else(|p| p.into_inner());
75123×
        if used.is_none() {
7521×
            *used = Some(self.cache.list("")?.iter().map(|e| e.size).sum());
75322×
        }
75423×
        Ok(used.unwrap_or(0))
75523×
    }
756
757
    /// Whether an object of `size` bytes is kept in the cache at all.
75868×
    fn cacheable(&self, size: u64) -> bool {
75968×
        size <= self.limit / 8
76068×
    }
761
762
    /// Keep a copy of an object the remote holds.
76366×
    fn keep(&self, key: &str, bytes: &[u8]) -> Result<()> {
76466×
        if !self.cacheable(bytes.len() as u64) {
7651×
            return Ok(());
76665×
        }
76765×
        match self.cache.put_new(key, bytes) {
76865×
            Ok(()) => self.added(bytes.len() as u64),
7690×
            Err(Error::Exists(_)) => Ok(()),
7700×
            Err(e) => Err(e),
771
        }
77266×
    }
773
774
    /// Count `n` new bytes, and remove the least recently used objects
775
    /// once the cache is past its limit, down to nine tenths of it.
77665×
    fn added(&self, n: u64) -> Result<()> {
77765×
        let total = {
77865×
            let mut used = self.used.lock().unwrap_or_else(|p| p.into_inner());
77965×
            let total = match *used {
78060×
                Some(u) => u + n,
781
                // Counted now, with the new object already in place.
7825×
                None => self.cache.list("")?.iter().map(|e| e.size).sum(),
783
            };
78465×
            *used = Some(total);
78565×
            total
786
        };
78765×
        if total <= self.limit {
78834×
            return Ok(());
78931×
        }
79031×
        let mut entries = self.cache.list("")?;
791650×
        entries.sort_by_key(|e| e.modified_ms.unwrap_or(0));
79231×
        let mut left: u64 = entries.iter().map(|e| e.size).sum();
79331×
        let target = self.limit / 10 * 9;
79462×
        for e in entries {
79562×
            if left <= target {
79631×
                break;
79731×
            }
79831×
            self.cache.delete(&e.key)?;
79931×
            left -= e.size;
800
        }
80131×
        *self.used.lock().unwrap_or_else(|p| p.into_inner()) = Some(left);
80231×
        Ok(())
80365×
    }
804
805
    /// An object's size: kept on disk once the remote has said it (objects
806
    /// never change), so it is asked once. `None` when there is no object.
8072×
    fn size_of(&self, key: &str) -> Result<Option<u64>> {
8082×
        let skey = format!("{SIZES}/{key}");
8092×
        if let Some(n) = self
8102×
            .cache
8112×
            .get(&skey)
8122×
            .ok()
8132×
            .and_then(|b| String::from_utf8(b).ok())
8142×
            .and_then(|t| t.parse().ok())
815
        {
8160×
            return Ok(Some(n));
8172×
        }
8182×
        let size = self.remote.stat(key)?.map(|e| e.size);
8192×
        if let Some(n) = size {
8202×
            self.keep(&skey, n.to_string().as_bytes())?;
8210×
        }
8222×
        Ok(size)
8232×
    }
824
825
    /// Mark a cached object used now, at most once a minute.
82632×
    fn touch(&self, key: &str) {
82732×
        let Ok(path) = self.cache.path(key) else {
8280×
            return;
829
        };
83032×
        let stale = fs::metadata(&path)
83132×
            .and_then(|m| m.modified())
83232×
            .ok()
83332×
            .and_then(|t| t.elapsed().ok())
83432×
            .is_some_and(|age| age > TOUCH_AFTER);
83532×
        if stale {
8360×
            if let Ok(f) = fs::File::options().append(true).open(&path) {
8370×
                let _ = f.set_modified(std::time::SystemTime::now());
8380×
            }
83932×
        }
84032×
    }
841
}
842
843
impl Store for CachingStore {
8441×
    fn describe(&self) -> String {
8451×
        format!(
846
            "{} (cached in {})",
8471×
            self.remote.describe(),
8481×
            self.cache.root().display()
849
        )
8501×
    }
851
85217×
    fn has(&self, key: &str) -> Result<bool> {
85317×
        Ok(self.cache.has(key)? || self.remote.has(key)?)
85417×
    }
855
85657×
    fn get(&self, key: &str) -> Result<Vec<u8>> {
85757×
        match self.cache.get(key) {
85829×
            Ok(bytes) => {
85929×
                self.touch(key);
86029×
                return Ok(bytes);
861
            }
862
            // Not cached, or removed by another process's eviction meanwhile.
86328×
            Err(Error::NotFound(_)) => {}
8640×
            Err(e) => return Err(e),
865
        }
86628×
        let bytes = self.remote.get(key)?;
86727×
        self.keep(key, &bytes)?;
86827×
        Ok(bytes)
86957×
    }
870
8716×
    fn get_range(&self, key: &str, range: Range<u64>) -> Result<Vec<u8>> {
8726×
        match self.cache.get_range(key, range.clone()) {
8732×
            Ok(bytes) => {
8742×
                self.touch(key);
8752×
                return Ok(bytes);
876
            }
8773×
            Err(Error::NotFound(_)) => {}
8781×
            Err(e) if self.cache.has(key)? => return Err(e),
8790×
            Err(_) => {}
880
        }
881
        // A large object's ranges are kept as they are read (packs never
882
        // change, oxygenhub#234): a server opens the store again for every
883
        // request, so only what is on disk carries over.
8843×
        let rkey = format!("{RANGES}/{key}/{}-{}", range.start, range.end);
8853×
        if let Ok(bytes) = self.cache.get(&rkey) {
8861×
            self.touch(&rkey);
8871×
            return Ok(bytes);
8882×
        }
889
        // A small object is fetched whole and kept; a large one is read by
890
        // range and the range kept. Its size is kept too, so the remote is
891
        // asked it once.
8922×
        match self.size_of(key)? {
8932×
            Some(size) if self.cacheable(size) => {
8941×
                let bytes = self.get(key)?;
8951×
                let len = bytes.len() as u64;
8961×
                if range.end > len || range.start > range.end {
8970×
                    return Err(Error::Backend(format!(
8980×
                        "{key}: bytes {}..{} asked of an object of {len} bytes",
8990×
                        range.start, range.end
9000×
                    )));
9011×
                }
9021×
                Ok(bytes[range.start as usize..range.end as usize].to_vec())
903
            }
904
            Some(_) => {
9051×
                let bytes = self.remote.get_range(key, range)?;
9061×
                self.keep(&rkey, &bytes)?;
9071×
                Ok(bytes)
908
            }
9090×
            None => Err(Error::NotFound(key.to_string())),
910
        }
9116×
    }
912
91329×
    fn put_new(&self, key: &str, bytes: &[u8]) -> Result<()> {
91429×
        self.remote.put_new(key, bytes)?;
91528×
        self.keep(key, bytes)
91629×
    }
917
9182×
    fn put_many_new(&self, objects: &[(String, Vec<u8>)]) -> Result<()> {
9192×
        self.remote.put_many_new(objects)?;
9208×
        for (key, bytes) in objects {
9218×
            self.keep(key, bytes)?;
922
        }
9232×
        Ok(())
9242×
    }
925
9260×
    fn put_new_file(&self, key: &str, path: &Path) -> Result<()> {
9270×
        let size = fs::metadata(path)?.len();
9280×
        let copy = if self.cacheable(size) {
9290×
            Some(fs::read(path)?)
930
        } else {
9310×
            None
932
        };
9330×
        self.remote.put_new_file(key, path)?;
9340×
        match copy {
9350×
            Some(bytes) => self.keep(key, &bytes),
9360×
            None => Ok(()),
937
        }
9380×
    }
939
94015×
    fn list(&self, prefix: &str) -> Result<Vec<Entry>> {
94115×
        self.remote.list(prefix)
94215×
    }
943
9442×
    fn stat(&self, key: &str) -> Result<Option<Entry>> {
9452×
        self.remote.stat(key)
9462×
    }
947
9485×
    fn delete(&self, key: &str) -> Result<()> {
9495×
        self.remote.delete(key)?;
950
        // Its kept ranges and size go with it.
9515×
        let mut extra = self.cache.list(&format!("{RANGES}/{key}/"))?;
9525×
        extra.extend(self.cache.stat(&format!("{SIZES}/{key}"))?);
9535×
        for e in extra {
9542×
            self.cache.delete(&e.key)?;
9552×
            let mut used = self.used.lock().unwrap_or_else(|p| p.into_inner());
9562×
            if let Some(u) = used.as_mut() {
9572×
                *u = u.saturating_sub(e.size);
9582×
            }
959
        }
9605×
        let size = self.cache.stat(key)?.map(|e| e.size);
9615×
        self.cache.delete(key)?;
9625×
        if let Some(n) = size {
9632×
            let mut used = self.used.lock().unwrap_or_else(|p| p.into_inner());
9642×
            if let Some(u) = used.as_mut() {
9652×
                *u = u.saturating_sub(n);
9662×
            }
9673×
        }
9685×
        Ok(())
9695×
    }
970
97111×
    fn read_ref(&self, key: &str) -> Result<Option<(Vec<u8>, Version)>> {
97211×
        self.remote.read_ref(key)
97311×
    }
974
9759×
    fn write_ref(&self, key: &str, bytes: &[u8], expected: Option<&Version>) -> Result<Version> {
9769×
        self.remote.write_ref(key, bytes, expected)
9779×
    }
978
}
979
980
// ---------------------------------------------------------------- conformance
981
982
/// Exercise a store under `prefix` (which must be free, and is left
983
/// empty) and report what passed: object round trips, ranges, create-only
984
/// refusal, listing, deletion, and compare-and-swap on a reference. Every
985
/// store runs this in its tests, and `oxy remote check` runs it against a
986
/// remote before trusting it with a repository.
9876×
pub fn conformance(store: &dyn Store, prefix: &str) -> Result<Vec<&'static str>> {
9886×
    let mut passed = Vec::new();
989144×
    let key = |name: &str| format!("{prefix}/{name}");
9906×
    let fail = |what: &str| Error::Backend(format!("{}: {what}", store.describe()));
991132×
    let expect = |ok: bool, what: &str| if ok { Ok(()) } else { Err(fail(what)) };
992
9936×
    expect(
9946×
        store.list(prefix)?.is_empty(),
9956×
        "the probe prefix is not empty",
9960×
    )?;
9976×
    expect(
9986×
        !store.has(&key("a"))?,
9996×
        "has() of a missing object says true",
10000×
    )?;
10016×
    expect(
10026×
        matches!(store.get(&key("a")), Err(Error::NotFound(_))),
10036×
        "get() of a missing object is not NotFound",
10040×
    )?;
10056×
    passed.push("missing objects are reported missing");
1006
10071572864×
    let a: Vec<u8> = (0..256u32 * 1024).map(|i| (i % 251) as u8).collect();
10086×
    store.put_new(&key("a"), &a)?;
10096×
    expect(store.has(&key("a"))?, "has() after put_new says false")?;
10106×
    expect(
10116×
        store.get(&key("a"))? == a,
10126×
        "get() returns other bytes than put_new wrote",
10130×
    )?;
10146×
    passed.push("an object round-trips");
1015
10166×
    expect(
10176×
        store.get_range(&key("a"), 1000..1500)? == a[1000..1500],
10186×
        "get_range() returns the wrong bytes",
10190×
    )?;
10206×
    expect(
10216×
        store.get_range(&key("a"), 0..0)?.is_empty(),
10226×
        "an empty range is not empty",
10230×
    )?;
10246×
    expect(
10256×
        store.get_range(&key("a"), 0..a.len() as u64 + 1).is_err(),
10266×
        "a range past the end is not refused",
10276×
    )?;
10286×
    passed.push("ranges read correctly and are bounded");
1029
10306×
    expect(
10316×
        matches!(store.put_new(&key("a"), b"other"), Err(Error::Exists(_))),
10326×
        "put_new() over an existing object is not refused",
10330×
    )?;
10346×
    expect(
10356×
        store.get(&key("a"))? == a,
10366×
        "a refused put_new() changed the object",
10370×
    )?;
10386×
    passed.push("objects are written once");
1039
10406×
    store.put_new(&key("dir/b"), b"bb")?;
10416×
    let listed: Vec<(String, u64)> = store
10426×
        .list(prefix)?
10436×
        .into_iter()
104412×
        .map(|e| (e.key, e.size))
10456×
        .collect();
10466×
    expect(
10476×
        listed == vec![(key("a"), a.len() as u64), (key("dir/b"), 2)],
10486×
        "list() does not return the two objects, sorted, with their sizes",
10496×
    )?;
10506×
    expect(
10516×
        store
10526×
            .stat(&key("dir/b"))?
10536×
            .is_some_and(|e| e.key == key("dir/b") && e.size == 2)
10546×
            && store.stat(&key("dir/c"))?.is_none(),
10556×
        "stat() does not describe one object, or describes a missing one",
10560×
    )?;
10576×
    expect(
10586×
        store
10596×
            .list(&key("dir/"))?
10606×
            .into_iter()
10616×
            .map(|e| (e.key, e.size))
10626×
            .collect::<Vec<_>>()
10636×
            == vec![(key("dir/b"), 2)],
10646×
        "list() of a sub-prefix is wrong",
10650×
    )?;
10666×
    expect(
10676×
        store.list(&key("nothing/"))?.is_empty(),
10686×
        "list() of an unused prefix is not empty",
10690×
    )?;
10706×
    passed.push("listing by prefix");
1071
10726×
    store.delete(&key("dir/b"))?;
10736×
    expect(
10746×
        !store.has(&key("dir/b"))?,
10756×
        "an object is still there after delete()",
10760×
    )?;
10776×
    store.delete(&key("dir/b"))?;
10786×
    passed.push("deletion, and deleting again, succeed");
1079
10806×
    let r = key("refs");
10816×
    expect(
10826×
        store.read_ref(&r)?.is_none(),
10836×
        "an unwritten reference reads as something",
10840×
    )?;
10856×
    let v1 = store.write_ref(&r, b"one", None)?;
10866×
    expect(
10876×
        matches!(store.write_ref(&r, b"again", None), Err(Error::Conflict(_))),
10886×
        "a second create-only write of the reference is not refused",
10890×
    )?;
10906×
    let (bytes, seen) = store
10916×
        .read_ref(&r)?
10926×
        .ok_or_else(|| fail("the reference vanished"))?;
10936×
    expect(
10946×
        bytes == b"one" && seen == v1,
10956×
        "the reference reads back wrong",
10960×
    )?;
10976×
    let v2 = store.write_ref(&r, b"two", Some(&v1))?;
10986×
    expect(v2 != v1, "the version did not change on write")?;
10996×
    expect(
11000×
        matches!(
11016×
            store.write_ref(&r, b"three", Some(&v1)),
1102
            Err(Error::Conflict(_))
1103
        ),
11046×
        "a write with a stale version is not refused",
11050×
    )?;
11066×
    let (bytes, seen) = store
11076×
        .read_ref(&r)?
11086×
        .ok_or_else(|| fail("the reference vanished"))?;
11096×
    expect(
11106×
        bytes == b"two" && seen == v2,
11116×
        "a refused write changed the reference",
11120×
    )?;
11136×
    passed.push("references update by compare-and-swap");
1114
11156×
    store.delete(&key("a"))?;
11166×
    store.delete(&r)?;
11176×
    expect(
11186×
        store.list(prefix)?.is_empty(),
11196×
        "the probe prefix is not empty after cleanup",
11200×
    )?;
11216×
    passed.push("cleanup");
11226×
    Ok(passed)
11236×
}
1124
1125
#[cfg(test)]
1126
mod tests {
1127
    use super::*;
1128
    use std::sync::Arc;
1129
1130
    /// Where hard links are refused (Android, oxygen#141), objects are
1131
    /// renamed into place, still never over a name that is taken.
1132
    #[test]
11331×
    fn a_create_only_rename_never_replaces() {
11341×
        let dir = tempfile::tempdir().unwrap();
11351×
        let (a, b, to) = (
11361×
            dir.path().join("a"),
11371×
            dir.path().join("b"),
11381×
            dir.path().join("to"),
11391×
        );
11401×
        fs::write(&a, b"first").unwrap();
11411×
        fs::write(&b, b"second").unwrap();
11421×
        rename_new(&a, &to).unwrap();
11431×
        assert_eq!(
11441×
            rename_new(&b, &to).unwrap_err().kind(),
1145
            io::ErrorKind::AlreadyExists
1146
        );
11471×
        assert_eq!(fs::read(&to).unwrap(), b"first");
11481×
        assert!(!a.exists() && b.exists());
11491×
    }
1150
1151
    #[test]
11521×
    fn keys_are_plain_relative_paths() {
11533×
        for ok in ["a", "packs/ab.pack", "a-b_c.d/e"] {
11543×
            check_key(ok).unwrap();
11553×
        }
115610×
        for bad in [
11571×
            "", "/a", "a/", "a//b", "..", "a/../b", "./a", "a b", "a\\b", "ä",
11581×
        ] {
115910×
            assert!(check_key(bad).is_err(), "{bad:?}");
1160
        }
11611×
        assert!(check_key(&"x".repeat(MAX_KEY_LEN + 1)).is_err());
11621×
    }
1163
1164
    #[test]
11651×
    fn a_caching_store_conforms_and_reads_and_writes_through() {
11661×
        let dir = tempfile::tempdir().unwrap();
11671×
        let remote: Arc<dyn Store> = Arc::new(MemoryStore::new());
11681×
        let store = CachingStore::open(remote.clone(), dir.path().join("cache"), 8 << 20).unwrap();
11691×
        assert_eq!(conformance(&store, "probe").unwrap().len(), 8);
11701×
        assert_eq!(
11711×
            store.cached_bytes().unwrap(),
1172
            0,
1173
            "the probe left nothing cached"
1174
        );
1175
1176
        // Written through: the remote has it, and so does the cache.
11771×
        store.put_new("loose/aa", b"written").unwrap();
11781×
        assert_eq!(remote.get("loose/aa").unwrap(), b"written");
11791×
        assert!(store.cache.has("loose/aa").unwrap());
1180
        // Read through: an object only the remote has is kept on first read.
11811×
        remote.put_new("loose/bb", b"remote only").unwrap();
11821×
        assert!(!store.cache.has("loose/bb").unwrap());
11831×
        assert_eq!(store.get_range("loose/bb", 0..6).unwrap(), b"remote");
11841×
        assert!(store.cache.has("loose/bb").unwrap());
1185
        // A cached object read after the remote lost it is still served.
11861×
        remote.delete("loose/bb").unwrap();
11871×
        assert_eq!(store.get("loose/bb").unwrap(), b"remote only");
1188
        // References are always the remote's.
11891×
        let v = remote.write_ref("refs", b"one", None).unwrap();
11901×
        assert_eq!(
11911×
            store.read_ref("refs").unwrap(),
11921×
            Some((b"one".to_vec(), v.clone()))
1193
        );
11941×
        remote.write_ref("refs", b"two", Some(&v)).unwrap();
11951×
        assert_eq!(store.read_ref("refs").unwrap().unwrap().0, b"two");
11961×
        assert!(matches!(
11971×
            store.write_ref("refs", b"three", Some(&v)),
1198
            Err(Error::Conflict(_))
1199
        ));
12001×
        assert!(!store.cache.has("refs").unwrap());
12011×
    }
1202
1203
    /// A remote that answers nothing: what is read through it was cached.
1204
    struct Unreachable;
1205
1206
    impl Store for Unreachable {
12070×
        fn describe(&self) -> String {
12080×
            "unreachable".into()
12090×
        }
12100×
        fn has(&self, _: &str) -> Result<bool> {
12110×
            Err(Error::Backend("asked the remote".into()))
12120×
        }
12130×
        fn get(&self, _: &str) -> Result<Vec<u8>> {
12140×
            Err(Error::Backend("asked the remote".into()))
12150×
        }
12160×
        fn get_range(&self, _: &str, _: Range<u64>) -> Result<Vec<u8>> {
12170×
            Err(Error::Backend("asked the remote".into()))
12180×
        }
12190×
        fn put_new(&self, _: &str, _: &[u8]) -> Result<()> {
12200×
            Err(Error::Backend("asked the remote".into()))
12210×
        }
12220×
        fn list(&self, _: &str) -> Result<Vec<Entry>> {
12230×
            Err(Error::Backend("asked the remote".into()))
12240×
        }
12250×
        fn delete(&self, _: &str) -> Result<()> {
12260×
            Err(Error::Backend("asked the remote".into()))
12270×
        }
12280×
        fn stat(&self, _: &str) -> Result<Option<Entry>> {
12290×
            Err(Error::Backend("asked the remote".into()))
12300×
        }
12310×
        fn read_ref(&self, _: &str) -> Result<Option<(Vec<u8>, Version)>> {
12320×
            Err(Error::Backend("asked the remote".into()))
12330×
        }
12340×
        fn write_ref(&self, _: &str, _: &[u8], _: Option<&Version>) -> Result<Version> {
12350×
            Err(Error::Backend("asked the remote".into()))
12360×
        }
1237
    }
1238
1239
    #[test]
12401×
    fn a_caching_store_stays_within_its_limit() {
12411×
        let dir = tempfile::tempdir().unwrap();
12421×
        let remote: Arc<dyn Store> = Arc::new(MemoryStore::new());
12431×
        let store = CachingStore::open(remote.clone(), dir.path().join("cache"), 8000).unwrap();
124420×
        for i in 0..20 {
124520×
            store
124620×
                .put_new(&format!("loose/{i:02}"), &[i as u8; 900])
124720×
                .unwrap();
124820×
            assert!(store.cached_bytes().unwrap() <= 8000);
1249
        }
12501×
        assert_eq!(
12511×
            remote.list("loose/").unwrap().len(),
1252
            20,
1253
            "the remote holds everything"
1254
        );
12551×
        let cached = store.cache.list("loose/").unwrap();
12561×
        assert!(cached.len() < 20 && cached.len() >= 7, "{}", cached.len());
12571×
        assert!(
12588×
            cached.iter().any(|e| e.key == "loose/19"),
1259
            "the newest is kept"
1260
        );
126120×
        for i in 0..20 {
126220×
            assert_eq!(
126320×
                store.get(&format!("loose/{i:02}")).unwrap(),
126420×
                vec![i as u8; 900]
1265
            );
1266
        }
1267
        // An object over an eighth of the limit is read by range, not kept.
12681×
        remote.put_new("pack-big", &[7u8; 2000]).unwrap();
12691×
        assert_eq!(store.get_range("pack-big", 10..20).unwrap(), vec![7u8; 10]);
12701×
        assert_eq!(store.get("pack-big").unwrap().len(), 2000);
12711×
        assert!(!store.cache.has("pack-big").unwrap());
1272
        // Its ranges are kept as read, and its size, so a store opened
1273
        // again (a server's next request) asks the remote nothing.
12741×
        let again =
12751×
            CachingStore::open(Arc::new(Unreachable), dir.path().join("cache"), 8000).unwrap();
12761×
        assert_eq!(again.get_range("pack-big", 10..20).unwrap(), vec![7u8; 10]);
1277
        // Counting from scratch agrees with what was kept track of.
12781×
        let fresh = CachingStore::open(remote, dir.path().join("cache"), 8000).unwrap();
12791×
        assert_eq!(fresh.cached_bytes().unwrap(), store.cached_bytes().unwrap());
1280
        // Deleting it takes its ranges and size too.
12811×
        store.delete("pack-big").unwrap();
12821×
        assert!(
12831×
            store.cache.list("cached-range/").unwrap().is_empty()
12841×
                && store.cache.list("cached-size/").unwrap().is_empty()
1285
        );
12861×
    }
1287
1288
    #[test]
12891×
    fn the_memory_store_conforms() {
12901×
        let store = MemoryStore::new();
12911×
        let passed = conformance(&store, "probe").unwrap();
12921×
        assert_eq!(passed.len(), 8, "{passed:?}");
12931×
    }
1294
1295
    #[test]
12961×
    fn the_directory_store_conforms() {
12971×
        let dir = tempfile::tempdir().unwrap();
12981×
        let store = LocalStore::open(dir.path().join("bucket")).unwrap();
12991×
        let passed = conformance(&store, "repos/one").unwrap();
13001×
        assert_eq!(passed.len(), 8, "{passed:?}");
1301
        // Nothing but the (empty) directories and no stray temporaries.
13021×
        let stray: Vec<_> = walkdir(dir.path())
13031×
            .into_iter()
13041×
            .filter(|p| p.is_file())
13051×
            .collect();
13061×
        assert!(stray.is_empty(), "{stray:?}");
13071×
        assert_eq!(
13081×
            fs::read_dir(dir.path().join("bucket")).unwrap().count(),
1309
            0,
1310
            "no empty directories left"
1311
        );
13121×
    }
1313
13142×
    fn walkdir(dir: &Path) -> Vec<PathBuf> {
13152×
        let mut out = vec![];
13162×
        if let Ok(rd) = fs::read_dir(dir) {
13172×
            for e in rd.flatten() {
13181×
                let p = e.path();
13191×
                if p.is_dir() {
13201×
                    out.extend(walkdir(&p));
13211×
                } else {
13220×
                    out.push(p);
13230×
                }
1324
            }
13250×
        }
13262×
        out
13272×
    }
1328
1329
    #[test]
13301×
    fn a_directory_store_refuses_keys_that_leave_its_root() {
13311×
        let dir = tempfile::tempdir().unwrap();
13321×
        let store = LocalStore::open(dir.path().join("bucket")).unwrap();
13331×
        assert!(matches!(
13341×
            store.put_new("../escape", b"x"),
1335
            Err(Error::Key(..))
1336
        ));
13371×
        assert!(matches!(store.get("/etc/passwd"), Err(Error::Key(..))));
13381×
        assert!(!dir.path().join("escape").exists());
13391×
    }
1340
1341
    /// Many threads race to move a reference from the same version: exactly
1342
    /// one wins each round, on both stores.
1343
    #[test]
13441×
    fn compare_and_swap_admits_one_writer_per_round() {
13451×
        let dir = tempfile::tempdir().unwrap();
13461×
        let local: Arc<dyn Store> = Arc::new(LocalStore::open(dir.path()).unwrap());
13471×
        let memory: Arc<dyn Store> = Arc::new(MemoryStore::new());
13482×
        for store in [local, memory] {
13492×
            let v0 = store.write_ref("refs", b"0", None).unwrap();
13502×
            let wins: Vec<bool> = (0..8)
135116×
                .map(|i| {
135216×
                    let store = store.clone();
135316×
                    let v0 = v0.clone();
135416×
                    std::thread::spawn(move || {
135516×
                        store
135616×
                            .write_ref("refs", format!("{}", i + 1).as_bytes(), Some(&v0))
135716×
                            .is_ok()
135816×
                    })
135916×
                })
13602×
                .collect::<Vec<_>>()
13612×
                .into_iter()
136216×
                .map(|h| h.join().unwrap())
13632×
                .collect();
13642×
            assert_eq!(
13652×
                wins.iter().filter(|w| **w).count(),
1366
                1,
1367
                "{}: {wins:?}",
13680×
                store.describe()
1369
            );
13702×
            let (bytes, _) = store.read_ref("refs").unwrap().unwrap();
13712×
            assert_ne!(bytes, b"0");
1372
        }
13731×
    }
1374
1375
    #[test]
13761×
    fn a_stale_lock_is_broken_and_a_live_one_waited_for() {
13771×
        let dir = tempfile::tempdir().unwrap();
13781×
        let store = LocalStore::open(dir.path()).unwrap();
13791×
        let lock = dir.path().join(".refs.lock");
13801×
        fs::write(&lock, "dead").unwrap();
13811×
        let old = std::time::SystemTime::now() - LOCK_STALE - Duration::from_secs(5);
13821×
        fs::File::open(&lock).unwrap().set_modified(old).unwrap();
13831×
        store.write_ref("refs", b"after a crash", None).unwrap();
13841×
        assert!(!lock.exists());
13851×
    }
1386
1387
    #[test]
13881×
    fn temporaries_and_locks_are_not_listed() {
13891×
        let dir = tempfile::tempdir().unwrap();
13901×
        let store = LocalStore::open(dir.path()).unwrap();
13911×
        store.put_new("packs/a", b"a").unwrap();
13921×
        fs::write(dir.path().join("packs/.a.tmp-ffffff"), "junk").unwrap();
13931×
        fs::write(dir.path().join(".refs.lock"), "1").unwrap();
13941×
        let keys: Vec<String> = store.list("").unwrap().into_iter().map(|e| e.key).collect();
13951×
        assert_eq!(keys, ["packs/a"]);
13961×
    }
1397
}