1use crate::checkpoint::*;
5use crate::crypto;
6use crate::dir::{DentryBlock, DirEntry};
7use crate::inode::{self, Inode};
8use crate::nat::{Nat, NatJournal, RawNatEntry, SummaryBlock};
9use crate::superblock::{
10 f2fs_crc32, SuperBlock, BLOCKS_PER_SEGMENT, BLOCK_SIZE, F2FS_MAGIC, SEGMENT_SIZE,
11 SUPERBLOCK_OFFSET,
12};
13use anyhow::{anyhow, bail, ensure, Error};
14use async_trait::async_trait;
15use std::collections::HashMap;
16use std::sync::Arc;
17use storage_device::buffer::Buffer;
18use storage_device::Device;
19use zerocopy::FromBytes;
20
21pub const NULL_ADDR: u32 = 0;
23pub const NEW_ADDR: u32 = 0xffffffff;
25
26#[async_trait]
29pub(super) trait Reader {
30 async fn read_raw_block(&self, block_addr: u32) -> Result<Buffer<'_>, Error>;
33
34 async fn read_node(&self, nid: u32) -> Result<Buffer<'_>, Error>;
36
37 fn get_key(&self, _identifier: &[u8; 16]) -> Option<&[u8; 64]> {
39 None
40 }
41
42 fn fs_uuid(&self) -> &[u8; 16];
44
45 fn get_decryptor_for_inode(&self, inode: &Inode) -> Option<crypto::PerFileDecryptor> {
48 if let Some(context) = inode.context {
49 if let Some(main_key) = self.get_key(&context.main_key_identifier) {
50 return Some(crypto::PerFileDecryptor::new(main_key, context, self.fs_uuid()));
51 }
52 }
53 None
54 }
55
56 async fn get_nat_entry(&self, nid: u32) -> Result<RawNatEntry, Error>;
58}
59
60pub struct F2fsReader {
61 device: Arc<dyn Device>,
62 pub superblock: SuperBlock, checkpoint: CheckpointPack, nat: Option<Nat>,
65
66 keys: HashMap<[u8; 16], [u8; 64]>,
68}
69
70impl Drop for F2fsReader {
71 fn drop(&mut self) {
72 self.keys.values_mut().for_each(|v| {
74 *v = [0u8; 64];
75 });
76 }
77}
78
79impl F2fsReader {
80 pub async fn open_device(device: Arc<dyn Device>) -> Result<Self, Error> {
81 let (superblock, checkpoint) =
82 match Self::try_from_superblock(device.as_ref(), SUPERBLOCK_OFFSET).await {
83 Ok(x) => x,
84 Err(e) => Self::try_from_superblock(device.as_ref(), SUPERBLOCK_OFFSET * 2)
85 .await
86 .map_err(|_| e)?,
87 };
88 let mut this =
89 Self { device, superblock, checkpoint, nat: None, keys: HashMap::with_capacity(16) };
90 let nat_journal = this.read_nat_journal().await?;
91 this.nat = Some(Nat::new(
92 this.superblock.nat_blkaddr,
93 this.checkpoint.nat_bitmap.clone(),
94 nat_journal,
95 ));
96 Ok(this)
97 }
98
99 async fn try_from_superblock(
100 device: &dyn Device,
101 superblock_offset: u64,
102 ) -> Result<(SuperBlock, CheckpointPack), Error> {
103 let superblock = SuperBlock::read_from_device(device, superblock_offset).await?;
104 let checkpoint_addr = superblock.cp_blkaddr;
105 let checkpoint_a_offset = BLOCK_SIZE as u64 * checkpoint_addr as u64;
106 let checkpoint_b_offset = checkpoint_a_offset + SEGMENT_SIZE as u64;
107 let checkpoint = match (
109 CheckpointPack::read_from_device(device, checkpoint_a_offset).await,
110 CheckpointPack::read_from_device(device, checkpoint_b_offset).await,
111 ) {
112 (Ok(a), Ok(b)) => {
113 Ok(if a.header.checkpoint_ver > b.header.checkpoint_ver { a } else { b })
114 }
115 (Ok(a), Err(_b)) => Ok(a),
116 (Err(_), Ok(b)) => Ok(b),
117 (Err(a), Err(_b)) => Err(a),
118 }?;
119
120 const MIN_METADATA_SEGMENT_COUNT: u32 = 8;
122
123 let metadata_segment_count = superblock.segment_count_sit
125 + superblock.segment_count_nat
126 + checkpoint.header.rsvd_segment_count
127 + superblock.segment_count_ssa
128 + superblock.segment_count_ckpt;
129 ensure!(
130 metadata_segment_count <= superblock.segment_count
131 && metadata_segment_count >= MIN_METADATA_SEGMENT_COUNT,
132 "Bad segment counts in checkpoint"
133 );
134 Ok((superblock, checkpoint))
135 }
136
137 fn checkpoint_start_addr(&self) -> u32 {
139 self.superblock.cp_blkaddr
140 + if self.checkpoint.header.checkpoint_ver % 2 == 1 {
141 0
142 } else {
143 BLOCKS_PER_SEGMENT as u32
144 }
145 }
146
147 fn nat(&self) -> &Nat {
148 self.nat.as_ref().unwrap()
149 }
150
151 async fn read_nat_journal(&mut self) -> Result<HashMap<u32, RawNatEntry>, Error> {
152 if self.checkpoint.header.ckpt_flags & CKPT_FLAG_COMPACT_SUMMARY != 0 {
153 let block = self
156 .read_raw_block(
157 self.checkpoint_start_addr() + self.checkpoint.header.cp_pack_start_sum,
158 )
159 .await?;
160 let n_nats = u16::read_from_bytes(&block.as_slice()[..2]).unwrap();
161 let nat_journal = NatJournal::read_from_bytes(
162 &block.as_slice()[2..2 + std::mem::size_of::<NatJournal>()],
163 )
164 .unwrap();
165 ensure!(
166 (n_nats as usize) <= nat_journal.entries.len(),
167 "n_nats larger than block size"
168 );
169 Ok(HashMap::from_iter(
170 nat_journal.entries[..n_nats as usize].into_iter().map(|e| (e.ino, e.entry)),
171 ))
172 } else {
173 let blk_addr = if self.checkpoint.header.ckpt_flags & CKPT_FLAG_UNMOUNT != 0 {
175 self.checkpoint_start_addr() + self.checkpoint.header.cp_pack_total_block_count - 5
176 } else {
177 self.checkpoint_start_addr() + self.checkpoint.header.cp_pack_total_block_count - 2
178 };
179 let block = self.read_raw_block(blk_addr).await?;
180 let summary = SummaryBlock::read_from_bytes(block.as_slice()).unwrap();
181 ensure!(summary.footer.entry_type == 0u8, "sum_type != 0 in summary footer");
182 let actual_checksum = f2fs_crc32(F2FS_MAGIC, &block.as_slice()[..BLOCK_SIZE - 4]);
183 let expected_checksum = summary.footer.check_sum;
184 ensure!(actual_checksum == expected_checksum, "Summary block has invalid checksum");
185 let mut out = HashMap::new();
186 for i in 0..summary.n_nats as usize {
187 out.insert(
188 summary.nat_journal.entries[i].ino,
189 summary.nat_journal.entries[i].entry,
190 );
191 }
192 Ok(out)
193 }
194 }
195
196 pub fn root_ino(&self) -> u32 {
197 self.superblock.root_ino
198 }
199
200 pub fn max_ino(&self) -> u32 {
203 (self.checkpoint.nat_bitmap.len() * 8) as u32
204 }
205
206 pub fn add_key(&mut self, main_key: &[u8; 64]) -> [u8; 16] {
209 let identifier = fscrypt::main_key_to_identifier(main_key);
210 println!("Adding key with identifier {}", hex::encode(identifier));
211 self.keys.insert(identifier.clone(), main_key.clone());
212 identifier
213 }
214
215 pub async fn readdir(&self, ino: u32) -> Result<Vec<DirEntry>, Error> {
217 let inode = Inode::try_load(self, ino).await?;
218 let decryptor = self.get_decryptor_for_inode(&inode);
219 let mode = inode.header.mode;
220 let advise_flags = inode.header.advise_flags;
221 let flags = inode.header.flags;
222 ensure!(mode.contains(inode::Mode::Directory), "not a directory");
223 if let Some(entries) = inode.get_inline_dir_entries(
224 advise_flags.contains(inode::AdviseFlags::Encrypted),
225 flags.contains(inode::Flags::Casefold),
226 &decryptor,
227 )? {
228 Ok(entries)
229 } else {
230 let mut entries = Vec::new();
231
232 for (_, block_addr) in inode.data_blocks() {
237 let dentry_block =
238 DentryBlock::read_from_bytes(self.read_raw_block(block_addr).await?.as_slice())
239 .unwrap();
240 entries.append(&mut dentry_block.get_entries(
241 ino,
242 advise_flags.contains(inode::AdviseFlags::Encrypted),
243 flags.contains(inode::Flags::Casefold),
244 &decryptor,
245 )?);
246 }
247 Ok(entries)
248 }
249 }
250
251 pub async fn read_inode(&self, ino: u32) -> Result<Box<Inode>, Error> {
253 Inode::try_load(self, ino).await
254 }
255
256 pub fn read_symlink(&self, inode: &Inode) -> Result<Box<[u8]>, Error> {
258 if let Some(inline_data) = inode.inline_data.as_deref() {
259 let mut filename = inline_data.to_vec();
260 if inode.header.advise_flags.contains(inode::AdviseFlags::Encrypted) {
261 ensure!(filename.len() >= 2, "invalid encrypted symlink");
263 let symlink_len = u16::read_from_bytes(&filename[..2]).unwrap();
264 filename.drain(..2);
265 filename.truncate(symlink_len as usize);
266 ensure!(symlink_len == filename.len() as u16, "invalid encrypted symlink");
267 if let Some(decryptor) = self.get_decryptor_for_inode(inode) {
268 decryptor.decrypt_filename_data(inode.footer.ino, &mut filename);
269 } else {
270 let proxy_filename: String =
271 fscrypt::proxy_filename::ProxyFilename::new(0, &filename).into();
272 filename = proxy_filename.as_bytes().to_vec();
273 }
274 while let Some(0) = filename.last() {
277 filename.pop();
278 }
279 }
280 Ok(filename.into_boxed_slice())
281 } else {
282 bail!("Not a valid symlink");
283 }
284 }
285
286 pub async fn read_data(
288 &self,
289 inode: &Inode,
290 block_num: u32,
291 ) -> Result<Option<Buffer<'_>>, Error> {
292 let inline_flags = inode.header.inline_flags;
293 ensure!(
294 !inline_flags.contains(crate::InlineFlags::Data),
295 "Can't use read_data() on inline file."
296 );
297 let block_addr = inode.data_block_addr(block_num);
298 if block_addr == NULL_ADDR || block_addr == NEW_ADDR {
299 return Ok(None);
301 }
302 let mut buffer = self.read_raw_block(block_addr).await?;
303 if let Some(decryptor) = self.get_decryptor_for_inode(inode) {
304 decryptor.decrypt_data(inode.footer.ino, block_num, buffer.as_mut().as_mut_slice());
305 }
306 Ok(Some(buffer))
307 }
308}
309
310#[async_trait]
311impl Reader for F2fsReader {
312 async fn read_raw_block(&self, block_addr: u32) -> Result<Buffer<'_>, Error> {
314 let mut block = self.device.allocate_buffer(BLOCK_SIZE).await;
315 self.device
316 .read(block_addr as u64 * BLOCK_SIZE as u64, block.as_mut())
317 .await
318 .map_err(|_| anyhow!("device read failed"))?;
319 Ok(block)
320 }
321
322 async fn read_node(&self, nid: u32) -> Result<Buffer<'_>, Error> {
323 let nat_entry = self.get_nat_entry(nid).await?;
324 self.read_raw_block(nat_entry.block_addr).await
325 }
326
327 fn get_key(&self, identifier: &[u8; 16]) -> Option<&[u8; 64]> {
328 self.keys.get(identifier)
329 }
330
331 fn fs_uuid(&self) -> &[u8; 16] {
332 &self.superblock.uuid
333 }
334
335 async fn get_nat_entry(&self, nid: u32) -> Result<RawNatEntry, Error> {
336 if let Some(entry) = self.nat().nat_journal.get(&nid) {
337 return Ok(*entry);
338 }
339 let nat_block_addr = self.nat().get_nat_block_for_entry(nid)?;
340 let offset = self.nat().get_nat_block_offset_for_entry(nid);
341 let block = self.read_raw_block(nat_block_addr).await?;
342 Ok(RawNatEntry::read_from_bytes(
343 &block.as_slice()[offset..offset + std::mem::size_of::<RawNatEntry>()],
344 )
345 .unwrap())
346 }
347}
348
349#[cfg(test)]
350mod test {
351 use super::*;
352 use crate::dir::FileType;
353 use crate::xattr;
354 use std::collections::HashSet;
355 use std::path::PathBuf;
356 use std::sync::Arc;
357
358 use storage_device::fake_device::FakeDevice;
359
360 fn open_test_image(path: &str) -> FakeDevice {
361 let path = std::path::PathBuf::from(path);
362 println!("path is {path:?}");
363 FakeDevice::from_image(
364 zstd::Decoder::new(std::fs::File::open(&path).expect("open image"))
365 .expect("decompress image"),
366 BLOCK_SIZE as u32,
367 )
368 .expect("open image")
369 }
370
371 #[fuchsia::test]
372 async fn test_open_fs() {
373 let device = open_test_image("/pkg/testdata/f2fs.img.zst");
374
375 let f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
376 assert_eq!(f2fs.root_ino(), 3);
378 let superblock = &f2fs.superblock;
379 let major_ver = superblock.major_ver;
380 let minor_ver = superblock.minor_ver;
381 assert_eq!(major_ver, 1);
382 assert_eq!(minor_ver, 16);
383 assert_eq!(superblock.get_total_size(), 256 << 20);
384 assert_eq!(superblock.get_volume_name().expect("get volume name"), "testimage");
385 }
386
387 async fn resolve_inode_path(f2fs: &F2fsReader, path: &str) -> Result<u32, Error> {
389 let path = PathBuf::from(path.strip_prefix("/").unwrap());
390 let mut ino = f2fs.root_ino();
391 for filename in &path {
392 let entries = f2fs.readdir(ino).await?;
393 if let Some(entry) = entries.iter().filter(|e| *e.filename == *filename).next() {
394 ino = entry.ino;
395 } else {
396 bail!("Not found.");
397 }
398 }
399 Ok(ino)
400 }
401
402 #[fuchsia::test]
403 async fn test_basic_dirs() {
404 let device = open_test_image("/pkg/testdata/f2fs.img.zst");
405
406 let f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
407 let root_ino = f2fs.root_ino();
408 let root_entries = f2fs.readdir(root_ino).await.expect("readdir");
409 assert_eq!(root_entries.len(), 6);
410 assert_eq!(root_entries[0].filename, "a");
411 assert_eq!(root_entries[0].file_type, FileType::Directory);
412 assert_eq!(root_entries[1].filename, "large_dir");
413 assert_eq!(root_entries[2].filename, "large_dir2");
414 assert_eq!(root_entries[3].filename, "sparse.dat");
415 assert_eq!(root_entries[4].filename, "fscrypt");
416 assert_eq!(root_entries[5].filename, "fscrypt_lblk32");
417
418 let inlined_file_ino =
419 resolve_inode_path(&f2fs, "/a/b/c/inlined").await.expect("resolve inlined");
420 let inode = Inode::try_load(&f2fs, inlined_file_ino).await.expect("load inode");
421 let block_size = inode.header.block_size;
422 let size = inode.header.size;
423 assert_eq!(block_size, 1);
424 assert_eq!(size, 12);
425 assert_eq!(inode.inline_data.unwrap().as_ref(), "inline_data\n".as_bytes());
426
427 const REG_FILE_SIZE: u64 = 8 * BLOCK_SIZE as u64 + 8;
428 const REG_FILE_BLOCKS: u64 = 9 + 1;
429 let regular_file_ino =
430 resolve_inode_path(&f2fs, "/a/b/c/regular").await.expect("resolve regular");
431 let inode = Inode::try_load(&f2fs, regular_file_ino).await.expect("load inode");
432 let block_size = inode.header.block_size;
433 let size = inode.header.size;
434 assert_eq!(block_size, REG_FILE_BLOCKS);
435 assert_eq!(size, REG_FILE_SIZE);
436 assert!(inode.inline_data.is_none());
437 for i in 0..8 {
438 assert_eq!(
439 f2fs.read_data(&inode, i).await.expect("read data").unwrap().as_slice(),
440 &[0u8; BLOCK_SIZE]
441 );
442 }
443 assert_eq!(
444 &f2fs.read_data(&inode, 8).await.expect("read data").unwrap().as_slice()[..9],
445 b"01234567\0"
446 );
447
448 let symlink_ino =
449 resolve_inode_path(&f2fs, "/a/b/c/symlink").await.expect("resolve symlink");
450 let inode = Inode::try_load(&f2fs, symlink_ino).await.expect("load inode");
451 assert_eq!(f2fs.read_symlink(&inode).expect("read_symlink").as_ref(), b"regular");
452
453 let hardlink_ino =
454 resolve_inode_path(&f2fs, "/a/b/c/hardlink").await.expect("resolve hardlink");
455 let inode = Inode::try_load(&f2fs, hardlink_ino).await.expect("load inode");
456 let block_size = inode.header.block_size;
457 let size = inode.header.size;
458 assert_eq!(block_size, REG_FILE_BLOCKS);
459 assert_eq!(size, REG_FILE_SIZE);
460
461 let chowned_ino =
462 resolve_inode_path(&f2fs, "/a/b/c/chowned").await.expect("resolve chowned");
463 let inode = Inode::try_load(&f2fs, chowned_ino).await.expect("load inode");
464 let uid = inode.header.uid;
465 let gid = inode.header.gid;
466 assert_eq!(uid, 999);
467 assert_eq!(gid, 999);
468
469 let large_dir = resolve_inode_path(&f2fs, "/large_dir").await.expect("resolve large_dir");
470 assert_eq!(f2fs.readdir(large_dir).await.expect("readdir").len(), 2001);
471
472 let large_dir2 = resolve_inode_path(&f2fs, "/large_dir2").await.expect("resolve large_dir");
473 assert_eq!(f2fs.readdir(large_dir2).await.expect("readdir").len(), 1);
474
475 let sparse_dat =
476 resolve_inode_path(&f2fs, "/sparse.dat").await.expect("resolve sparse.dat");
477 let inode = Inode::try_load(&f2fs, sparse_dat).await.expect("load inode");
478 let data_blocks: Vec<_> = inode.data_blocks().into_iter().collect();
479 assert_eq!(data_blocks.len(), 7);
480 assert_eq!(data_blocks[0].0, 0);
481 let block = f2fs.read_raw_block(data_blocks[0].1).await.expect("read sparse");
483 assert_eq!(&block.as_slice()[..3], b"foo");
484 assert_eq!(data_blocks[1].0, 923);
486 assert_eq!(data_blocks[2].0, 1941);
487 assert_eq!(data_blocks[3].0, 2959);
488 assert_eq!(data_blocks[4].0, 1039283);
489 assert_eq!(data_blocks[5].0, 104671683);
490 let block = f2fs.read_raw_block(data_blocks[5].1).await.expect("read sparse");
491 assert_eq!(block.as_slice(), &[0; BLOCK_SIZE]);
492 assert_eq!(data_blocks[6].0, 104671684);
493 assert_eq!(
495 &f2fs
496 .read_data(&inode, data_blocks[6].0)
497 .await
498 .expect("read data block")
499 .unwrap()
500 .as_slice()[..3],
501 b"bar"
502 );
503 assert!(f2fs
505 .read_data(&inode, data_blocks[6].0 - 10)
506 .await
507 .expect("read data block")
508 .is_none());
509 }
510
511 #[fuchsia::test]
512 async fn test_xattr() {
513 let device = open_test_image("/pkg/testdata/f2fs.img.zst");
514
515 let f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
516 let sparse_dat =
517 resolve_inode_path(&f2fs, "/sparse.dat").await.expect("resolve sparse.dat");
518 let inode = Inode::try_load(&f2fs, sparse_dat).await.expect("load inode");
519 assert_eq!(
520 inode.xattr,
521 vec![
522 xattr::XattrEntry {
523 index: xattr::Index::User,
524 name: Box::new(b"a".to_owned()),
525 value: Box::new(b"value".to_owned())
526 },
527 xattr::XattrEntry {
528 index: xattr::Index::User,
529 name: Box::new(b"c".to_owned()),
530 value: Box::new(b"value".to_owned())
531 },
532 ]
533 );
534 }
535
536 #[fuchsia::test]
537 async fn test_fbe() {
538 let str_a = "2t5HJwAAAAAuQMWhq8f-7u6NHW32gAX4"; let str_b = "1yoAWgAAAADMBhUlTCdadXsBMsR13lQn"; let str_symlink = "x6_E8QAAAADpQkQZBwcpIFjrR8sZgtkE"; let bytes_symlink_content = b"AAAAAAAAAACWWJ_1EsQmJ6LGq1s0QKf6";
549 let mut expected : HashSet<_> = [
550 "2paW0gAAAADUgfvyVGd09PwKYGFvEtrO",
551 "2t5HJwAAAAAuQMWhq8f-7u6NHW32gAX4",
552 "67KydQAAAAAoAsqfMHTmJge6f057J6wx",
553 "6NLwDQAAAAC4Ob3JGP77NRZPuQIzQBgO",
554 "hg-bUgAAAAB_QIYd05srvJf50NxvuMbPKketflvaYlVFCUjzS6mUNXuwnqC_2UVbFOeYe2rzgDCS7uwF88vhY0DiUZ-74Fq4acLVKCVUjOwmEWgWTwp_gQWn3XmQRcfwlqODvknOJKskGxRH9mHAbCPicN36qkJFzkbALRiSiCK_qGXbbVqJiee2xG7oO5jNmbkxWekkjSx8ZleID_s3cbjpv3uQ9Oz4Df8CzM-ZW6jvw_Js1MxX8LI5Ez_Q",
555 "m__yfAAAAAA6hASozPlJsSCCZ5NZa_l-",
556 "UNHjjwAAAAA-I-GWH-KjkF9vHO8Rlajo"].into_iter().collect();
557
558 let device = open_test_image("/pkg/testdata/f2fs.img.zst");
559
560 let mut f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
561
562 resolve_inode_path(&f2fs, "/fscrypt/a/b/regular")
566 .await
567 .expect_err("resolve fscrypt regular");
568 let fscrypt_dir_ino =
569 resolve_inode_path(&f2fs, "/fscrypt").await.expect("resolve encrypted dir");
570 let entries = f2fs.readdir(fscrypt_dir_ino).await.expect("readdir");
571 println!("entries {entries:?}");
572
573 for entry in entries {
574 assert!(expected.remove(entry.filename.as_str()), "unexpected entry {entry:?}");
575 }
576
577 resolve_inode_path(&f2fs, &format!("/fscrypt/{str_a}"))
578 .await
579 .expect("resolve encrypted dir");
580 let enc_symlink_ino =
581 resolve_inode_path(&f2fs, &format!("/fscrypt/{str_a}/{str_b}/{str_symlink}"))
582 .await
583 .expect("resolve encrypted symlink");
584 let symlink_inode =
585 Inode::try_load(&f2fs, enc_symlink_ino).await.expect("load symlink inode");
586 assert_eq!(
587 &*f2fs.read_symlink(&symlink_inode).expect("read_symlink"),
588 bytes_symlink_content
589 );
590
591 f2fs.add_key(&[0u8; 64]);
593 resolve_inode_path(&f2fs, "/fscrypt/a/b/regular").await.expect("resolve fscrypt regular");
594 let inlined_ino = resolve_inode_path(&f2fs, "/fscrypt/a/b/inlined")
595 .await
596 .expect("resolve fscrypt inlined");
597 let short_file = Inode::try_load(&f2fs, inlined_ino).await.expect("load symlink inode");
598 assert!(
599 !short_file.header.inline_flags.contains(inode::InlineFlags::Data),
600 "encrypted files shouldn't be inlined"
601 );
602 let short_data =
603 f2fs.read_data(&short_file, 0).await.expect("read_data").expect("non-empty page");
604 assert_eq!(
605 &short_data.as_slice()[..short_file.header.size as usize],
606 b"test45678abcdef_12345678"
607 );
608
609 let symlink_ino = resolve_inode_path(&f2fs, "/fscrypt/a/b/symlink")
610 .await
611 .expect("resolve fscrypt symlink");
612 assert_eq!(symlink_ino, enc_symlink_ino);
613
614 let symlink_inode = Inode::try_load(&f2fs, symlink_ino).await.expect("load symlink inode");
615 let symlink = f2fs.read_symlink(&symlink_inode).expect("read_symlink");
616 assert_eq!(*symlink, *b"inlined");
617
618 let ino = resolve_inode_path(&f2fs, "/fscrypt_lblk32/file").await.expect("lblk32 ino");
620 let inode = Inode::try_load(&f2fs, ino).await.expect("load inode");
621 assert!(
622 !inode.header.inline_flags.contains(inode::InlineFlags::Data),
623 "encrypted files shouldn't be inlined"
624 );
625 let data = f2fs.read_data(&inode, 0).await.expect("read_data").expect("non-empty page");
626 assert_eq!(
627 &data.as_slice()[..short_file.header.size as usize],
628 b"test45678abcdef_12345678"
629 );
630 let symlink_ino = resolve_inode_path(&f2fs, "/fscrypt_lblk32/symlink")
631 .await
632 .expect("resolve fscrypt symlink");
633 let symlink_inode = Inode::try_load(&f2fs, symlink_ino).await.expect("load symlink inode");
634 let symlink = f2fs.read_symlink(&symlink_inode).expect("read_symlink");
635 assert_eq!(*symlink, *b"file");
636 }
637}