1#![deny(missing_docs)]
8
9use anyhow::{bail, Context, Error};
10use block_client::cache::Cache;
11use block_client::RemoteBlockClientSync;
12use byteorder::{LittleEndian, WriteBytesExt};
13use fidl_fuchsia_hardware_block::BlockMarker;
14use fidl_fuchsia_io as fio;
15use fuchsia_fs::directory::{readdir_recursive, DirEntry, DirentKind};
16use futures::StreamExt;
17use std::io::Write;
18
19const FACTORYFS_MAGIC: u64 = 0xa55d3ff91e694d21;
20const BLOCK_SIZE: u32 = 4096;
21const DIRENT_START_BLOCK: u32 = 1;
22const SUPERBLOCK_DATA_SIZE: u32 = 52;
26const FACTORYFS_MAJOR_VERSION: u32 = 1;
27const FACTORYFS_MINOR_VERSION: u32 = 0;
28
29fn round_up_to_align(x: u32, align: u32) -> u32 {
31 debug_assert_ne!(align, 0);
32 debug_assert_eq!(align & (align - 1), 0);
33 (x + align - 1) & !(align - 1)
34}
35
36fn num_blocks(bytes: u32) -> u32 {
41 (bytes + BLOCK_SIZE - 1) / BLOCK_SIZE
43}
44
45fn round_up_to_block_size(bytes: u32) -> u32 {
48 num_blocks(bytes) * BLOCK_SIZE
49}
50
51fn block_align<Writer>(writer: &mut Writer, written_bytes: u32) -> Result<(), Error>
55where
56 Writer: Write,
57{
58 let fill = round_up_to_block_size(written_bytes) - written_bytes;
59 for _ in 0..fill {
60 writer.write_u8(0)?;
61 }
62 Ok(())
63}
64
65struct FactoryFS {
67 major_version: u32,
68 minor_version: u32,
69 flags: u32,
70 block_size: u32,
71 entries: Vec<DirectoryEntry>,
72}
73
74impl FactoryFS {
75 fn serialize_superblock<Writer>(&self, writer: &mut Writer) -> Result<(u32, u32), Error>
76 where
77 Writer: Write,
78 {
79 writer.write_u64::<LittleEndian>(FACTORYFS_MAGIC).context("failed to write magic")?;
106 writer.write_u32::<LittleEndian>(self.major_version)?;
107 writer.write_u32::<LittleEndian>(self.minor_version)?;
108 writer.write_u32::<LittleEndian>(self.flags)?;
109
110 let data_blocks = self
112 .entries
113 .iter()
114 .fold(0, |blocks, entry| blocks + num_blocks(entry.data.len() as u32));
115 writer.write_u32::<LittleEndian>(data_blocks)?;
116
117 let entries_bytes = self.entries.iter().fold(0, |size, entry| size + entry.metadata_size());
119 let entries_blocks = num_blocks(entries_bytes);
120 writer.write_u32::<LittleEndian>(entries_bytes)?;
121 writer.write_u32::<LittleEndian>(self.entries.len() as u32)?;
122
123 writer.write_u64::<LittleEndian>(0)?;
125
126 writer.write_u32::<LittleEndian>(self.block_size)?;
127
128 writer.write_u32::<LittleEndian>(entries_blocks)?;
129 writer.write_u32::<LittleEndian>(DIRENT_START_BLOCK)?;
130
131 Ok((entries_bytes, entries_blocks))
132 }
133
134 fn serialize<Writer>(&self, writer: &mut Writer) -> Result<(), Error>
142 where
143 Writer: Write,
144 {
145 let (entries_bytes, entries_blocks) =
146 self.serialize_superblock(writer).context("failed to serialize superblock")?;
147
148 block_align(writer, SUPERBLOCK_DATA_SIZE)?;
150
151 let mut data_offset = DIRENT_START_BLOCK + entries_blocks;
153 for entry in &self.entries {
155 entry.serialize_metadata(writer, data_offset)?;
156 data_offset += num_blocks(entry.data.len() as u32);
157 }
158
159 block_align(writer, entries_bytes)?;
160
161 for entry in &self.entries {
163 entry.serialize_data(writer)?;
164 }
165
166 Ok(())
167 }
168}
169
170#[derive(Debug, PartialEq, Eq)]
173struct DirectoryEntry {
174 name: Vec<u8>,
175 data: Vec<u8>,
176}
177
178impl DirectoryEntry {
179 fn metadata_size(&self) -> u32 {
181 let name_len = self.name.len() as u32;
182 let padding = round_up_to_align(name_len, 4) - name_len;
183
184 4
186 + 4
188 + 4
190 + name_len
192 + padding
194 }
195
196 fn serialize_metadata<Writer>(&self, writer: &mut Writer, data_offset: u32) -> Result<(), Error>
200 where
201 Writer: Write,
202 {
203 let name_len = self.name.len() as u32;
224 writer.write_u32::<LittleEndian>(name_len)?;
225 writer.write_u32::<LittleEndian>(self.data.len() as u32)?;
226 writer.write_u32::<LittleEndian>(data_offset)?;
227 writer.write_all(&self.name)?;
228
229 let padding = round_up_to_align(name_len, 4) - name_len;
231 for _ in 0..padding {
232 writer.write_u8(0)?;
233 }
234
235 Ok(())
236 }
237
238 fn serialize_data<Writer>(&self, writer: &mut Writer) -> Result<(), Error>
243 where
244 Writer: Write,
245 {
246 writer.write_all(&self.data)?;
247 block_align(writer, self.data.len() as u32)?;
248 Ok(())
249 }
250}
251
252async fn get_entries(dir: &fio::DirectoryProxy) -> Result<Vec<DirectoryEntry>, Error> {
253 let out: Vec<DirEntry> = readdir_recursive(dir, None).map(|x| x.unwrap()).collect().await;
254
255 let mut entries = vec![];
256 for ent in out {
257 if ent.kind != DirentKind::File {
258 bail!("Directory entry '{}' is not a file. FactoryFS can only contain files.", ent.name)
261 }
262
263 let (file_proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
266 dir.deprecated_open(
267 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY,
268 fio::ModeType::empty(),
269 &ent.name,
270 fidl::endpoints::ServerEnd::new(server_end.into_channel()),
271 )
272 .with_context(|| format!("failed to open file {}", ent.name))?;
273 let (status, attrs) = file_proxy.get_attr().await.with_context(|| {
274 format!("failed to get attributes of file {}: (fidl failure)", ent.name)
275 })?;
276 if zx::Status::from_raw(status) != zx::Status::OK {
277 bail!("failed to get attributes of file {}", ent.name);
278 }
279 let data = file_proxy
280 .read(attrs.content_size)
281 .await
282 .with_context(|| {
283 format!("failed to read contents of file {}: (fidl failure)", ent.name)
284 })?
285 .map_err(zx::Status::from_raw)
286 .with_context(|| format!("failed to read contents of file {}", ent.name))?;
287
288 entries.push(DirectoryEntry { name: ent.name.as_bytes().to_vec(), data });
289 }
290
291 entries.sort_by(|a, b| a.name.cmp(&b.name));
292
293 Ok(entries)
294}
295
296async fn write_directory<W: Write>(dir: &fio::DirectoryProxy, device: &mut W) -> Result<(), Error> {
297 let entries = get_entries(dir).await.context("failed to get entries from directory")?;
298
299 let factoryfs = FactoryFS {
300 major_version: FACTORYFS_MAJOR_VERSION,
301 minor_version: FACTORYFS_MINOR_VERSION,
302 flags: 0,
303 block_size: BLOCK_SIZE,
304 entries,
305 };
306
307 factoryfs.serialize(device).context("failed to serialize factoryfs")?;
308
309 Ok(())
310}
311
312pub async fn export_directory(
318 dir: &fio::DirectoryProxy,
319 client_end: fidl::endpoints::ClientEnd<BlockMarker>,
320) -> Result<(), Error> {
321 let device = RemoteBlockClientSync::new(client_end)
322 .context("failed to create remote block device client")?;
323 let mut device = Cache::new(device).context("failed to create cache layer for block device")?;
324
325 write_directory(dir, &mut device).await.context("failed to write out directory")?;
326
327 device.flush().context("failed to flush to device")?;
328
329 Ok(())
330}
331
332#[cfg(test)]
333mod tests {
334 use super::{
335 block_align, export_directory, get_entries, num_blocks, round_up_to_block_size,
336 DirectoryEntry, FactoryFS, BLOCK_SIZE, FACTORYFS_MAJOR_VERSION, FACTORYFS_MINOR_VERSION,
337 SUPERBLOCK_DATA_SIZE,
338 };
339
340 use assert_matches::assert_matches;
341 use fidl::endpoints;
342 use fidl_fuchsia_io as fio;
343 use ramdevice_client::RamdiskClient;
344 use vfs::directory::entry_container::Directory;
345 use vfs::execution_scope::ExecutionScope;
346 use vfs::file::vmo::read_only;
347 use vfs::pseudo_directory;
348
349 #[test]
350 fn test_num_blocks() {
351 assert_eq!(num_blocks(0), 0);
352 assert_eq!(num_blocks(1), 1);
353 assert_eq!(num_blocks(10), 1);
354 assert_eq!(num_blocks(BLOCK_SIZE - 1), 1);
355 assert_eq!(num_blocks(BLOCK_SIZE), 1);
356 assert_eq!(num_blocks(BLOCK_SIZE + 1), 2);
357 }
358
359 #[test]
360 fn test_round_up() {
361 assert_eq!(round_up_to_block_size(0), 0);
362 assert_eq!(round_up_to_block_size(1), BLOCK_SIZE);
363 assert_eq!(round_up_to_block_size(BLOCK_SIZE - 1), BLOCK_SIZE);
364 assert_eq!(round_up_to_block_size(BLOCK_SIZE), BLOCK_SIZE);
365 assert_eq!(round_up_to_block_size(BLOCK_SIZE + 1), BLOCK_SIZE * 2);
366 }
367
368 #[test]
369 fn test_block_align() {
370 let mut cases = vec![
371 (0, 0),
373 (1, BLOCK_SIZE - 1),
374 (BLOCK_SIZE - 1, 1),
375 (BLOCK_SIZE, 0),
376 (BLOCK_SIZE + 1, BLOCK_SIZE - 1),
377 ];
378
379 for case in &mut cases {
380 let mut w = vec![];
381 assert_matches!(block_align(&mut w, case.0), Ok(()));
382 assert_eq!(w.len(), case.1 as usize);
383 assert!(w.into_iter().all(|v| v == 0));
384 }
385 }
386
387 #[test]
388 fn test_superblock_data() {
389 let name = "test_name".as_bytes();
390 let data = vec![1, 2, 3, 4, 5];
391
392 let entry = DirectoryEntry { name: name.to_owned(), data: data.clone() };
393
394 let metadata_size = entry.metadata_size();
395 let metadata_blocks = num_blocks(metadata_size);
396
397 let factoryfs = FactoryFS {
398 major_version: FACTORYFS_MAJOR_VERSION,
399 minor_version: FACTORYFS_MINOR_VERSION,
400 flags: 0,
401 block_size: BLOCK_SIZE,
402 entries: vec![entry],
403 };
404
405 let mut out = vec![];
406 assert_eq!(
407 factoryfs.serialize_superblock(&mut out).unwrap(),
408 (metadata_size, metadata_blocks),
409 );
410
411 assert_eq!(out.len() as u32, SUPERBLOCK_DATA_SIZE);
412 }
413
414 #[test]
415 fn test_dirent_metadata() {
416 let data_offset = 12;
417 let data = vec![1, 2, 3, 4, 5];
418
419 let mut out: Vec<u8> = vec![];
420 let name = "test_name".as_bytes();
421 let dirent = DirectoryEntry { name: name.to_owned(), data };
422
423 assert_matches!(dirent.serialize_metadata(&mut out, data_offset), Ok(()));
424 assert_eq!(dirent.metadata_size(), out.len() as u32);
425 }
426
427 #[fuchsia::test]
428 async fn test_export() {
429 let dir = pseudo_directory! {
430 "a" => read_only("a content"),
431 "b" => pseudo_directory! {
432 "c" => read_only("c content"),
433 },
434 };
435 let (dir_proxy, dir_server) = endpoints::create_proxy::<fio::DirectoryMarker>();
436 let scope = ExecutionScope::new();
437 dir.open(
438 scope,
439 fio::OpenFlags::RIGHT_READABLE
440 | fio::OpenFlags::RIGHT_WRITABLE
441 | fio::OpenFlags::DIRECTORY,
442 vfs::path::Path::dot(),
443 endpoints::ServerEnd::new(dir_server.into_channel()),
444 );
445
446 let ramdisk = RamdiskClient::create(512, 1 << 16).await.unwrap();
447 let channel = ramdisk.open().unwrap();
448
449 assert_matches!(export_directory(&dir_proxy, channel).await, Ok(()));
450 }
451
452 #[fuchsia::test]
453 async fn test_get_entries() {
454 let dir = pseudo_directory! {
455 "a" => read_only("a content"),
456 "d" => read_only("d content"),
457 "b" => pseudo_directory! {
458 "c" => read_only("c content"),
459 },
460 };
461 let (dir_proxy, dir_server) = endpoints::create_proxy::<fio::DirectoryMarker>();
462 let scope = ExecutionScope::new();
463 dir.open(
464 scope,
465 fio::OpenFlags::RIGHT_READABLE
466 | fio::OpenFlags::RIGHT_WRITABLE
467 | fio::OpenFlags::DIRECTORY,
468 vfs::path::Path::dot(),
469 endpoints::ServerEnd::new(dir_server.into_channel()),
470 );
471
472 let entries = get_entries(&dir_proxy).await.unwrap();
473
474 assert_eq!(
475 entries,
476 vec![
477 DirectoryEntry { name: b"a".to_vec(), data: b"a content".to_vec() },
478 DirectoryEntry { name: b"b/c".to_vec(), data: b"c content".to_vec() },
479 DirectoryEntry { name: b"d".to_vec(), data: b"d content".to_vec() },
480 ],
481 );
482 }
483}