use std::ffi::OsString;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Mutex;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;

use rusqlite::{
    Connection,
    OpenFlags,
    Transaction,
    TransactionBehavior,
    params,
    types::ValueRef,
};

use sequoia_openpgp as openpgp;
use openpgp::packet::signature::cache::SignatureVerificationCache;

use crate::Result;

const TRACE: bool = cfg!(test) || super::TRACE;

/// We save a low precision (one day) timestamp to minimize the amount
/// of on-disk churn.
const TICK_SECONDS: u64 = 24 * 60 * 60;

fn ticks_since_unix_epoch() -> i64 {
    let ticks = SystemTime::now().duration_since(UNIX_EPOCH)
        .map(|t| t.as_secs())
        .unwrap_or(0)
        / TICK_SECONDS;
    // If TICKS_SECONDS is at least 2, then the result is guaranteed
    // to fix in an i64.
    ticks as i64
}

/// We evict cache entries that haven't been accessed in this much
/// time (in ticks).
const EVICTION_THRESHOLD: i64 = 30;

/// Some information about the cache at the time it was restored.
struct RestoredState {
    /// The time that the cache was restored as the number of ticks
    /// since the UNIX epoch.  (A tick is defined as
    /// `TICK_SECONDS` seconds.)
    restored_at: i64,

    /// Observation: Ticks are relatively long.  As such, even if an
    /// entry was accessed, we may not have to write it out to the
    /// database if the last access time did not change.  In many
    /// cases, this means that we don't have to update the database at
    /// all as the same set of entries are usually accessed over and
    /// over again.
    ///
    /// Optimization: When we load the cache, we record what entries
    /// were already accessing during the current (`restored_at`) tick
    /// here.  When we later save the cache, if the tick hasn't
    /// changed (i.e., the current tick is still `restored_at`), then
    /// these entries don't need to be written out: the new access
    /// tick is the same as the old one!
    ///
    /// This vector is sorted so that we can use binary_search.
    accessed_near_restore: Vec<Vec<u8>>,

    /// The oldest entry that we restored from the cache as the number
    /// of ticks since the UNIX epoch.  (A tick is defined as
    /// `TICK_SECONDS` seconds.)  We use this to decide whether we
    /// need to evict any entries.
    least_recently_accessed: i64,
}

/// Whether the cache was restored.  If we didn't restore (or finish
/// restoring!) the cache, we don't write it out.
static RESTORED_STATE: Mutex<Option<RestoredState>>
    = Mutex::new(None);

/// A handle to the signature verification cache.
///
/// When this handle is dropped, the cache is saved to disk.
pub struct CertdSignatureVerificationCache {
    /// The database.
    filename: PathBuf,

    /// At least when using the OpenSSL crypto backend, we can't use
    /// OpenSSL after the main thread exits.  As such, if we load the
    /// cache from a separate thread, then we join the thread in the
    /// drop handler.
    thread_handle: Option<std::thread::JoinHandle<()>>,
}

impl CertdSignatureVerificationCache {
    const DATABASE_VERSION: u32 = 1;
    const DATABASE_ID: &'static str = "sequoia signature verification cache v1";

