EROFS 이미지 다루기 — 생성·검사·추출과 재현 가능한 빌드

읽기 전용 이미지 포맷으로 EROFS를 쓰기로 정하고 나면 그다음 벽은 도구다. SquashFS가 mksquashfs/unsquashfs 두 개로 끝나는 것과 달리 EROFS는 생성·조회·검사·마운트가 네 개 명령으로 나뉘어 있고, 그중 하나는 아예 별도 패키지다. 게다가 배포판이 담고 있는 버전에서는 옵션 조합에 따라 조용히 잘못된 이미지가 나오기도 한다. 압축률과 읽기 성능 비교는 EROFS와 SquashFS 비교에서 다뤘고, 이 글에서는 이미지를 만들고, 들여다보고, 되돌리고, 빌드를 재현 가능하게 만드는 실제 작업을 정리한다. 측정 환경은 Ubuntu 24.04.4 LTS(커널 7.0.0-28-generic), erofs-utils 1.7.1이다.

도구 구성

명령하는 일패키지
mkfs.erofs디렉터리나 tar에서 이미지 생성erofs-utils
dump.erofs슈퍼블록·inode·통계 조회erofs-utils
fsck.erofs무결성 검사, 마운트 없이 추출erofs-utils
erofsfuseroot 권한 없이 마운트erofsfuse (별도 설치)

이미지 만들기

헤더 파일과 문서를 섞은 약 4.8MB(파일 790개) 디렉터리를 실습 대상으로 썼다. 입력은 디렉터리와 tar 아카이브 둘 다 받는다.

# 디렉터리에서
mkfs.erofs -zdeflate -Efragments,ztailpacking a.img src/

# tar 아카이브에서 (데이터까지 포함하는 full 모드)
tar -C src -cf src.tar .
mkfs.erofs -zdeflate --tar=f b.img src.tar
입력 / 옵션이미지 크기내용 검증
원본 tar 아카이브5,652,480
디렉터리, -zdeflate -Efragments,ztailpacking2,039,808정상
tar, -zdeflate2,957,312정상
tar, 무압축5,136,384정상
tar, -zdeflate -Efragments,ztailpacking258,048200/200 파일 내용 불일치

마지막 줄이 이 글에서 가장 조심해야 할 지점이다. --tar-Efragments를 같이 주면 mkfs.erofs는 에러 없이 종료하지만 데이터가 들어가지 않은 이미지가 나온다. 자세한 증상은 주의사항에 정리했다.

이미지 들여다보기

$ dump.erofs -s a.img
Filesystem magic number:                      0xE0F5E1E2
Filesystem blocks:                            498
Filesystem root nid:                          37
Filesystem packed nid:                        63616
Filesystem compr_algs:                        deflate
Filesystem inode count:                       824
Filesystem features:                          sb_csum mtime 0padding compr_cfgs big_pcluster ztailpacking fragments dedupe
Filesystem UUID:                              e248a898-bf41-4a4c-84bc-c6822c94afea

features 줄로 어떤 확장 옵션이 실제로 적용됐는지 확인할 수 있다. -S는 파일 종류별 개수와 크기 분포까지 보여준다.

$ dump.erofs -S a.img
Filesystem total file count:            824
Filesystem regular file count:          791
Filesystem directory count:             31
Filesystem symlink file count:          2
Filesystem compressed files:            792
Filesystem uncompressed files:          33
Filesystem total original file size:    5035264 Bytes
Filesystem total file size:             1937408 Bytes
Filesystem compress rate:               38.48%

Original file size distribution:
>=(KB) .. <(KB)        count            ratio |distribution                                      |
     0 .. 1        245            30.97% |###############                                   |
     1 .. 2        129            16.31% |########                                          |
     2 .. 4        144            18.20% |#########                                         |
     4 .. 8        141            17.83% |########                                          |

개별 파일은 --path로, 디렉터리 목록은 --ls로 본다.

$ dump.erofs --path=/linux/acrn.h a.img
Path : /linux/acrn.h
Size: 18956  On-disk size: 8192  regular file
NID: 66682   Links: 1   Layout: 3   Compression ratio: 43.22%

$ dump.erofs --ls --path=/bash-doc a.img
       NID TYPE  FILENAME
        43    2  .
        37    2  ..
        75    1  COMPAT.gz
        78    1  INTRO.gz
        81    1  NEWS.gz

검사와 추출

fsck.erofs는 기본적으로 아무것도 출력하지 않고 종료 코드로만 답한다. 압축률까지 보려면 -p에 메시지 레벨 -d3을 같이 줘야 한다.

$ fsck.erofs -p -d3 a.img
<I> erofs: No errors found
<I> erofs: Compression ratio: 34.41(%)

마운트하지 않고 내용을 꺼낼 때는 --extract를 쓴다. 확장 옵션 없이 만든 이미지는 원본과 완전히 일치했다.

$ mkfs.erofs -zdeflate plain.img src/ > /dev/null
$ fsck.erofs --extract=out --no-preserve plain.img ; echo "exit=$?"
exit=0
$ diff -r --no-dereference src out | wc -l
0

재현 가능한 빌드

같은 입력으로 같은 이미지가 나와야 배포물 검증이나 캐시가 의미를 갖는데, 기본값으로는 만들 때마다 결과가 달라진다.

$ mkfs.erofs -zdeflate r1.img src/ ; sleep 1 ; mkfs.erofs -zdeflate r2.img src/
$ sha256sum r1.img r2.img
54bc2f223c593d4d4a958c8397d053aa...  r1.img
939a2c7cc9a78f9e4b4fce73c938b9e7...  r2.img

타임스탬프와 UUID, 소유자를 고정하면 바이트 단위로 같은 이미지가 나온다.

$ OPTS="-zdeflate -T 1700000000 -U 00000000-0000-0000-0000-000000000000 --all-root"
$ mkfs.erofs $OPTS f1.img src/ ; sleep 1 ; mkfs.erofs $OPTS f2.img src/
$ sha256sum f1.img f2.img
8a3bab4f5e13d60743cb0b07be4b310b...  f1.img
8a3bab4f5e13d60743cb0b07be4b310b...  f2.img
$ cmp f1.img f2.img && echo "바이트 단위로 동일"
바이트 단위로 동일

마운트

$ sudo mount -o loop,ro a.img /mnt/erofs
$ grep /mnt/erofs /proc/mounts
/dev/loop23 /mnt/erofs erofs ro,relatime,user_xattr,acl,cache_strategy=readaround 0 0

$ stat -c '%n links=%h type=%F' /mnt/erofs/linux/fs.h /mnt/erofs/hardlink-fs.h /mnt/erofs/symlink-fs.h
/mnt/erofs/linux/fs.h links=2 type=regular file
/mnt/erofs/hardlink-fs.h links=2 type=regular file
/mnt/erofs/symlink-fs.h links=1 type=symbolic link

$ touch /mnt/erofs/newfile
touch: cannot touch '/mnt/erofs/newfile': Read-only file system

하드링크는 링크 수까지 그대로 보존된다. cache_strategy=readaround는 따로 주지 않아도 붙는 기본값이다. root 권한이 없으면 erofsfuse로 마운트한다.

$ id -un
noble
$ erofsfuse a.img /tmp/m ; echo "exit=$?"
exit=0
$ grep /tmp/m /proc/mounts
erofsfuse /tmp/m fuse.erofsfuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
$ touch /tmp/m/x
touch: cannot touch '/tmp/m/x': Function not implemented
$ fusermount -u /tmp/m

마운트 옵션에 rw가 붙어 있지만 쓰기는 구현되어 있지 않다. 다만 erofsfuse는 압축 이미지에서 읽기가 실패하는 경우가 있어 그대로 신뢰하면 안 된다(아래 주의사항).

주의사항

  • --tar-Efragments를 같이 쓰면 안 된다. 같은 tar로 만든 이미지가 2,957,312바이트(-zdeflate만)에서 258,048바이트로 줄어들고, 마운트해서 읽어보면 검사한 200개 파일이 전부 원본과 다르다. mkfs.erofs는 종료 코드 0이고 fsck.erofs -p -d3조차 No errors found를 돌려준다. 유일한 단서는 dump.erofs -S의 압축률로, 정상 이미지의 38.48%가 3.02%로 찍힌다.
  • 이미지를 만든 뒤에는 dump.erofs -S의 압축률이 상식적인 값인지 보고, 커널로 마운트해 원본과 체크섬을 비교하는 절차를 빌드 파이프라인에 넣는 편이 안전하다. 위 사고는 두 확인 중 어느 쪽이라도 있었으면 잡혔다.
  • fsck.erofs --extract-Efragments로 만든 이미지에서 종료 코드 1로 중단되고 일부 파일을 풀지 못한다. 같은 이미지를 커널로 마운트하면 원본과 차이가 0줄이므로, 이미지가 깨진 게 아니라 유저스페이스 추출 구현의 한계다.
  • erofsfuse 1.7.1은 압축 이미지의 일부 파일을 읽지 못하고 Input/output error를 낸다. 실습 이미지에서 790개 중 56개가 실패했고 실패한 파일은 모두 16KB 이상이었지만, 35KB 파일이 정상적으로 읽히기도 해 크기만으로 갈리지는 않는다. 같은 이미지의 무압축 버전은 790개 전부 정상이고 커널 마운트도 전부 정상이다. 검증 용도로는 커널 마운트를 쓸 것.
  • dump.erofs --path는 경로가 아니라 inode를 기준으로 답한다. 하드링크된 파일을 조회하면 요청한 경로가 아니라 같은 inode의 다른 경로명이 출력될 수 있다(--path=/linux/fs.hPath : /hardlink-fs.h).
  • --tar=i는 메타데이터만 담은 이미지를 만든다(실습 데이터 기준 104,448바이트). 데이터는 원본 tar에 남아 있으므로 이미지 단독으로는 파일 내용을 읽을 수 없다.

마무리

EROFS 도구 자체는 단순하다. 디렉터리나 tar를 mkfs.erofs에 넣고, dump.erofs로 확인하고, 커널이나 erofsfuse로 마운트하면 끝이다. 문제는 배포판이 담고 있는 1.7.1이 확장 옵션 주변에서 조용히 어긋난다는 점이다. 옵션을 켤 때마다 만들어진 이미지를 실제로 마운트해서 원본과 대조하는 습관이, 이 버전에서는 선택이 아니라 필수다.

참고

답글 남기기