harmos_runtime/filesystem/publication/
mod.rs1#[cfg(unix)]
13mod unix;
14
15#[cfg(windows)]
17#[allow(unsafe_code)]
18mod windows;
19
20use std::{
26 fs::{self, File, OpenOptions},
27 io::{self, Write},
28 path::{Path, PathBuf},
29};
30use tempfile::NamedTempFile;
32#[cfg(unix)]
34use unix as platform;
35
36#[cfg(windows)]
37use windows as platform;
38
39struct Staging(PathBuf);
48
49#[derive(Debug, thiserror::Error)]
52pub enum ConfirmError<E> {
53 #[error(transparent)]
54 Filesystem(crate::filesystem::Error),
55
56 #[error(transparent)]
57 Checkpoint(E),
58}
59
60impl Staging {
65 fn file(directory: &Path) -> io::Result<(Self, File)> {
66 let temporary = NamedTempFile::new_in(directory)?;
67 let (file, path) = temporary.keep().map_err(|error| error.error)?;
68 Ok((Self(path), file))
69 }
70}
71
72impl Drop for Staging {
73 fn drop(&mut self) {
74 let _ = fs::remove_file(&self.0);
75 }
76}
77
78pub fn create_dir_all(path: &Path) -> crate::filesystem::Result<()> {
81 platform::create_dir_all(path).map_err(|error| crate::filesystem::Error::publication("create directory", path, error))
82}
83
84pub fn move_directory(source: &Path, destination: &Path) -> crate::filesystem::Result<()> {
85 let metadata =
86 fs::symlink_metadata(source).map_err(|error| crate::filesystem::Error::publication("inspect directory", source, error))?;
87 if !metadata.is_dir() || metadata.file_type().is_symlink() {
88 return Err(crate::filesystem::Error::publication(
89 "move directory",
90 source,
91 io::Error::other("source must be a regular directory"),
92 ));
93 }
94 platform::move_directory(source, destination)
95 .map_err(|error| crate::filesystem::Error::publication("move directory", destination, error))
96}
97
98pub fn install(source: &Path, destination: &Path) -> crate::filesystem::Result<()> {
99 sync_file(source).map_err(|error| crate::filesystem::Error::publication("flush staged file", source, error))?;
100 platform::install(source, destination).map_err(|error| crate::filesystem::Error::publication("install file", destination, error))
101}
102
103pub fn replace(source: &Path, destination: &Path) -> crate::filesystem::Result<()> {
104 sync_file(source).map_err(|error| crate::filesystem::Error::publication("flush staged file", source, error))?;
105 platform::replace(source, destination).map_err(|error| crate::filesystem::Error::publication("replace file", destination, error))
106}
107
108pub fn remove(path: &Path) -> crate::filesystem::Result<()> {
109 match fs::symlink_metadata(parent(path)) {
110 Ok(metadata) if !metadata.is_dir() || metadata.file_type().is_symlink() => {
111 return Err(crate::filesystem::Error::publication(
112 "remove file",
113 path,
114 io::Error::new(io::ErrorKind::InvalidInput, "parent must be a regular directory"),
115 ));
116 }
117 Ok(_) => (),
118 Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),
119 Err(error) => {
120 return Err(crate::filesystem::Error::publication("inspect parent", path, error));
121 }
122 }
123 match fs::symlink_metadata(path) {
124 Ok(metadata) if !metadata.is_file() || metadata.file_type().is_symlink() => {
125 return Err(crate::filesystem::Error::publication(
126 "remove file",
127 path,
128 io::Error::new(io::ErrorKind::InvalidInput, "source must be a regular file"),
129 ));
130 }
131 Ok(_) => (),
132 Err(error) if error.kind() == io::ErrorKind::NotFound => (),
133 Err(error) => {
134 return Err(crate::filesystem::Error::publication("inspect file", path, error));
135 }
136 }
137 platform::remove(path).map_err(|error| crate::filesystem::Error::publication("remove file", path, error))
138}
139
140pub fn confirm(path: &Path) -> crate::filesystem::Result<()> {
141 match confirm_with_checkpoint(path, || Ok::<(), std::convert::Infallible>(())) {
142 Ok(()) => Ok(()),
143 Err(ConfirmError::Filesystem(error)) => Err(error),
144 Err(ConfirmError::Checkpoint(error)) => match error {},
145 }
146}
147
148pub fn confirm_with_checkpoint<E>(path: &Path, mut checkpoint: impl FnMut() -> Result<(), E>) -> Result<(), ConfirmError<E>> {
149 platform::confirm_with_checkpoint(path, &mut checkpoint)
150}
151
152#[tracing::instrument(level = "debug", skip_all)]
153pub fn write(path: &Path, bytes: &[u8]) -> crate::filesystem::Result<()> {
154 write_document(path, bytes).map_err(|error| crate::filesystem::Error::publication("write document", path, error))
155}
156
157fn confirm_error<E>(path: &Path, error: io::Error) -> ConfirmError<E> {
160 let error = crate::filesystem::Error::publication("confirm file durability", path, error);
161 ConfirmError::Filesystem(error)
162}
163
164fn write_document(path: &Path, bytes: &[u8]) -> io::Result<()> {
165 create_dir_all(parent(path))?;
166 let (staging, mut file) = Staging::file(parent(path))?;
167 file.write_all(bytes)?;
168 file.sync_all()?;
169 drop(file);
170 replace(&staging.0, path).map_err(Into::into)
171}
172
173fn sync_file(path: &Path) -> io::Result<()> {
174 OpenOptions::new().read(true).write(true).open(path)?.sync_all()
177}
178
179fn parent(path: &Path) -> &Path {
180 path.parent().filter(|parent| !parent.as_os_str().is_empty()).unwrap_or(Path::new("."))
181}
182
183