1use crate::retrieve::{RetrievalMetadata, RetrievedDigest};
2use anyhow::Context;
3use sha2::{Sha256, Sha512};
4use std::{path::Path, time::SystemTime};
5use tokio::fs;
6
7pub const ATTR_ETAG: &str = "etag";
8
9#[derive(Debug, thiserror::Error)]
10pub enum StoreError {
11 #[error("{0:#}")]
12 Io(anyhow::Error),
13 #[error("Failed to construct filename from URL: {0}")]
14 Filename(String),
15 #[error("Serialize key error: {0:#}")]
16 SerializeKey(anyhow::Error),
17}
18
19pub struct Document<'a> {
20 pub data: &'a [u8],
22 pub sha256: &'a Option<RetrievedDigest<Sha256>>,
24 pub sha512: &'a Option<RetrievedDigest<Sha512>>,
26 pub signature: &'a Option<String>,
28
29 pub changed: SystemTime,
31
32 pub metadata: &'a RetrievalMetadata,
34
35 pub no_timestamps: bool,
36 pub no_xattrs: bool,
37}
38
39pub async fn store_document(file: &Path, document: Document<'_>) -> Result<(), StoreError> {
40 log::debug!("Writing {}", file.display());
41
42 if let Some(parent) = file.parent() {
43 fs::create_dir_all(parent)
44 .await
45 .with_context(|| format!("Failed to create parent directory: {}", parent.display()))
46 .map_err(StoreError::Io)?;
47 }
48
49 fs::write(&file, document.data)
50 .await
51 .with_context(|| format!("Failed to write advisory: {}", file.display()))
52 .map_err(StoreError::Io)?;
53
54 if let Some(sha256) = &document.sha256 {
55 let file = format!("{}.sha256", file.display());
56 fs::write(&file, &sha256.expected)
57 .await
58 .with_context(|| format!("Failed to write checksum: {file}"))
59 .map_err(StoreError::Io)?;
60 }
61 if let Some(sha512) = &document.sha512 {
62 let file = format!("{}.sha512", file.display());
63 fs::write(&file, &sha512.expected)
64 .await
65 .with_context(|| format!("Failed to write checksum: {file}"))
66 .map_err(StoreError::Io)?;
67 }
68 if let Some(sig) = &document.signature {
69 let file = format!("{}.asc", file.display());
70 fs::write(&file, &sig)
71 .await
72 .with_context(|| format!("Failed to write signature: {file}"))
73 .map_err(StoreError::Io)?;
74 }
75
76 if !document.no_timestamps {
77 let mtime = document
80 .metadata
81 .last_modification
82 .map(SystemTime::from)
83 .unwrap_or_else(|| document.changed)
84 .into();
85 filetime::set_file_mtime(file, mtime)
86 .with_context(|| {
87 format!(
88 "Failed to set last modification timestamp: {}",
89 file.display()
90 )
91 })
92 .map_err(StoreError::Io)?;
93 }
94
95 if !document.no_xattrs {
96 if let Some(etag) = &document.metadata.etag {
97 fsquirrel::set(file, ATTR_ETAG, etag.as_bytes())
98 .with_context(|| format!("Failed to store {}: {}", ATTR_ETAG, file.display()))
99 .map_err(StoreError::Io)?;
100 }
101 }
102
103 Ok(())
104}