常時稼働しているLinuxマシンでハードディスクの状態を定期的にチェックして、異常な状態を早めに把握できるようにするため、libatasmartを使って、ハードディスクのS.M.A.R.T.情報をチェックするプログラムを作成しましたので、その概要を紹介します
用意するもの
- C言語の開発環境
- S.M.A.R.T.のライブラリ libatasmart (http://0pointer.de/blog/projects/being-smart.html)
libatasmartは各ディストリビューションからパッケージがあれば、yumやemerge、apt-getなどのコマンドでインストールしてください。
もし用意されていなければ、以下からソースコードを取得します
libatasmartのコンパイルは prefix と libdir(64bit環境の場合) くらいを入れておけば良いと思います
$ ./configure --prefix=/usr --libdir=/usr/lib64
$ make
$ sudo make install
サンプルコード
ライブラリのインストールができたら、以下のコードをエディタに入れてコンパイルしてみましょう
# include <stdio.h>
# include <string.h>
# include <errno.h>
# include <atasmart.h> /* libatasmart */
void usage(const char *command) {
printf("Usage: %s DEVICE\n", command);
}
int get_hd_health(const char *disk) {
int ret = -1;
SkDisk *skdisk;
SkBool smart_available = 0;
SkBool disk_awake = 0;
SkBool disk_status = 0;
uint64_t disk_temp = 0;
uint64_t disk_ontime = 0;
if (sk_disk_open(disk, &skdisk) < 0) {
fprintf(stderr, "Failed to open disk %s: %s(%d)\n", disk, strerror(errno), errno);
return -1;
}
if (sk_disk_smart_is_available(skdisk, &smart_available) < 0) {
fprintf(stderr, "Failed to query whether SMART is available %s: %s(%d)\n", disk, strerror(errno), errno);
goto done;
}
if (!smart_available) {
fprintf(stderr, "%s is not support SMART\n", disk);
goto done;
}
if (sk_disk_smart_read_data(skdisk) < 0) {
fprintf(stderr, "Failed to read SMART data %s: %s(%d)\n", disk, strerror(errno), errno);
goto done;
}
if (sk_disk_check_sleep_mode(skdisk, &disk_awake) < 0) {
fprintf(stderr, "Failed to get sleep mode\n");
} else {
printf("drive is %s\n", disk_awake ? "active" : "sleep");
}
ret = 0;
if (sk_disk_smart_get_temperature(skdisk, &disk_temp) == 0) {
uint64_t celsius = (disk_temp - 273150) / 1000; /* convert milli kelvin to celsius */
printf("drive temperture: %zu C\n", celsius);
}
if (sk_disk_smart_get_power_on(skdisk, &disk_ontime) == 0) {
unsigned long long onhour = disk_ontime / 1000 / 3600;
printf("drive power on hour: %llu h\n", onhour);
}
if (sk_disk_smart_status(skdisk, &disk_status) == 0) {
printf("drive status: %s\n", disk_status ? "GOOD" : "BAD");
}
done:
sk_disk_free(skdisk);
return ret;
}
int main(int argc, char *argv[]) {
int ret;
if (argc <= 1) {
usage(argv[0]);
return 1;
}
ret = get_hd_health(argv[1]);
return ret;
}
コンパイル
$ gcc -o check_hd_health check_hd_health.c -latasmart -Wall
何もエラーが出なければ、実行バイナリが出来上がるはずです。
使い方
引数にハードディスクのデバイス(/dev/sdaなど)を指定してください
./check_hd_health %%HDDデバイス%%
以下の内容が出力されます
- ディスクのsleep状態 (active / sleep)
- ハードディスクの温度
- 通電時間
- ハードディスクの健康状態 (GOOD / BAD)
実行例
./check_hd_health /dev/sdc
drive is active
drive temperture: 51 C
drive power on hour: 4418 h
drive status: GOOD
APIの説明
サンプルで上げた以外に使えそうなAPIも含めて、libatasmartに用意されているものを示します
API | 説明 |
---|---|
int sk_disk_open(const char *name, SkDisk **d) | SkDiskインスタンスを生成します。nameにハードディスクのデバイスを指定してください |
int sk_disk_smart_read_data(SkDisk *d) | S.M.A.R.T.の情報を取得する時に実行します |
void sk_disk_free(SkDisk *d) | SkDiskインスタンスを解放します |
int sk_disk_get_size(SkDisk *d, uint64_t *bytes) | ハードディスクの容量を取得します |
int sk_disk_check_sleep_mode(SkDisk *d, SkBool *awake) | ハードディスクの(awake/sleep)状態を取得します |
int sk_disk_identify_is_available(SkDisk *d, SkBool *available) | 不明 |
int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **data) | 不明 |
int sk_disk_smart_is_available(SkDisk *d, SkBool *available) | S.M.A.R.T.が有効であることを確認します |
int sk_disk_smart_status(SkDisk *d, SkBool *good) | ディスクの健康状態(GOOD/BAD)を取得します |
int sk_disk_get_blob(SkDisk *d, const void **blob, size_t *size) | 不明 |
int sk_disk_set_blob(SkDisk *d, const void *blob, size_t size) | 不明 |
int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **data) | 不明 |
int sk_disk_smart_parse_attributes(SkDisk d, SkSmartAttributeParseCallback cb, void userdata) | 任意の項目のS.M.A.R.T.情報を取得する時に使います |
int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test) | 自己診断を実行します |
int sk_disk_smart_get_power_on(SkDisk *d, uint64_t *mseconds) | ハードディスクの通電時間を取得します |
int sk_disk_smart_get_power_cycle(SkDisk *d, uint64_t *count) | 電源ONにされた回数を取得します |
int sk_disk_smart_get_bad(SkDisk *d, uint64_t *sectors) | 不良セクタの数を取得します |
int sk_disk_smart_get_temperature(SkDisk *d, uint64_t *mkelvin) | ハードディスクの温度を取得します。単位がミリケルビンなので、℃表記にするには273150を引いて1000で割ってください |
int sk_disk_smart_get_overall(SkDisk *d, SkSmartOverall *overall) | 不明 |
int sk_disk_dump(SkDisk *d) | S.M.A.R.T.の情報を全て画面に表示します |
※ 公式のドキュメントがリンク切れになってましたので、ソースコードを読んで推測しています。不明な物もありますし、間違いがあるかもしれませんがご了承ください
任意のS.M.A.R.T.の情報を得るには
APIから直接取得できる値もいくつかありますが、それ以外の値を取得するには、sk_disk_smart_parse_attributes を使います
int sk_disk_smart_parse_attributes(SkDisk d, SkSmartAttributeParseCallback cb, void userdata)
第2引数で指定した関数がS.M.A.R.T.項目毎に呼ばれますので、関数の中で目的のidが来た時に値を取得するようにします
※ IDについてはwikipediaなどでご確認ください
第3引数で、第2引数の中で取得した値を入れる変数のポインタを指定します
渡された、SkSmartAttributeParsedData構造体の中のpretty_valueから値がuint64_t型で取得できます
例) udma-crc-error-count を取得する
# include <stdio.h>
# include <string.h>
# include <errno.h>
# include <atasmart.h> /* libatasmart */
void usage(const char *command) {
printf("Usage: %s DEVICE\n", command);
}
static void get_errorcount_cb(SkDisk *d, const SkSmartAttributeParsedData *a, uint64_t *value) {
if (a->id != 199) /* udma-crc-error-count */
return;
*value = a->pretty_value;
}
int get_errorcount(const char *disk) {
int ret = -1;
uint64_t value;
SkDisk *skdisk;
if (sk_disk_open(disk, &skdisk) < 0) {
fprintf(stderr, "Failed to open disk %s: %s(%d)\n", disk, strerror(errno), errno);
return -1;
}
if (sk_disk_smart_read_data(skdisk) < 0) {
fprintf(stderr, "Failed to read SMART data %s: %s(%d)\n", disk, strerror(errno), errno);
goto done;
}
if (sk_disk_smart_parse_attributes(skdisk, (SkSmartAttributeParseCallback) get_errorcount_cb, &value) < 0) {
fprintf(stderr, "Failed to get attribute: %s(%d)\n", strerror(errno), errno);
goto done;
}
printf("udma-crc-error-count: %zu\n", value);
ret = 0;
done:
sk_disk_free(skdisk);
return ret;
}
int main(int argc, char *argv[]) {
int ret;
if (argc <= 1) {
usage(argv[0]);
return 1;
}
ret = get_errorcount(argv[1]);
return ret;
}
※ 注意
get_errorcount_cb() は SkSmartAttributeParseCallback型で何かの値を返して、動作を変える事も出来るのかもしれませんが、今回は値が取れれば十分ですので、戻り値をstatic voidにしています
補足
ハードディスクのデバイスにはroot権限が必要な場合があるため、一般ユーザーで実行するとpermission deniedになってしまうことがあります。
$ ./check_hd_health /dev/sda
Failed to open disk /dev/sda: Permission denied(13)
そんな時には check_hd_health のオーナーをrootにしてSUID (Set User ID)を付与すれば、一般ユーザーでも実行可能になります
# chown root check_hd_health
# chmod 4755 check_hd_health
ls で見た時に、ユーザーのパーミッションが rws (3つ目が"s")となっていればSUIDがついている状態です
# ls -l check_hd_health
-rwsr-xr-x 1 root users 17048 11月 1 20:16 check_hd_health