Skip to main content

fs_management/
format.rs

1// Copyright 2022 The Fuchsia 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
5pub mod constants;
6
7use anyhow::{Context as _, Error, anyhow, ensure};
8use block_client::{BlockClient, MutableBufferSlice, RemoteBlockClient};
9use fidl_fuchsia_storage_block::BlockProxy;
10
11#[derive(Debug, PartialEq, Clone, Copy)]
12pub enum DiskFormat {
13    Unknown,
14    Gpt,
15    Mbr,
16    Minfs,
17    Fat,
18    Blobfs,
19    Fvm,
20    Zxcrypt,
21    BlockVerity,
22    VbMeta,
23    Fxfs,
24    F2fs,
25    NandBroker,
26}
27
28impl DiskFormat {
29    // These are copied verbatim from //src/storage/lib/fs_management/cpp/format.cc, and should be
30    // kept in sync.
31    pub fn as_str(&self) -> &'static str {
32        match self {
33            Self::Unknown => "unknown!",
34            Self::Gpt => "gpt",
35            Self::Mbr => "mbr",
36            Self::Minfs => "minfs",
37            Self::Fat => "fat",
38            Self::Blobfs => "blobfs",
39            Self::Fvm => "fvm",
40            Self::Zxcrypt => "zxcrypt",
41            Self::BlockVerity => "block verity",
42            Self::VbMeta => "vbmeta",
43            Self::Fxfs => "fxfs",
44            Self::F2fs => "f2fs",
45            Self::NandBroker => "nand broker",
46        }
47    }
48}
49
50/// The inverse of DiskFormat::as_str().
51impl From<&str> for DiskFormat {
52    fn from(s: &str) -> Self {
53        match s {
54            "gpt" => Self::Gpt,
55            "mbr" => Self::Mbr,
56            "minfs" => Self::Minfs,
57            "fat" => Self::Fat,
58            "blobfs" => Self::Blobfs,
59            "fvm" => Self::Fvm,
60            "zxcrypt" => Self::Zxcrypt,
61            "block verity" => Self::BlockVerity,
62            "vbmeta" => Self::VbMeta,
63            "fxfs" => Self::Fxfs,
64            "f2fs" => Self::F2fs,
65            "nand broker" => Self::NandBroker,
66            _ => Self::Unknown,
67        }
68    }
69}
70
71pub fn round_up(val: u64, divisor: u64) -> Option<u64> {
72    // Checked version of ((val + (divisor - 1)) / divisor) * divisor
73    ((val.checked_add(divisor.checked_sub(1)?)?).checked_div(divisor)?).checked_mul(divisor)
74}
75
76pub async fn detect_disk_format(block_proxy: &BlockProxy) -> DiskFormat {
77    match detect_disk_format_res(block_proxy).await {
78        Ok(format) => format,
79        Err(e) => {
80            log::warn!("detect_disk_format failed: {:?}", e);
81            return DiskFormat::Unknown;
82        }
83    }
84}
85
86async fn detect_disk_format_res(block_proxy: &BlockProxy) -> Result<DiskFormat, Error> {
87    let block_info = block_proxy
88        .get_info()
89        .await
90        .context("transport error on get_info call")?
91        .map_err(zx::Status::from_raw)
92        .context("get_info call failed")?;
93    ensure!(block_info.block_size > 0, "block size expected to be non-zero");
94
95    let double_block_size = (block_info.block_size)
96        .checked_mul(2)
97        .ok_or_else(|| anyhow!("overflow calculating double block size"))?;
98
99    let header_size = if constants::HEADER_SIZE > double_block_size {
100        constants::HEADER_SIZE
101    } else {
102        double_block_size
103    };
104
105    let device_size = (block_info.block_size as u64)
106        .checked_mul(block_info.block_count)
107        .ok_or_else(|| anyhow!("overflow calculating device size"))?;
108
109    if header_size as u64 > device_size {
110        // The header we want to read is bigger than the actual device. This isn't necessarily an
111        // error, but it does mean we can't inspect the content.
112        return Ok(DiskFormat::Unknown);
113    }
114
115    let buffer_size = round_up(header_size as u64, block_info.block_size as u64)
116        .ok_or_else(|| anyhow!("overflow rounding header size up"))?;
117    let client =
118        RemoteBlockClient::new(block_proxy).await.context("RemoteBlockClient::new failed")?;
119    let mut data = vec![0; buffer_size as usize];
120    client.read_at(MutableBufferSlice::Memory(&mut data), 0).await.context("read_at failed")?;
121
122    if data.starts_with(&constants::FVM_MAGIC) {
123        return Ok(DiskFormat::Fvm);
124    }
125
126    if data.starts_with(&constants::ZXCRYPT_MAGIC) {
127        return Ok(DiskFormat::Zxcrypt);
128    }
129
130    if data.starts_with(&constants::BLOCK_VERITY_MAGIC) {
131        return Ok(DiskFormat::BlockVerity);
132    }
133
134    if &data[block_info.block_size as usize..block_info.block_size as usize + 16]
135        == &constants::GPT_MAGIC
136    {
137        return Ok(DiskFormat::Gpt);
138    }
139
140    if data.starts_with(&constants::MINFS_MAGIC) {
141        return Ok(DiskFormat::Minfs);
142    }
143
144    if data.starts_with(&constants::BLOBFS_MAGIC) {
145        return Ok(DiskFormat::Blobfs);
146    }
147
148    if data.starts_with(&constants::VB_META_MAGIC) {
149        return Ok(DiskFormat::VbMeta);
150    }
151
152    if &data[1024..1024 + constants::F2FS_MAGIC.len()] == &constants::F2FS_MAGIC {
153        return Ok(DiskFormat::F2fs);
154    }
155
156    if data.starts_with(&constants::FXFS_MAGIC) {
157        return Ok(DiskFormat::Fxfs);
158    }
159
160    // Check for Mbr and Fat last.  Since they only have two bytes of magic, it's fairly easy to
161    // randomly encounter this combination (this has happened multiple times in unit tests).
162    if data[510] == 0x55 && data[511] == 0xAA {
163        if data[38] == 0x29 || data[66] == 0x29 {
164            // 0x55AA are always placed at offset 510 and 511 for FAT filesystems.
165            // 0x29 is the Boot Signature, but it is placed at either offset 38 or
166            // 66 (depending on FAT type).
167            return Ok(DiskFormat::Fat);
168        }
169        return Ok(DiskFormat::Mbr);
170    }
171
172    return Ok(DiskFormat::Unknown);
173}
174
175#[cfg(test)]
176mod tests {
177    use super::{DiskFormat, constants, detect_disk_format_res};
178    use anyhow::Error;
179    use fidl::endpoints::create_proxy_and_stream;
180    use fidl_fuchsia_storage_block::BlockMarker;
181    use futures::{FutureExt, select};
182    use std::pin::pin;
183    use vmo_backed_block_server::VmoBackedServer;
184
185    async fn get_detected_disk_format(
186        content: &[u8],
187        block_count: u64,
188        block_size: u32,
189    ) -> Result<DiskFormat, Error> {
190        let (proxy, stream) = create_proxy_and_stream::<BlockMarker>();
191
192        let fake_server = VmoBackedServer::new(block_count, block_size, content)
193            .expect("Failed to create VmoBackedServer");
194        let mut request_handler = pin!(fake_server.serve(stream).fuse());
195
196        select! {
197          _ = request_handler => unreachable!(),
198          format = detect_disk_format_res(&proxy).fuse() => format,
199        }
200    }
201
202    /// Confirm that we map to/from string consistently.
203    #[test]
204    fn to_from_str_test() {
205        for fmt in vec![
206            DiskFormat::Unknown,
207            DiskFormat::Gpt,
208            DiskFormat::Mbr,
209            DiskFormat::Minfs,
210            DiskFormat::Fat,
211            DiskFormat::Blobfs,
212            DiskFormat::Fvm,
213            DiskFormat::Zxcrypt,
214            DiskFormat::BlockVerity,
215            DiskFormat::VbMeta,
216            DiskFormat::Fxfs,
217            DiskFormat::F2fs,
218            DiskFormat::NandBroker,
219        ] {
220            assert_eq!(fmt, fmt.as_str().into());
221        }
222    }
223
224    #[fuchsia::test]
225    async fn detect_disk_format_too_small() {
226        assert_eq!(get_detected_disk_format(&[], 1, 512).await.unwrap(), DiskFormat::Unknown);
227    }
228
229    #[fuchsia::test]
230    async fn detect_format_fvm() {
231        let mut data = vec![0; 4096];
232        data[..constants::FVM_MAGIC.len()].copy_from_slice(&constants::FVM_MAGIC);
233        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fvm);
234    }
235
236    #[fuchsia::test]
237    async fn detect_format_zxcrypt() {
238        let mut data = vec![0; 4096];
239        data[0..constants::ZXCRYPT_MAGIC.len()].copy_from_slice(&constants::ZXCRYPT_MAGIC);
240        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Zxcrypt);
241    }
242
243    #[fuchsia::test]
244    async fn detect_format_block_verity() {
245        let mut data = vec![0; 4096];
246        data[0..constants::BLOCK_VERITY_MAGIC.len()]
247            .copy_from_slice(&constants::BLOCK_VERITY_MAGIC);
248        assert_eq!(
249            get_detected_disk_format(&data, 1000, 512).await.unwrap(),
250            DiskFormat::BlockVerity
251        );
252    }
253
254    #[fuchsia::test]
255    async fn detect_format_gpt() {
256        let mut data = vec![0; 4096];
257        data[512..512 + 16].copy_from_slice(&constants::GPT_MAGIC);
258        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Gpt);
259    }
260
261    #[fuchsia::test]
262    async fn detect_format_minfs() {
263        let mut data = vec![0; 4096];
264        data[0..constants::MINFS_MAGIC.len()].copy_from_slice(&constants::MINFS_MAGIC);
265        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Minfs);
266    }
267
268    #[fuchsia::test]
269    async fn detect_format_minfs_large_block_device() {
270        let mut data = vec![0; 32768];
271        data[0..constants::MINFS_MAGIC.len()].copy_from_slice(&constants::MINFS_MAGIC);
272        assert_eq!(
273            get_detected_disk_format(&data, 1250000, 16384).await.unwrap(),
274            DiskFormat::Minfs
275        );
276    }
277
278    #[fuchsia::test]
279    async fn detect_format_blobfs() {
280        let mut data = vec![0; 4096];
281        data[0..constants::BLOBFS_MAGIC.len()].copy_from_slice(&constants::BLOBFS_MAGIC);
282        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Blobfs);
283    }
284
285    #[fuchsia::test]
286    async fn detect_format_vb_meta() {
287        let mut data = vec![0; 4096];
288        data[0..constants::VB_META_MAGIC.len()].copy_from_slice(&constants::VB_META_MAGIC);
289        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::VbMeta);
290    }
291
292    #[fuchsia::test]
293    async fn detect_format_mbr() {
294        let mut data = vec![0; 4096];
295        data[510] = 0x55 as u8;
296        data[511] = 0xAA as u8;
297        data[38] = 0x30 as u8;
298        data[66] = 0x30 as u8;
299        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Mbr);
300    }
301
302    #[fuchsia::test]
303    async fn detect_format_fat_1() {
304        let mut data = vec![0; 4096];
305        data[510] = 0x55 as u8;
306        data[511] = 0xAA as u8;
307        data[38] = 0x29 as u8;
308        data[66] = 0x30 as u8;
309        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fat);
310    }
311
312    #[fuchsia::test]
313    async fn detect_format_fat_2() {
314        let mut data = vec![0; 4096];
315        data[510] = 0x55 as u8;
316        data[511] = 0xAA as u8;
317        data[38] = 0x30 as u8;
318        data[66] = 0x29 as u8;
319        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fat);
320    }
321
322    #[fuchsia::test]
323    async fn detect_format_f2fs() {
324        let mut data = vec![0; 4096];
325        data[1024..1024 + constants::F2FS_MAGIC.len()].copy_from_slice(&constants::F2FS_MAGIC);
326        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::F2fs);
327    }
328
329    #[fuchsia::test]
330    async fn detect_format_fxfs() {
331        let mut data = vec![0; 4096];
332        data[0..constants::FXFS_MAGIC.len()].copy_from_slice(&constants::FXFS_MAGIC);
333        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fxfs);
334    }
335
336    #[fuchsia::test]
337    async fn detect_format_unknown() {
338        assert_eq!(
339            get_detected_disk_format(&vec![0; 4096], 1000, 512).await.unwrap(),
340            DiskFormat::Unknown
341        );
342    }
343}