おれが…おれこそがBtrfsckだ!、という話

Btrfsは非常に安定した安心なファイルシステムです - Togetter

まあ、上のリンクのような話なのだけど、そこからどういうバグだったの?とかどういう状況だったの?とかどうやって直したの?というところを補則するための記事

もともとは多分ここのエラーで、leafがcorruptしたんじゃないかな?と思う。

http://twitpic.com/cv01wy/full

そんでここでささるようになった

http://via.me/-cjxgikk

こっち側を分析していく。

RIPを見ると死んでる場所は btrfs_drop_inode+0x10 であることがわかる。 btrfs_drop_inode() はこんだけの関数。ちょろい。

int btrfs_drop_inode(struct inode *inode)
{
	struct btrfs_root *root = BTRFS_I(inode)->root;

	/* the snap/subvol tree is on deleting */
	if (btrfs_root_refs(&root->root_item) == 0 &&
	    root != root->fs_info->tree_root)
		return 1;
	else
		return generic_drop_inode(inode);
}

この中で NULL pointer dereferenceで落ちているので、なんとなく見当はついて、 "inode"か"root"かがNULLなんだろなーと感じたと思う。

一応 objdump -xS inode.o しておくと

000000000000f020 <btrfs_drop_inode>:
free:
        call_rcu(&inode->i_rcu, btrfs_i_callback);
}

int btrfs_drop_inode(struct inode *inode)
{
    f020:       e8 00 00 00 00          callq  f025 <btrfs_drop_inode+0x5>
                        f021: R_X86_64_PC32     __fentry__-0x4
        struct btrfs_root *root = BTRFS_I(inode)->root;
    f025:       48 8b 97 20 fe ff ff    mov    -0x1e0(%rdi),%rdx
free:
        call_rcu(&inode->i_rcu, btrfs_i_callback);
}

int btrfs_drop_inode(struct inode *inode)
{
    f02c:       55                      push   %rbp
    f02d:       48 89 e5                mov    %rsp,%rbp
        struct btrfs_root *root = BTRFS_I(inode)->root;

        /* the snap/subvol tree is on deleting */
        if (btrfs_root_refs(&root->root_item) == 0 &&
    f030:       8b b2 f8 00 00 00       mov    0xf8(%rdx),%esi
    f036:       85 f6                   test   %esi,%esi
    f038:       75 16                   jne    f050 <btrfs_drop_inode+0x30>
            root != root->fs_info->tree_root)

ということで、rootがNULLになってるよね、って感じです。 写真のやつ見てもRDXが0になってるもんね。 RDXってf025でmovされてるとこだし、rootがNULLだよね。

じゃあなんで、ここがNULLなのよ?ってことなんですが、Stack traceを見てみると。 btrfs_new_inode+0x135からiputが呼ばれていて、rootがNULLになってるとこがあるんだなあ、とわかります。

btrfs_new_inode()に、こんな感じのコードがあって

	inode = new_inode(root->fs_info->sb);
	if (!inode) {
		btrfs_free_path(path);
		return ERR_PTR(-ENOMEM);
	}

	/*
	 * we have to initialize this early, so we can reclaim the inode
	 * number if we fail afterwards in this function.
	 */
	inode->i_ino = objectid;

	if (dir) {
		trace_btrfs_inode_request(dir);

		ret = btrfs_set_inode_index(dir, index);
		if (ret) {
			btrfs_free_path(path);
			iput(inode);
			return ERR_PTR(ret);
		}
	}
	/*
	 * index_cnt is ignored for everything but a dir,
	 * btrfs_get_inode_index_count has an explanation for the magic
	 * number
	 */
	BTRFS_I(inode)->index_cnt = 2;
	BTRFS_I(inode)->root = root;
	BTRFS_I(inode)->generation = trans->transid;
	inode->i_generation = BTRFS_I(inode)->generation;

あれれ〜? new_inodeでinode作った後に、rootがNULLのままiputしちゃってるよ〜?ってことがわかりますね。 ちなみにここは、ディレクトリにファイルのindexとれなかった時の処理なんで、多分(u64)-1ぐらいのファイル作ったら誰でもbtrfs落とせるんじゃないかしらん。ということで、多分btrfs_drop_inode()のところに、rootがNULLかどうかチェックすればいいんじゃないか、と思う。

さて、ここまではまあバグなんだけど、「ファイルを作りまくって」死んでいるのではなくて、どこかのblockがcorruptしててここのバグをふんでしまっているので、直すべき場所はまだ他にあるんだな。

dmesgを見てみると

[  188.559920] btrfs: corrupt leaf, slot offset bad: block=387949473792,root=1, slot=5

こんなのが大量にあるのでみんな大好きな "btrfs-debug-tree -b 387949473792 " してみる

	item 5 key (814282 DIR_ITEM 3304014366) itemoff 3749 itemsize 46
		location key (987934 INODE_ITEM 0) type 2
		namelen 16 datalen 0 name: initramfs.L0vZCH
	item 6 key (814282 DIR_ITEM 3827348333) itemoff 69239 itemsize 46
		location key (0 UNKNOWN.0 0) type 0
		namelen 0 datalen 0 name: 
		location key (0 UNKNOWN.0 0) type 0
		namelen 0 datalen 0 name: 
	item 7 key (814282 DIR_ITEM 3858130265) itemoff 3657 itemsize 46
		location key (987937 INODE_ITEM 0) type 2
		namelen 16 datalen 0 name: initramfs.TbTWJF

あー、 item 6がどう見てもおかしい感じですねー。 本来は

<item 6のitemoff> = <item 7のitemoff> + <item 7のitemsize>

になっているべきだし、そのせいか、itemの中身もとれてません。(本来は他のitemのようにファイル名がとれるべき)

(そんで、このまわりのitemの中を見てみるとどーもこれが/var/tmpの下のファイルを管理してるものっぽい気がするよねーということがわかる。実際ls /var/tmpするとIOエラーで読みこめなかった)

そんで、このitem 6を直したいなー、ここ直さないと(/var/tmpだし)結局ここに読み書きしたらファイルシステム死んじゃうし直さないとだめだなー、となる。 じゃあどう直すか?なんだけれど、こういうコードでoreorebtrfsckでも書いて実行してやりゃ直る。 (btrfs-progsのヘッダやらを使っている)

https://gist.github.com/naota/5703328 (なんかgistはれないや、よくわからない)

コメント書いてあるし、なにやってるかなんとなーくわかるかと思う・・・。

ようするに

  • 指定したブロック番号に対応するディスク上の場所を探す (RAID1されてるので全てを)
  • item 6のoffsetを正しく書きかえる
  • (btrfs_print_leafで書きかえがうまくいってitem 6 keyの中が読めてること確認)
  • checksumを更新する
  • diskに書いてfsyncする

とやっているだけ。

そんでrebootしたらきれいに動く。よかったね。 item 6はちゃんと読めるとこうなる

	item 6 key (814282 DIR_ITEM 3827348333) itemoff 3703 itemsize 46
		location key (1840149 INODE_ITEM 0) type 2
		namelen 16 datalen 0 name: initramfs.JzzElY

rm -rf /var/tmp/initramfs.* してもささらなかったのでこれからも安心して生きていける。