ic_backup/ops/persistence/json/
mod.rs1use crate::ops::persistence::PersistenceError;
8
9use std::{
10 fs::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 encode_into(value, io::sink(), max_bytes).map(|_| ())
25}
26
27pub(super) fn encode_json(
29 value: &impl Serialize,
30 max_bytes: u64,
31) -> Result<Vec<u8>, PersistenceError> {
32 encode_into(value, Vec::new(), max_bytes)
33}
34
35fn encode_into<W: Write>(
36 value: &impl Serialize,
37 sink: W,
38 max_bytes: u64,
39) -> Result<W, PersistenceError> {
40 let mut writer = ic_host_artifacts::artifact::BoundedWriter::new(sink, max_bytes);
41 let result = serde_json::to_writer_pretty(&mut writer, value);
42 if writer.limit_exceeded() {
43 return Err(PersistenceError::RecordTooLarge { limit: max_bytes });
44 }
45 result?;
46 Ok(writer.into_inner())
47}
48
49pub(super) fn write_encoded(path: &Path, bytes: &[u8]) -> Result<(), PersistenceError> {
50 publish_bytes_at_barriers(path, bytes, PublicationMode::Replace, |_| {})
51}
52
53pub(super) fn create_encoded(path: &Path, bytes: &[u8]) -> Result<(), PersistenceError> {
54 publish_bytes_at_barriers(path, bytes, PublicationMode::CreateNew, |_| {})
55}
56
57pub fn write_json_durable<T>(path: &Path, value: &T) -> Result<(), PersistenceError>
63where
64 T: Serialize,
65{
66 let bytes = encode_json(value, u64::MAX)?;
67 write_encoded(path, &bytes)
68}
69
70pub fn create_json_durable<T>(path: &Path, value: &T) -> Result<(), PersistenceError>
76where
77 T: Serialize,
78{
79 let bytes = encode_json(value, u64::MAX)?;
80 create_encoded(path, &bytes)
81}
82
83pub fn read_json<T>(path: &Path, max_bytes: u64) -> Result<T, PersistenceError>
91where
92 T: DeserializeOwned,
93{
94 #[cfg(unix)]
95 {
96 let bytes = ic_host_fs::read::read_file_no_follow(
97 path,
98 usize::try_from(max_bytes).unwrap_or(usize::MAX),
99 )
100 .map_err(|error| record_read_error(error, max_bytes))?;
101 Ok(serde_json::from_slice(&bytes)?)
102 }
103 #[cfg(not(unix))]
104 {
105 let _ = (path, max_bytes);
106 Err(io::Error::from(io::ErrorKind::Unsupported).into())
107 }
108}
109
110#[cfg(unix)]
111fn record_read_error(
112 error: ic_host_artifacts::artifact::ArtifactError,
113 max_bytes: u64,
114) -> PersistenceError {
115 use ic_host_artifacts::artifact::ArtifactError;
116 match error {
117 ArtifactError::NotRegularFile => PersistenceError::Io(io::Error::new(
118 io::ErrorKind::InvalidInput,
119 "record must be a regular file",
120 )),
121 ArtifactError::LimitExceeded { .. } => {
122 PersistenceError::RecordTooLarge { limit: max_bytes }
123 }
124 error => PersistenceError::Io(error.into()),
125 }
126}
127
128#[derive(Clone, Copy, Debug, Eq, PartialEq)]
129pub(crate) enum DurableWriteBarrier {
130 BeforeRename,
131 AfterDirectorySync,
132}
133
134pub(super) fn publish_bytes_at_barriers(
136 path: &Path,
137 bytes: &[u8],
138 mode: PublicationMode,
139 mut barrier: impl FnMut(DurableWriteBarrier),
140) -> Result<(), PersistenceError> {
141 let parent = path
142 .parent()
143 .filter(|parent| !parent.as_os_str().is_empty())
144 .unwrap_or_else(|| Path::new("."));
145 ic_host_fs::durable::prepare_private_parents(parent)?;
146 let options = WriteOptions {
147 mode,
148 permissions: 0o600,
149 };
150 let produce = |file: &mut File| file.write_all(bytes);
151 #[cfg(unix)]
152 let result = {
153 use std::os::fd::AsFd;
154 let name = path.file_name().ok_or_else(|| {
155 io::Error::new(
156 io::ErrorKind::InvalidInput,
157 "durable write target has no filename",
158 )
159 })?;
160 let directory = File::open(parent)?;
161 ic_host_fs::durable::write_at_validated_with(
162 directory.as_fd(),
163 name,
164 options,
165 produce,
166 |()| {
167 barrier(DurableWriteBarrier::BeforeRename);
168 Ok(())
169 },
170 )
171 };
172 #[cfg(not(unix))]
173 let result = ic_host_fs::durable::write_validated_with(path, options, produce, |_, ()| {
174 barrier(DurableWriteBarrier::BeforeRename);
175 Ok(())
176 });
177 result.map_err(|error| {
178 PersistenceError::Publication(super::PublicationError::from_host(error))
179 })?;
180 barrier(DurableWriteBarrier::AfterDirectorySync);
182 Ok(())
183}
184
185#[cfg(test)]
186pub(crate) fn write_json_durable_at_barriers<T>(
187 path: &Path,
188 value: &T,
189 barrier: impl FnMut(DurableWriteBarrier),
190) -> Result<(), PersistenceError>
191where
192 T: Serialize,
193{
194 let bytes = serde_json::to_vec_pretty(value)?;
195 publish_bytes_at_barriers(path, &bytes, PublicationMode::Replace, barrier)
196}
197
198#[cfg(test)]
199pub(crate) fn create_json_durable_at_barriers<T>(
200 path: &Path,
201 value: &T,
202 mut before_publication: impl FnMut(),
203 mut after_directory_sync: impl FnMut(),
204) -> Result<(), PersistenceError>
205where
206 T: Serialize,
207{
208 let bytes = serde_json::to_vec_pretty(value)?;
209 publish_bytes_at_barriers(
210 path,
211 &bytes,
212 PublicationMode::CreateNew,
213 |barrier| match barrier {
214 DurableWriteBarrier::BeforeRename => before_publication(),
215 DurableWriteBarrier::AfterDirectorySync => after_directory_sync(),
216 },
217 )
218}
219
220#[cfg(test)]
225mod regressions;
226#[cfg(test)]
227mod tests;