XFSを解析した話 (2) - inode 編
目次
想定読者
XFS Filesystem の生データを解析したいと思っている方に向けた記事です。
- XFSを解析した話 (1) SuperBlock 編
- XFSを解析した話 (2) inode 編 ← 本記事
- XFSを解析した話 (3) Directory 編
- XFSを解析した話 (4) RegularFile 編
今回の記事では、前回作成した XFS のバイナリデータを用いて、XFSの inode について触れていきたいと思います。
はじめに
前回も少し説明しましたが、inode は Linux 上のファイル に一意に付与される番号です。つまりファイルシステムに管理できるファイルは、inode の総数に依存します。
ファイルの種類は以下の7つになります。
- Regular file
- Directory
- Character special device
- Block special device
- FIFO
- Socket
- Symlink
XFS ではファイルの inode と実際のデータは別で管理されているため、inode にはデータが保存されている場所が保存されています。
その他にも、ファイルの所有者やタイムスタンプの情報などが格納されています。
今回は XFS 上に存在する inode の探索方法と、inode の作りについて解説していこうと思います。 前回の記事で作成したイメージを使って解説します。
XFSを解析した話(SuperBlock 編)デバッグ用のイメージを作成
また、XFSの inode は version 1,2 と 3 で異なります。本記事では version 3 を前提として解説します。
inode について
XFSの inode について説明します。仕様書 の Chapter 16 On-disk Inode に詳細が記載されているのでそれらをもとに解説します。
XFSの inode は以下の3つの構造体からなります。
- inode core
- data fork
- extended attribute fork
inode core には file stat の情報と、data fork, extended attribute fork に記載されるデータの種別を保持しています。
data fork には inode に関連するデータ(通常のファイルやディレクトリなど)へ参照するための情報が入っています。
extended attribute fork には inode に対するメタデータが含まれます。実は自分もあまりわかっておらず、SELinuxなどの機構を入れるための場所なのかなというイメージです。
inode の解析
任意の inode を解析するにあたって、まずは root inode を取得する必要があります。
ファイルシステム上の任意のディレクトリに行くにしても必ずルートディレクトリから辿るように(絶対パスの場合)ファイルシステムを探索する際も root inode(ルートディレクトリの inode)をみる必要があります。
任意の inode の情報をバイナリ上で取得するのに必要なのは inode 番号です。 root inode の番号はスーパーブロックに記載されています。
root inode 番号の調査
xfs_db を用いて root inode 番号を探しましょう。
# xfs.img は、前回記事で作ったものです。 $ xfs_db xfs.img xfs_db> sb xfs_db> p ... 色々割愛 rootino = 128
rootino を見ることで、このXFSの root inode 番号は 128 であることがわかります。
ちなみに xfs_db なんて使わねえ!という方向けにバイナリから探す方法だと、下記のコードを参考にスーパーブロックのデータから rootino を探すことも可能です。
構造体を見るとスーパブロックの 56 bytes から 8 bytes が rootino であることがわかるため、下記のようにすることで rootino の値が取得できます。
$ xxd -s 56 -l 8 xfs.img 00000038: 0000 0000 0000 0080 ........
このように 0x80 なので 128 で rootino が 128 であることが確認できます。
次に root inode のデータがバイナリ上のどこに配置されているかを計算します。
root inode の offset を計算
XFS の inode 番号は2つの形式を持っており、AG相対番号と絶対番号を持ってます。
AG相対番号は 4 bytes で表現できるよう設計されており、絶対番号は 8 bytes で表現できるよう設計されています。
AGFやAGI(確保済みの inode や空いている inode を保持している)ではAG相対番号を利用していますが、root inode や あるディレクトリが持っている inode 番号などは絶対番号です。詳細が気になる方は仕様書の(13.3 AG Inode Management)を確認してください。
本記事では 絶対番号 (以下 inode 番号) を利用して解説します。
inode 番号は以下の図のように3つの要素を含んでおり、スーパーブロックに記載されている bit 長をもとに分解できます。
- AG number
- Block number
- inode position
AG number はその inode がどの AG に配置されているかを示しています。
Block number はその AG 内の何ブロック目かを示しています。
inode position はそのブロックの何番目かを示しています。
イメージとしてはこんな感じです。
ということで計算していきましょう。 root inode は128と小さな数字なので簡単です。 はじめに必要な情報をスーパーブロックから取得しましょう。
$ xfs_db xfs.img xfs_db> sb xfs_db> p いろいろ割愛 blocksize = 4096 agblocks = 4862 inodesize = 512 inopblock = 8 inopblog = 3 agblklog = 13
※ inopblock は 1 blockあたりの inode 数です。 blocksize / inodesize でも計算できますが、スーパーブロックに入ってるので利用します。
※ agblocks は AG あたりの block 数です。
はじめに inode 番号から AG number, Block number, inode positionを計算します。
bit計算などをするので pythonを使います。
inode_number = 128 inopblog = 3 agblklog = 13 inopblock = 8 ag_musk = inopblog + agblklog low_musk = (1 << ag_musk) - 1 ag_number = inode_number >> ag_musk block_number = (inode_number & low_musk) / inopblock inode_position = (inode_number & low_musk) % inopblock
ここまでで、どこのAGの何ブロック目の何番目に root inode が入っていることがわかります。 あとはこれらの情報をもとに offset を計算します。
blocksize = 4096 agblocks = 4862 inodesize = 512 # AGのサイズを計算 ag_size = agblocks * blocksize # オフセットを計算 inode_offset = (ag_size * ag_number ) + (block_number * blocksize) + (inode_position * inodesize)
これらを計算すると、 inode の offset は 65536 であることがわかります。(みんな気がつくと思いますが、あまり面白くない inode 番号)
root inode を解析
先ほどまでで、 root inode の offset を計算したので、実際に見てみましょう。
$ xxd -s 65536 -l 512 xfs.img 00010000: 494e 41ed 0301 0000 0000 0000 0000 0000 INA............. 00010010: 0000 0004 0000 0000 0000 0000 0000 0000 ................ 00010020: 6350 1d2e 04e4 de45 6350 1a4b 0ddb 729e cP.....EcP.K..r. 00010030: 6350 1a4b 0ddb 729e 0000 0000 0000 001c cP.K..r......... 00010040: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00010050: 0000 0002 0000 0000 0000 0000 0000 0000 ................ 00010060: ffff ffff 6af2 441d 0000 0000 0000 0006 ....j.D......... 00010070: 0000 0001 0000 0030 0000 0000 0000 0000 .......0........ 00010080: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00010090: 6350 1834 2a46 72e0 0000 0000 0000 0080 cP.4*Fr......... 000100a0: 1b1b cec8 c7eb 4ff3 bfd5 1fad cd95 ee46 ......O........F 000100b0: 0200 0000 0080 0300 6065 7463 0200 0000 ........`etc.... 000100c0: 8303 0070 7661 7202 0001 2b40 0000 0000 ...pvar...+@.... 000100d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ ... 全部 0x00 なので割愛 000101f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
inode のマジックバイトが IN(0x494e) なのであってそうです。仕様書の(16.1 Inode Core)を参考にするとバイナリのまま読むことができますが、xfs_dbでも確認できるので今回はそちらでみます。
$ xfs_db xfs.img xfs_db> inode 128 xfs_db> p core.magic = 0x494e core.mode = 040755 core.version = 3 core.format = 1 (local) かなりでたと思いますが、一旦割愛
重要なのは verision と mode と format です。 version は 3 であることを確認してください。(本記事は 1, 2 が対象外なので 3 以外が出たら困る)
次に mode です。これは linux の file mode です。普段目にするのは permission の 755 の部分だけかと思いますが、今回は 40000 がついています。man コマンドで inode の詳細が検索できるのでみましょう。
$ man 7 inode ... 割愛 The file type and mode The stat.st_mode field (for statx(2), the statx.stx_mode field) contains the file type and mode. POSIX refers to the stat.st_mode bits corresponding to the mask S_IFMT (see below) as the file type, the 12 bits corresponding to the mask 07777 as the file mode bits and the least significant 9 bits (0777) as the file permission bits. The following mask values are defined for the file type: S_IFMT 0170000 bit mask for the file type bit field S_IFSOCK 0140000 socket S_IFLNK 0120000 symbolic link S_IFREG 0100000 regular file S_IFBLK 0060000 block device S_IFDIR 0040000 directory S_IFCHR 0020000 character device S_IFIFO 0010000 FIFO ...
man コマンドでこの様な文章を読むことが可能です。 S_IFDIR に書いてある通り、0040000 は directory のようです。
最後に format ですが、これは inode に紐づく data fork のフォーマットのことです。この inode はディレクトリでその詳細は format 1(local)で表現されていることがわかります。
format は以下の種類が存在します。
- XFS_DINODE_FMT_DEV
- XFS_DINODE_FMT_LOCAL
- XFS_DINODE_FMT_EXTENTS
- XFS_DINODE_FMT_BTREE
- XFS_DINODE_FMT_UUID
- XFS_DINODE_FMT_RMAP
詳細は(16.1 Inode Core)に記載されています。 主に利用されているのは、dev, local, extents, bree の 4つです。詳細は後述します。
ファイルには複数の type が存在しますが、regular file(通常のファイル)と directory だけでも以下の組み合わせが存在します。
- local × directory
- extents × directory
- btree × directory
- extents × regular file
- btree × regular file
詳細は(仕様書: 16.3 Data Fork)に記載されています。
これらの組み合わせで data fork をパースすることで、directory であれば配下ディレクトリのデータを取得できたり、regular file であれば実際のデータを取得することが可能です。
おわり
今回の記事はここまでとして、次の記事で data fork について記載しようと思います。 おそらく、regular file編 と directory編に分けると思います。