vdrive.rs
1 //! Virtual drive handling for ambient-run. 2 3 use std::{ 4 fs::{File, OpenOptions}, 5 path::{Path, PathBuf}, 6 process::Command, 7 }; 8 9 /// A virtual drive. 10 #[derive(Debug, Clone)] 11 pub struct VirtualDrive { 12 filename: PathBuf, 13 } 14 15 impl VirtualDrive { 16 /// Path to file containing virtual drive. 17 pub fn filename(&self) -> &Path { 18 self.filename.as_path() 19 } 20 21 /// Extract files in the virtual drive into a directory. Create 22 /// the directory if it doesn't exist. 23 pub fn extract_to(&self, dirname: &Path) -> Result<(), VirtualDriveError> { 24 if !dirname.exists() { 25 std::fs::create_dir(dirname) 26 .map_err(|e| VirtualDriveError::Extract(dirname.into(), e))?; 27 } 28 tar_extract(&self.filename, dirname)?; 29 Ok(()) 30 } 31 } 32 33 /// Builder for [`VirtualDrive`]. 34 #[derive(Debug, Default)] 35 pub struct VirtualDriveBuilder { 36 filename: Option<PathBuf>, 37 root: Option<PathBuf>, 38 size: Option<u64>, 39 } 40 41 impl VirtualDriveBuilder { 42 /// Set filename for virtual drive. 43 pub fn filename(mut self, filename: &Path) -> Self { 44 self.filename = Some(filename.into()); 45 self 46 } 47 48 /// Set directory of tree to copy into virtual drive. 49 pub fn root_directory(mut self, dirname: &Path) -> Self { 50 self.root = Some(dirname.into()); 51 self 52 } 53 54 /// Set size of new drive. This is important when the build VM 55 /// writes to the drive. 56 pub fn size(mut self, size: u64) -> Self { 57 self.size = Some(size); 58 self 59 } 60 61 /// Create a virtual drive. 62 pub fn create(self) -> Result<VirtualDrive, VirtualDriveError> { 63 let filename = self.filename.expect("filename has been set"); 64 65 // Create the file, either empty or to the desired size. If we 66 // don't have self.root set, the file created (and maybe 67 // truncated) will be. 68 { 69 let archive = File::create(&filename) 70 .map_err(|e| VirtualDriveError::Create(filename.clone(), e))?; 71 if let Some(size) = self.size { 72 archive 73 .set_len(size) 74 .map_err(|e| VirtualDriveError::Create(filename.clone(), e))?; 75 } 76 } 77 78 if let Some(root) = self.root { 79 match tar_create(&filename, &root) { 80 Ok(_) => (), 81 Err(VirtualDriveError::TarFailed(cmd, filename, exit, stderr)) => { 82 Err(VirtualDriveError::TarFailed(cmd, filename, exit, stderr))?; 83 } 84 Err(err) => { 85 Err(err)?; 86 } 87 } 88 } 89 90 Ok(VirtualDrive { filename }) 91 } 92 93 /// Open an existing virtual drive. 94 pub fn open(self) -> Result<VirtualDrive, VirtualDriveError> { 95 let filename = self.filename.expect("filename has been set"); 96 Ok(VirtualDrive { filename }) 97 } 98 } 99 100 /// Create a tar archive out of a directory. 101 pub fn create_tar( 102 tar_filename: PathBuf, 103 dirname: &Path, 104 ) -> Result<VirtualDrive, VirtualDriveError> { 105 assert!(!tar_filename.starts_with(dirname)); 106 VirtualDriveBuilder::default() 107 .filename(&tar_filename) 108 .root_directory(dirname) 109 .create() 110 } 111 112 /// Create a tar archive with a fixed length, out of a directory. 113 pub fn create_tar_with_size( 114 tar_filename: PathBuf, 115 dirname: &Path, 116 size: u64, 117 ) -> Result<VirtualDrive, VirtualDriveError> { 118 let tar = VirtualDriveBuilder::default() 119 .filename(&tar_filename) 120 .root_directory(dirname) 121 .size(size) 122 .create()?; 123 124 let metadata = std::fs::metadata(&tar_filename) 125 .map_err(|err| VirtualDriveError::Metadata(tar_filename.clone(), err))?; 126 if metadata.len() < size { 127 let file = OpenOptions::new() 128 .write(true) 129 .truncate(false) 130 .open(&tar_filename) 131 .map_err(|err| VirtualDriveError::CreateTar(tar_filename.clone(), err))?; 132 file.set_len(size) 133 .map_err(|err| VirtualDriveError::SetLen(size, tar_filename.clone(), err))?; 134 } 135 136 let metadata = std::fs::metadata(&tar_filename) 137 .map_err(|err| VirtualDriveError::Metadata(tar_filename.clone(), err))?; 138 if metadata.len() > size { 139 return Err(VirtualDriveError::DriveTooBig(metadata.len(), size)); 140 } 141 142 Ok(tar) 143 } 144 145 fn tar_create(tar_filename: &Path, dirname: &Path) -> Result<(), VirtualDriveError> { 146 let output = Command::new("tar") 147 .arg("-cvf") 148 .arg(tar_filename) 149 .arg("-C") 150 .arg(dirname) 151 .arg(".") 152 .output() 153 .map_err(|err| VirtualDriveError::Tar("create", dirname.into(), err))?; 154 155 if let Some(exit) = output.status.code() { 156 if exit != 0 { 157 let stderr = String::from_utf8_lossy(&output.stderr).to_string(); 158 return Err(VirtualDriveError::TarFailed( 159 "create", 160 dirname.into(), 161 exit, 162 stderr, 163 )); 164 } 165 } 166 167 Ok(()) 168 } 169 170 fn tar_extract(tar_filename: &Path, dirname: &Path) -> Result<(), VirtualDriveError> { 171 let output = Command::new("tar") 172 .arg("-xvvvf") 173 .arg(tar_filename) 174 .arg("-C") 175 .arg(dirname) 176 .arg("--no-same-owner") 177 .output() 178 .map_err(|err| VirtualDriveError::Tar("extract", dirname.into(), err))?; 179 180 if let Some(exit) = output.status.code() { 181 if exit != 0 { 182 let stderr = String::from_utf8_lossy(&output.stderr).to_string(); 183 return Err(VirtualDriveError::TarFailed( 184 "extract", 185 dirname.into(), 186 exit, 187 stderr, 188 )); 189 } 190 } 191 192 Ok(()) 193 } 194 195 /// Errors that may be returned from [`VirtualDrive`] use. 196 #[allow(missing_docs)] 197 #[derive(Debug, thiserror::Error)] 198 pub enum VirtualDriveError { 199 #[error("failed to create virtual drive {0}")] 200 Create(PathBuf, #[source] std::io::Error), 201 202 #[error("failed to create tar archive for virtual drive from {0}")] 203 CreateTar(PathBuf, #[source] std::io::Error), 204 205 #[error("failed to open virtual drive {0}")] 206 Open(PathBuf, #[source] std::io::Error), 207 208 #[error("failed to list files in virtual drive {0}")] 209 List(PathBuf, #[source] std::io::Error), 210 211 #[error("failed to create directory {0}")] 212 Extract(PathBuf, #[source] std::io::Error), 213 214 #[error("failed to extract {0} to {1}")] 215 ExtractEntry(PathBuf, PathBuf, #[source] std::io::Error), 216 217 #[error(transparent)] 218 Util(#[from] crate::util::UtilError), 219 220 #[error("failed to get length of file {0}")] 221 Metadata(PathBuf, #[source] std::io::Error), 222 223 #[error("failed to set length of file to {0}: {1}")] 224 SetLen(u64, PathBuf, #[source] std::io::Error), 225 226 #[error("virtual drive is too big: {0} > {1}")] 227 DriveTooBig(u64, u64), 228 229 /// Can't run tar. 230 #[error("failed to run system tar command: {0}: {1}")] 231 Tar(&'static str, PathBuf, #[source] std::io::Error), 232 233 /// Tar failed. 234 #[error("failed to run system tar command: {0}: {1}")] 235 TarFailed(&'static str, PathBuf, i32, String), 236 }