e2fsprogs의 깃허브 코드를 참조해 분석한 글입니다.

📌 e2fsprogs 코드 분석 : ext2fs_read_block_bitmap

🫧 ext2fs_read_block_bitmap(fs);

  • ext2에서 제대로 작동, ext4에서도 가능하나 정확히 동작하지 않을 가능성 높음.
  • 역할: group 번호에 해당하는 블록 그룹의 블록 할당 비트맵(block bitmap)을 읽어서 bitmap 객체에 저장
  • 반환값: 성공 시 0, 실패 시 오류코드 반환
  • 사용 이유: 블록 그룹 내에서 할당된 블록/사용 가능한 블록 정보가 비트맵에 있기 때문에 이걸 읽음

🫧 과정

alt text

🫧 코드

✨ ext2fs_read_block_bitmap(fs)

    • libs/ext2fs/rw_bitmaps.c, $660
errcode_t ext2fs_read_bitmaps(ext2_filsys fs)
{
	int flags = 0;

    // inode_map이 로드되어 있지 않다면 로드
	if (!fs->inode_map)
		flags |= EXT2FS_BITMAPS_INODE;

    // block_map이 로드되어 있지 않다면 로드
	if (!fs->block_map)
		flags |= EXT2FS_BITMAPS_BLOCK;
	if (flags == 0)
		return 0;
	return ext2fs_rw_bitmaps(fs, flags, -1);
}

✨ ext4fs_rw_bitmaps()

  • libs/ext2fs/rw_bitmaps.c, $521
  • inode, bitmap을 그룹 단위로 디스크에 읽거나 쓰는 함수
errcode_t ext2fs_rw_bitmaps(ext2_filsys fs, int flags, int num_threads)
{
    // 멀티스레드 지원이 되면 필요한 값들 초기화
#ifdef HAVE_PTHREAD
	pthread_attr_t	attr;
	pthread_t *thread_ids = NULL;
	struct read_bitmaps_thread_info *thread_infos = NULL;
	pthread_mutex_t rbt_mutex = PTHREAD_MUTEX_INITIALIZER;
	errcode_t retval;
	errcode_t rc;
	unsigned flexbg_size = 1U << fs->super->s_log_groups_per_flex;
	dgrp_t average_group;
	int i, tail_flags = 0;
#endif

    // 정의되어 있지 않은 flag일 경우 에러
	if (flags & ~EXT2FS_BITMAPS_VALID_FLAGS)
		return EXT2_ET_INVALID_ARGUMENT;

    // 저널 전용 장치일 경우 (외부 저널) 리턴 (지원 X)
	if (ext2fs_has_feature_journal_dev(fs->super))
		return EXT2_ET_EXTERNAL_JOURNAL_NOSUPP;

        // 비트맵 쓰기 권한이 허용되어 있다면 write_bitmap() 호출해 디스크에 비트맵 저장
	if (flags & EXT2FS_BITMAPS_WRITE)
		return write_bitmaps(fs, flags & EXT2FS_BITMAPS_INODE,
				     flags & EXT2FS_BITMAPS_BLOCK);

// 멀티스레드가 사용 가능한지
#ifdef HAVE_PTHREAD
	if (((fs->io->flags & CHANNEL_FLAGS_THREADS) == 0) ||
	    (num_threads == 1) || (fs->flags & EXT2_FLAG_IMAGE_FILE))
		goto fallback;

#if defined(HAVE_SYSCONF) && defined(_SC_NPROCESSORS_CONF)
	if (num_threads < 0)
		num_threads = sysconf(_SC_NPROCESSORS_CONF);
#endif
	/*
	 * Guess for now; eventually we should probably define
	 * ext2fs_get_num_cpus() and teach it how to get this info on
	 * MacOS, FreeBSD, etc.
	 * ref: https://stackoverflow.com/questions/150355
	 */
	if (num_threads <= 0)
		num_threads = 4;

	if ((unsigned) num_threads > fs->group_desc_count)
		num_threads = fs->group_desc_count;
	average_group = fs->group_desc_count / num_threads;
	
    // flex_bg 기능이 켜져 있을 때 수행
    if (ext2fs_has_feature_flex_bg(fs->super)) {
		average_group = (average_group / flexbg_size) * flexbg_size;
	}
	if ((num_threads <= 1) || (average_group == 0))
		goto fallback;

	io_channel_set_options(fs->io, "cache=off");
	retval = pthread_attr_init(&attr);
	if (retval)
		return retval;

	thread_ids = calloc(sizeof(pthread_t), num_threads);
	if (!thread_ids)
		return ENOMEM;

	thread_infos = calloc(sizeof(struct read_bitmaps_thread_info),
				num_threads);
	if (!thread_infos)
		goto out;

    // 비트맵 읽기 전 공통 수행 함수
	retval = read_bitmaps_range_prepare(fs, flags);
	if (retval)
		goto out;

//	fprintf(stdout, "Multiple threads triggered to read bitmaps\n");
	for (i = 0; i < num_threads; i++) {
		thread_infos[i].rbt_fs = fs;
		thread_infos[i].rbt_flags = flags;
		thread_infos[i].rbt_mutex = &rbt_mutex;
		thread_infos[i].rbt_tail_flags = 0;
		if (i == 0)
			thread_infos[i].rbt_grp_start = 0;
		else
			thread_infos[i].rbt_grp_start = average_group * i + 1;

		if (i == num_threads - 1)
			thread_infos[i].rbt_grp_end = fs->group_desc_count - 1;
		else
			thread_infos[i].rbt_grp_end = average_group * (i + 1);
		// 스레드 생성
        retval = pthread_create(&thread_ids[i], &attr,
					&read_bitmaps_thread, &thread_infos[i]);
		if (retval)
			break;
	}
	for (i = 0; i < num_threads; i++) {
		if (!thread_ids[i])
			break;
        // 스레드 종료 대기
		rc = pthread_join(thread_ids[i], NULL);
		if (rc && !retval)
			retval = rc;
		rc = thread_infos[i].rbt_retval;
		if (rc && !retval)
			retval = rc;
		tail_flags |= thread_infos[i].rbt_tail_flags;
	}
out:
	rc = pthread_attr_destroy(&attr);
	if (rc && !retval)
		retval = rc;
	free(thread_infos);
	free(thread_ids);

	if (retval == 0)
		retval = read_bitmaps_range_end(fs, flags, tail_flags);
	if (retval)
		read_bitmaps_cleanup_on_error(fs, flags);
	/* XXX should save and restore cache setting */
	io_channel_set_options(fs->io, "cache=on");
	return retval;
fallback:
#endif /* HAVE_PTHREAD */
    // 모든 그룹을 순차적으로 읽어 처리
	return read_bitmaps_range(fs, flags, 0, fs->group_desc_count - 1);
}

