XFSを解析した話 (2) - inode 編

目次

想定読者

XFS Filesystem の生データを解析したいと思っている方に向けた記事です。 

今回の記事では、前回作成した 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

Figure 16.1: On-disk inode sections

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 を探すことも可能です。

https://github.com/torvalds/linux/blob/4a3bb4200a5958d76cc26ebe4db4257efa56812b/fs/xfs/libxfs/xfs_format.h#L104

構造体を見るとスーパブロックの 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編に分けると思います。