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, VmoBackedServerTestingExt as _};
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        let mut request_handler = pin!(fake_server.serve(stream).fuse());
194
195        select! {
196          _ = request_handler => unreachable!(),
197          format = detect_disk_format_res(&proxy).fuse() => format,
198        }
199    }
200
201    /// Confirm that we map to/from string consistently.
202    #[test]
203    fn to_from_str_test() {
204        for fmt in vec![
205            DiskFormat::Unknown,
206            DiskFormat::Gpt,
207            DiskFormat::Mbr,
208            DiskFormat::Minfs,
209            DiskFormat::Fat,
210            DiskFormat::Blobfs,
211            DiskFormat::Fvm,
212            DiskFormat::Zxcrypt,
213            DiskFormat::BlockVerity,
214            DiskFormat::VbMeta,
215            DiskFormat::Fxfs,
216            DiskFormat::F2fs,
217            DiskFormat::NandBroker,
218        ] {
219            assert_eq!(fmt, fmt.as_str().into());
220        }
221    }
222
223    #[fuchsia::test]
224    async fn detect_disk_format_too_small() {
225        assert_eq!(
226            get_detected_disk_format(&Vec::new(), 1, 512).await.unwrap(),
227            DiskFormat::Unknown
228        );
229    }
230
231    #[fuchsia::test]
232    async fn detect_format_fvm() {
233        let mut data = vec![0; 4096];
234        data[..constants::FVM_MAGIC.len()].copy_from_slice(&constants::FVM_MAGIC);
235        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fvm);
236    }
237
238    #[fuchsia::test]
239    async fn detect_format_zxcrypt() {
240        let mut data = vec![0; 4096];
241        data[0..constants::ZXCRYPT_MAGIC.len()].copy_from_slice(&constants::ZXCRYPT_MAGIC);
242        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Zxcrypt);
243    }
244
245    #[fuchsia::test]
246    async fn detect_format_block_verity() {
247        let mut data = vec![0; 4096];
248        data[0..constants::BLOCK_VERITY_MAGIC.len()]
249            .copy_from_slice(&constants::BLOCK_VERITY_MAGIC);
250        assert_eq!(
251            get_detected_disk_format(&data, 1000, 512).await.unwrap(),
252            DiskFormat::BlockVerity
253        );
254    }
255
256    #[fuchsia::test]
257    async fn detect_format_gpt() {
258        let mut data = vec![0; 4096];
259        data[512..512 + 16].copy_from_slice(&constants::GPT_MAGIC);
260        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Gpt);
261    }
262
263    #[fuchsia::test]
264    async fn detect_format_minfs() {
265        let mut data = vec![0; 4096];
266        data[0..constants::MINFS_MAGIC.len()].copy_from_slice(&constants::MINFS_MAGIC);
267        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Minfs);
268    }
269
270    #[fuchsia::test]
271    async fn detect_format_minfs_large_block_device() {
272        let mut data = vec![0; 32768];
273        data[0..constants::MINFS_MAGIC.len()].copy_from_slice(&constants::MINFS_MAGIC);
274        assert_eq!(
275            get_detected_disk_format(&data, 1250000, 16384).await.unwrap(),
276            DiskFormat::Minfs
277        );
278    }
279
280    #[fuchsia::test]
281    async fn detect_format_blobfs() {
282        let mut data = vec![0; 4096];
283        data[0..constants::BLOBFS_MAGIC.len()].copy_from_slice(&constants::BLOBFS_MAGIC);
284        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Blobfs);
285    }
286
287    #[fuchsia::test]
288    async fn detect_format_vb_meta() {
289        let mut data = vec![0; 4096];
290        data[0..constants::VB_META_MAGIC.len()].copy_from_slice(&constants::VB_META_MAGIC);
291        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::VbMeta);
292    }
293
294    #[fuchsia::test]
295    async fn detect_format_mbr() {
296        let mut data = vec![0; 4096];
297        data[510] = 0x55 as u8;
298        data[511] = 0xAA as u8;
299        data[38] = 0x30 as u8;
300        data[66] = 0x30 as u8;
301        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Mbr);
302    }
303
304    #[fuchsia::test]
305    async fn detect_format_fat_1() {
306        let mut data = vec![0; 4096];
307        data[510] = 0x55 as u8;
308        data[511] = 0xAA as u8;
309        data[38] = 0x29 as u8;
310        data[66] = 0x30 as u8;
311        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fat);
312    }
313
314    #[fuchsia::test]
315    async fn detect_format_fat_2() {
316        let mut data = vec![0; 4096];
317        data[510] = 0x55 as u8;
318        data[511] = 0xAA as u8;
319        data[38] = 0x30 as u8;
320        data[66] = 0x29 as u8;
321        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fat);
322    }
323
324    #[fuchsia::test]
325    async fn detect_format_f2fs() {
326        let mut data = vec![0; 4096];
327        data[1024..1024 + constants::F2FS_MAGIC.len()].copy_from_slice(&constants::F2FS_MAGIC);
328        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::F2fs);
329    }
330
331    #[fuchsia::test]
332    async fn detect_format_fxfs() {
333        let mut data = vec![0; 4096];
334        data[0..constants::FXFS_MAGIC.len()].copy_from_slice(&constants::FXFS_MAGIC);
335        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fxfs);
336    }
337
338    #[fuchsia::test]
339    async fn detect_format_unknown() {
340        assert_eq!(
341            get_detected_disk_format(&vec![0; 4096], 1000, 512).await.unwrap(),
342            DiskFormat::Unknown
343        );
344    }
345}