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 <uekawa@chromium.org>
Commit-Queue: Keiichi Watanabe <keiichiw@chromium.org>
This commit is contained in:
Keiichi Watanabe 2024-05-27 20:52:52 +09:00 committed by crosvm LUCI
parent ac825c410a
commit c8bb0b0be5
3 changed files with 81 additions and 3 deletions

View file

@ -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<P: AsRef<Path>>(&mut self, arena: &'a Arena<'a>, src_dir: P) -> Result<()> {
self.copy_dirtree_rec(arena, InodeNum(2), src_dir)

View file

@ -152,7 +152,6 @@ impl InodeBlock {
Self::max_inline_symlink_len()
);
}
self.0[..bytes.len()].copy_from_slice(bytes);
Ok(())
}

View file

@ -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);
}