Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

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

用意するもの

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

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

http://0pointer.de/public/libatasmart-0.19.tar.xz

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
suzutsuki0220
C言語の組み込み開発ばかりだった危機感から、NodeJS、electron、KotlinのAndroidアプリ開発など色々チャレンジ中、最近はTypeScriptを少しだけ触り始めた。エディタはvimからVS Codeに乗り換え中。今一番欲しいものRaspberry Pi。将来の夢は子供と一緒にログハウスを建てること
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away