✨ write_bitmaps()

  • libs/ext2fs/rw_bitmaps.c, $538
  • inode, bitmap을 디스크에 쓰는 함수
static errcode_t write_bitmaps(ext2_filsys fs, int do_inode, int do_block)
{
	dgrp_t 		i;
	unsigned int	j;
	int		block_nbytes, inode_nbytes;
	unsigned int	nbits;
	errcode_t	retval;
	char		*block_buf = NULL, *inode_buf = NULL;
	int		csum_flag;
	blk64_t		blk;
	blk64_t		blk_itr = EXT2FS_B2C(fs, fs->super->s_first_data_block);
	ext2_ino_t	ino_itr = 1;

	EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS);

	// 읽기 전용이면 에러 반환
	if (!(fs->flags & EXT2_FLAG_RW))
		return EXT2_ET_RO_FILSYS;

	csum_flag = ext2fs_has_group_desc_csum(fs);

	// 버퍼 할당
	inode_nbytes = block_nbytes = 0;
	if (do_block) {
        // block bitmap 한 그룹 바이트 수
		block_nbytes = EXT2_CLUSTERS_PER_GROUP(fs->super) / 8;
        // block 비트맵 버퍼 하나 만들기
		retval = io_channel_alloc_buf(fs->io, 0, &block_buf);
		if (retval)
			goto errout;
		memset(block_buf, 0xff, fs->blocksize);
	}
	if (do_inode) {
        // inode bitmap 한 그룹 바이트 수
		inode_nbytes = (size_t)
			((EXT2_INODES_PER_GROUP(fs->super)+7) / 8);
		// inode 비트맵 버퍼 하나 만들기
		retval = io_channel_alloc_buf(fs->io, 0, &inode_buf);
		if (retval)
			goto errout;
		memset(inode_buf, 0xff, fs->blocksize);
	}

	// 블록 그룹별 순회 돌기
	for (i = 0; i < fs->group_desc_count; i++) {
		// do_block이 0이 아닌 경우 비트맵에 쓰기
		if (!do_block)
			goto skip_block_bitmap;

		if (csum_flag && ext2fs_bg_flags_test(fs, i, EXT2_BG_BLOCK_UNINIT)
		    )
			goto skip_this_block_bitmap;

        // 메모리에 있는 블록 비트맵에서 해당 블록 그룹의 데이터를 꺼내 block_buf에 채움
		retval = ext2fs_get_block_bitmap_range2(fs->block_map,
				blk_itr, block_nbytes << 3, block_buf);
		if (retval)
			goto errout;

        // 마지막 그룹은 비트맵 끝부분에 존재하지 않는 블록들을 무조건 사용중(1)으로 표시
		if (i == fs->group_desc_count - 1) {
			/* Force bitmap padding for the last group */
			nbits = EXT2FS_NUM_B2C(fs,
				((ext2fs_blocks_count(fs->super)
				  - (__u64) fs->super->s_first_data_block)
				 % (__u64) EXT2_BLOCKS_PER_GROUP(fs->super)));
			if (nbits)
				for (j = nbits; j < fs->blocksize * 8; j++)
					ext2fs_set_bit(j, block_buf);
		}

        // 블록 비트맵 checksum 업데이트
		retval = ext2fs_block_bitmap_csum_set(fs, i, block_buf,
						      block_nbytes);
		if (retval)
			return retval;
        // 그룹 디스크립터 checksum 업데이트
		ext2fs_group_desc_csum_set(fs, i);
		fs->flags |= EXT2_FLAG_DIRTY;

        // 그룹 디스크립터에서 블록 비트맵 위치를 가져와 디스크에 쓰기
		blk = ext2fs_block_bitmap_loc(fs, i);
		if (blk && blk < ext2fs_blocks_count(fs->super)) {
			retval = io_channel_write_blk64(fs->io, blk, 1,
							block_buf);
			if (retval) {
				retval = EXT2_ET_BLOCK_BITMAP_WRITE;
				goto errout;
			}
		}
	skip_this_block_bitmap:
		blk_itr += block_nbytes << 3;
	skip_block_bitmap:


        // 블록 비트맵과 유사하게, inode bitmap도 쓰기
		if (!do_inode)
			continue;

		if (csum_flag && ext2fs_bg_flags_test(fs, i, EXT2_BG_INODE_UNINIT)
		    )
			goto skip_this_inode_bitmap;

		retval = ext2fs_get_inode_bitmap_range2(fs->inode_map,
				ino_itr, inode_nbytes << 3, inode_buf);
		if (retval)
			goto errout;

		retval = ext2fs_inode_bitmap_csum_set(fs, i, inode_buf,
						      inode_nbytes);
		if (retval)
			goto errout;
		ext2fs_group_desc_csum_set(fs, i);
		fs->flags |= EXT2_FLAG_DIRTY;

		blk = ext2fs_inode_bitmap_loc(fs, i);
		if (blk && blk < ext2fs_blocks_count(fs->super)) {
			retval = io_channel_write_blk64(fs->io, blk, 1,
						      inode_buf);
			if (retval) {
				retval = EXT2_ET_INODE_BITMAP_WRITE;
				goto errout;
			}
		}
	skip_this_inode_bitmap:
		ino_itr += inode_nbytes << 3;

	}

    // 자원 정리
	if (do_block) {
		fs->flags &= ~EXT2_FLAG_BB_DIRTY;
		ext2fs_free_mem(&block_buf);
	}
	if (do_inode) {
		fs->flags &= ~EXT2_FLAG_IB_DIRTY;
		ext2fs_free_mem(&inode_buf);
	}
	return 0;
