storage_proofs_core/
parameter_cache.rs

1use std::collections::{BTreeMap, HashSet};
2use std::fs::{create_dir_all, File, OpenOptions};
3use std::io::{self, Read, Seek, SeekFrom, Write};
4use std::path::{Path, PathBuf};
5use std::sync::Mutex;
6use std::time::Instant;
7
8use anyhow::bail;
9use bellperson::{groth16, Circuit};
10use blake2b_simd::Params as Blake2bParams;
11use blstrs::{Bls12, Scalar as Fr};
12use fs2::FileExt;
13use itertools::Itertools;
14use lazy_static::lazy_static;
15use log::info;
16use memmap2::MmapOptions;
17use rand::RngCore;
18use serde::{Deserialize, Serialize};
19use sha2::{Digest, Sha256};
20
21use crate::{
22    error::{Error, Result},
23    settings::SETTINGS,
24};
25
26/// Bump this when circuits change to invalidate the cache.
27pub const VERSION: usize = 28;
28pub const SRS_MAX_PROOFS_TO_AGGREGATE: usize = 65536; // FIXME: placeholder value
29
30pub const GROTH_PARAMETER_EXT: &str = "params";
31pub const PARAMETER_METADATA_EXT: &str = "meta";
32pub const VERIFYING_KEY_EXT: &str = "vk";
33pub const SRS_KEY_EXT: &str = "srs";
34pub const SRS_SHARED_KEY_NAME: &str = "fil-inner-product-v1";
35
36#[derive(Debug)]
37pub struct LockedFile(File);
38
39pub type ParameterMap = BTreeMap<String, ParameterData>;
40#[cfg(not(feature = "cuda-supraseal"))]
41pub type Bls12GrothParams = groth16::MappedParameters<Bls12>;
42#[cfg(feature = "cuda-supraseal")]
43pub type Bls12GrothParams = groth16::SuprasealParameters<Bls12>;
44
45#[derive(Debug, Deserialize, Serialize)]
46pub struct ParameterData {
47    pub cid: String,
48    pub digest: String,
49    pub sector_size: u64,
50}
51
52pub const PARAMETERS_DATA: &str = include_str!("../parameters.json");
53pub const SRS_PARAMETERS_DATA: &str = include_str!("../srs-inner-product.json");
54
55lazy_static! {
56    pub static ref PARAMETERS: ParameterMap =
57        serde_json::from_str(PARAMETERS_DATA).expect("Invalid parameters.json");
58    pub static ref SRS_PARAMETERS: ParameterMap =
59        serde_json::from_str(SRS_PARAMETERS_DATA).expect("Invalid srs-inner-product.json");
60    /// Contains the parameters that were previously verified. This way the parameter files are
61    /// only hashed once and not on every usage.
62    static ref VERIFIED_PARAMETERS: Mutex<HashSet<String>> = Mutex::new(HashSet::new());
63}
64
65pub fn parameter_id(cache_id: &str) -> String {
66    format!("v{}-{}.params", VERSION, cache_id)
67}
68
69pub fn verifying_key_id(cache_id: &str) -> String {
70    format!("v{}-{}.vk", VERSION, cache_id)
71}
72
73pub fn metadata_id(cache_id: &str) -> String {
74    format!("v{}-{}.meta", VERSION, cache_id)
75}
76
77/// Get the correct parameter data for a given cache id.
78pub fn get_parameter_data_from_id(parameter_id: &str) -> Option<&ParameterData> {
79    PARAMETERS.get(parameter_id)
80}
81
82/// Get the correct srs parameter data for a given cache id.
83pub fn get_srs_parameter_data_from_id(parameter_id: &str) -> Option<&ParameterData> {
84    SRS_PARAMETERS.get(parameter_id)
85}
86
87/// Get the correct parameter data for a given cache id.
88pub fn get_parameter_data(cache_id: &str) -> Option<&ParameterData> {
89    PARAMETERS.get(&parameter_id(cache_id))
90}
91
92/// Get the correct verifying key data for a given cache id.
93pub fn get_verifying_key_data(cache_id: &str) -> Option<&ParameterData> {
94    PARAMETERS.get(&verifying_key_id(cache_id))
95}
96
97// TODO: use in memory lock as well, as file locks do not guarantee exclusive access across OSes.
98
99impl LockedFile {
100    pub fn open_exclusive_read<P: AsRef<Path>>(p: P) -> io::Result<Self> {
101        let f = OpenOptions::new().read(true).create(false).open(p)?;
102        f.lock_exclusive()?;
103
104        Ok(LockedFile(f))
105    }
106
107    pub fn open_exclusive<P: AsRef<Path>>(p: P) -> io::Result<Self> {
108        let f = OpenOptions::new()
109            .read(true)
110            .write(true)
111            .create_new(true)
112            .open(p)?;
113        f.lock_exclusive()?;
114
115        Ok(LockedFile(f))
116    }
117
118    pub fn open_shared_read<P: AsRef<Path>>(p: P) -> io::Result<Self> {
119        let f = OpenOptions::new().read(true).create(false).open(p)?;
120        f.lock_shared()?;
121
122        Ok(LockedFile(f))
123    }
124}
125
126impl AsRef<File> for LockedFile {
127    fn as_ref(&self) -> &File {
128        &self.0
129    }
130}
131
132impl Write for LockedFile {
133    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
134        self.0.write(buf)
135    }
136
137    fn flush(&mut self) -> io::Result<()> {
138        self.0.flush()
139    }
140}
141
142impl Read for LockedFile {
143    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
144        self.0.read(buf)
145    }
146}
147
148impl Seek for LockedFile {
149    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
150        self.0.seek(pos)
151    }
152}
153
154impl Drop for LockedFile {
155    fn drop(&mut self) {
156        self.0
157            .unlock()
158            .unwrap_or_else(|e| panic!("{}: failed to {:?} unlock file safely", e, &self.0));
159    }
160}
161
162pub fn parameter_cache_dir_name() -> String {
163    SETTINGS.parameter_cache.clone()
164}
165
166pub fn parameter_cache_dir() -> PathBuf {
167    Path::new(&parameter_cache_dir_name()).to_path_buf()
168}
169
170pub fn parameter_cache_params_path(parameter_set_identifier: &str) -> PathBuf {
171    let dir = Path::new(&parameter_cache_dir_name()).to_path_buf();
172    dir.join(format!(
173        "v{}-{}.{}",
174        VERSION, parameter_set_identifier, GROTH_PARAMETER_EXT
175    ))
176}
177
178pub fn parameter_cache_metadata_path(parameter_set_identifier: &str) -> PathBuf {
179    let dir = Path::new(&parameter_cache_dir_name()).to_path_buf();
180    dir.join(format!(
181        "v{}-{}.{}",
182        VERSION, parameter_set_identifier, PARAMETER_METADATA_EXT
183    ))
184}
185
186pub fn parameter_cache_verifying_key_path(parameter_set_identifier: &str) -> PathBuf {
187    let dir = Path::new(&parameter_cache_dir_name()).to_path_buf();
188    dir.join(format!(
189        "v{}-{}.{}",
190        VERSION, parameter_set_identifier, VERIFYING_KEY_EXT
191    ))
192}
193
194pub fn parameter_cache_srs_key_path(
195    _parameter_set_identifier: &str,
196    _num_proofs_to_aggregate: usize,
197) -> PathBuf {
198    let dir = Path::new(&parameter_cache_dir_name()).to_path_buf();
199    dir.join(format!(
200        "v{}-{}.{}",
201        VERSION, SRS_SHARED_KEY_NAME, SRS_KEY_EXT
202    ))
203}
204
205fn ensure_ancestor_dirs_exist(cache_entry_path: PathBuf) -> Result<PathBuf> {
206    info!(
207        "ensuring that all ancestor directories for: {:?} exist",
208        cache_entry_path
209    );
210
211    if let Some(parent_dir) = cache_entry_path.parent() {
212        if let Err(err) = create_dir_all(parent_dir) {
213            match err.kind() {
214                io::ErrorKind::AlreadyExists => {}
215                _ => return Err(From::from(err)),
216            }
217        }
218    } else {
219        bail!("{:?} has no parent directory", cache_entry_path);
220    }
221
222    Ok(cache_entry_path)
223}
224
225pub trait ParameterSetMetadata {
226    fn identifier(&self) -> String;
227    fn sector_size(&self) -> u64;
228}
229
230#[derive(Clone, Debug, Serialize, Deserialize)]
231pub struct CacheEntryMetadata {
232    pub sector_size: u64,
233}
234
235pub trait CacheableParameters<C, P>
236where
237    C: Circuit<Fr>,
238    P: ParameterSetMetadata,
239{
240    fn cache_prefix() -> String;
241
242    fn cache_meta(pub_params: &P) -> CacheEntryMetadata {
243        CacheEntryMetadata {
244            sector_size: pub_params.sector_size(),
245        }
246    }
247
248    fn cache_identifier(pub_params: &P) -> String {
249        let param_identifier = pub_params.identifier();
250        info!("parameter set identifier for cache: {}", param_identifier);
251        let mut hasher = Sha256::default();
252        hasher.update(param_identifier.into_bytes());
253        let circuit_hash = hasher.finalize();
254        format!(
255            "{}-{:02x}",
256            Self::cache_prefix(),
257            circuit_hash.iter().format("")
258        )
259    }
260
261    fn get_param_metadata(_circuit: C, pub_params: &P) -> Result<CacheEntryMetadata> {
262        let id = Self::cache_identifier(pub_params);
263
264        // generate (or load) metadata
265        let meta_path = ensure_ancestor_dirs_exist(parameter_cache_metadata_path(&id))?;
266        read_cached_metadata(&meta_path)
267            .or_else(|_| write_cached_metadata(&meta_path, Self::cache_meta(pub_params)))
268            .map_err(Into::into)
269    }
270
271    /// If the rng option argument is set, parameters will be
272    /// generated using it.  This is used for testing only, or where
273    /// parameters are otherwise unavailable (e.g. benches).  If rng
274    /// is not set, an error will result if parameters are not
275    /// present.
276    fn get_groth_params<R: RngCore>(
277        rng: Option<&mut R>,
278        circuit: C,
279        pub_params: &P,
280    ) -> Result<Bls12GrothParams> {
281        let id = Self::cache_identifier(pub_params);
282        let cache_path = ensure_ancestor_dirs_exist(parameter_cache_params_path(&id))?;
283
284        let generate = || -> Result<_> {
285            if let Some(rng) = rng {
286                info!("Actually generating groth params. (id: {})", &id);
287                let start = Instant::now();
288                let parameters = groth16::generate_random_parameters::<Bls12, _, _>(circuit, rng)?;
289                let generation_time = start.elapsed();
290                info!(
291                    "groth_parameter_generation_time: {:?} (id: {})",
292                    generation_time, &id
293                );
294                Ok(parameters)
295            } else {
296                bail!(
297                    "No cached parameters found for {} [failure finding {}]",
298                    id,
299                    cache_path.display()
300                );
301            }
302        };
303
304        // load or generate Groth parameter mappings
305        read_cached_params(&cache_path).or_else(|err| match err.downcast::<Error>() {
306            Ok(error @ Error::InvalidParameters(_)) => Err(error.into()),
307            _ => {
308                // if the file already exists, another process is already trying to generate these.
309                if !cache_path.exists() {
310                    match write_cached_params(&cache_path, generate()?) {
311                        Ok(_) => {}
312                        Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
313                            // other thread just wrote it, do nothing
314                        }
315                        Err(e) => panic!("{}: failed to write generated parameters to cache", e),
316                    }
317                }
318                Ok(read_cached_params(&cache_path)?)
319            }
320        })
321    }
322
323    /// If the rng option argument is set, parameters will be
324    /// generated using it.  This is used for testing only, or where
325    /// parameters are otherwise unavailable (e.g. benches).  If rng
326    /// is not set, an error will result if parameters are not
327    /// present.
328    fn get_inner_product<R: RngCore>(
329        rng: Option<&mut R>,
330        _circuit: C,
331        pub_params: &P,
332        num_proofs_to_aggregate: usize,
333    ) -> Result<groth16::aggregate::GenericSRS<Bls12>> {
334        let id = Self::cache_identifier(pub_params);
335        let cache_path =
336            ensure_ancestor_dirs_exist(parameter_cache_srs_key_path(&id, num_proofs_to_aggregate))?;
337
338        let generate = || -> Result<groth16::aggregate::GenericSRS<Bls12>> {
339            if let Some(rng) = rng {
340                info!(
341                    "get_inner_product called with {} [max {}] proofs to aggregate",
342                    num_proofs_to_aggregate, SRS_MAX_PROOFS_TO_AGGREGATE
343                );
344                Ok(groth16::aggregate::setup_fake_srs(
345                    rng,
346                    num_proofs_to_aggregate,
347                ))
348            } else {
349                bail!(
350                    "No cached srs key found for {} [failure finding {}]",
351                    id,
352                    cache_path.display()
353                );
354            }
355        };
356
357        // generate (or load) srs key
358        match read_cached_srs_key(&cache_path) {
359            Ok(key) => Ok(key),
360            Err(_) => write_cached_srs_key(&cache_path, generate()?).map_err(Into::into),
361        }
362    }
363
364    /// If the rng option argument is set, parameters will be
365    /// generated using it.  This is used for testing only, or where
366    /// parameters are otherwise unavailable (e.g. benches).  If rng
367    /// is not set, an error will result if parameters are not
368    /// present.
369    fn get_verifying_key<R: RngCore>(
370        #[allow(unused_variables)] rng: Option<&mut R>,
371        #[allow(unused_variables)] circuit: C,
372        pub_params: &P,
373    ) -> Result<groth16::VerifyingKey<Bls12>> {
374        let id = Self::cache_identifier(pub_params);
375
376        #[cfg(not(feature = "cuda-supraseal"))]
377        let generate = || -> Result<groth16::VerifyingKey<Bls12>> {
378            let groth_params = Self::get_groth_params(rng, circuit, pub_params)?;
379            info!("Getting verifying key. (id: {})", &id);
380            Ok(groth_params.vk)
381        };
382        #[cfg(feature = "cuda-supraseal")]
383        let generate = || -> Result<groth16::VerifyingKey<Bls12>> {
384            Err(anyhow::anyhow!("Cannot find parameters file. For SupraSeal it is expected that the parameter files already exist and don't need to be generated."))
385        };
386
387        // generate (or load) verifying key
388        let cache_path = ensure_ancestor_dirs_exist(parameter_cache_verifying_key_path(&id))?;
389        match read_cached_verifying_key(&cache_path) {
390            Ok(key) => Ok(key),
391            Err(_) => write_cached_verifying_key(&cache_path, generate()?).map_err(Into::into),
392        }
393    }
394}
395
396fn ensure_parent(path: &Path) -> io::Result<()> {
397    match path.parent() {
398        Some(dir) => {
399            create_dir_all(dir)?;
400            Ok(())
401        }
402        None => Ok(()),
403    }
404}
405
406type GetParameterDataCallback = fn(&str) -> Option<&ParameterData>;
407
408// This method verifies that the parameter/verifying_key file
409// specified appears in the parameters.json manifest and that the
410// content digest matches the recorded entry.
411pub fn verify_production_entry(
412    cache_entry_path: &Path,
413    cache_key: String,
414    selector: GetParameterDataCallback,
415) -> Result<bool> {
416    match selector(&cache_key) {
417        Some(data) => {
418            // Verify the actual hash only once per parameters file
419            let not_yet_verified = VERIFIED_PARAMETERS
420                .lock()
421                .expect("verified parameters lock failed")
422                .get(&cache_key)
423                .is_none();
424            if not_yet_verified {
425                info!("generating consistency digest for parameters");
426                let hash =
427                    with_exclusive_read_lock::<_, io::Error, _>(cache_entry_path, |mut file| {
428                        let mut hasher = Blake2bParams::new().to_state();
429                        io::copy(&mut file, &mut hasher).expect("copying file into hasher failed");
430                        Ok(hasher.finalize())
431                    })?;
432                info!("generated consistency digest for parameters");
433
434                // The hash in the parameters file is truncated to 256 bits.
435                let digest_hex = &hash.to_hex()[..32];
436                if digest_hex != data.digest {
437                    info!("parameter data is INVALID [{}]", digest_hex);
438                    return Err(
439                        Error::InvalidParameters(cache_entry_path.display().to_string()).into(),
440                    );
441                }
442
443                info!("parameter data is VALID [{}]", digest_hex);
444                VERIFIED_PARAMETERS
445                    .lock()
446                    .expect("verified parameters lock failed")
447                    .insert(cache_key);
448            }
449        }
450        None => {
451            return Err(Error::InvalidParameters(cache_entry_path.display().to_string()).into());
452        }
453    }
454
455    Ok(true)
456}
457
458/// Reads parameter from parameter cache.
459pub fn read_cached_params(cache_entry_path: &Path) -> Result<Bls12GrothParams> {
460    info!("checking cache_path: {:?} for parameters", cache_entry_path);
461
462    let verify_production_params = SETTINGS.verify_production_params;
463    info!(
464        "Verify production parameters is {}",
465        verify_production_params
466    );
467
468    // If the verify production params setting is used, we make sure
469    // that the path being accessed matches a production cache key,
470    // found in the 'parameters.json' file. The parameter data file is
471    // also hashed and matched against the hash in the
472    // 'parameters.json' file.
473    if verify_production_params {
474        let cache_key = cache_entry_path
475            .file_name()
476            .expect("failed to get cached parameter filename")
477            .to_str()
478            .expect("failed to convert to str")
479            .to_string();
480
481        let selector: GetParameterDataCallback = get_parameter_data_from_id;
482        verify_production_entry(cache_entry_path, cache_key, selector)?;
483    }
484
485    read_cached_params_inner(cache_entry_path).map_err(Into::into)
486}
487
488#[cfg(not(feature = "cuda-supraseal"))]
489fn read_cached_params_inner(
490    cache_entry_path: &Path,
491) -> std::result::Result<groth16::MappedParameters<Bls12>, io::Error> {
492    with_exclusive_read_lock(cache_entry_path, |_file| {
493        let mapped_params =
494            groth16::Parameters::build_mapped_parameters(cache_entry_path.to_path_buf(), false);
495        info!("read parameters from cache {:?} ", cache_entry_path);
496        mapped_params
497    })
498}
499
500#[cfg(feature = "cuda-supraseal")]
501fn read_cached_params_inner(
502    cache_entry_path: &Path,
503) -> std::result::Result<groth16::SuprasealParameters<Bls12>, io::Error> {
504    let supraseal_params = Bls12GrothParams::new(cache_entry_path.to_path_buf());
505    info!(
506        "read parameters into SuprasSeal from cache {:?} ",
507        cache_entry_path
508    );
509    supraseal_params
510}
511
512fn read_cached_verifying_key(cache_entry_path: &Path) -> Result<groth16::VerifyingKey<Bls12>> {
513    info!(
514        "checking cache_path: {:?} for verifying key",
515        cache_entry_path
516    );
517
518    let verify_production_params = SETTINGS.verify_production_params;
519    info!(
520        "Verify production parameters is {}",
521        verify_production_params
522    );
523
524    // If the verify production params setting is used, we make sure
525    // that the path being accessed matches a production cache key,
526    // found in the 'parameters.json' file. The parameter data file is
527    // also hashed and matched against the hash in the
528    // 'parameters.json' file.
529    if verify_production_params {
530        let cache_key = cache_entry_path
531            .file_name()
532            .expect("failed to get cached verifying key filename")
533            .to_str()
534            .expect("failed to convert to str")
535            .to_string();
536
537        let selector: GetParameterDataCallback = get_parameter_data_from_id;
538        verify_production_entry(cache_entry_path, cache_key, selector)?;
539    }
540
541    with_exclusive_read_lock(cache_entry_path, |file| {
542        let key = groth16::VerifyingKey::read(file)?;
543        info!("read verifying key from cache {:?} ", cache_entry_path);
544
545        Ok(key)
546    })
547}
548
549fn read_cached_srs_key(cache_entry_path: &Path) -> Result<groth16::aggregate::GenericSRS<Bls12>> {
550    info!("checking cache_path: {:?} for srs", cache_entry_path);
551
552    let verify_production_params = SETTINGS.verify_production_params;
553    info!(
554        "Verify production parameters is {}",
555        verify_production_params
556    );
557
558    // If the verify production params setting is used, we make sure
559    // that the path being accessed matches a production cache key,
560    // found in the 'srs-inner-product.json' file. The parameter data
561    // file is also hashed and matched against the hash in the
562    // 'srs-inner-product.json' file.
563    if verify_production_params {
564        let cache_key = cache_entry_path
565            .file_name()
566            .expect("failed to get cached srs filename")
567            .to_str()
568            .expect("failed to convert to str")
569            .to_string();
570
571        let selector: GetParameterDataCallback = get_srs_parameter_data_from_id;
572        verify_production_entry(cache_entry_path, cache_key, selector)?;
573    }
574
575    with_exclusive_read_lock(cache_entry_path, |file| {
576        let srs_map = unsafe { MmapOptions::new().map(file.as_ref())? };
577        // NOTE: We do not currently support lengths higher than this,
578        // even though the SRS file can handle up to (2 << 19) + 1
579        // elements.  Specifying under that limit speeds up
580        // performance quite a bit.
581        let max_len = (2 << 14) + 1;
582        let key = groth16::aggregate::GenericSRS::read_mmap(&srs_map, max_len)?;
583        info!("read srs key from cache {:?} ", cache_entry_path);
584
585        Ok(key)
586    })
587}
588
589fn read_cached_metadata(cache_entry_path: &Path) -> io::Result<CacheEntryMetadata> {
590    info!("checking cache_path: {:?} for metadata", cache_entry_path);
591    with_exclusive_read_lock(cache_entry_path, |file| {
592        let value = serde_json::from_reader(file)?;
593        info!("read metadata from cache {:?} ", cache_entry_path);
594
595        Ok(value)
596    })
597}
598
599fn write_cached_metadata(
600    cache_entry_path: &Path,
601    value: CacheEntryMetadata,
602) -> io::Result<CacheEntryMetadata> {
603    with_exclusive_lock(cache_entry_path, |file| {
604        serde_json::to_writer(file, &value)?;
605        info!("wrote metadata to cache {:?} ", cache_entry_path);
606
607        Ok(value)
608    })
609}
610
611fn write_cached_verifying_key(
612    cache_entry_path: &Path,
613    value: groth16::VerifyingKey<Bls12>,
614) -> io::Result<groth16::VerifyingKey<Bls12>> {
615    with_exclusive_lock(cache_entry_path, |mut file| {
616        value.write(&mut file)?;
617        file.flush()?;
618        info!("wrote verifying key to cache {:?} ", cache_entry_path);
619
620        Ok(value)
621    })
622}
623
624fn write_cached_srs_key(
625    cache_entry_path: &Path,
626    value: groth16::aggregate::GenericSRS<Bls12>,
627) -> io::Result<groth16::aggregate::GenericSRS<Bls12>> {
628    with_exclusive_lock(cache_entry_path, |mut file| {
629        value.write(&mut file)?;
630        file.flush()?;
631        info!("wrote srs key to cache {:?} ", cache_entry_path);
632
633        Ok(value)
634    })
635}
636
637fn write_cached_params(
638    cache_entry_path: &Path,
639    value: groth16::Parameters<Bls12>,
640) -> io::Result<groth16::Parameters<Bls12>> {
641    with_exclusive_lock(cache_entry_path, |mut file| {
642        value.write(&mut file)?;
643        file.flush()?;
644        info!("wrote groth parameters to cache {:?} ", cache_entry_path);
645
646        Ok(value)
647    })
648}
649
650pub fn with_exclusive_lock<T, E, F>(file_path: &Path, f: F) -> std::result::Result<T, E>
651where
652    F: FnOnce(&mut LockedFile) -> std::result::Result<T, E>,
653    E: From<io::Error>,
654{
655    with_open_file(file_path, LockedFile::open_exclusive, f)
656}
657
658pub fn with_exclusive_read_lock<T, E, F>(file_path: &Path, f: F) -> std::result::Result<T, E>
659where
660    F: FnOnce(&mut LockedFile) -> std::result::Result<T, E>,
661    E: From<io::Error>,
662{
663    with_open_file(file_path, LockedFile::open_exclusive_read, f)
664}
665
666pub fn with_open_file<'a, T, E, F, G>(
667    file_path: &'a Path,
668    open_file: G,
669    f: F,
670) -> std::result::Result<T, E>
671where
672    F: FnOnce(&mut LockedFile) -> std::result::Result<T, E>,
673    G: FnOnce(&'a Path) -> io::Result<LockedFile>,
674    E: From<io::Error>,
675{
676    ensure_parent(file_path)?;
677    f(&mut open_file(file_path)?)
678}