ディスク情報を取得するコマンドを試作してみました

この投稿は「[さくらインターネット Advent Calendar 2019](https://qiita.com/advent-calendar/2019/sakura)」の9日目の記事です。

## 概要

* ディスク情報を取得するコマンドを Go 言語で実装してみました。
* [さくらのクラウド](https://cloud.sakura.ad.jp/)で試してみます。
* コマンドの仕様について説明します。

## 動機について

普段 Go 言語をメインで使っているので、Go 製のライブラリやツールなどをよく検索しています。
そんな中、Go 言語で実装されたハードウェア情報を取得するツール [ghw](https://github.com/jaypipes/ghw) を見つけたのですが、ghw をはじめ、`udevadm info` や [util-linux](https://github.com/karelzak/util-linux) の `findmnt` などがどのようにディスク情報を取得しているのかが気になり、学習がてら実装してみました。

## ディスク情報を取得する試作コマンドについて

以下がリポジトリとなります。

https://github.com/blp1526/blkinfo

なお、本コマンドを実行するには、大雑把に言いますと、Linux であり、systemd が使われており、コンテナではない環境が必要になります。
ここでは例としてさくらのクラウドの Ubuntu 18.04 を利用してみます。

## さくらのクラウドのサーバで試す手順について

ご紹介したい機能がありますので、シンプルモードではない方法で説明します。
サーバの追加画面から、右上の「シンプルモード」のチェックを外します。

<img width="753" alt="simple.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6915/04028ef8-26aa-5792-2c4d-e859630bd87d.png">

サーバの詳細な作成画面から、仮想コアやメモリや OS などを埋めた上で「ディスクの修正」の項目まで進みます。
「github.comから取得」を設定すると、指定したアカウントの公開鍵があらかじめサーバに設定されます。

<img width="593" alt="add_server.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6915/d6696813-0401-30f8-7653-4228c76d7b53.png">

非常に便利ですので、ぜひご利用ください。

サーバ作成後、ssh でログインもしくはコントロールパネルの「コンソール」のタブから作業をしてみることとします。
「コンソール」のタブから作業する場合は、以下の位置になります。

<img width="993" alt="console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6915/62ab5cc4-1a6e-dbee-4963-fcd23f553a5d.png">

## コマンドのインストール方法について

こちらの [README.md](https://github.com/blp1526/blkinfo#installation) にも記載していますが、

```
$ wget https://github.com/blp1526/blkinfo/releases/latest/download/blkinfo_linux_x86_64.tar.gz
$ tar zxvf blkinfo_linux_x86_64.tar.gz
```

を実行し、コマンドをインストールしてから、

```
$ ./blkinfo /dev/vda3
```

を実行してみると、若干の差異はあると思いますが、以下のようなディスク情報が JSON で取得できると思います。
なお、Ubuntu 18.04 のパブリックアーカイブでない場合には `/dev/vda3` は存在しないかもしれませんので、別のパスを指定してみてください。

```
{
  "path": "/dev/vda3",
  "resolved_path": "/dev/vda3",
  "parent_path": "/dev/vda",
  "child_paths": [],
  "sys_path": "/sys/block/vda/vda3",
  "resolved_sys_path": "/sys/devices/pci0000:00/0000:00:05.0/virtio2/block/vda/vda3",
  "sys": {
    "uevent": [
      "MAJOR=252",
      "MINOR=3",
      "DEVNAME=vda3",
      "DEVTYPE=partition",
      "PARTN=3"
    ],
    "slaves": [],
    "holders": []
  },
  "major_minor": "252:3",
  "udev_data_path": "/run/udev/data/b252:3",
  "udev_data": [
    "S:disk/by-uuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "S:disk/by-partuuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "S:disk/by-path/virtio-pci-0000:00:05.0-part3",
    "S:disk/by-path/pci-0000:00:05.0-part3",
    "W:4",
    "I:1583813",
    "E:ID_SCSI=1",
    "E:ID_PART_TABLE_TYPE=gpt",
    "E:ID_PART_TABLE_UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "E:ID_PATH=pci-0000:00:05.0",
    "E:ID_PATH_TAG=pci-0000_00_05_0",
    "E:ID_FS_UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "E:ID_FS_UUID_ENC=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "E:ID_FS_VERSION=1.0",
    "E:ID_FS_TYPE=ext4",
    "E:ID_FS_USAGE=filesystem",
    "E:ID_PART_ENTRY_SCHEME=gpt",
    "E:ID_PART_ENTRY_UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "E:ID_PART_ENTRY_TYPE=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "E:ID_PART_ENTRY_NUMBER=3",
    "E:ID_PART_ENTRY_OFFSET=8392704",
    "E:ID_PART_ENTRY_SIZE=33548288",
    "E:ID_PART_ENTRY_DISK=252:0",
    "E:net.ifnames=0",
    "G:systemd"
  ],
  "mount_info_path": "/proc/self/mountinfo",
  "mount_info": {
    "mount_id": "28",
    "parent_id": "0",
    "major_minor": "252:3",
    "root": "/",
    "mount_point": "/",
    "mount_options": [
      "rw",
      "relatime"
    ],
    "optional_fields": [
      "shared:1"
    ],
    "filesystem_type": "ext4",
    "mount_source": "/dev/vda3",
    "super_options": [
      "rw",
      "errors=remount-ro",
      "data=ordered"
    ]
  },
  "os_release_path": "/etc/os-release",
  "os_release": {
    "BUG_REPORT_URL": "https://bugs.launchpad.net/ubuntu/",
    "HOME_URL": "https://www.ubuntu.com/",
    "ID": "ubuntu",
    "ID_LIKE": "debian",
    "NAME": "Ubuntu",
    "PRETTY_NAME": "Ubuntu 18.04.3 LTS",
    "PRIVACY_POLICY_URL": "https://www.ubuntu.com/legal/terms-and-policies/privacy-policy",
    "SUPPORT_URL": "https://help.ubuntu.com/",
    "UBUNTU_CODENAME": "bionic",
    "VERSION": "18.04.3 LTS (Bionic Beaver)",
    "VERSION_CODENAME": "bionic",
    "VERSION_ID": "18.04"
  }
}
```

## 自作コマンドの仕様について

出力されたフィールドについて説明します。

* `path`
  * コマンドの引数として与えたパスを入れています。
* `resolved_path`
  * `path` がシンボリックリンクだった場合 resolved なパスを入れています。
    * 例えば、 `path` として `/dev/disk/by-uuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`あたりのパスを指定したとすると、そのパスの resolved なパスが入ることになります。
* `sys_path`, `parent_path`, `child_paths`
  * `sys_path` は `/sys` 配下のディレクトリから、指定したディスクの情報を取得できるパスを入れています。
  * `parent_path`, `child_paths` は、`sys_path` から見て、親階層、子階層のブロックデバイスを入れています。
* `resolved_sys_path`
  * `sys_path` はシンボリックリンクのため、そのパスの resolved なパスを入れています。
* `sys` の `uevent`, `slaves`, `holders`
  * `sys_path` ディレクトリの `uevent` ファイルの中身と、`slaves`, `holders` ディレクトリに存在するファイルの情報となります。
  * `slaves`, `holders` は LVM の OS をブートしたときに存在するようです。さくらのクラウドを利用して検証する場合には、ISO イメージからサーバを作成し、パーティションの作成時に LVM を指定するとよさそうです。
    * LVM の OS だった場合のサンプルを https://godoc.org/github.com/blp1526/blkinfo#Sys にコメントとして残してあります。
* `major_minor`
  * デバイスのメジャー番号とマイナー番号をコロンで結合したものを入れています。デバイスのメジャー番号とマイナー番号は、（たとえば） `lsblk` コマンドで表示でき、 `cat /proc/devices` で一覧を確認できる番号となります。
* `udev_data_path`, `udev_data`
  * `udev_data` は `udevadm info /dev/vda3` などで取得できる情報を入れています。 `udev_data_path` から取得できるものです。
    * `udevadm info` がどこから情報を取得しているのか `man` などからわからなかったため、`strace udevadm info /dev/vda3` でファイルを開いている場所を探し、当たりをつけました。
    * ソースコードは[このあたり](https://github.com/systemd/systemd/blob/21b40f16622f171a9969dc334d74fb5eb2f575c2/src/libsystemd/sd-device/sd-device.c#L1364)でしょうか...
* `mount_info_path`, `mount_info`
  * `findmnt` コマンドで取得できる情報が欲しくて追加したフィールドです。 `path` で指定したブロックデバイスがマウントされているのであれば、値を入れるようにしています。
  * `findmnt` コマンドについては `man 8 findmnt` で `/proc/self/mountinfo` を参照していることがわかりました。
  * ただ、 `/proc/self/mountinfo` で表示される各項目が何を表しているのか不明でしたので、調査したものをコメントとして https://godoc.org/github.com/blp1526/blkinfo#MountInfo に残してあります。
* `os_release_path`, `os_release`
  * `マウントポイント + /etc/os-release` から取得できる `os-release` の情報を入れています。
    * `マウントポイント + /etc/os-release` という実装にしたので、たとえば Ubuntu サーバ内の `/mnt` に CentOS のディスクをマウントしていた場合、その CentOS のブロックデバイスのパスをコマンドの引数として指定すれば、（Ubuntu ではなく）CentOS の `/etc/os-release` の情報が取得できます。

## まとめ

 コンテナ上では `/dev` 配下などの問題でこのコマンドは実行できず、 さくらのクラウドという IaaS で検証できるものとして、この記事を書いてみました。
 以上、「[さくらインターネット Advent Calendar 2019](https://qiita.com/advent-calendar/2019/sakura)」の9日目の記事でした。