errout:
	if (inode_buf)
		ext2fs_free_mem(&inode_buf);
	if (block_buf)
		ext2fs_free_mem(&block_buf);
	return retval;
}

✨ io_channel_alloc_buf()

  • libs/ext2fs/io_manager.c, $126
  • I/O 버퍼 할당 함수 (여기서는 inode bitmap, block bitmap 할당 시 사용)
errcode_t io_channel_alloc_buf(io_channel io, int count, void *ptr)
{
	size_t	size;

    // count가 0이면 블록 사이즈만큼 할당
	if (count == 0)
		size = io->block_size;
    // count가 0보다 크면 블록 사이즈 * 크기만큼 할당
	else if (count > 0)
		size = io->block_size * count;
	else
		size = -count;

	if (io->align > 0) {
		if ((unsigned) io->align > size)
			size = io->align;
		return ext2fs_get_memalign(size, io->align, ptr);
	} else
		return ext2fs_get_mem(size, ptr);
}

✨ ext2fs_bg_flags_test()

  • libs/ext2fs/blknum.c, $489
  • 특정 블록 그룹의 플래그 값이 설정되어 있는지 확인하는 함수
/*
 * Get the value of a particular flag for this block group
 */
int ext2fs_bg_flags_test(ext2_filsys fs, dgrp_t group, __u16 bg_flag)
{
	struct ext4_group_desc *gdp;

	// 그룹 디스크립터 위치 가져오기
	// group 번째 블록 그룹의 그룹 디스크립터 구조체를 반환
	// fs->group_desc는 모든 그룹 디스크립터 테이블의 시작 주소
	gdp = ext4fs_group_desc(fs, fs->group_desc, group);
	return gdp->bg_flags & bg_flag;
}

