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