1use crate::block_cache::BlockCache;
5use crate::checkpoint::*;
6use crate::crypto;
7use crate::dir::{DentryBlock, DirEntry};
8use crate::inode::{self, Inode};
9use crate::nat::{Nat, NatJournal, RawNatEntry, SummaryBlock};
10use crate::superblock::{
11 BLOCK_SIZE, BLOCKS_PER_SEGMENT, F2FS_MAGIC, SEGMENT_SIZE, SUPERBLOCK_OFFSET, SuperBlock,
12 f2fs_crc32,
13};
14use anyhow::{Error, anyhow, bail, ensure};
15use async_trait::async_trait;
16use std::collections::HashMap;
17use std::ops::Deref;
18use std::sync::Arc;
19use storage_device::Device;
20use storage_device::buffer::Buffer;
21use zerocopy::FromBytes;
22
23pub const NULL_ADDR: u32 = 0;
25pub const NEW_ADDR: u32 = 0xffffffff;
27
28#[async_trait]
31pub(super) trait Reader {
32 async fn read_raw_block(&self, block_addr: u32) -> Result<Buffer<'_>, Error>;
35
36 async fn read_node(&self, nid: u32) -> Result<Buffer<'_>, Error>;
38
39 fn get_key(&self, _identifier: &[u8; 16]) -> Option<&[u8; 64]> {
41 None
42 }
43
44 fn fs_uuid(&self) -> &[u8; 16];
46
47 fn get_decryptor_for_inode(&self, inode: &Inode) -> Option<crypto::PerFileDecryptor> {
50 if let Some(context) = inode.context {
51 if let Some(main_key) = self.get_key(&context.main_key_identifier) {
52 return Some(crypto::PerFileDecryptor::new(main_key, context, self.fs_uuid()));
53 }
54 }
55 None
56 }
57
58 async fn get_nat_entry(&self, nid: u32) -> Result<RawNatEntry, Error>;
60}
61
62pub struct F2fsReader {
63 device: Arc<dyn Device>,
64 superblock: SuperBlock, checkpoint: CheckpointPack, nat: Option<Nat>,
67
68 keys: HashMap<[u8; 16], [u8; 64]>,
70 cache: BlockCache,
71}
72
73impl Drop for F2fsReader {
74 fn drop(&mut self) {
75 self.keys.values_mut().for_each(|v| {
77 *v = [0u8; 64];
78 });
79 }
80}
81
82impl F2fsReader {
83 pub fn superblock(&self) -> &SuperBlock {
84 &self.superblock
85 }
86
87 pub fn checkpoint(&self) -> &CheckpointPack {
88 &self.checkpoint
89 }
90
91 pub async fn open_device(device: Arc<dyn Device>) -> Result<Self, Error> {
92 let (superblock, checkpoint) =
93 match Self::try_from_superblock(device.as_ref(), SUPERBLOCK_OFFSET).await {
94 Ok(x) => x,
95 Err(e) => Self::try_from_superblock(device.as_ref(), SUPERBLOCK_OFFSET * 2)
96 .await
97 .map_err(|_| e)?,
98 };
99 let mut this = Self {
100 device,
101 superblock,
102 checkpoint,
103 nat: None,
104 keys: HashMap::with_capacity(16),
105 cache: BlockCache::new(1024, BLOCK_SIZE),
106 };
107 let nat_journal = this.read_nat_journal().await?;
108 this.nat = Some(Nat::new(
109 this.superblock.nat_blkaddr,
110 this.checkpoint.nat_bitmap.clone(),
111 nat_journal,
112 ));
113 Ok(this)
114 }
115
116 async fn try_from_superblock(
117 device: &dyn Device,
118 superblock_offset: u64,
119 ) -> Result<(SuperBlock, CheckpointPack), Error> {
120 let superblock = SuperBlock::read_from_device(device, superblock_offset).await?;
121 let checkpoint_addr = superblock.cp_blkaddr;
122 let checkpoint_a_offset = BLOCK_SIZE as u64 * checkpoint_addr as u64;
123 let checkpoint_b_offset = checkpoint_a_offset + SEGMENT_SIZE as u64;
124 let checkpoint = match (
126 CheckpointPack::read_from_device(device, checkpoint_a_offset).await,
127 CheckpointPack::read_from_device(device, checkpoint_b_offset).await,
128 ) {
129 (Ok(a), Ok(b)) => {
130 Ok(if a.header.checkpoint_ver > b.header.checkpoint_ver { a } else { b })
131 }
132 (Ok(a), Err(_b)) => Ok(a),
133 (Err(_), Ok(b)) => Ok(b),
134 (Err(a), Err(_b)) => Err(a),
135 }?;
136
137 const MIN_METADATA_SEGMENT_COUNT: u32 = 8;
139
140 let metadata_segment_count = superblock.segment_count_sit
142 + superblock.segment_count_nat
143 + checkpoint.header.rsvd_segment_count
144 + superblock.segment_count_ssa
145 + superblock.segment_count_ckpt;
146 ensure!(
147 metadata_segment_count <= superblock.segment_count
148 && metadata_segment_count >= MIN_METADATA_SEGMENT_COUNT,
149 "Bad segment counts in checkpoint"
150 );
151 Ok((superblock, checkpoint))
152 }
153
154 pub fn checkpoint_start_addr(&self) -> u32 {
156 self.superblock.cp_blkaddr
157 + if self.checkpoint.header.checkpoint_ver % 2 == 1 {
158 0
159 } else {
160 BLOCKS_PER_SEGMENT as u32
161 }
162 }
163
164 fn nat(&self) -> &Nat {
165 self.nat.as_ref().unwrap()
166 }
167
168 async fn read_nat_journal(&mut self) -> Result<HashMap<u32, RawNatEntry>, Error> {
169 if self.checkpoint.header.ckpt_flags & CKPT_FLAG_COMPACT_SUMMARY != 0 {
170 let block = self
173 .read_raw_block(
174 self.checkpoint_start_addr() + self.checkpoint.header.cp_pack_start_sum,
175 )
176 .await?;
177 let n_nats = u16::read_from_bytes(&block.as_slice()[..2]).unwrap();
178 let nat_journal = NatJournal::read_from_bytes(
179 &block.as_slice()[2..2 + std::mem::size_of::<NatJournal>()],
180 )
181 .unwrap();
182 ensure!(
183 (n_nats as usize) <= nat_journal.entries.len(),
184 "n_nats larger than block size"
185 );
186 Ok(HashMap::from_iter(
187 nat_journal.entries[..n_nats as usize].into_iter().map(|e| (e.ino, e.entry)),
188 ))
189 } else {
190 let blk_addr = if self.checkpoint.header.ckpt_flags & CKPT_FLAG_UNMOUNT != 0 {
192 self.checkpoint_start_addr() + self.checkpoint.header.cp_pack_total_block_count - 5
193 } else {
194 self.checkpoint_start_addr() + self.checkpoint.header.cp_pack_total_block_count - 2
195 };
196 let block = self.read_raw_block(blk_addr).await?;
197 let summary = SummaryBlock::read_from_bytes(block.as_slice()).unwrap();
198 ensure!(summary.footer.entry_type == 0u8, "sum_type != 0 in summary footer");
199 let actual_checksum = f2fs_crc32(F2FS_MAGIC, &block.as_slice()[..BLOCK_SIZE - 4]);
200 let expected_checksum = summary.footer.check_sum;
201 ensure!(actual_checksum == expected_checksum, "Summary block has invalid checksum");
202 let mut out = HashMap::new();
203 for i in 0..summary.n_nats as usize {
204 out.insert(
205 summary.nat_journal.entries[i].ino,
206 summary.nat_journal.entries[i].entry,
207 );
208 }
209 Ok(out)
210 }
211 }
212
213 pub fn root_ino(&self) -> u32 {
214 self.superblock.root_ino
215 }
216
217 pub fn max_ino(&self) -> u32 {
220 (self.checkpoint.nat_bitmap.len() * 8) as u32
221 }
222
223 pub fn add_key(&mut self, main_key: &[u8; 64]) -> [u8; 16] {
226 let identifier = fscrypt::main_key_to_identifier(main_key);
227 println!("Adding key with identifier {}", hex::encode(identifier));
228 self.keys.insert(identifier.clone(), main_key.clone());
229 identifier
230 }
231
232 pub async fn readdir(&self, ino: u32) -> Result<Vec<DirEntry>, Error> {
234 let inode = Inode::try_load(self, ino).await?;
235 let decryptor = self.get_decryptor_for_inode(&inode);
236 let mode = inode.header.mode;
237 let advise_flags = inode.header.advise_flags;
238 let flags = inode.header.flags;
239 ensure!(mode.contains(inode::Mode::Directory), "not a directory");
240 if let Some(entries) = inode.get_inline_dir_entries(
241 advise_flags.contains(inode::AdviseFlags::Encrypted),
242 flags.contains(inode::Flags::Casefold),
243 &decryptor,
244 )? {
245 Ok(entries)
246 } else {
247 let mut entries = Vec::new();
248
249 for mut extent in inode.data_blocks() {
254 for _ in 0..extent.length {
255 let dentry_block = DentryBlock::read_from_bytes(
256 self.read_raw_block(extent.physical_block_num).await?.as_slice(),
257 )
258 .unwrap();
259 entries.append(&mut dentry_block.get_entries(
260 ino,
261 advise_flags.contains(inode::AdviseFlags::Encrypted),
262 flags.contains(inode::Flags::Casefold),
263 &decryptor,
264 )?);
265 extent.physical_block_num += 1;
266 }
267 }
268 Ok(entries)
269 }
270 }
271
272 pub async fn read_inode(&self, ino: u32) -> Result<Box<Inode>, Error> {
274 Inode::try_load(self, ino).await
275 }
276
277 pub fn read_symlink(&self, inode: &Inode) -> Result<Box<[u8]>, Error> {
279 if let Some(inline_data) = inode.inline_data.as_deref() {
280 let mut filename = inline_data.to_vec();
281 if inode.header.advise_flags.contains(inode::AdviseFlags::Encrypted) {
282 ensure!(filename.len() >= 2, "invalid encrypted symlink");
284 let symlink_len = u16::read_from_bytes(&filename[..2]).unwrap();
285 filename.drain(..2);
286 filename.truncate(symlink_len as usize);
287 ensure!(symlink_len == filename.len() as u16, "invalid encrypted symlink");
288 if let Some(decryptor) = self.get_decryptor_for_inode(inode) {
289 decryptor.decrypt_filename_data(inode.footer.ino, &mut filename);
290 } else {
291 let proxy_filename: String =
293 fscrypt::proxy_filename::ProxyFilename::new_with_hash_code(0, &filename)
294 .into();
295 filename = proxy_filename.as_bytes().to_vec();
296 }
297 while let Some(0) = filename.last() {
300 filename.pop();
301 }
302 }
303 Ok(filename.into_boxed_slice())
304 } else {
305 bail!("Not a valid symlink");
306 }
307 }
308
309 pub async fn read_data(
312 &self,
313 inode: &Inode,
314 block_num: u32,
315 ) -> Result<Option<Buffer<'_>>, Error> {
316 let inline_flags = inode.header.inline_flags;
317 ensure!(
318 !inline_flags.contains(crate::InlineFlags::Data),
319 "Can't use read_data() on inline file."
320 );
321 let block_addr = inode.data_block_addr(block_num);
322 if block_addr == NULL_ADDR || block_addr == NEW_ADDR {
323 return Ok(None);
325 }
326 let mut buffer = self.read_raw_block(block_addr).await?;
327 if let Some(decryptor) = self.get_decryptor_for_inode(inode) {
328 decryptor.decrypt_data(inode.footer.ino, block_num, buffer.as_mut().as_mut_slice());
329 }
330 Ok(Some(buffer))
331 }
332}
333
334#[async_trait]
335impl Reader for F2fsReader {
336 async fn read_raw_block(&self, block_addr: u32) -> Result<Buffer<'_>, Error> {
338 if let Some(block) = self.cache.get_buffer(block_addr, self.device.deref()).await {
339 return Ok(block);
340 }
341
342 const READAHEAD: u64 = 16;
343 let end = std::cmp::min(block_addr as u64 + READAHEAD, self.device.block_count());
344 let count = end.saturating_sub(block_addr as u64).max(1) as usize;
345
346 let mut buffer = self.device.allocate_buffer(count * BLOCK_SIZE).await;
347 self.device
348 .read(block_addr as u64 * BLOCK_SIZE as u64, buffer.as_mut())
349 .await
350 .map_err(|_| anyhow!("device read failed"))?;
351
352 for i in 0..count {
353 let slice = &buffer.as_slice()[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE];
354 self.cache.insert(block_addr + i as u32, slice.to_vec());
355 }
356 Ok(self.cache.get_buffer(block_addr, self.device.deref()).await.unwrap())
357 }
358
359 async fn read_node(&self, nid: u32) -> Result<Buffer<'_>, Error> {
360 let nat_entry = self.get_nat_entry(nid).await?;
361 self.read_raw_block(nat_entry.block_addr).await
362 }
363
364 fn get_key(&self, identifier: &[u8; 16]) -> Option<&[u8; 64]> {
365 self.keys.get(identifier)
366 }
367
368 fn fs_uuid(&self) -> &[u8; 16] {
369 &self.superblock.uuid
370 }
371
372 async fn get_nat_entry(&self, nid: u32) -> Result<RawNatEntry, Error> {
373 if let Some(entry) = self.nat().nat_journal.get(&nid) {
374 return Ok(*entry);
375 }
376 let nat_block_addr = self.nat().get_nat_block_for_entry(nid)?;
377 let offset = self.nat().get_nat_block_offset_for_entry(nid);
378 let block = self.read_raw_block(nat_block_addr).await?;
379 Ok(RawNatEntry::read_from_bytes(
380 &block.as_slice()[offset..offset + std::mem::size_of::<RawNatEntry>()],
381 )
382 .unwrap())
383 }
384}
385
386#[cfg(test)]
387mod test {
388 use super::*;
389 use crate::dir::FileType;
390 use crate::xattr;
391 use std::collections::HashSet;
392 use std::path::PathBuf;
393 use std::sync::Arc;
394
395 use storage_device::fake_device::FakeDevice;
396
397 fn open_test_image(path: &str) -> FakeDevice {
398 let path = std::path::PathBuf::from(path);
399 println!("path is {path:?}");
400 FakeDevice::from_image(
401 zstd::Decoder::new(std::fs::File::open(&path).expect("open image"))
402 .expect("decompress image"),
403 BLOCK_SIZE as u32,
404 )
405 .expect("open image")
406 }
407
408 #[fuchsia::test]
409 async fn test_open_fs() {
410 let device = open_test_image("/pkg/testdata/f2fs.img.zst");
411
412 let f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
413 assert_eq!(f2fs.root_ino(), 3);
415 let superblock = &f2fs.superblock;
416 let major_ver = superblock.major_ver;
417 let minor_ver = superblock.minor_ver;
418 assert_eq!(major_ver, 1);
419 assert_eq!(minor_ver, 16);
420 assert_eq!(superblock.get_total_size(), 256 << 20);
421 assert_eq!(superblock.get_volume_name().expect("get volume name"), "testimage");
422 }
423
424 async fn resolve_inode_path(f2fs: &F2fsReader, path: &str) -> Result<u32, Error> {
426 let path = PathBuf::from(path.strip_prefix("/").unwrap());
427 let mut ino = f2fs.root_ino();
428 for filename in &path {
429 let entries = f2fs.readdir(ino).await?;
430 if let Some(entry) = entries.iter().filter(|e| *e.filename == *filename).next() {
431 ino = entry.ino;
432 } else {
433 bail!("Not found.");
434 }
435 }
436 Ok(ino)
437 }
438
439 #[fuchsia::test]
440 async fn test_basic_dirs() {
441 let device = open_test_image("/pkg/testdata/f2fs.img.zst");
442
443 let f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
444 let root_ino = f2fs.root_ino();
445 let root_entries = f2fs.readdir(root_ino).await.expect("readdir");
446 assert_eq!(root_entries.len(), 7);
447 assert_eq!(root_entries[0].filename, "a");
448 assert_eq!(root_entries[0].file_type, FileType::Directory);
449 assert_eq!(root_entries[1].filename, "large_dir");
450 assert_eq!(root_entries[2].filename, "large_dir2");
451 assert_eq!(root_entries[3].filename, "sparse.dat");
452 assert_eq!(root_entries[4].filename, "verity");
453 assert_eq!(root_entries[5].filename, "fscrypt");
454 assert_eq!(root_entries[6].filename, "large_zero");
455
456 let inlined_file_ino =
457 resolve_inode_path(&f2fs, "/a/b/c/inlined").await.expect("resolve inlined");
458 let inode = Inode::try_load(&f2fs, inlined_file_ino).await.expect("load inode");
459 let block_size = inode.header.block_size;
460 let size = inode.header.size;
461 assert_eq!(block_size, 1);
462 assert_eq!(size, 12);
463 assert_eq!(inode.inline_data.unwrap().as_ref(), "inline_data\n".as_bytes());
464
465 const REG_FILE_SIZE: u64 = 8 * BLOCK_SIZE as u64 + 8;
466 const REG_FILE_BLOCKS: u64 = 9 + 1;
467 let regular_file_ino =
468 resolve_inode_path(&f2fs, "/a/b/c/regular").await.expect("resolve regular");
469 let inode = Inode::try_load(&f2fs, regular_file_ino).await.expect("load inode");
470 let block_size = inode.header.block_size;
471 let size = inode.header.size;
472 assert_eq!(block_size, REG_FILE_BLOCKS);
473 assert_eq!(size, REG_FILE_SIZE);
474 assert!(inode.inline_data.is_none());
475 for i in 0..8 {
476 assert_eq!(
477 f2fs.read_data(&inode, i).await.expect("read data").unwrap().as_slice(),
478 &[0u8; BLOCK_SIZE]
479 );
480 }
481 assert_eq!(
482 &f2fs.read_data(&inode, 8).await.expect("read data").unwrap().as_slice()[..9],
483 b"01234567\0"
484 );
485
486 let symlink_ino =
487 resolve_inode_path(&f2fs, "/a/b/c/symlink").await.expect("resolve symlink");
488 let inode = Inode::try_load(&f2fs, symlink_ino).await.expect("load inode");
489 assert_eq!(f2fs.read_symlink(&inode).expect("read_symlink").as_ref(), b"regular");
490
491 let hardlink_ino =
492 resolve_inode_path(&f2fs, "/a/b/c/hardlink").await.expect("resolve hardlink");
493 let inode = Inode::try_load(&f2fs, hardlink_ino).await.expect("load inode");
494 let block_size = inode.header.block_size;
495 let size = inode.header.size;
496 assert_eq!(block_size, REG_FILE_BLOCKS);
497 assert_eq!(size, REG_FILE_SIZE);
498
499 let chowned_ino =
500 resolve_inode_path(&f2fs, "/a/b/c/chowned").await.expect("resolve chowned");
501 let inode = Inode::try_load(&f2fs, chowned_ino).await.expect("load inode");
502 let uid = inode.header.uid;
503 let gid = inode.header.gid;
504 assert_eq!(uid, 999);
505 assert_eq!(gid, 999);
506
507 let large_dir = resolve_inode_path(&f2fs, "/large_dir").await.expect("resolve large_dir");
508 assert_eq!(f2fs.readdir(large_dir).await.expect("readdir").len(), 2001);
509
510 let large_dir2 = resolve_inode_path(&f2fs, "/large_dir2").await.expect("resolve large_dir");
511 assert_eq!(f2fs.readdir(large_dir2).await.expect("readdir").len(), 1);
512
513 let sparse_dat =
514 resolve_inode_path(&f2fs, "/sparse.dat").await.expect("resolve sparse.dat");
515 let inode = Inode::try_load(&f2fs, sparse_dat).await.expect("load inode");
516 let data_blocks: Vec<_> = inode.data_blocks().into_iter().collect();
517 assert_eq!(data_blocks.len(), 6);
518 assert_eq!(data_blocks[0].logical_block_num, 0);
519 assert_eq!(data_blocks[0].length, 1);
520 let block =
522 f2fs.read_raw_block(data_blocks[0].physical_block_num).await.expect("read sparse");
523 assert_eq!(&block.as_slice()[..3], b"foo");
524 assert_eq!(data_blocks[1].logical_block_num, 923);
526 assert_eq!(data_blocks[1].length, 1);
527 assert_eq!(data_blocks[2].logical_block_num, 1941);
528 assert_eq!(data_blocks[2].length, 1);
529 assert_eq!(data_blocks[3].logical_block_num, 2959);
530 assert_eq!(data_blocks[3].length, 1);
531 assert_eq!(data_blocks[4].logical_block_num, 1039283);
532 assert_eq!(data_blocks[4].length, 1);
533 assert_eq!(data_blocks[5].logical_block_num, 104671683);
534 assert_eq!(data_blocks[5].length, 2);
535 let block =
536 f2fs.read_raw_block(data_blocks[5].physical_block_num).await.expect("read sparse");
537 assert_eq!(block.as_slice(), &[0; BLOCK_SIZE]);
538 assert_eq!(
540 &f2fs.read_data(&inode, 104671684).await.expect("read data block").unwrap().as_slice()
541 [..3],
542 b"bar"
543 );
544 assert!(f2fs.read_data(&inode, 104671684 - 10).await.expect("read data block").is_none());
546 }
547
548 #[fuchsia::test]
549 async fn test_xattr() {
550 let device = open_test_image("/pkg/testdata/f2fs.img.zst");
551
552 let f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
553 let sparse_dat =
554 resolve_inode_path(&f2fs, "/sparse.dat").await.expect("resolve sparse.dat");
555 let inode = Inode::try_load(&f2fs, sparse_dat).await.expect("load inode");
556 assert_eq!(
557 inode.xattr,
558 vec![
559 xattr::XattrEntry {
560 index: xattr::Index::User,
561 name: Box::new(b"a".to_owned()),
562 value: Box::new(b"value".to_owned())
563 },
564 xattr::XattrEntry {
565 index: xattr::Index::User,
566 name: Box::new(b"c".to_owned()),
567 value: Box::new(b"value".to_owned())
568 },
569 xattr::XattrEntry {
570 index: xattr::Index::User,
571 name: Box::new(b"padding_test_1".to_owned()),
572 value: Box::new(b"v".to_owned())
573 },
574 xattr::XattrEntry {
575 index: xattr::Index::User,
576 name: Box::new(b"padding_test_2".to_owned()),
577 value: Box::new(b"va".to_owned())
578 },
579 xattr::XattrEntry {
580 index: xattr::Index::User,
581 name: Box::new(b"padding_test_3".to_owned()),
582 value: Box::new(b"val".to_owned())
583 },
584 xattr::XattrEntry {
585 index: xattr::Index::User,
586 name: Box::new(b"padding_test_4".to_owned()),
587 value: Box::new(b"valu".to_owned())
588 },
589 xattr::XattrEntry {
590 index: xattr::Index::User,
591 name: Box::new(b"padding_test_5".to_owned()),
592 value: Box::new(b"value".to_owned())
593 },
594 ]
595 );
596 }
597
598 #[fuchsia::test]
599 async fn test_fsverity() {
600 let device = open_test_image("/pkg/testdata/f2fs.img.zst");
601 let mut f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
602 f2fs.add_key(&[0u8; 64]);
603 let verity_files = vec![
604 "/verity/inlined",
605 "/verity/regular",
606 "/verity/merkle_layers.dat",
607 "/fscrypt/a/b/regular",
608 ];
609 for file_path in verity_files {
610 let file = resolve_inode_path(&f2fs, file_path).await.expect("resolve file");
611 let inode = Inode::try_load(&f2fs, file).await.expect("load inode");
612 assert!(inode.header.advise_flags.contains(inode::AdviseFlags::Verity));
613 }
614 let file = resolve_inode_path(&f2fs, "/a/b/c/regular").await.expect("resolve file");
616 let inode = Inode::try_load(&f2fs, file).await.expect("load inode");
617 assert!(!inode.header.advise_flags.contains(inode::AdviseFlags::Verity));
618 }
620
621 #[fuchsia::test]
622 async fn test_fbe() {
623 let str_a = "2ll82QAAAADywluz1Ule7OVNBxUfa5Mw";
632 let str_b = "sttckQAAAADLBOCVVgjrZ-CXNkj5E6Cr";
633 let str_symlink = "zHAtQgAAAACRNPQYvCKuQo5F8rQUORg3";
634 let bytes_symlink_content = b"AAAAAAAAAADUsYZ_qNiiouF7e40xm65S";
635
636 let mut expected : HashSet<_> = [ "2ll82QAAAADywluz1Ule7OVNBxUfa5Mw",
638 "65OSUQAAAADqOiZJcQ1El2dpVdYMy84l",
639 "7vcnbgAAAAAOWdQfi4wK46uRGQBD0YSy",
640 "9Gsv9QAAAADjTeJ_9WdCxZMVTiSWhsWR",
641 "FAqGXAAAAAD1jOLXaZN-o8X9PoS67GI7",
642 "Rq5qZAAAAAA3y2lvAqesYDnVJWMklWnj",
643 "S93sdgAAAABo-YmXNPKtv4wxQCcUslTu",
644 "VP8QBwAAAAATw6Ozex0N2gMYrnDsB2aH",
645 "xUNjwgAAAADB0pEx5ovwx-AS02L0d1j7VMBRXzM4YnBri2pbasOqbFLhtegXr9kDGNcYd_hyk2mOkQIqu8hk7eARlFl-bq1yLhikhIT9HVC3FMrI7vQ-ewncEjXLDP3KK6RtH3r34S89AlzJZ4DVfXrr_Q5N5mANBbGTzeO70aJHL0Ms-MgkKwjHcbIxXLwcjE2B-mssLAvXam58pSD-aazxS_J2hrxOHGoUYiVJ-rXHozmKxBdWAO6OUW65",
646 ].into_iter().collect();
647
648 let device = open_test_image("/pkg/testdata/f2fs.img.zst");
649
650 let mut f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
651
652 resolve_inode_path(&f2fs, "/fscrypt/a/b/regular")
656 .await
657 .expect_err("resolve fscrypt regular");
658 let fscrypt_dir_ino =
659 resolve_inode_path(&f2fs, "/fscrypt").await.expect("resolve encrypted dir");
660 let entries = f2fs.readdir(fscrypt_dir_ino).await.expect("readdir");
661 println!("entries {entries:?}");
662
663 for entry in entries {
664 assert!(expected.remove(entry.filename.as_str()), "unexpected entry {entry:?}");
665 }
666 assert!(expected.is_empty());
667
668 resolve_inode_path(&f2fs, &format!("/fscrypt/{str_a}"))
669 .await
670 .expect("resolve encrypted dir");
671 let enc_symlink_ino =
672 resolve_inode_path(&f2fs, &format!("/fscrypt/{str_a}/{str_b}/{str_symlink}"))
673 .await
674 .expect("resolve encrypted symlink");
675 let symlink_inode =
676 Inode::try_load(&f2fs, enc_symlink_ino).await.expect("load symlink inode");
677 assert_eq!(
678 &*f2fs.read_symlink(&symlink_inode).expect("read_symlink"),
679 bytes_symlink_content
680 );
681
682 f2fs.add_key(&[0u8; 64]);
684 resolve_inode_path(&f2fs, "/fscrypt/a/b/regular").await.expect("resolve fscrypt regular");
685 let inlined_ino = resolve_inode_path(&f2fs, "/fscrypt/a/b/inlined")
686 .await
687 .expect("resolve fscrypt inlined");
688 let short_file = Inode::try_load(&f2fs, inlined_ino).await.expect("load symlink inode");
689 assert!(
690 !short_file.header.inline_flags.contains(inode::InlineFlags::Data),
691 "encrypted files shouldn't be inlined"
692 );
693 let short_data =
694 f2fs.read_data(&short_file, 0).await.expect("read_data").expect("non-empty page");
695 assert_eq!(
696 &short_data.as_slice()[..short_file.header.size as usize],
697 b"test45678abcdef_12345678"
698 );
699
700 let symlink_ino = resolve_inode_path(&f2fs, "/fscrypt/a/b/symlink")
701 .await
702 .expect("resolve fscrypt symlink");
703 assert_eq!(symlink_ino, enc_symlink_ino);
704
705 let symlink_inode = Inode::try_load(&f2fs, symlink_ino).await.expect("load symlink inode");
706 let symlink = f2fs.read_symlink(&symlink_inode).expect("read_symlink");
707 assert_eq!(*symlink, *b"inlined");
708 }
709}