    /// Initializes the database.
    ///
    /// Any existing content is lost.
    fn initialize_v1(tx: &Transaction) -> std::result::Result<(), rusqlite::Error> {
        tx.execute_batch("\
-- A table identifying the version and a human-readable magic.
DROP TABLE IF EXISTS version;
CREATE TABLE version (
    id INTEGER PRIMARY KEY,
    version INTEGER NOT NULL,
    comment TEXT NOT NULL
);")?;

        tx.execute("\
-- Record the schema version.
INSERT OR IGNORE INTO version VALUES (0, ?1, ?2);
",
                   (Self::DATABASE_VERSION, Self::DATABASE_ID))?;

        tx.execute("\
-- The signature verification cache with the last access time.  The last
-- access time is the number of days since the UNIX epoch, i.e., the
-- number of seconds divided by 24 * 60 * 60.
CREATE TABLE IF NOT EXISTS entries (
    value BLOB PRIMARY KEY,
    last_accessed INTEGER
) WITHOUT ROWID",
                   ())?;

        Ok(())
    }

    /// Returns the database version.
    fn db_version(tx: &Transaction) -> rusqlite::Result<u32> {
        let mut stmt = tx.prepare_cached(
            "SELECT version FROM version WHERE id == 0")?;
        let r = stmt.query([])?.mapped(|r| r.get(0)).next().unwrap_or(Ok(0))?;
        Ok(r)
    }

    /// Returns a connection to the database.
    ///
    /// If `fail_fast` is set, then we'd rather fail then wait too
    /// long for the database lock.
    fn open(filename: &Path, fail_fast: bool) -> Result<Connection> {
        tracer!(TRACE, "CertdSignatureVerificationCache::open");
        t!("Opening {}", filename.display());

        let open = || -> std::result::Result<Connection, rusqlite::Error> {
            let mut conn = Connection::open_with_flags(
                filename,
                OpenFlags::SQLITE_OPEN_READ_WRITE
                    | OpenFlags::SQLITE_OPEN_CREATE
                    | OpenFlags::SQLITE_OPEN_PRIVATE_CACHE)?;

            // Use WAL logging
            //
            // https://www.sqlite.org/wal.html
            conn.execute_batch("PRAGMA journal_mode=WAL")?;

            // Disable synchronous mode.  This mode is more dangerous, but
            // since this database is only a cache, a corrupted database
            // will not result in data loss.
            //
            // https://www.sqlite.org/pragma.html#pragma_synchronous
            conn.execute_batch("PRAGMA synchronous=OFF")?;

            // See which database version we're dealing with.

            let mut transaction_mode = TransactionBehavior::Deferred;
            let (mut retries, wait) = if cfg!(test) {
                // When running tests, retry to exhaustion.  This is
                // needed by the unit tests (in particular, see
                // test_signature_cache), which tests that the cache
                // is updated even under contention.
                (usize::MAX, std::time::Duration::from_millis(1))
            } else if fail_fast {
                // 4 * 1 = 4ms.
                (4, std::time::Duration::from_millis(1))
            } else {
                // 32 * 10 = 320ms.
                (32, std::time::Duration::from_millis(10))
            };

            loop {
                let tx = Transaction::new(&mut conn, transaction_mode)?;

                match Self::db_version(&tx) {
                    Ok(1) => {
                        tx.rollback()?;
                        break;
                    },
                    Ok(n) => {
                        t!("Expected version 1 DB, got version {} DB.  \
                            Re-initializing.",
                           n);
                        if let Err(err) = Self::initialize_v1(&tx) {
                            t!("Failed to initialize database: {}", err);
                            return Err(err);
                        }
                        tx.commit()?;
                        break;
                    },
                    Err(rusqlite::Error::SqliteFailure(e, _))
                        if e.code == rusqlite::ErrorCode::Unknown =>
                    {
                        match Self::initialize_v1(&tx) {
                            // Initializing the database, may fail if
                            // another process has lock the database
                            // (e.g., it is also initializing the
                            // database).  Sleep a bit and try again.
                            Err(rusqlite::Error::SqliteFailure(e, _))
                                if retries > 0
                                    && e.code == rusqlite::ErrorCode::DatabaseLocked =>
                            {
                                retries -= 1;
                                transaction_mode = TransactionBehavior::Immediate;
                                tx.rollback()?;
                                std::thread::sleep(wait);
                                continue;
                            },
                            other => other?,
                        }
                        tx.commit()?;
                        return Ok(conn);
                    },
                    Err(rusqlite::Error::SqliteFailure(e, _))
                        if retries > 0
                        && (e.code == rusqlite::ErrorCode::DatabaseBusy
                            || e.code == rusqlite::ErrorCode::DatabaseLocked) =>
                    {
                        // When trying to initialize the database, we
                        // may find that it is locked.  If so, sleep a
                        // bit and then retry.
                        retries -= 1;
                        transaction_mode = TransactionBehavior::Immediate;
                        tx.rollback()?;
                        std::thread::sleep(wait);
                        continue;
                    }
                    Err(err) => {
                        t!("Can't open sqlite DB: {:?}", err);
                        return Err(err.into());
                    }
                }
            }

            Ok(conn)
        };

        match open() {
            Ok(conn) => Ok(conn),
            Err(err) => {
                t!("Error opening database: {}", err);

                if let rusqlite::Error::SqliteFailure(e, _) = err {
                    t!("Error code returned from sqlite: {:?}", e.code);

                    if e.code == rusqlite::ErrorCode::Unknown
                        || e.code == rusqlite::ErrorCode::DatabaseCorrupt
                        || e.code == rusqlite::ErrorCode::SchemaChanged
                        || e.code == rusqlite::ErrorCode::NotADatabase
                    {
                        // The database is corrupted.  Remove the file
                        // and try again.
                        //
                        // We need to do this indirectly.
                        // `std::fs::remove_file` doesn't guarantee
                        // that the file is removed immediately
                        //
                        // "Note that there is no guarantee that the
                        // file is immediately deleted (e.g.,
                        // depending on platform, other open file
                        // descriptors may prevent immediate
                        // removal)."
                        //
                        // https://doc.rust-lang.org/stable/std/fs/fn.remove_file.html
                        //
                        // If that happens (and it is the behavior
                        // that we observe on Windows), then we can't
                        // create a new database.
                        //
                        // To work around this, we rename the file,
                        // and then remove the file using its new
                        // name.
                        t!("Corrupted database ({:?}), \
                            removing and recreating {}",
                           err, filename.display());

                        let mut tmp = filename.to_path_buf();
                        tmp.set_extension("~");
                        if let Err(err) = std::fs::rename(filename, &tmp) {
                            t!("Couldn't remove file or move it \
                                out of the way: {}",
                               err);
                            // Renaming failed.  Just try and remove
                            // the original file.
                            tmp = filename.to_path_buf();
                        } else {
                            t!("Renamed {} to {}",
                               filename.display(), tmp.display());
                        }

                        match std::fs::remove_file(&tmp) {
                            Ok(()) => {
                                let result = open();
                                if let Err(err) = result.as_ref() {
                                    t!("Failed to recreate database: {}", err);
                                    if let rusqlite::Error::SqliteFailure(e, _) = err {
                                        t!("Error code returned from sqlite: {:?}",
                                           e.code);
                                    }
                                } else {
                                    t!("Successfully recreated database.");
                                }

                                return Ok(result?);
                            }
                            Err(err) => {
                                t!("Removing {}: {}", tmp.display(), err);
                            }
                        }
                    }
                }

                Err(err.into())
            }
        }
    }

    /// Returns the cache's filename for the given certd.
    pub fn cache_file<P>(certd: P) -> Result<PathBuf>
    where P: AsRef<Path>
    {
        let certd = certd.as_ref();

        std::fs::create_dir_all(certd)?;

        let mut filename =
            OsString::from("_sequoia_signature_verification_cache_v1_on_");
        filename.push(&gethostname::gethostname());
        filename.push(".sqlite");

        Ok(certd.join(filename))
    }

    /// Loads the signature verification cache from disk.
    ///
    /// This returns a `CertdSignatureVerificationCache`.  When the
    /// `CertdSignatureVerificationCache` is dropped, it automatically
    /// saves the cache to disk.
    ///
    /// To avoid blocking the thread, this function spawns a separate
    /// thread to do the actual load.
    pub fn load(filename: PathBuf) -> CertdSignatureVerificationCache {
        tracer!(TRACE, "CertdSignatureVerificationCache::load");

        let filename_copy = filename.clone();
        let handle = std::thread::spawn(move || {
            if let Err(err) = Self::load_internal(filename_copy.clone()) {
                t!("Error loading signature verification cache from {}: {}",
                   filename_copy.display(), err);
            }
        });

        CertdSignatureVerificationCache {
            filename,
            thread_handle: Some(handle),
        }
    }

    /// Loads the signature verification cache from disk.
    ///
    /// Returns a `CertdSignatureVerificationCache`, and an optional
    /// error.  When the `CertdSignatureVerificationCache` is dropped,
    /// it automatically saves the cache to disk.
    ///
    /// If an error occurs, it is returned separately.  An error means
    /// that the signature verification cache wasn't restored.  But,
    /// that doesn't mean that the signature verification cache can't
    /// be saved later.
    ///
    /// Unlike [`CertdSignatureVerificationCache::load`], this
    /// function blocks the current thread until the cache is
    /// restored.
    #[allow(dead_code)]
    pub fn load_blocking(filename: PathBuf)
        -> (CertdSignatureVerificationCache, Option<anyhow::Error>)
    {
        let result = Self::load_internal(filename.clone());

        (
            CertdSignatureVerificationCache {
                filename,
                thread_handle: None,
            },
            result.err(),
        )
    }

    /// Loads the signature verification cache from disk.
    ///
    /// On success, this returns a `CertdSignatureVerificationCache`.
    /// When the `CertdSignatureVerificationCache` is dropped, it
    /// automatically saves the cache to disk.
    fn load_internal(filename: PathBuf) -> Result<()> {
        tracer!(TRACE, "CertdSignatureVerificationCache::load");

        let mut conn = Self::open(&filename, false)?;

        let tx = Transaction::new(
            &mut conn, TransactionBehavior::Deferred)?;

        // Load the cache.
        let restored_at = ticks_since_unix_epoch();
        let mut least_recently_accessed = restored_at;

        let mut accessed_near_restore = Vec::new();

        let mut stmt = tx.prepare("SELECT value, last_accessed FROM entries")?;
        let rows = stmt.query_map([], |row| {
            let value: Vec<u8> = row.get(0)?;
            let last_accessed = row.get_ref(1)?;

            if let ValueRef::Integer(last_accessed) = last_accessed {
                if last_accessed == restored_at {
                    accessed_near_restore.push(value.clone());
                } else {
                    least_recently_accessed
                        = least_recently_accessed.min(last_accessed);
                }
            }

            Ok(value)
        })?;

        let entries = rows.collect::<Vec<_>>();

        drop(stmt);
        drop(tx);

        t!("Loading {} entries", entries.len());

        accessed_near_restore.sort();

        SignatureVerificationCache::restore(
            entries.into_iter().filter_map(|e| e.ok()),
            move || {
                let mut restored_state = RESTORED_STATE.lock().unwrap();
                if restored_state.is_none() {
                    *restored_state = Some(RestoredState {
                        restored_at,
                        accessed_near_restore,
                        least_recently_accessed,
                    });
                }
                t!("Finished restoring the cache.");
            });

        drop(conn);

        Ok(())
    }

    /// Save the signature verification cache to the database.
    ///
    /// This is normally called when `CertdSignatureVerificationCache`
    /// is dropped.
    fn save(filename: &Path) -> Result<()> {
        tracer!(TRACE, "CertdSignatureVerificationCache::save");

        // Normally save is called synchronously at program exit.  If
        // we can't lock the database fast, then we should just not
        // bother to save the cache.
        let mut conn = Self::open(filename, true)?;

        let tx = Transaction::new(
            &mut conn, TransactionBehavior::Immediate)?;

        // Save the cache.
        let now = ticks_since_unix_epoch();

        let restored_state = RESTORED_STATE.lock().unwrap();

        // If an entry's last access time is the same as the last
        // access time in the db, then that entry is already up to
        // date.
        let mut ignore = &Vec::new();

        // The on-disk entry with the oldest last access time at the
        // time of the restore.  If this entry isn't old enough to be
        // considered for deletion, then no entries are old enough.
        let mut least_recently_accessed = 0;

        if let Some(restored_state) = restored_state.as_ref() {
            let restored_at = restored_state.restored_at;
            t!("{} ticks since restore", restored_at - now);
            if restored_at == now {
                ignore = &restored_state.accessed_near_restore;
            }

            least_recently_accessed
                = restored_state.least_recently_accessed;
        } else {
            t!("Didn't restore the cache.");
        }

        t!("ignore contains {} entries", ignore.len());

        // Add the list of entries that need to be updated to an
        // in-memory table, and then do a single insert.  Using an
        // INSERT per update takes about five times as long.
        tx.execute("ATTACH ':memory:' as in_memory", ())?;

        tx.execute("CREATE TABLE in_memory.accessed \
                    (value BLOB PRIMARY KEY) \
                    WITHOUT ROWID",
                   ())?;
        let mut accessed_stmt = tx.prepare(
            "INSERT OR IGNORE INTO in_memory.accessed (value) VALUES (?1)")?;

        // Number of entries that were inserted.
        let mut inserted = 0;
        // Number of entries that were accessed.
        let mut accessed = 0;
        // Number of entries that were accessed and need their
        // last_access_time field updated.
        let mut accessed_old = 0;

        for entry in SignatureVerificationCache::dump() {
            let entry_inserted = entry.inserted();
            let entry_accessed = entry.accessed();

            if entry_inserted {
                inserted += 1;
                accessed_stmt.execute(params!(entry.value()))?;
            } else if entry_accessed {
                accessed += 1;
                if ignore.binary_search_by(|probe| {
                    probe[..].cmp(entry.value())
                }).is_err()
                {
                    accessed_old += 1;
                    accessed_stmt.execute(params!(entry.value()))?;
                }
            }
        }

        t!("{} new entries, {} accessed old ({} accessed total)",
           inserted, accessed_old, accessed);

        if inserted > 0 || accessed_old > 0 {
            tx.execute(
                "INSERT OR REPLACE INTO entries \
                 SELECT value, ?1 FROM in_memory.accessed",
                params!(now))?;
        }

        drop(accessed_stmt);

        if now > EVICTION_THRESHOLD
            && least_recently_accessed < now - EVICTION_THRESHOLD
        {
            tx.execute(
                "DELETE FROM entries WHERE last_accessed < ?1",
                params!(now - EVICTION_THRESHOLD))?;
        }

        tx.commit()?;

        Ok(())
    }
}

impl Drop for CertdSignatureVerificationCache {
    fn drop(&mut self) {
        tracer!(TRACE, "CertdSignatureVerificationCache::drop");
        t!("saving cache");

        if let Some(handle) = self.thread_handle.take() {
            let _ = handle.join();
        }

        match CertdSignatureVerificationCache::save(&self.filename) {
            Ok(()) => {
                t!("Saved signature cache to {}.",
                   self.filename.display());
            }
            Err(err) => {
                t!("Error saving signature cache to {}: {}",
                   self.filename.display(), err);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use std::io::Write;

    // The signature verification cache is a global singleton.  For
    // the following tests to be reliable, they need to run in their
    // own process.
    use rusty_fork::rusty_fork_test;

    use openpgp::cert::CertBuilder;
    use openpgp::policy::StandardPolicy;

    const P: &StandardPolicy = &StandardPolicy::new();

    fn test_signature_cache(prefix: &str, filename: &Path, can_update: bool)
                            -> Result<()>
    {
        eprintln!("{}1. Restore the signature cache", prefix);
        let (cache, err) = CertdSignatureVerificationCache::load_blocking(
            filename.to_path_buf());
        if let Some(err) = err {
            eprintln!("{}1. loading cache: {}", prefix, err);
        }
        eprintln!("{}2. Save the signature cache", prefix);
        drop(cache);

        eprintln!("{}3. Restore the signature cache (2)", prefix);
        let (cache, err) = CertdSignatureVerificationCache::load_blocking(
            filename.to_path_buf());
        if let Some(err) = err {
            eprintln!("{}3. loading cache: {}", prefix, err);
        }

        eprintln!("{}4. Count the number of entries in the signature cache database",
                  prefix);
        let conn = Connection::open_with_flags(
            filename,
            OpenFlags::SQLITE_OPEN_READ_WRITE
                | OpenFlags::SQLITE_OPEN_CREATE
                | OpenFlags::SQLITE_OPEN_PRIVATE_CACHE)?;

        // Count the number of cache entries that we already have.
        let mut count_stmt
            = conn.prepare_cached("SELECT count(*) FROM entries")?;
        let precount = count_stmt
            .query([])?
            .mapped(|r| r.get(0)).next().unwrap_or(Ok(0))?;
        eprintln!("{}5. Have {} entries in db", prefix, precount);

        // Create a certificate, and turn it into a verified
        // certificate.  This should create a few new cache entries.
        eprintln!("{}6. Generate a certificate", prefix);
        let (alice, _rev) = CertBuilder::general_purpose(Some("xxx"))
            .generate()
            .expect("can generate certificate");

        let _vc = alice.with_policy(P, None).expect("is valid");
        eprintln!("{}7. Save the signature cache (2)", prefix);
        drop(cache);

        // Count the number of cache entries that we have now.
        eprintln!("{}8. Count the number of entries in the signature cache database",
                  prefix);
        let postcount = count_stmt
            .query([])?
            .mapped(|r| r.get(0)).next().unwrap_or(Ok(0))?;
        eprintln!("{}9. Have {} entries in db", prefix, postcount);

        if can_update {
            // Make sure that we have more cache entries than before.
            assert!(postcount > precount,
                    "{}: assertion failed: postcount ({}) > precount ({})",
                    prefix, postcount, precount);
        } else {
            assert_eq!(postcount, precount);
        }

        Ok(())
    }

    rusty_fork_test! {
        #[test]
        fn signature_cache_good() {
            let tempfile = tempfile::NamedTempFile::new()
                .expect("Can create temp files");

            if let Err(err) = test_signature_cache("", tempfile.path(), true) {
                panic!("test_signature_cache(\"\", {}, true) failed: {}",
                       tempfile.path().display(), err);
            }
        }

        #[test]
        fn signature_cache_corrupted() {
            let tempfile = tempfile::NamedTempFile::new()
                .expect("Can create temp files");

            std::fs::write(tempfile.path(), "this is a corrupted sqlite db.")
                .expect("can write");

            // Make sure sqlite fails to use the database.
            let result = Connection::open_with_flags(
                tempfile.path(),
                OpenFlags::SQLITE_OPEN_READ_WRITE
                    | OpenFlags::SQLITE_OPEN_CREATE
                    | OpenFlags::SQLITE_OPEN_PRIVATE_CACHE)
                .and_then(|conn| {
                    let mut count_stmt
                        = conn.prepare_cached("SELECT count(*) FROM entries")?;
                    let _precount = count_stmt
                        .query([])?
                        .mapped(|r| r.get(0)).next().unwrap_or(Ok(0))?;

                    Ok(())
                });
            assert!(result.is_err());
            // Make sure the file descriptor is closed.
            drop(result);

            // This should work, because we should replace the corrupted
            // database.
            if let Err(err) = test_signature_cache("", tempfile.path(), true) {
                panic!("test_signature_cache(\"\", {}, true) failed: {}",
                       tempfile.path().display(), err);
            }
        }

        #[test]
        fn signature_cache_readonly() {
            let dir = tempfile::tempdir().expect("can create temp directory");

            let tempfile = dir.path().join("cache.sqlite");

            eprintln!("1. Loading signature cache (1)");
            let cache = CertdSignatureVerificationCache::load_blocking(
                tempfile.as_path().to_path_buf());
            eprintln!("2. Saving signature cache (1)");
            drop(cache);

            assert!(tempfile.exists());

            let metadata = tempfile.metadata().expect("can get metadata");
            eprintln!("{}'s metadata: {:?}",
                      tempfile.display(), metadata);

            eprintln!("3. Making cache file readonly");
            let mut permissions = metadata.permissions();
            permissions.set_readonly(true);

            std::fs::set_permissions(&tempfile, permissions)
                .expect("can make read only");

            eprintln!("{}'s metadata after making read only: {:?}",
                      tempfile.display(), tempfile.metadata().unwrap());

            let result = std::fs::OpenOptions::new().write(true).open(&tempfile)
                .and_then(|mut file| {
                    file.write_all(b"foo")
                });
            if result.is_ok() {
                eprintln!("Read-only permission ignored (are you \
                           running as root).  Skipping test.");
                return;
            }

            eprintln!("4. Loading signature cache (2)");
            let cache = CertdSignatureVerificationCache::load_blocking(
                tempfile.as_path().to_path_buf());
            eprintln!("5. Saving signature cache (2)");
            eprintln!("metadata: {:?}", tempfile.metadata().unwrap());
            drop(cache);

            eprintln!("6. Running signature cache test");
            if let Err(err) = test_signature_cache("", tempfile.as_path(), false) {
                panic!("test_signature_cache(\"\", {}, false) failed: {}",
                       tempfile.as_path().display(), err);
            }
        }

        #[test]
        fn signature_cache_readonly_directory() {
            let tmp_dir = tempfile::tempdir().expect("can create temp directory");
            let dir = tmp_dir.path();

            let tempfile = dir.join("cache.sqlite");
            assert!(!tempfile.exists());

            let metadata = dir.metadata().expect("can get metadata");
            let mut permissions = metadata.permissions();
            permissions.set_readonly(true);

            std::fs::set_permissions(&dir, permissions)
                .expect("can make read only");

            // If readonly is not respected (e.g., when running as
            // root), skip the test:
            if std::fs::write(&tempfile, "hi").is_ok() {
                eprintln!("Read-only permission ignored (are you \
                           running as root).  Skipping test.");
                return;
            }

            let cache = CertdSignatureVerificationCache::load_blocking(
                tempfile.to_path_buf());
            drop(cache);
        }

        // Have a lot of threads load the same cache to check that the
        // code doesn't panic.
        #[test]
        fn signature_cache_hammer_time() {
            let tempfile = tempfile::NamedTempFile::new()
                .expect("Can create temp files");

            let file = tempfile.path();

            let threads: Vec<_> = (0..2)
                .map(|thread| {
                    let file = file.to_path_buf();
                    std::thread::Builder::new()
                        .name(format!("thread number {}", thread))
                        .spawn(move || {
                            for i in 0..10 {
                                let prefix = format!("{}.{}. ", thread, i);
                                if let Err(err) = test_signature_cache(
                                    &prefix[..], &file, true) {
                                    panic!("Thread {}: test_signature_cache: {}",
                                           thread, err);
                                }
                            }
                        })
                        .unwrap()
                })
                .collect();

            for t in threads.into_iter() {
                t.join().expect("ok");
            }
        }
    }
}
