LoginSignup
7
8

More than 5 years have passed since last update.

[Linux][C言語] ハードディスクのS.M.A.R.T.情報を自作プログラムで取得する

Last updated at Posted at 2015-11-01

常時稼働しているLinuxマシンでハードディスクの状態を定期的にチェックして、異常な状態を早めに把握できるようにするため、libatasmartを使って、ハードディスクのS.M.A.R.T.情報をチェックするプログラムを作成しましたので、その概要を紹介します

用意するもの

libatasmartは各ディストリビューションからパッケージがあれば、yumやemerge、apt-getなどのコマンドでインストールしてください。

もし用意されていなければ、以下からソースコードを取得します

libatasmartのコンパイルは prefix と libdir(64bit環境の場合) くらいを入れておけば良いと思います


$ ./configure --prefix=/usr --libdir=/usr/lib64
$ make
$ sudo make install

サンプルコード

ライブラリのインストールができたら、以下のコードをエディタに入れてコンパイルしてみましょう

check_hd_health.c
#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デバイス%%

以下の内容が出力されます

  1. ディスクのsleep状態 (active / sleep)
  2. ハードディスクの温度
  3. 通電時間
  4. ハードディスクの健康状態 (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 を取得する

get_errorcount.c
#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
7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8