1 // Copyright 2019 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use std::cmp::{max, min};
6 use std::fmt::{self, Display};
7 use std::fs::{File, OpenOptions};
8 use std::io::{self, ErrorKind, Read, Seek, SeekFrom};
9 use std::ops::Range;
10
11 use crate::{create_disk_file, DiskFile, DiskGetLen, ImageType};
12 use base::{
13 AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile, FileSetLen, FileSync, PunchHole,
14 RawDescriptor, WriteZeroesAt,
15 };
16 use data_model::VolatileSlice;
17 use protos::cdisk_spec;
18 use remain::sorted;
19
20 #[sorted]
21 #[derive(Debug)]
22 pub enum Error {
23 DiskError(Box<crate::Error>),
24 InvalidMagicHeader,
25 InvalidProto(protobuf::ProtobufError),
26 InvalidSpecification(String),
27 OpenFile(io::Error, String),
28 ReadSpecificationError(io::Error),
29 UnknownVersion(u64),
30 UnsupportedComponent(ImageType),
31 }
32
33 impl Display for Error {
34 #[remain::check]
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result35 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36 use self::Error::*;
37
38 #[sorted]
39 match self {
40 DiskError(e) => write!(f, "failed to use underlying disk: \"{}\"", e),
41 InvalidMagicHeader => write!(f, "invalid magic header for composite disk format"),
42 InvalidProto(e) => write!(f, "failed to parse specification proto: \"{}\"", e),
43 InvalidSpecification(s) => write!(f, "invalid specification: \"{}\"", s),
44 OpenFile(e, p) => write!(f, "failed to open component file \"{}\": \"{}\"", p, e),
45 ReadSpecificationError(e) => write!(f, "failed to read specification: \"{}\"", e),
46 UnknownVersion(v) => write!(f, "unknown version {} in specification", v),
47 UnsupportedComponent(c) => write!(f, "unsupported component disk type \"{:?}\"", c),
48 }
49 }
50 }
51
52 pub type Result<T> = std::result::Result<T, Error>;
53
54 #[derive(Debug)]
55 struct ComponentDiskPart {
56 file: Box<dyn DiskFile>,
57 offset: u64,
58 length: u64,
59 }
60
61 impl ComponentDiskPart {
range(&self) -> Range<u64>62 fn range(&self) -> Range<u64> {
63 self.offset..(self.offset + self.length)
64 }
65 }
66
67 /// Represents a composite virtual disk made out of multiple component files. This is described on
68 /// disk by a protocol buffer file that lists out the component file locations and their offsets
69 /// and lengths on the virtual disk. The spaces covered by the component disks must be contiguous
70 /// and not overlapping.
71 #[derive(Debug)]
72 pub struct CompositeDiskFile {
73 component_disks: Vec<ComponentDiskPart>,
74 }
75
ranges_overlap(a: &Range<u64>, b: &Range<u64>) -> bool76 fn ranges_overlap(a: &Range<u64>, b: &Range<u64>) -> bool {
77 // essentially !range_intersection(a, b).is_empty(), but that's experimental
78 let intersection = range_intersection(a, b);
79 intersection.start < intersection.end
80 }
81
range_intersection(a: &Range<u64>, b: &Range<u64>) -> Range<u64>82 fn range_intersection(a: &Range<u64>, b: &Range<u64>) -> Range<u64> {
83 Range {
84 start: max(a.start, b.start),
85 end: min(a.end, b.end),
86 }
87 }
88
89 /// A magic string placed at the beginning of a composite disk file to identify it.
90 pub static CDISK_MAGIC: &str = "composite_disk\x1d";
91 /// The length of the CDISK_MAGIC string. Created explicitly as a static constant so that it is
92 /// possible to create a character array of the same length.
93 pub const CDISK_MAGIC_LEN: usize = 15;
94
95 impl CompositeDiskFile {
new(mut disks: Vec<ComponentDiskPart>) -> Result<CompositeDiskFile>96 fn new(mut disks: Vec<ComponentDiskPart>) -> Result<CompositeDiskFile> {
97 disks.sort_by(|d1, d2| d1.offset.cmp(&d2.offset));
98 let contiguous_err = disks
99 .windows(2)
100 .map(|s| {
101 if s[0].offset == s[1].offset {
102 let text = format!("Two disks at offset {}", s[0].offset);
103 Err(Error::InvalidSpecification(text))
104 } else {
105 Ok(())
106 }
107 })
108 .find(|r| r.is_err());
109 if let Some(Err(e)) = contiguous_err {
110 return Err(e);
111 }
112 Ok(CompositeDiskFile {
113 component_disks: disks,
114 })
115 }
116
117 /// Set up a composite disk by reading the specification from a file. The file must consist of
118 /// the CDISK_MAGIC string followed by one binary instance of the CompositeDisk protocol
119 /// buffer. Returns an error if it could not read the file or if the specification was invalid.
from_file(mut file: File) -> Result<CompositeDiskFile>120 pub fn from_file(mut file: File) -> Result<CompositeDiskFile> {
121 file.seek(SeekFrom::Start(0))
122 .map_err(Error::ReadSpecificationError)?;
123 let mut magic_space = [0u8; CDISK_MAGIC_LEN];
124 file.read_exact(&mut magic_space[..])
125 .map_err(Error::ReadSpecificationError)?;
126 if magic_space != CDISK_MAGIC.as_bytes() {
127 return Err(Error::InvalidMagicHeader);
128 }
129 let proto: cdisk_spec::CompositeDisk =
130 protobuf::parse_from_reader(&mut file).map_err(Error::InvalidProto)?;
131 if proto.get_version() != 1 {
132 return Err(Error::UnknownVersion(proto.get_version()));
133 }
134 let mut open_options = OpenOptions::new();
135 open_options.read(true);
136 let mut disks: Vec<ComponentDiskPart> = proto
137 .get_component_disks()
138 .iter()
139 .map(|disk| {
140 open_options.write(
141 disk.get_read_write_capability() == cdisk_spec::ReadWriteCapability::READ_WRITE,
142 );
143 let file = open_options
144 .open(disk.get_file_path())
145 .map_err(|e| Error::OpenFile(e, disk.get_file_path().to_string()))?;
146 Ok(ComponentDiskPart {
147 file: create_disk_file(file).map_err(|e| Error::DiskError(Box::new(e)))?,
148 offset: disk.get_offset(),
149 length: 0, // Assigned later
150 })
151 })
152 .collect::<Result<Vec<ComponentDiskPart>>>()?;
153 disks.sort_by(|d1, d2| d1.offset.cmp(&d2.offset));
154 for i in 0..(disks.len() - 1) {
155 let length = disks[i + 1].offset - disks[i].offset;
156 if length == 0 {
157 let text = format!("Two disks at offset {}", disks[i].offset);
158 return Err(Error::InvalidSpecification(text));
159 }
160 if let Some(disk) = disks.get_mut(i) {
161 disk.length = length;
162 } else {
163 let text = format!("Unable to set disk length {}", length);
164 return Err(Error::InvalidSpecification(text));
165 }
166 }
167 let num_disks = disks.len();
168 if let Some(last_disk) = disks.get_mut(num_disks - 1) {
169 if proto.get_length() <= last_disk.offset {
170 let text = format!(
171 "Full size of disk doesn't match last offset. {} <= {}",
172 proto.get_length(),
173 last_disk.offset
174 );
175 return Err(Error::InvalidSpecification(text));
176 }
177 last_disk.length = proto.get_length() - last_disk.offset;
178 } else {
179 let text = format!(
180 "Unable to set last disk length to end at {}",
181 proto.get_length()
182 );
183 return Err(Error::InvalidSpecification(text));
184 }
185
186 CompositeDiskFile::new(disks)
187 }
188
length(&self) -> u64189 fn length(&self) -> u64 {
190 if let Some(disk) = self.component_disks.last() {
191 disk.offset + disk.length
192 } else {
193 0
194 }
195 }
196
disk_at_offset(&mut self, offset: u64) -> io::Result<&mut ComponentDiskPart>197 fn disk_at_offset(&mut self, offset: u64) -> io::Result<&mut ComponentDiskPart> {
198 self.component_disks
199 .iter_mut()
200 .find(|disk| disk.range().contains(&offset))
201 .ok_or(io::Error::new(
202 ErrorKind::InvalidData,
203 format!("no disk at offset {}", offset),
204 ))
205 }
206
disks_in_range<'a>(&'a mut self, range: &Range<u64>) -> Vec<&'a mut ComponentDiskPart>207 fn disks_in_range<'a>(&'a mut self, range: &Range<u64>) -> Vec<&'a mut ComponentDiskPart> {
208 self.component_disks
209 .iter_mut()
210 .filter(|disk| ranges_overlap(&disk.range(), range))
211 .collect()
212 }
213 }
214
215 impl DiskGetLen for CompositeDiskFile {
get_len(&self) -> io::Result<u64>216 fn get_len(&self) -> io::Result<u64> {
217 Ok(self.length())
218 }
219 }
220
221 impl FileSetLen for CompositeDiskFile {
set_len(&self, _len: u64) -> io::Result<()>222 fn set_len(&self, _len: u64) -> io::Result<()> {
223 Err(io::Error::new(ErrorKind::Other, "unsupported operation"))
224 }
225 }
226
227 impl FileSync for CompositeDiskFile {
fsync(&mut self) -> io::Result<()>228 fn fsync(&mut self) -> io::Result<()> {
229 for disk in self.component_disks.iter_mut() {
230 disk.file.fsync()?;
231 }
232 Ok(())
233 }
234 }
235
236 // Implements Read and Write targeting volatile storage for composite disks.
237 //
238 // Note that reads and writes will return early if crossing component disk boundaries.
239 // This is allowed by the read and write specifications, which only say read and write
240 // have to return how many bytes were actually read or written. Use read_exact_volatile
241 // or write_all_volatile to make sure all bytes are received/transmitted.
242 //
243 // If one of the component disks does a partial read or write, that also gets passed
244 // transparently to the parent.
245 impl FileReadWriteAtVolatile for CompositeDiskFile {
read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize>246 fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
247 let cursor_location = offset;
248 let disk = self.disk_at_offset(cursor_location)?;
249 let subslice = if cursor_location + slice.size() as u64 > disk.offset + disk.length {
250 let new_size = disk.offset + disk.length - cursor_location;
251 slice
252 .sub_slice(0, new_size as usize)
253 .map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))?
254 } else {
255 slice
256 };
257 disk.file
258 .read_at_volatile(subslice, cursor_location - disk.offset)
259 }
write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize>260 fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
261 let cursor_location = offset;
262 let disk = self.disk_at_offset(cursor_location)?;
263 let subslice = if cursor_location + slice.size() as u64 > disk.offset + disk.length {
264 let new_size = disk.offset + disk.length - cursor_location;
265 slice
266 .sub_slice(0, new_size as usize)
267 .map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))?
268 } else {
269 slice
270 };
271 disk.file
272 .write_at_volatile(subslice, cursor_location - disk.offset)
273 }
274 }
275
276 impl PunchHole for CompositeDiskFile {
punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()>277 fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()> {
278 let range = offset..(offset + length);
279 let disks = self.disks_in_range(&range);
280 for disk in disks {
281 let intersection = range_intersection(&range, &disk.range());
282 if intersection.start >= intersection.end {
283 continue;
284 }
285 let result = disk.file.punch_hole(
286 intersection.start - disk.offset,
287 intersection.end - intersection.start,
288 );
289 if result.is_err() {
290 return result;
291 }
292 }
293 Ok(())
294 }
295 }
296
297 impl FileAllocate for CompositeDiskFile {
allocate(&mut self, offset: u64, length: u64) -> io::Result<()>298 fn allocate(&mut self, offset: u64, length: u64) -> io::Result<()> {
299 let range = offset..(offset + length);
300 let disks = self.disks_in_range(&range);
301 for disk in disks {
302 let intersection = range_intersection(&range, &disk.range());
303 if intersection.start >= intersection.end {
304 continue;
305 }
306 let result = disk.file.allocate(
307 intersection.start - disk.offset,
308 intersection.end - intersection.start,
309 );
310 if result.is_err() {
311 return result;
312 }
313 }
314 Ok(())
315 }
316 }
317
318 impl WriteZeroesAt for CompositeDiskFile {
write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize>319 fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize> {
320 let cursor_location = offset;
321 let disk = self.disk_at_offset(cursor_location)?;
322 let offset_within_disk = cursor_location - disk.offset;
323 let new_length = if cursor_location + length as u64 > disk.offset + disk.length {
324 (disk.offset + disk.length - cursor_location) as usize
325 } else {
326 length
327 };
328 disk.file.write_zeroes_at(offset_within_disk, new_length)
329 }
330 }
331
332 impl AsRawDescriptors for CompositeDiskFile {
as_raw_descriptors(&self) -> Vec<RawDescriptor>333 fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
334 self.component_disks
335 .iter()
336 .map(|d| d.file.as_raw_descriptors())
337 .flatten()
338 .collect()
339 }
340 }
341
342 #[cfg(test)]
343 mod tests {
344 use super::*;
345 use base::AsRawDescriptor;
346 use data_model::VolatileMemory;
347 use tempfile::tempfile;
348
349 #[test]
block_duplicate_offset_disks()350 fn block_duplicate_offset_disks() {
351 let file1 = tempfile().unwrap();
352 let file2 = tempfile().unwrap();
353 let disk_part1 = ComponentDiskPart {
354 file: Box::new(file1),
355 offset: 0,
356 length: 100,
357 };
358 let disk_part2 = ComponentDiskPart {
359 file: Box::new(file2),
360 offset: 0,
361 length: 100,
362 };
363 assert!(CompositeDiskFile::new(vec![disk_part1, disk_part2]).is_err());
364 }
365
366 #[test]
get_len()367 fn get_len() {
368 let file1 = tempfile().unwrap();
369 let file2 = tempfile().unwrap();
370 let disk_part1 = ComponentDiskPart {
371 file: Box::new(file1),
372 offset: 0,
373 length: 100,
374 };
375 let disk_part2 = ComponentDiskPart {
376 file: Box::new(file2),
377 offset: 100,
378 length: 100,
379 };
380 let composite = CompositeDiskFile::new(vec![disk_part1, disk_part2]).unwrap();
381 let len = composite.get_len().unwrap();
382 assert_eq!(len, 200);
383 }
384
385 #[test]
single_file_passthrough()386 fn single_file_passthrough() {
387 let file = tempfile().unwrap();
388 let disk_part = ComponentDiskPart {
389 file: Box::new(file),
390 offset: 0,
391 length: 100,
392 };
393 let mut composite = CompositeDiskFile::new(vec![disk_part]).unwrap();
394 let mut input_memory = [55u8; 5];
395 let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
396 composite
397 .write_all_at_volatile(input_volatile_memory.get_slice(0, 5).unwrap(), 0)
398 .unwrap();
399 let mut output_memory = [0u8; 5];
400 let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
401 composite
402 .read_exact_at_volatile(output_volatile_memory.get_slice(0, 5).unwrap(), 0)
403 .unwrap();
404 assert_eq!(input_memory, output_memory);
405 }
406
407 #[test]
triple_file_fds()408 fn triple_file_fds() {
409 let file1 = tempfile().unwrap();
410 let file2 = tempfile().unwrap();
411 let file3 = tempfile().unwrap();
412 let mut in_fds = vec![
413 file1.as_raw_descriptor(),
414 file2.as_raw_descriptor(),
415 file3.as_raw_descriptor(),
416 ];
417 in_fds.sort();
418 let disk_part1 = ComponentDiskPart {
419 file: Box::new(file1),
420 offset: 0,
421 length: 100,
422 };
423 let disk_part2 = ComponentDiskPart {
424 file: Box::new(file2),
425 offset: 100,
426 length: 100,
427 };
428 let disk_part3 = ComponentDiskPart {
429 file: Box::new(file3),
430 offset: 200,
431 length: 100,
432 };
433 let composite = CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
434 let mut out_fds = composite.as_raw_descriptors();
435 out_fds.sort();
436 assert_eq!(in_fds, out_fds);
437 }
438
439 #[test]
triple_file_passthrough()440 fn triple_file_passthrough() {
441 let file1 = tempfile().unwrap();
442 let file2 = tempfile().unwrap();
443 let file3 = tempfile().unwrap();
444 let disk_part1 = ComponentDiskPart {
445 file: Box::new(file1),
446 offset: 0,
447 length: 100,
448 };
449 let disk_part2 = ComponentDiskPart {
450 file: Box::new(file2),
451 offset: 100,
452 length: 100,
453 };
454 let disk_part3 = ComponentDiskPart {
455 file: Box::new(file3),
456 offset: 200,
457 length: 100,
458 };
459 let mut composite =
460 CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
461 let mut input_memory = [55u8; 200];
462 let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
463 composite
464 .write_all_at_volatile(input_volatile_memory.get_slice(0, 200).unwrap(), 50)
465 .unwrap();
466 let mut output_memory = [0u8; 200];
467 let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
468 composite
469 .read_exact_at_volatile(output_volatile_memory.get_slice(0, 200).unwrap(), 50)
470 .unwrap();
471 assert!(input_memory.iter().eq(output_memory.iter()));
472 }
473
474 #[test]
triple_file_punch_hole()475 fn triple_file_punch_hole() {
476 let file1 = tempfile().unwrap();
477 let file2 = tempfile().unwrap();
478 let file3 = tempfile().unwrap();
479 let disk_part1 = ComponentDiskPart {
480 file: Box::new(file1),
481 offset: 0,
482 length: 100,
483 };
484 let disk_part2 = ComponentDiskPart {
485 file: Box::new(file2),
486 offset: 100,
487 length: 100,
488 };
489 let disk_part3 = ComponentDiskPart {
490 file: Box::new(file3),
491 offset: 200,
492 length: 100,
493 };
494 let mut composite =
495 CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
496 let mut input_memory = [55u8; 300];
497 let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
498 composite
499 .write_all_at_volatile(input_volatile_memory.get_slice(0, 300).unwrap(), 0)
500 .unwrap();
501 composite.punch_hole(50, 200).unwrap();
502 let mut output_memory = [0u8; 300];
503 let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
504 composite
505 .read_exact_at_volatile(output_volatile_memory.get_slice(0, 300).unwrap(), 0)
506 .unwrap();
507
508 for i in 50..250 {
509 input_memory[i] = 0;
510 }
511 assert!(input_memory.iter().eq(output_memory.iter()));
512 }
513
514 #[test]
triple_file_write_zeroes()515 fn triple_file_write_zeroes() {
516 let file1 = tempfile().unwrap();
517 let file2 = tempfile().unwrap();
518 let file3 = tempfile().unwrap();
519 let disk_part1 = ComponentDiskPart {
520 file: Box::new(file1),
521 offset: 0,
522 length: 100,
523 };
524 let disk_part2 = ComponentDiskPart {
525 file: Box::new(file2),
526 offset: 100,
527 length: 100,
528 };
529 let disk_part3 = ComponentDiskPart {
530 file: Box::new(file3),
531 offset: 200,
532 length: 100,
533 };
534 let mut composite =
535 CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
536 let mut input_memory = [55u8; 300];
537 let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
538 composite
539 .write_all_at_volatile(input_volatile_memory.get_slice(0, 300).unwrap(), 0)
540 .unwrap();
541 let mut zeroes_written = 0;
542 while zeroes_written < 200 {
543 zeroes_written += composite
544 .write_zeroes_at(50 + zeroes_written as u64, 200 - zeroes_written)
545 .unwrap();
546 }
547 let mut output_memory = [0u8; 300];
548 let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
549 composite
550 .read_exact_at_volatile(output_volatile_memory.get_slice(0, 300).unwrap(), 0)
551 .unwrap();
552
553 for i in 50..250 {
554 input_memory[i] = 0;
555 }
556 for i in 0..300 {
557 println!(
558 "input[{0}] = {1}, output[{0}] = {2}",
559 i, input_memory[i], output_memory[i]
560 );
561 }
562 assert!(input_memory.iter().eq(output_memory.iter()));
563 }
564 }
565