✨ ext4fs_group_desc()

  • libs/ext2fs/blknum.c, $243
/* Do the same but as an ext4 group desc for internal use here */
static struct ext4_group_desc *ext4fs_group_desc(ext2_filsys fs,
					  struct opaque_ext2_group_desc *gdp,
					  dgrp_t group)
{
	return (struct ext4_group_desc *)ext2fs_group_desc(fs, gdp, group);
}

✨ ext2fs_group_desc()

  • libs/ext2fs/rw_bitmaps.c, $521
  • 그룹 디스크립터를 읽어 메모리 주소를 반환하는 함수
/*
 * Get a pointer to a block group descriptor.  We need the explicit
 * pointer to the group desc for code that swaps block group
 * descriptors before writing them out, as it wants to make a copy and
 * do the swap there.
 */
struct ext2_group_desc *ext2fs_group_desc(ext2_filsys fs,
					  struct opaque_ext2_group_desc *gdp,
					  dgrp_t group)
{
	struct ext2_group_desc *ret_gdp;
	errcode_t	retval;
	static char	*buf = 0;
	static unsigned	bufsize = 0;
	blk64_t		blk;
	// 슈퍼블록의 디스크립터 사이즈 반환
	int		desc_size = EXT2_DESC_SIZE(fs->super) & ~7;
	// 한 블록 내 들어갈 수 있는 그룹 디스크립터 수
	int		desc_per_blk = EXT2_DESC_PER_BLOCK(fs->super);

	if (group > fs->group_desc_count)
		return NULL;

	// 오프셋 계산
	if (gdp)
		return (struct ext2_group_desc *)((char *)gdp +
						  group * desc_size);
	/*
	 * If fs->group_desc wasn't read in when the file system was
	 * opened, then read it on demand here.
	 */
	if (bufsize < fs->blocksize)
		ext2fs_free_mem(&buf);
	if (!buf) {
		retval = ext2fs_get_mem(fs->blocksize, &buf);
		if (retval)
			return NULL;
		bufsize = fs->blocksize;
	}
	blk = ext2fs_descriptor_block_loc2(fs, fs->super->s_first_data_block,
					   group / desc_per_blk);
	retval = io_channel_read_blk(fs->io, blk, 1, buf);
	if (retval)
		return NULL;
	ret_gdp = (struct ext2_group_desc *)
		(buf + ((group % desc_per_blk) * desc_size));
#ifdef WORDS_BIGENDIAN
	ext2fs_swap_group_desc2(fs, ret_gdp);
#endif
	return ret_gdp;
}

✨ ext2fs_get_block_bitmap_range2()

  • libs/ext2fs/bitmaps.c, $315
errcode_t ext2fs_get_block_bitmap_range2(ext2fs_block_bitmap bmap,
					 blk64_t start, size_t num,
					 void *out)
{
	return (ext2fs_get_generic_bmap_range(bmap, start, num, out));
}

✨ ext2fs_get_generic_bmap_range()

  • libs/ext2fs/rw_bitmaps.c, $521
  • 비트맵의 일부 구간을 읽어오는 함수
