1use std::cmp::min;
8use std::fmt::Debug;
9use std::fs::File;
10use std::io;
11use std::io::Seek;
12use std::io::SeekFrom;
13use std::path::PathBuf;
14use std::sync::Arc;
15
16use async_trait::async_trait;
17use base::info;
18use base::AsRawDescriptors;
19use base::FileAllocate;
20use base::FileReadWriteAtVolatile;
21use base::FileSetLen;
22use cros_async::BackingMemory;
23use cros_async::Executor;
24use cros_async::IoOptions;
25use cros_async::IoSource;
26use cros_async::MemRegionIter;
27use thiserror::Error as ThisError;
28
29mod asynchronous;
30#[allow(unused)]
31pub(crate) use asynchronous::AsyncDiskFileWrapper;
32#[cfg(feature = "qcow")]
33mod qcow;
34#[cfg(feature = "qcow")]
35pub use qcow::QcowFile;
36#[cfg(feature = "qcow")]
37pub use qcow::QCOW_MAGIC;
38mod sys;
39
40#[cfg(feature = "composite-disk")]
41mod composite;
42#[cfg(feature = "composite-disk")]
43use composite::CompositeDiskFile;
44#[cfg(feature = "composite-disk")]
45use composite::CDISK_MAGIC;
46#[cfg(feature = "composite-disk")]
47mod gpt;
48#[cfg(feature = "composite-disk")]
49pub use composite::create_composite_disk;
50#[cfg(feature = "composite-disk")]
51pub use composite::create_zero_filler;
52#[cfg(feature = "composite-disk")]
53pub use composite::Error as CompositeError;
54#[cfg(feature = "composite-disk")]
55pub use composite::ImagePartitionType;
56#[cfg(feature = "composite-disk")]
57pub use composite::PartitionInfo;
58#[cfg(feature = "composite-disk")]
59pub use gpt::Error as GptError;
60
61#[cfg(feature = "android-sparse")]
62mod android_sparse;
63#[cfg(feature = "android-sparse")]
64use android_sparse::AndroidSparse;
65#[cfg(feature = "android-sparse")]
66use android_sparse::SPARSE_HEADER_MAGIC;
67use sys::read_from_disk;
68
69#[cfg(feature = "zstd")]
70mod zstd;
71#[cfg(feature = "zstd")]
72use zstd::ZstdDisk;
73#[cfg(feature = "zstd")]
74use zstd::ZSTD_FRAME_MAGIC;
75#[cfg(feature = "zstd")]
76use zstd::ZSTD_SKIPPABLE_MAGIC_HIGH;
77#[cfg(feature = "zstd")]
78use zstd::ZSTD_SKIPPABLE_MAGIC_LOW;
79
80const MAX_NESTING_DEPTH: u32 = 10;
82
83#[derive(ThisError, Debug)]
84pub enum Error {
85 #[error("failed to create block device: {0}")]
86 BlockDeviceNew(base::Error),
87 #[error("requested file conversion not supported")]
88 ConversionNotSupported,
89 #[cfg(feature = "android-sparse")]
90 #[error("failure in android sparse disk: {0}")]
91 CreateAndroidSparseDisk(android_sparse::Error),
92 #[cfg(feature = "composite-disk")]
93 #[error("failure in composite disk: {0}")]
94 CreateCompositeDisk(composite::Error),
95 #[cfg(feature = "zstd")]
96 #[error("failure in zstd disk: {0}")]
97 CreateZstdDisk(anyhow::Error),
98 #[error("failure creating single file disk: {0}")]
99 CreateSingleFileDisk(cros_async::AsyncError),
100 #[error("failed to set O_DIRECT on disk image: {0}")]
101 DirectFailed(base::Error),
102 #[error("failure with fdatasync: {0}")]
103 Fdatasync(cros_async::AsyncError),
104 #[error("failure with fsync: {0}")]
105 Fsync(cros_async::AsyncError),
106 #[error("failed to lock file: {0}")]
107 LockFileFailure(base::Error),
108 #[error("failure with fdatasync: {0}")]
109 IoFdatasync(io::Error),
110 #[error("failure with flush: {0}")]
111 IoFlush(io::Error),
112 #[error("failure with fsync: {0}")]
113 IoFsync(io::Error),
114 #[error("failure to punch hole: {0}")]
115 IoPunchHole(io::Error),
116 #[error("checking host fs type: {0}")]
117 HostFsType(base::Error),
118 #[error("maximum disk nesting depth exceeded")]
119 MaxNestingDepthExceeded,
120 #[error("failed to open disk file \"{0}\": {1}")]
121 OpenFile(String, base::Error),
122 #[error("failure to punch hole: {0}")]
123 PunchHole(cros_async::AsyncError),
124 #[error("failure to punch hole for block device file: {0}")]
125 PunchHoleBlockDeviceFile(base::Error),
126 #[cfg(feature = "qcow")]
127 #[error("failure in qcow: {0}")]
128 QcowError(qcow::Error),
129 #[error("failed to read data: {0}")]
130 ReadingData(io::Error),
131 #[error("failed to read header: {0}")]
132 ReadingHeader(io::Error),
133 #[error("failed to read to memory: {0}")]
134 ReadToMem(cros_async::AsyncError),
135 #[error("failed to seek file: {0}")]
136 SeekingFile(io::Error),
137 #[error("failed to set file size: {0}")]
138 SettingFileSize(io::Error),
139 #[error("unknown disk type")]
140 UnknownType,
141 #[error("failed to write from memory: {0}")]
142 WriteFromMem(cros_async::AsyncError),
143 #[error("failed to write from vec: {0}")]
144 WriteFromVec(cros_async::AsyncError),
145 #[error("failed to write zeroes: {0}")]
146 WriteZeroes(io::Error),
147 #[error("failed to write data: {0}")]
148 WritingData(io::Error),
149 #[error("failed to convert to async: {0}")]
150 ToAsync(cros_async::AsyncError),
151 #[cfg(windows)]
152 #[error("failed to set disk file sparse: {0}")]
153 SetSparseFailure(io::Error),
154 #[error("failure with guest memory access: {0}")]
155 GuestMemory(cros_async::mem::Error),
156 #[error("unsupported operation")]
157 UnsupportedOperation,
158}
159
160pub type Result<T> = std::result::Result<T, Error>;
161
162pub trait DiskGetLen {
164 fn get_len(&self) -> io::Result<u64>;
166}
167
168impl DiskGetLen for File {
169 fn get_len(&self) -> io::Result<u64> {
170 let mut s = self;
171 let orig_seek = s.stream_position()?;
172 let end = s.seek(SeekFrom::End(0))?;
173 s.seek(SeekFrom::Start(orig_seek))?;
174 Ok(end)
175 }
176}
177
178pub trait DiskFile:
180 FileSetLen + DiskGetLen + FileReadWriteAtVolatile + ToAsyncDisk + Send + AsRawDescriptors + Debug
181{
182 fn try_clone(&self) -> io::Result<Box<dyn DiskFile>> {
189 Err(io::Error::new(
190 io::ErrorKind::Unsupported,
191 "unsupported operation",
192 ))
193 }
194}
195
196pub trait ToAsyncDisk: AsRawDescriptors + DiskGetLen + Send {
198 fn to_async_disk(self: Box<Self>, ex: &Executor) -> Result<Box<dyn AsyncDisk>>;
204}
205
206impl ToAsyncDisk for File {
207 fn to_async_disk(self: Box<Self>, ex: &Executor) -> Result<Box<dyn AsyncDisk>> {
208 Ok(Box::new(SingleFileDisk::new(*self, ex)?))
209 }
210}
211
212#[derive(Clone, Copy, Debug, PartialEq, Eq)]
214pub enum ImageType {
215 Raw,
216 Qcow2,
217 CompositeDisk,
218 AndroidSparse,
219 Zstd,
220}
221
222pub fn detect_image_type(file: &File, overlapped_mode: bool) -> Result<ImageType> {
224 let mut f = file;
225 let disk_size = f.get_len().map_err(Error::SeekingFile)?;
226 let orig_seek = f.stream_position().map_err(Error::SeekingFile)?;
227
228 info!("disk size {}", disk_size);
229
230 const MAGIC_BLOCK_SIZE: usize = 4096;
232 #[repr(align(4096))]
233 struct BlockAlignedBuffer {
234 data: [u8; MAGIC_BLOCK_SIZE],
235 }
236 let mut magic = BlockAlignedBuffer {
237 data: [0u8; MAGIC_BLOCK_SIZE],
238 };
239 let magic_read_len = if disk_size > MAGIC_BLOCK_SIZE as u64 {
240 MAGIC_BLOCK_SIZE
241 } else {
242 disk_size as usize
245 };
246
247 read_from_disk(f, 0, &mut magic.data[0..magic_read_len], overlapped_mode)?;
248 f.seek(SeekFrom::Start(orig_seek))
249 .map_err(Error::SeekingFile)?;
250
251 #[cfg(feature = "composite-disk")]
252 if let Some(cdisk_magic) = magic.data.get(0..CDISK_MAGIC.len()) {
253 if cdisk_magic == CDISK_MAGIC.as_bytes() {
254 return Ok(ImageType::CompositeDisk);
255 }
256 }
257
258 #[allow(unused_variables)] if let Some(magic4) = magic
260 .data
261 .get(0..4)
262 .and_then(|v| <&[u8] as std::convert::TryInto<[u8; 4]>>::try_into(v).ok())
263 {
264 #[cfg(feature = "qcow")]
265 if magic4 == QCOW_MAGIC.to_be_bytes() {
266 return Ok(ImageType::Qcow2);
267 }
268 #[cfg(feature = "android-sparse")]
269 if magic4 == SPARSE_HEADER_MAGIC.to_le_bytes() {
270 return Ok(ImageType::AndroidSparse);
271 }
272 #[cfg(feature = "zstd")]
273 if u32::from_le_bytes(magic4) == ZSTD_FRAME_MAGIC
274 || (u32::from_le_bytes(magic4) >= ZSTD_SKIPPABLE_MAGIC_LOW
275 && u32::from_le_bytes(magic4) <= ZSTD_SKIPPABLE_MAGIC_HIGH)
276 {
277 return Ok(ImageType::Zstd);
278 }
279 }
280
281 Ok(ImageType::Raw)
282}
283
284impl DiskFile for File {
285 fn try_clone(&self) -> io::Result<Box<dyn DiskFile>> {
286 Ok(Box::new(self.try_clone()?))
287 }
288}
289
290pub struct DiskFileParams {
291 pub path: PathBuf,
292 pub is_read_only: bool,
293 pub is_sparse_file: bool,
296 pub is_overlapped: bool,
298 pub is_direct: bool,
300 pub lock: bool,
302 pub depth: u32,
305}
306
307impl Default for DiskFileParams {
308 fn default() -> Self {
309 Self {
310 path: PathBuf::new(),
311 is_read_only: false,
312 is_sparse_file: false,
313 is_overlapped: false,
314 is_direct: false,
315 lock: true,
316 depth: 0,
317 }
318 }
319}
320
321pub fn open_disk_file(params: DiskFileParams) -> Result<Box<dyn DiskFile>> {
323 if params.depth > MAX_NESTING_DEPTH {
324 return Err(Error::MaxNestingDepthExceeded);
325 }
326
327 let raw_image = sys::open_raw_disk_image(¶ms)?;
328 let image_type = detect_image_type(&raw_image, params.is_overlapped)?;
329 disk_file_from_file(raw_image, params, image_type)
330}
331
332pub fn open_disk_file_as(
334 params: DiskFileParams,
335 image_type: ImageType,
336) -> Result<Box<dyn DiskFile>> {
337 if params.depth > MAX_NESTING_DEPTH {
338 return Err(Error::MaxNestingDepthExceeded);
339 }
340
341 let raw_image = sys::open_raw_disk_image(¶ms)?;
342 disk_file_from_file(raw_image, params, image_type)
343}
344
345pub(crate) fn disk_file_from_file(
346 raw_image: File,
347 params: DiskFileParams,
348 image_type: ImageType,
349) -> Result<Box<dyn DiskFile>> {
350 Ok(match image_type {
351 ImageType::Raw => {
352 sys::apply_raw_disk_file_options(&raw_image, params.is_sparse_file)?;
353 Box::new(raw_image) as Box<dyn DiskFile>
354 }
355 #[cfg(feature = "qcow")]
356 ImageType::Qcow2 => Box::new(QcowFile::from(raw_image, params).map_err(Error::QcowError)?)
357 as Box<dyn DiskFile>,
358 #[cfg(feature = "composite-disk")]
359 ImageType::CompositeDisk => {
360 Box::new(
362 CompositeDiskFile::from_file(raw_image, params)
363 .map_err(Error::CreateCompositeDisk)?,
364 ) as Box<dyn DiskFile>
365 }
366 #[cfg(feature = "android-sparse")]
367 ImageType::AndroidSparse => {
368 Box::new(AndroidSparse::from_file(raw_image).map_err(Error::CreateAndroidSparseDisk)?)
369 as Box<dyn DiskFile>
370 }
371 #[cfg(feature = "zstd")]
372 ImageType::Zstd => Box::new(ZstdDisk::from_file(raw_image).map_err(Error::CreateZstdDisk)?)
373 as Box<dyn DiskFile>,
374 #[allow(unreachable_patterns)]
375 _ => return Err(Error::UnknownType),
376 })
377}
378
379#[async_trait(?Send)]
381pub trait AsyncDisk: DiskGetLen + FileSetLen + FileAllocate {
382 async fn flush(&self) -> Result<()>;
384
385 async fn fsync(&self) -> Result<()>;
387
388 async fn fdatasync(&self) -> Result<()>;
391
392 async fn read_to_mem<'a>(
395 &'a self,
396 file_offset: u64,
397 mem: Arc<dyn BackingMemory + Send + Sync>,
398 mem_offsets: cros_async::MemRegionIter<'a>,
399 options: IoOptions,
400 ) -> Result<usize>;
401
402 async fn write_from_mem<'a>(
404 &'a self,
405 file_offset: u64,
406 mem: Arc<dyn BackingMemory + Send + Sync>,
407 mem_offsets: cros_async::MemRegionIter<'a>,
408 options: IoOptions,
409 ) -> Result<usize>;
410
411 async fn punch_hole(&self, file_offset: u64, length: u64) -> Result<()>;
413
414 async fn write_zeroes_at(&self, file_offset: u64, length: u64) -> Result<()>;
416
417 async fn read_double_buffered(&self, file_offset: u64, buf: &mut [u8]) -> Result<usize> {
421 let backing_mem = Arc::new(cros_async::VecIoWrapper::from(vec![0u8; buf.len()]));
422 let region = cros_async::MemRegion {
423 offset: 0,
424 len: buf.len(),
425 };
426 let n = self
427 .read_to_mem(
428 file_offset,
429 backing_mem.clone(),
430 MemRegionIter::new(&[region]),
431 Default::default(),
432 )
433 .await?;
434 backing_mem
435 .get_volatile_slice(region)
436 .expect("BUG: the VecIoWrapper shrank?")
437 .sub_slice(0, n)
438 .expect("BUG: read_to_mem return value too large?")
439 .copy_to(buf);
440 Ok(n)
441 }
442
443 async fn write_double_buffered(&self, file_offset: u64, buf: &[u8]) -> Result<usize> {
447 let backing_mem = Arc::new(cros_async::VecIoWrapper::from(buf.to_vec()));
448 let region = cros_async::MemRegion {
449 offset: 0,
450 len: buf.len(),
451 };
452 self.write_from_mem(
453 file_offset,
454 backing_mem,
455 cros_async::MemRegionIter::new(&[region]),
456 Default::default(),
457 )
458 .await
459 }
460}
461
462pub struct SingleFileDisk {
464 inner: IoSource<File>,
465 #[cfg(any(target_os = "android", target_os = "linux"))]
467 is_block_device_file: bool,
468}
469
470impl DiskGetLen for SingleFileDisk {
471 fn get_len(&self) -> io::Result<u64> {
472 self.inner.as_source().get_len()
473 }
474}
475
476impl FileSetLen for SingleFileDisk {
477 fn set_len(&self, len: u64) -> io::Result<()> {
478 self.inner.as_source().set_len(len)
479 }
480}
481
482impl FileAllocate for SingleFileDisk {
483 fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
484 self.inner.as_source().allocate(offset, len)
485 }
486}
487
488#[async_trait(?Send)]
489impl AsyncDisk for SingleFileDisk {
490 async fn flush(&self) -> Result<()> {
491 Ok(())
493 }
494
495 async fn fsync(&self) -> Result<()> {
496 self.inner.fsync().await.map_err(Error::Fsync)
497 }
498
499 async fn fdatasync(&self) -> Result<()> {
500 self.inner.fdatasync().await.map_err(Error::Fdatasync)
501 }
502
503 async fn read_to_mem<'a>(
504 &'a self,
505 file_offset: u64,
506 mem: Arc<dyn BackingMemory + Send + Sync>,
507 mem_offsets: cros_async::MemRegionIter<'a>,
508 options: IoOptions,
509 ) -> Result<usize> {
510 self.inner
511 .read_to_mem(Some(file_offset), mem, mem_offsets, options)
512 .await
513 .map_err(Error::ReadToMem)
514 }
515
516 async fn write_from_mem<'a>(
517 &'a self,
518 file_offset: u64,
519 mem: Arc<dyn BackingMemory + Send + Sync>,
520 mem_offsets: cros_async::MemRegionIter<'a>,
521 options: IoOptions,
522 ) -> Result<usize> {
523 self.inner
524 .write_from_mem(Some(file_offset), mem, mem_offsets, options)
525 .await
526 .map_err(Error::WriteFromMem)
527 }
528
529 async fn punch_hole(&self, file_offset: u64, length: u64) -> Result<()> {
530 #[cfg(any(target_os = "android", target_os = "linux"))]
531 if self.is_block_device_file {
532 return base::linux::discard_block(self.inner.as_source(), file_offset, length)
533 .map_err(Error::PunchHoleBlockDeviceFile);
534 }
535 self.inner
536 .punch_hole(file_offset, length)
537 .await
538 .map_err(Error::PunchHole)
539 }
540
541 async fn write_zeroes_at(&self, file_offset: u64, length: u64) -> Result<()> {
542 if self
543 .inner
544 .write_zeroes_at(file_offset, length)
545 .await
546 .is_ok()
547 {
548 return Ok(());
549 }
550
551 let buf_size = min(length, 0x10000);
553 let mut nwritten = 0;
554 while nwritten < length {
555 let remaining = length - nwritten;
556 let write_size = min(remaining, buf_size) as usize;
557 let buf = vec![0u8; write_size];
558 nwritten += self
559 .inner
560 .write_from_vec(Some(file_offset + nwritten), buf, Default::default())
561 .await
562 .map(|(n, _)| n as u64)
563 .map_err(Error::WriteFromVec)?;
564 }
565 Ok(())
566 }
567}