blob: b553705c57ce4c3892ad8a5e9af47b8f54e859a1 [file] [log] [blame]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +00001// Copyright 2023, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Support for parsing GUID partition tables.
16
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000017use core::cmp::min;
18use core::fmt;
19use core::mem::size_of;
20use core::ops::RangeInclusive;
21use core::slice;
22use static_assertions::const_assert;
23use static_assertions::const_assert_eq;
24use uuid::Uuid;
25use virtio_drivers::device::blk::SECTOR_SIZE;
Alice Wangeacb7382023-06-05 12:53:54 +000026use vmbase::util::ceiling_div;
Alice Wangeade1672023-06-08 14:56:20 +000027use vmbase::virtio::pci::VirtIOBlk;
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +010028use zerocopy::FromBytes;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000029
30pub enum Error {
31 /// VirtIO error during read operation.
32 FailedRead(virtio_drivers::Error),
33 /// VirtIO error during write operation.
34 FailedWrite(virtio_drivers::Error),
35 /// Invalid GPT header.
36 InvalidHeader,
37 /// Invalid partition block index.
38 BlockOutsidePartition(usize),
39}
40
41impl fmt::Display for Error {
42 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43 match self {
44 Self::FailedRead(e) => write!(f, "Failed to read from disk: {e}"),
45 Self::FailedWrite(e) => write!(f, "Failed to write to disk: {e}"),
46 Self::InvalidHeader => write!(f, "Found invalid GPT header"),
47 Self::BlockOutsidePartition(i) => write!(f, "Accessed invalid block index {i}"),
48 }
49 }
50}
51
52pub type Result<T> = core::result::Result<T, Error>;
53
54pub struct Partition {
55 partitions: Partitions,
56 indices: RangeInclusive<usize>,
57}
58
59impl Partition {
60 pub fn get_by_name(device: VirtIOBlk, name: &str) -> Result<Option<Self>> {
61 Partitions::new(device)?.get_partition_by_name(name)
62 }
63
64 fn new(partitions: Partitions, entry: &Entry) -> Self {
65 let first = entry.first_lba().try_into().unwrap();
66 let last = entry.last_lba().try_into().unwrap();
67
68 Self { partitions, indices: first..=last }
69 }
70
71 pub fn indices(&self) -> RangeInclusive<usize> {
72 self.indices.clone()
73 }
74
75 pub fn read_block(&mut self, index: usize, blk: &mut [u8]) -> Result<()> {
76 let index = self.block_index(index).ok_or(Error::BlockOutsidePartition(index))?;
77 self.partitions.read_block(index, blk)
78 }
79
80 pub fn write_block(&mut self, index: usize, blk: &[u8]) -> Result<()> {
81 let index = self.block_index(index).ok_or(Error::BlockOutsidePartition(index))?;
82 self.partitions.write_block(index, blk)
83 }
84
85 fn block_index(&self, index: usize) -> Option<usize> {
86 if self.indices.contains(&index) {
87 Some(index)
88 } else {
89 None
90 }
91 }
92}
93
94pub struct Partitions {
95 device: VirtIOBlk,
96 entries_count: usize,
97}
98
99impl Partitions {
100 pub const LBA_SIZE: usize = SECTOR_SIZE;
101
102 fn new(mut device: VirtIOBlk) -> Result<Self> {
103 let mut blk = [0; Self::LBA_SIZE];
104 device.read_block(Header::LBA, &mut blk).map_err(Error::FailedRead)?;
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100105 let header = Header::read_from_prefix(blk.as_slice()).unwrap();
106 if !header.is_valid() {
107 return Err(Error::InvalidHeader);
108 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000109 let entries_count = usize::try_from(header.entries_count()).unwrap();
110
111 Ok(Self { device, entries_count })
112 }
113
114 fn get_partition_by_name(mut self, name: &str) -> Result<Option<Partition>> {
115 const_assert_eq!(Partitions::LBA_SIZE.rem_euclid(size_of::<Entry>()), 0);
116 let entries_per_blk = Partitions::LBA_SIZE.checked_div(size_of::<Entry>()).unwrap();
117
118 // Create a UTF-16 reference against which we'll compare partition names. Note that unlike
119 // the C99 wcslen(), this comparison will cover bytes past the first L'\0' character.
120 let mut needle = [0; Entry::NAME_SIZE / size_of::<u16>()];
121 for (dest, src) in needle.iter_mut().zip(name.encode_utf16()) {
122 *dest = src;
123 }
124
125 let mut blk = [0; Self::LBA_SIZE];
126 let mut rem = self.entries_count;
127 let num_blocks = ceiling_div(self.entries_count, entries_per_blk).unwrap();
128 for i in Header::ENTRIES_LBA..Header::ENTRIES_LBA.checked_add(num_blocks).unwrap() {
129 self.read_block(i, &mut blk)?;
130 let entries = blk.as_ptr().cast::<Entry>();
131 // SAFETY - blk is assumed to be properly aligned for Entry and its size is assert-ed
132 // above. All potential values of the slice will produce valid Entry values.
133 let entries = unsafe { slice::from_raw_parts(entries, min(rem, entries_per_blk)) };
134 for entry in entries {
135 let entry_name = entry.name;
136 if entry_name == needle {
137 return Ok(Some(Partition::new(self, entry)));
138 }
139 rem -= 1;
140 }
141 }
142 Ok(None)
143 }
144
145 fn read_block(&mut self, index: usize, blk: &mut [u8]) -> Result<()> {
146 self.device.read_block(index, blk).map_err(Error::FailedRead)
147 }
148
149 fn write_block(&mut self, index: usize, blk: &[u8]) -> Result<()> {
150 self.device.write_block(index, blk).map_err(Error::FailedWrite)
151 }
152}
153
154type Lba = u64;
155
156/// Structure as defined in release 2.10 of the UEFI Specification (5.3.2 GPT Header).
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100157#[derive(FromBytes)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000158#[repr(C, packed)]
159struct Header {
160 signature: u64,
161 revision: u32,
162 header_size: u32,
163 header_crc32: u32,
164 reserved0: u32,
165 current_lba: Lba,
166 backup_lba: Lba,
167 first_lba: Lba,
168 last_lba: Lba,
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100169 disk_guid: u128,
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000170 entries_lba: Lba,
171 entries_count: u32,
172 entry_size: u32,
173 entries_crc32: u32,
174}
175const_assert!(size_of::<Header>() < Partitions::LBA_SIZE);
176
177impl Header {
178 const SIGNATURE: u64 = u64::from_le_bytes(*b"EFI PART");
179 const REVISION_1_0: u32 = 1 << 16;
180 const LBA: usize = 1;
181 const ENTRIES_LBA: usize = 2;
182
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000183 fn is_valid(&self) -> bool {
184 self.signature() == Self::SIGNATURE
185 && self.header_size() == size_of::<Self>().try_into().unwrap()
186 && self.revision() == Self::REVISION_1_0
187 && self.entry_size() == size_of::<Entry>().try_into().unwrap()
188 && self.current_lba() == Self::LBA.try_into().unwrap()
189 && self.entries_lba() == Self::ENTRIES_LBA.try_into().unwrap()
190 }
191
192 fn signature(&self) -> u64 {
193 u64::from_le(self.signature)
194 }
195
196 fn entries_count(&self) -> u32 {
197 u32::from_le(self.entries_count)
198 }
199
200 fn header_size(&self) -> u32 {
201 u32::from_le(self.header_size)
202 }
203
204 fn revision(&self) -> u32 {
205 u32::from_le(self.revision)
206 }
207
208 fn entry_size(&self) -> u32 {
209 u32::from_le(self.entry_size)
210 }
211
212 fn entries_lba(&self) -> Lba {
213 Lba::from_le(self.entries_lba)
214 }
215
216 fn current_lba(&self) -> Lba {
217 Lba::from_le(self.current_lba)
218 }
219}
220
221/// Structure as defined in release 2.10 of the UEFI Specification (5.3.3 GPT Partition Entry
222/// Array).
223#[repr(C, packed)]
224struct Entry {
225 type_guid: Uuid,
226 guid: Uuid,
227 first_lba: Lba,
228 last_lba: Lba,
229 flags: u64,
230 name: [u16; Entry::NAME_SIZE / size_of::<u16>()], // UTF-16
231}
232
233impl Entry {
234 const NAME_SIZE: usize = 72;
235
236 fn first_lba(&self) -> Lba {
237 Lba::from_le(self.first_lba)
238 }
239
240 fn last_lba(&self) -> Lba {
241 Lba::from_le(self.last_lba)
242 }
243}