errcode_t ext2fs_get_generic_bmap_range(ext2fs_generic_bitmap gen_bmap,
					__u64 start, unsigned int num,
					void *out)
{
	ext2fs_generic_bitmap_64 bmap = (ext2fs_generic_bitmap_64) gen_bmap;

	if (!bmap)
		return EINVAL;

	// 비트맵 처리
	if (EXT2FS_IS_32_BITMAP(bmap)) {
		if ((start+num-1) & ~0xffffffffULL) {
			ext2fs_warn_bitmap2(gen_bmap,
					    EXT2FS_UNMARK_ERROR, 0xffffffff);
			return EINVAL;
		}
		return ext2fs_get_generic_bitmap_range(gen_bmap, bmap->magic,
						       start, num, out);
	}

	if (!EXT2FS_IS_64_BITMAP(bmap))
		return EINVAL;

	INC_STAT(bmap, get_range_count);

	return bmap->bitmap_ops->get_bmap_range(bmap, start, num, out);
}

✨ ext2fs_get_generic_bitmap_range()

  • libs/ext2fs/gen_bitmap.c, $408
  • inode, bitmap을 그룹 단위로 디스크에 읽거나 쓰는 함수
errcode_t ext2fs_get_generic_bitmap_range(ext2fs_generic_bitmap gen_bmap,
					  errcode_t magic,
					  __u32 start, __u32 num,
					  void *out)
{
	ext2fs_generic_bitmap_32 bmap = (ext2fs_generic_bitmap_32) gen_bmap;

	// 매직 넘버 검사
	if (!bmap || (bmap->magic != magic))
		return magic;

	// 범위 검사
	if ((start < bmap->start) || (start+num-1 > bmap->real_end))
		return EXT2_ET_INVALID_ARGUMENT;

	// 메모리에서 읽어오기
	memcpy(out, bmap->bitmap + ((start - bmap->start) >> 3), (num+7) >> 3);
	return 0;
}

✨ read_bitmaps_range_prepare()

  • libs/ext2fs/rw_bitmaps.c, $521
  • 메모리 준비 단계 함수, 읽어오기 전 공통 작업 수행
static errcode_t read_bitmaps_range_prepare(ext2_filsys fs, int flags)
{
	errcode_t retval;
	int block_nbytes = EXT2_CLUSTERS_PER_GROUP(fs->super) / 8;
	int inode_nbytes = EXT2_INODES_PER_GROUP(fs->super) / 8;
	char *buf;

	EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS);

	if ((block_nbytes > (int) fs->blocksize) ||
	    (inode_nbytes > (int) fs->blocksize))
		return EXT2_ET_CORRUPT_SUPERBLOCK;

	fs->write_bitmaps = ext2fs_write_bitmaps;

	// 임시 버퍼 할당
	retval = ext2fs_get_mem(strlen(fs->device_name) + 80, &buf);
	if (retval)
		return retval;

	// 블록 비트맵 구조체 생성 (읽기 전)
	if (flags & EXT2FS_BITMAPS_BLOCK) {
		if (fs->block_map)
			ext2fs_free_block_bitmap(fs->block_map);
		strcpy(buf, "block bitmap for ");
		strcat(buf, fs->device_name);
		retval = ext2fs_allocate_block_bitmap(fs, buf, &fs->block_map);
		if (retval)
			goto cleanup;
	}

	// inode 비트맵 구조체 생성 (읽기 전)
	if (flags & EXT2FS_BITMAPS_INODE) {
		if (fs->inode_map)
			ext2fs_free_inode_bitmap(fs->inode_map);
		strcpy(buf, "inode bitmap for ");
		strcat(buf, fs->device_name);
		retval = ext2fs_allocate_inode_bitmap(fs, buf, &fs->inode_map);
		if (retval)
			goto cleanup;
	}
	ext2fs_free_mem(&buf);
	return retval;

cleanup:
	if (flags & EXT2FS_BITMAPS_BLOCK) {
		ext2fs_free_block_bitmap(fs->block_map);
		fs->block_map = 0;
	}
	if (flags & EXT2FS_BITMAPS_INODE) {
		ext2fs_free_inode_bitmap(fs->inode_map);
		fs->inode_map = 0;
	}
	ext2fs_free_mem(&buf);
	return retval;
}

카테고리:

업데이트: