From c8bb0b0be5426b479940fcf4411f035090282889 Mon Sep 17 00:00:00 2001 From: Keiichi Watanabe Date: Mon, 27 May 2024 20:52:52 +0900 Subject: [PATCH] ext2: Support longer symlinks Symbolic links that are longer than or equal to 60 bytes require a block allocated to store its destination because it won't fit in Inode's i_block. BUG=b:342937495 TEST=cargo test Change-Id: Iadab10887c136019fe85e74fa981542384f48ad2 Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/5569438 Reviewed-by: Junichi Uekawa Commit-Queue: Keiichi Watanabe --- ext2/src/fs.rs | 43 +++++++++++++++++++++++++++++++++++++++++-- ext2/src/inode.rs | 1 - ext2/tests/tests.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/ext2/src/fs.rs b/ext2/src/fs.rs index fe60df0cfe..218ff8c4a2 100644 --- a/ext2/src/fs.rs +++ b/ext2/src/fs.rs @@ -558,8 +558,7 @@ impl<'a> Ext2<'a> { .context("failed to convert symlink destination to str")?; if dst.len() >= InodeBlock::max_inline_symlink_len() { - // TODO(b/342937495): Support symlink longer than or equal to 60 bytes. - unimplemented!("long symlink is not supported yet: {:?}", dst); + return self.add_long_symlink(arena, parent, &link, dst); } let inode_num = self.allocate_inode()?; @@ -584,6 +583,46 @@ impl<'a> Ext2<'a> { Ok(()) } + fn add_long_symlink( + &mut self, + arena: &'a Arena<'a>, + parent: InodeNum, + link: &Path, + dst: &str, + ) -> Result<()> { + let dst_len = dst.len(); + if dst_len > self.block_size() as usize { + bail!("symlink longer than block size: {:?}", dst); + } + + // Copy symlink's destination to the block. + let symlink_block = self.allocate_block()?; + let buf = arena.allocate_slice(symlink_block, 0, dst_len)?; + buf.copy_from_slice(dst.as_bytes()); + + let inode_num = self.allocate_inode()?; + let mut block = InodeBlock::default(); + block.set_direct_blocks(&[symlink_block])?; + + let block_size = self.block_size() as u32; + let inode = Inode::from_metadata( + arena, + &mut self.group_metadata, + inode_num, + &std::fs::symlink_metadata(link)?, + dst_len as u32, + 1, //links_count, + InodeBlocksCount::from_bytes_len(block_size), + block, + )?; + self.add_inode(inode_num, inode)?; + + let link_name = link.file_name().context("failed to get symlink name")?; + self.allocate_dir_entry(arena, parent, inode_num, InodeType::Symlink, link_name)?; + + Ok(()) + } + /// Walks through `src_dir` and copies directories and files to the new file system. fn copy_dirtree>(&mut self, arena: &'a Arena<'a>, src_dir: P) -> Result<()> { self.copy_dirtree_rec(arena, InodeNum(2), src_dir) diff --git a/ext2/src/inode.rs b/ext2/src/inode.rs index dda79736ba..d09a08d846 100644 --- a/ext2/src/inode.rs +++ b/ext2/src/inode.rs @@ -152,7 +152,6 @@ impl InodeBlock { Self::max_inline_symlink_len() ); } - self.0[..bytes.len()].copy_from_slice(bytes); Ok(()) } diff --git a/ext2/tests/tests.rs b/ext2/tests/tests.rs index 72508b3369..f97a5d0f9a 100644 --- a/ext2/tests/tests.rs +++ b/ext2/tests/tests.rs @@ -426,3 +426,43 @@ fn test_mkfs_symlink_to_deleted() { assert_eq_dirs(&td, &dir, &disk); } + +#[test] +fn test_mkfs_long_symlink() { + // testdata + // ├── /(long name directory)/a.txt + // └── symlink -> /(long name directory)/a.txt + // ├── (60-byte filename) + // └── symlink60 -> (60-byte filename) + + let td = tempdir().unwrap(); + let dir = td.path().join("testdata"); + + create_dir(&dir).unwrap(); + + const LONG_DIR_NAME: &str = + "this_is_a_very_long_directory_name_so_that_name_cannoot_fit_in_60_characters_in_inode"; + assert!(LONG_DIR_NAME.len() > 60); + + let long_dir = dir.join(LONG_DIR_NAME); + create_dir(&long_dir).unwrap(); + File::create(long_dir.join("a.txt")).unwrap(); + symlink(long_dir.join("a.txt"), dir.join("symlink")).unwrap(); + + const SIXTY_CHAR_DIR_NAME: &str = + "./this_is_just_60_byte_long_so_it_can_work_as_a_corner_case."; + assert_eq!(SIXTY_CHAR_DIR_NAME.len(), 60); + File::create(dir.join(SIXTY_CHAR_DIR_NAME)).unwrap(); + symlink(SIXTY_CHAR_DIR_NAME, dir.join("symlink60")).unwrap(); + + let disk = mkfs( + &td, + &Config { + blocks_per_group: 2048, + inodes_per_group: 4096, + }, + Some(&dir), + ); + + assert_eq_dirs(&td, &dir, &disk); +}