3行でまとめると
- mke2fsのフォーマット時のデフォルトパラメータを理解するのはめんどい
- ので、引数に明示的にパラメータ書いたほうがよい
- 作った後dumpe2fsで確認するのを忘れずに
はじめに
突然ですが、mke2fsでext4をフォーマットするときのデフォルトパラメータは下記のどれで決まるでしょうか。
- ボリュームのサイズ
- /etc/mke2fs.conf
- 環境変数
- 上記のすべて
- 上記のすべて以外にもたくさんある
正解は5でした。日頃からext4のsuperblockをhexdumpしているような皆さんには簡単だったかと思います。
というわけで、このへんのぐだぐだした話を書くわけですが、はっきり言って理解するのは無駄だと思います。明示的にmke2fsに引数を指定してフォーマットし、できあがったイメージをdumpe2fsして確認するのが手っ取り早くて確実だと思います。
下記では、Ubuntu-18.04での例を示しつつ、ソースコードはe2fsprogs-v1.45.6を見ています。
e2fsprogs
mke2fsは、e2fsprogsで提供されるコマンドになっている。おおむねここがext2, ext3, ext4の総本家と思って良い。make_ext4fsとかExt2Fsdとかもあるが、細かいオプションで必ずしも本家を追随できているわけではないので、あらぬバグを踏んだりすることがある。
そのe2fsprogsも、必ずしも挙動がすべてmanに記載されているわけではないため、細かいとこを追おうとしたら、ソースコードを直接確認せざるを得ない。
mke2fsの実行コマンド名
mke2fsは、実行するときのコマンド名(つまりargv[0])で挙動が変わる。
e2fsprogs/misc/mke2fs.cのparse_fs_type()とPRS()より、
if (fs_type)
ext_type = fs_type;
else if (is_hurd)
ext_type = "ext2";
else if (!strcmp(program_name, "mke3fs"))
ext_type = "ext3";
else if (!strcmp(program_name, "mke4fs"))
ext_type = "ext4";
else if (progname) {
ext_type = strrchr(progname, '/');
if (ext_type)
ext_type++;
else
ext_type = progname;
if (!strncmp(ext_type, "mkfs.", 5)) {
ext_type += 5;
if (ext_type[0] == 0)
ext_type = 0;
} else
ext_type = 0;
}
if (argc && *argv) {
program_name = get_progname(*argv);
/* If called as mkfs.ext3, create a journal inode */
if (!strcmp(program_name, "mkfs.ext3") ||
!strcmp(program_name, "mke3fs"))
journal_size = -1;
}
となっていて、ext_type(-tオプション)の指定がない場合に、プログラム名から、ext2, ext3, ext4のext_typeを決めている。通常はmkfs.ext4でお世話になっている人が多いと思う。
[rarul@tina e2fsprogs]$ ls -l /sbin/mkfs.ext*
lrwxrwxrwx 1 root root 6 Jan 25 2019 /sbin/mkfs.ext2 -> mke2fs
lrwxrwxrwx 1 root root 6 Jan 25 2019 /sbin/mkfs.ext3 -> mke2fs
lrwxrwxrwx 1 root root 6 Jan 25 2019 /sbin/mkfs.ext4 -> mke2fs
なおこのへんはmke2fs(8)にも記載がある。
If mke2fs is run as mkfs.XXX (i.e., mkfs.ext2, mkfs.ext3, or
mkfs.ext4) the option -t XXX is implied; so mkfs.ext3 will create a
file system for use with ext3, mkfs.ext4 will create a file system
for use with ext4, and so on.
ボリュームサイズ
mke2fsは、フォーマットしようとしているボリュームサイズによっても挙動を変える。e2fsprogs/misc/mke2fs.cのparse_fs_type()より、
if (fs_blocks_count < 3 * meg)
size_type = "floppy";
else if (fs_blocks_count < 512 * meg)
size_type = "small";
else if (fs_blocks_count < 4 * 1024 * 1024 * meg)
size_type = "default";
else if (fs_blocks_count < 16 * 1024 * 1024 * meg)
size_type = "big";
else
size_type = "huge";
if (!usage_types)
usage_types = size_type;
となっていて、usage_types(-Tオプション)の指定がない場合に、3MiB, 512MiB, 4TiB, 16TiBで、size_typeの選択を変更している。ここもmanに言及があるとはいえ、サイズとか**"floppy"**とかの具体的な指定の仕方までは言及されておらず、ソースコードを見ざるを得ない。
mke2fs.conf
先のようにext_typeやusage_typesが決まるが、これらは、mke2fs.confに記載されたセクションを選ぶための値として使われる。
[rarul@tina e2fsprogs]$ cat /etc/mke2fs.conf
[defaults]
base_features = sparse_super,large_file,filetype,resize_inode,dir_index,ext_attr
default_mntopts = acl,user_xattr
enable_periodic_fsck = 0
blocksize = 4096
inode_size = 256
inode_ratio = 16384
[fs_types]
ext3 = {
features = has_journal
}
ext4 = {
features = has_journal,extent,huge_file,flex_bg,metadata_csum,64bit,dir_nlink,extra_isize
inode_size = 256
}
small = {
blocksize = 1024
inode_size = 128
inode_ratio = 4096
}
floppy = {
blocksize = 1024
inode_size = 128
inode_ratio = 8192
}
big = {
inode_ratio = 32768
}
huge = {
inode_ratio = 65536
}
news = {
inode_ratio = 4096
}
largefile = {
inode_ratio = 1048576
blocksize = -1
}
largefile4 = {
inode_ratio = 4194304
blocksize = -1
}
hurd = {
blocksize = 4096
inode_size = 128
}
こんな感じに、mke2fs.confは種類に応じたオプションを持っている。ext_typeとusage_typesに応じて、ここからパラメータが選ばれることになる。このファイルのテンプレートはe2fsprogsが提供するが、実際の中身はLinuxディストリビューションごとに変更が加わっているかもしれない。記事の最後で触れるが、metadata_csumあたりがくせ者。
このmke2fs.confについては、わざわざman mke2fs.conf(5)が提供されていて、オプションも細かく指定することができるようになっている。
このmke2fs.confファイルについても、ファイルをどこから探すかをカスタマイズ可能で、↓の章へ続く。
mke2fs.confの探し方
環境変数MKE2FS_CONFIGでmke2fs.confファイルの場所を指定できる。e2fsprogs/misc/mke2fs.cのPRS()より、
if ((tmp = getenv("MKE2FS_CONFIG")) != NULL)
config_fn[0] = tmp;
環境変数MKE2FS_CONFIGが定義されていない場合は、コンパイル時に決まるデフォルトの場所(ROOT_SYSCONFDIR)が使われ、これは通常/etcになっている。
mke2fs.confがない場合
mke2fs.confが見つからなかった場合、const char *mke2fs_default_profileの値が使われる。が、ソースコードだけ見てるとこの変数の中身が見つからない。この変数は実は、ビルド時に自動生成される。
#!/bin/awk
BEGIN {
printf("const char *mke2fs_default_profile = \n");
}
{
printf(" \"%s\\n\"\n", $0);
}
END {
printf(";\n", str)
}
となっていて、profile-to-c.awkを実行するときの引数が展開される。引数は、
default_profile.c: mke2fs.conf $(srcdir)/profile-to-c.awk
$(E) " PROFILE_TO_C mke2fs.conf"
$(Q) $(AWK) -f $(srcdir)/profile-to-c.awk < mke2fs.conf \
> default_profile.c
となっていて、./configure時にmke2fs.conf.inから展開されるファイルが使われる。
[defaults]
base_features = sparse_super,large_file,filetype,resize_inode,dir_index,ext_attr
default_mntopts = acl,user_xattr
enable_periodic_fsck = 0
blocksize = 4096
inode_size = 256
inode_ratio = 16384
[fs_types]
ext3 = {
features = has_journal
}
ext4 = {
features = has_journal,extent,huge_file,flex_bg,metadata_csum,64bit,dir_nlink,extra_isize
inode_size = 256
}
small = {
blocksize = 1024
inode_size = 128
inode_ratio = 4096
}
floppy = {
blocksize = 1024
inode_size = 128
inode_ratio = 8192
}
big = {
inode_ratio = 32768
}
huge = {
inode_ratio = 65536
}
news = {
inode_ratio = 4096
}
largefile = {
inode_ratio = 1048576
blocksize = -1
}
largefile4 = {
inode_ratio = 4194304
blocksize = -1
}
hurd = {
blocksize = 4096
inode_size = 128
}
[options]
fname_encoding = utf8
つまり、e2fsprogsのデフォルトのmke2fs.confをそのままmke2fsプログラムの中に抱えている。
metadata_csumのワナ
metadata_csumにはプチ事件があって、metadata_csumを有効にしたext4を古いUbuntu(16.04)のe2fsprogsが扱えないという問題があった。
これは、Linuxカーネルはmetadata_csumを理解できるけど、e2fsprogsは古いままだったのでmetadata_csumを理解できなかった、という話。このUbuntu-16.04の場合はLinux-4.15だけど、環境によってはkernelのバージョンが予想できずに、思いもよらぬ環境でmetadata_csumが有効になってしまうという事件が起こる可能性がある。あまりにkernelが古いと、metadata_csumが扱えるとはいえまだext4バグ持ちだったりするわけで、えっと、その、非常に怖いですね、はい。
おわりに
というわけで、mke2fsがどうやってデフォルトパラメータを決めるのかを細かく確認してみた。なにげなく疑問に思ったinodeサイズの違いが起点だったけど、いざ調べてみるときちんと理解するのがめんどい泥沼だった。というか、理解するのはやめたほうが良いと思った。
理解するのはやめて、できるだけmke2fsを実行するときの引数で指定するようにしよう。そして、できあがったイメージに対しdumpe2fsで目的のパラメータになっているかを確認するようにしよう。