Help us understand the problem. What is going on with this article?

ext4フォーマットするときはmke2fs.confに注意しようという話

3行でまとめると

  • mke2fsのフォーマット時のデフォルトパラメータを理解するのはめんどい
  • ので、引数に明示的にパラメータ書いたほうがよい
  • 作った後dumpe2fsで確認するのを忘れずに

はじめに

突然ですが、mke2fsでext4をフォーマットするときのデフォルトパラメータは下記のどれで決まるでしょうか。

  1. ボリュームのサイズ
  2. /etc/mke2fs.conf
  3. 環境変数
  4. 上記のすべて
  5. 上記のすべて以外にもたくさんある

正解は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()より、

e2fsprogs/misc/mke2fs.c
    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;
    }
e2fsprogs/misc/mke2fs.c
    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()より、

e2fsprogs/misc/mke2fs.c
    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_typeusage_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()より、

e2fsprogs/misc/mke2fs.c
    if ((tmp = getenv("MKE2FS_CONFIG")) != NULL)
        config_fn[0] = tmp;

環境変数MKE2FS_CONFIGが定義されていない場合は、コンパイル時に決まるデフォルトの場所(ROOT_SYSCONFDIR)が使われ、これは通常/etcになっている。

mke2fs.confがない場合

mke2fs.confが見つからなかった場合、const char *mke2fs_default_profileの値が使われる。が、ソースコードだけ見てるとこの変数の中身が見つからない。この変数は実は、ビルド時に自動生成される。

e2fsprogs/misc/profile-to-c.awk
#!/bin/awk
BEGIN {
  printf("const char *mke2fs_default_profile = \n");
}

{
  printf("  \"%s\\n\"\n", $0);
}

END {
  printf(";\n", str)
}

となっていて、profile-to-c.awkを実行するときの引数が展開される。引数は、

e2fsprogs/misc/Makefile.in
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から展開されるファイルが使われる。

2fsprogs/misc/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が扱えないという問題があった。
- 2018年3月23日号 ext4のmetadata_csumオプションへの対処・initramfsの圧縮方式の変更:Ubuntu Weekly Topics|gihyo.jp … 技術評論社

これは、Linuxカーネルはmetadata_csumを理解できるけど、e2fsprogsは古いままだったのでmetadata_csumを理解できなかった、という話。このUbuntu-16.04の場合はLinux-4.15だけど、環境によってはkernelのバージョンが予想できずに、思いもよらぬ環境でmetadata_csumが有効になってしまうという事件が起こる可能性がある。あまりにkernelが古いと、metadata_csumが扱えるとはいえまだext4バグ持ちだったりするわけで、えっと、その、非常に怖いですね、はい。

おわりに

というわけで、mke2fsがどうやってデフォルトパラメータを決めるのかを細かく確認してみた。なにげなく疑問に思ったinodeサイズの違いが起点だったけど、いざ調べてみるときちんと理解するのがめんどい泥沼だった。というか、理解するのはやめたほうが良いと思った。

理解するのはやめて、できるだけmke2fsを実行するときの引数で指定するようにしよう。そして、できあがったイメージに対しdumpe2fsで目的のパラメータになっているかを確認するようにしよう。

参考

rarul
部署まるごとすぐにもリストラされそうになってきたので、就職活動がてら、Qiitaに記事を投稿しています。っというのは仮の姿で、Twitterでリン廃キャラを演じるのが本業です。
http://www.rarul.com/twitter.html
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away