pg_embedded_setup_unpriv/cache/operations/
copy.rs1use std::{fs, io, path::Path};
6
7use camino::Utf8Path;
8use color_eyre::eyre::Context;
9use tracing::debug;
10
11use crate::error::BootstrapResult;
12
13const LOG_TARGET: &str = "pg_embed::cache";
15
16pub fn copy_from_cache(source: &Utf8Path, target: &Utf8Path) -> BootstrapResult<()> {
45 log_copy_start(source, target);
46
47 fs::create_dir_all(target)
48 .with_context(|| format!("failed to create target directory for cache copy: {target}"))?;
49
50 copy_dir_recursive(source.as_std_path(), target.as_std_path())
51 .with_context(|| format!("failed to copy cached binaries from {source} to {target}"))?;
52
53 log_copy_complete(source, target);
54 Ok(())
55}
56
57fn log_copy_start(source: &Utf8Path, target: &Utf8Path) {
59 debug!(
60 target: LOG_TARGET,
61 source = %source,
62 target = %target,
63 "copying binaries from cache"
64 );
65}
66
67fn log_copy_complete(source: &Utf8Path, target: &Utf8Path) {
69 debug!(
70 target: LOG_TARGET,
71 source = %source,
72 target = %target,
73 "cache copy completed"
74 );
75}
76
77pub(crate) fn copy_dir_recursive(src: &Path, dst: &Path) -> io::Result<()> {
81 if !dst.exists() {
82 fs::create_dir_all(dst)?;
83 }
84
85 for dir_entry in fs::read_dir(src)? {
86 let entry = dir_entry?;
87 let file_type = entry.file_type()?;
88 let src_path = entry.path();
89 let dst_path = dst.join(entry.file_name());
90
91 if file_type.is_dir() {
92 copy_dir_recursive(&src_path, &dst_path)?;
93 } else if file_type.is_symlink() {
94 copy_symlink(&src_path, &dst_path)?;
95 } else {
96 copy_file_with_permissions(&src_path, &dst_path)?;
97 }
98 }
99
100 copy_permissions(src, dst);
101
102 Ok(())
103}
104
105fn copy_file_with_permissions(src: &Path, dst: &Path) -> io::Result<()> {
107 if is_already_installed(src, dst) {
108 return Ok(());
109 }
110 fs::copy(src, dst)?;
111 copy_permissions(src, dst);
112 Ok(())
113}
114
115fn is_already_installed(src: &Path, dst: &Path) -> bool {
123 match (fs::metadata(src), fs::metadata(dst)) {
124 (Ok(source), Ok(target)) => target.is_file() && source.len() == target.len(),
125 _ => false,
126 }
127}
128
129fn copy_permissions(src: &Path, dst: &Path) {
131 let Ok(metadata) = fs::metadata(src) else {
132 return;
133 };
134 if let Err(err) = fs::set_permissions(dst, metadata.permissions()) {
135 log_permission_copy_failure(src, dst, &err);
136 }
137}
138
139fn log_permission_copy_failure(src: &Path, dst: &Path, err: &io::Error) {
141 debug!(
142 target: LOG_TARGET,
143 src = %src.display(),
144 dst = %dst.display(),
145 error = %err,
146 "failed to copy permissions (best effort)"
147 );
148}
149
150#[cfg(unix)]
152fn copy_symlink(src: &Path, dst: &Path) -> io::Result<()> {
153 let target = fs::read_link(src)?;
154 std::os::unix::fs::symlink(&target, dst)?;
155 Ok(())
156}
157
158#[cfg(not(unix))]
159fn copy_symlink(src: &Path, dst: &Path) -> io::Result<()> {
160 if src.is_file() {
163 fs::copy(src, dst)?;
164 } else if src.is_dir() {
165 copy_dir_recursive(src, dst)?;
166 } else {
167 return Err(io::Error::new(
168 io::ErrorKind::NotFound,
169 format!(
170 "symlink target does not exist or cannot be read: {}",
171 src.display()
172 ),
173 ));
174 }
175 Ok(())
176}