ic_backup/ops/persistence/json/
mod.rs1use crate::ops::persistence::PersistenceError;
8
9use std::{
10 fs::{self, File},
11 io::{self, Write},
12 path::Path,
13};
14
15use serde::{Serialize, de::DeserializeOwned};
16
17use ic_host_fs::durable::{PublicationMode, WriteOptions};
18
19pub(super) fn check_json_size(
21 value: &impl Serialize,
22 max_bytes: u64,
23) -> Result<(), PersistenceError> {
24 let mut writer = ic_host_artifacts::artifact::BoundedWriter::new(io::sink(), max_bytes);
25 let result = serde_json::to_writer_pretty(&mut writer, value);
26 if writer.limit_exceeded() {
27 return Err(PersistenceError::RecordTooLarge { limit: max_bytes });
28 }
29 result?;
30 Ok(())
31}
32
33pub fn write_json_durable<T>(path: &Path, value: &T) -> Result<(), PersistenceError>
39where
40 T: Serialize,
41{
42 let bytes = serde_json::to_vec_pretty(value)?;
43 publish_bytes_at_barriers(path, &bytes, PublicationMode::Replace, |_| {})
44}
45
46pub fn create_json_durable<T>(path: &Path, value: &T) -> Result<(), PersistenceError>
52where
53 T: Serialize,
54{
55 let bytes = serde_json::to_vec_pretty(value)?;
56 publish_bytes_at_barriers(path, &bytes, PublicationMode::CreateNew, |_| {})
57}
58
59pub fn read_json<T>(path: &Path, max_bytes: u64) -> Result<T, PersistenceError>
67where
68 T: DeserializeOwned,
69{
70 #[cfg(unix)]
71 {
72 let bytes = ic_host_fs::read::read_file_no_follow(
73 path,
74 usize::try_from(max_bytes).unwrap_or(usize::MAX),
75 )
76 .map_err(|error| record_read_error(error, max_bytes))?;
77 Ok(serde_json::from_slice(&bytes)?)
78 }
79 #[cfg(not(unix))]
80 {
81 let _ = (path, max_bytes);
82 Err(io::Error::from(io::ErrorKind::Unsupported).into())
83 }
84}
85
86#[cfg(unix)]
87fn record_read_error(
88 error: ic_host_artifacts::artifact::ArtifactError,
89 max_bytes: u64,
90) -> PersistenceError {
91 use ic_host_artifacts::artifact::ArtifactError;
92 match error {
93 ArtifactError::NotRegularFile => PersistenceError::Io(io::Error::new(
94 io::ErrorKind::InvalidInput,
95 "record must be a regular file",
96 )),
97 ArtifactError::LimitExceeded { .. } => {
98 PersistenceError::RecordTooLarge { limit: max_bytes }
99 }
100 error => PersistenceError::Io(error.into()),
101 }
102}
103
104#[derive(Clone, Copy, Debug, Eq, PartialEq)]
105pub(crate) enum DurableWriteBarrier {
106 BeforeRename,
107 AfterDirectorySync,
108}
109
110fn publish_bytes_at_barriers(
112 path: &Path,
113 bytes: &[u8],
114 mode: PublicationMode,
115 mut barrier: impl FnMut(DurableWriteBarrier),
116) -> Result<(), PersistenceError> {
117 let parent = path
118 .parent()
119 .filter(|parent| !parent.as_os_str().is_empty())
120 .unwrap_or_else(|| Path::new("."));
121 create_private_parents(parent)?;
122 let options = WriteOptions {
123 mode,
124 permissions: 0o600,
125 };
126 let produce = |file: &mut File| -> io::Result<()> {
127 file.write_all(bytes)?;
128 file.sync_all()?;
131 barrier(DurableWriteBarrier::BeforeRename);
132 Ok(())
133 };
134 #[cfg(unix)]
135 let result = {
136 use std::os::fd::AsFd;
137 let name = path.file_name().ok_or_else(|| {
138 io::Error::new(
139 io::ErrorKind::InvalidInput,
140 "durable write target has no filename",
141 )
142 })?;
143 let directory = File::open(parent)?;
144 ic_host_fs::durable::write_at_with(directory.as_fd(), name, options, produce)
145 };
146 #[cfg(not(unix))]
147 let result = ic_host_fs::durable::write_typed_with(path, options, produce);
148 result.map_err(PersistenceError::Publication)?;
149 barrier(DurableWriteBarrier::AfterDirectorySync);
151 Ok(())
152}
153
154#[cfg(test)]
155pub(crate) fn write_json_durable_at_barriers<T>(
156 path: &Path,
157 value: &T,
158 barrier: impl FnMut(DurableWriteBarrier),
159) -> Result<(), PersistenceError>
160where
161 T: Serialize,
162{
163 let bytes = serde_json::to_vec_pretty(value)?;
164 publish_bytes_at_barriers(path, &bytes, PublicationMode::Replace, barrier)
165}
166
167#[cfg(test)]
168pub(crate) fn create_json_durable_at_barriers<T>(
169 path: &Path,
170 value: &T,
171 mut before_publication: impl FnMut(),
172 mut after_directory_sync: impl FnMut(),
173) -> Result<(), PersistenceError>
174where
175 T: Serialize,
176{
177 let bytes = serde_json::to_vec_pretty(value)?;
178 publish_bytes_at_barriers(
179 path,
180 &bytes,
181 PublicationMode::CreateNew,
182 |barrier| match barrier {
183 DurableWriteBarrier::BeforeRename => before_publication(),
184 DurableWriteBarrier::AfterDirectorySync => after_directory_sync(),
185 },
186 )
187}
188
189#[cfg(test)]
194mod regressions;
195#[cfg(test)]
196mod tests;
197
198fn create_private_parents(parent: &Path) -> io::Result<()> {
199 let mut missing = Vec::new();
200 let mut current = parent;
201 while !current.try_exists()? {
202 missing.push(current.to_path_buf());
203 current = current
204 .parent()
205 .filter(|path| !path.as_os_str().is_empty())
206 .unwrap_or_else(|| Path::new("."));
207 }
208 let mut builder = fs::DirBuilder::new();
209 builder.recursive(true);
210 #[cfg(unix)]
211 {
212 use std::os::unix::fs::DirBuilderExt;
213 builder.mode(0o700);
214 }
215 builder.create(parent)?;
216 for directory in missing {
218 File::open(&directory)?.sync_all()?;
219 let ancestor = directory
220 .parent()
221 .filter(|path| !path.as_os_str().is_empty())
222 .unwrap_or_else(|| Path::new("."));
223 File::open(ancestor)?.sync_all()?;
224 }
225 Ok(())
226}