xtask_todo_lib/devshell/vm/
sync.rs1use std::path::{Path, PathBuf};
7
8use super::super::sandbox;
9use super::super::vfs::{Node, Vfs, VfsError};
10
11#[derive(Debug)]
13pub enum VmSyncError {
14 Io(std::io::Error),
15 Vfs(VfsError),
16 Sandbox(sandbox::SandboxError),
17}
18
19impl std::fmt::Display for VmSyncError {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 match self {
22 Self::Io(e) => write!(f, "{e}"),
23 Self::Vfs(e) => write!(f, "{e}"),
24 Self::Sandbox(e) => write!(f, "{e}"),
25 }
26 }
27}
28
29impl std::error::Error for VmSyncError {
30 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
31 match self {
32 Self::Io(e) => Some(e),
33 Self::Vfs(e) => Some(e),
34 Self::Sandbox(e) => Some(e),
35 }
36 }
37}
38
39fn vfs_child_vfs_path(parent: &str, name: &str) -> String {
40 let p = parent.trim_end_matches('/');
41 if p.is_empty() || p == "/" {
42 format!("/{name}")
43 } else {
44 format!("{p}/{name}")
45 }
46}
47
48fn walk_vfs_files_recurse(
49 vfs: &Vfs,
50 dir_vfs: &str,
51 rel: &Path,
52 out: &mut Vec<(PathBuf, Vec<u8>)>,
53) -> Result<(), VfsError> {
54 for name in vfs.list_dir(dir_vfs)? {
55 let child = vfs_child_vfs_path(dir_vfs, &name);
56 let n = vfs.resolve_absolute(&child)?;
57 match n {
58 Node::File { content, .. } => {
59 out.push((rel.join(&name), content));
60 }
61 Node::Dir { .. } => {
62 let next_rel = rel.join(&name);
63 walk_vfs_files_recurse(vfs, &child, &next_rel, out)?;
64 }
65 }
66 }
67 Ok(())
68}
69
70pub fn push_full(vfs: &Vfs, vfs_path: &str, workspace_parent: &Path) -> Result<(), VmSyncError> {
76 std::fs::create_dir_all(workspace_parent).map_err(VmSyncError::Io)?;
77 let host_leaf = sandbox::host_export_root(workspace_parent, vfs_path);
78 if host_leaf.exists() {
79 std::fs::remove_dir_all(&host_leaf).map_err(VmSyncError::Io)?;
80 }
81 vfs.copy_tree_to_host(vfs_path, workspace_parent)
82 .map_err(VmSyncError::Vfs)?;
83 let work_dir = sandbox::host_export_root(workspace_parent, vfs_path);
84 sandbox::restore_execute_bits_for_build_artifacts(&work_dir).map_err(VmSyncError::Sandbox)?;
85 Ok(())
86}
87
88pub fn push_incremental(
93 vfs: &Vfs,
94 vfs_path: &str,
95 workspace_parent: &Path,
96) -> Result<(), VmSyncError> {
97 let abs = vfs.resolve_to_absolute(vfs_path);
98 let host_root = sandbox::host_export_root(workspace_parent, vfs_path);
99 std::fs::create_dir_all(&host_root).map_err(VmSyncError::Io)?;
100
101 let mut files: Vec<(PathBuf, Vec<u8>)> = Vec::new();
102 let root_rel = PathBuf::new();
103 walk_vfs_files_recurse(vfs, &abs, &root_rel, &mut files).map_err(VmSyncError::Vfs)?;
104
105 for (rel, content) in files {
106 let host_file = host_root.join(&rel);
107 let write_it = match std::fs::read(&host_file) {
108 Ok(existing) => existing != content,
109 Err(e) if e.kind() == std::io::ErrorKind::NotFound => true,
110 Err(e) => return Err(VmSyncError::Io(e)),
111 };
112 if write_it {
113 if let Some(d) = host_file.parent() {
114 std::fs::create_dir_all(d).map_err(VmSyncError::Io)?;
115 }
116 std::fs::write(&host_file, &content).map_err(VmSyncError::Io)?;
117 }
118 }
119
120 sandbox::restore_execute_bits_for_build_artifacts(&host_root).map_err(VmSyncError::Sandbox)?;
121 Ok(())
122}
123
124pub fn pull_workspace_to_vfs(
131 workspace_parent: &Path,
132 vfs_path: &str,
133 vfs: &mut Vfs,
134) -> Result<(), sandbox::SandboxError> {
135 sandbox::sync_host_dir_to_vfs(workspace_parent, vfs_path, vfs)
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn push_incremental_writes_only_changed_file() {
144 let mut vfs = Vfs::new();
145 vfs.mkdir("/p").unwrap();
146 vfs.write_file("/p/a.txt", b"one").unwrap();
147 vfs.write_file("/p/b.txt", b"fix").unwrap();
148
149 let base = std::env::temp_dir().join(format!(
150 "devshell_vm_sync_{}_{}",
151 std::process::id(),
152 std::time::SystemTime::now()
153 .duration_since(std::time::UNIX_EPOCH)
154 .unwrap()
155 .as_nanos()
156 ));
157 let _ = std::fs::remove_dir_all(&base);
158 push_full(&vfs, "/p", &base).unwrap();
159
160 let p_host = base.join("p");
161 assert_eq!(
162 std::fs::read_to_string(p_host.join("a.txt")).unwrap(),
163 "one"
164 );
165 assert_eq!(
166 std::fs::read_to_string(p_host.join("b.txt")).unwrap(),
167 "fix"
168 );
169
170 vfs.write_file("/p/a.txt", b"two").unwrap();
171 push_incremental(&vfs, "/p", &base).unwrap();
172 assert_eq!(
173 std::fs::read_to_string(p_host.join("a.txt")).unwrap(),
174 "two"
175 );
176 assert_eq!(
177 std::fs::read_to_string(p_host.join("b.txt")).unwrap(),
178 "fix"
179 );
180
181 let mut vfs2 = Vfs::new();
182 vfs2.mkdir("/p").unwrap();
183 pull_workspace_to_vfs(&base, "/p", &mut vfs2).unwrap();
184 assert_eq!(vfs2.read_file("/p/a.txt").unwrap(), b"two");
185 assert_eq!(vfs2.read_file("/p/b.txt").unwrap(), b"fix");
186
187 let _ = std::fs::remove_dir_all(&base);
188 }
189}