Kompira開発者ブログ

Kompiraを日々の開発していくなかで気づいた点や調べたことなどを書いていきます。

PythonでHardware UUIDを取得する

ノードロックライセンス機能を実装するにあたって,ハードウェアUUIDを取得する必要があったため,少し調べてみました.

■ はじめに

いわゆるノードロックライセンスでは,コンピュータ固有のIDに紐づけてライセンスを発行するため,ハードウェアを識別するための何かが必要となります.そのための「何か」として簡単に思いつくものとしては,MACアドレスやIPアドレス,ホスト名などがあるかと思います.このうちIPアドレス,ホスト名は,簡単に取得することができますが,変更されやすい性質のものなので,今回の用途にはあまり向いていません.Pythonのuuidモジュールには,getnode()というハードウェアアドレス(MACアドレス)を取得する関数があるので,これを使うのが簡単な方法かもしれませんが,MACアドレスも,NICが故障するなどして交換したら変わってしまいます.

CPUID命令を用いて,CPU固有のシリアル番号(PSN)を取得し,これを利用するという手もありますが,調べてみたところ,PSNはPentium IIIにのみ実装されているあだ花的な機能なので,これも却下です.(ちなみに,PyCPUIDというパッケージを使えば,Pythonから簡単にCPUID命令の実行結果を取得することが可能です.)さらにいろいろ調べてみたところ(参考サイト[1]),ハードウェア固有のIDとして,Hardware UUIDが利用できそうなことが分かったので,今回はこれを利用しようと思います.

■ コマンドからのHardware UUID取得

Hardware UUIDは,dmidecodeコマンドを使って調べることが可能です.

# dmidecode | grep UUID

もしくは,hal-get-propertyコマンドを使って以下のようにしても取得できます.

$ hal-get-property --udi /org/freedesktop/Hal/devices/computer --key system.hardware.uuid

ただし,hal-get-propertyの実行には,messagebusとhaldaemonの各サービスが立ち上がっている必要があります.

# service messagebus start
# service haldaemon start

■ HALライブラリを用いてのUUID取得
さて,上記のコマンドを,Popenなどで,Pythonから実行して結果を取得するという方法でも良いのですが,できればライブラリ経由などで直接取得したいところです.参考サイト[1]を見ると,PythonからHALライブラリ(libhal)を呼び出すサンプルコードがあったので,今回はこれを利用することにしました.以下がその抜粋です.

import ctypes
from ctypes.util import find_library
from ctypes import Structure

class DBusError(Structure):
    _fields_ = [("name", ctypes.c_char_p),
    # ... 省略 ...

class HardwareUuid(object):

    # ... 省略 ...

    @property
    def _uuid(self):
        if not self._uuid_:
            udi = ctypes.c_char_p("/org/freedesktop/Hal/devices/computer")
            key = ctypes.c_char_p("system.hardware.uuid")
            self._hal.libhal_device_get_property_string.restype = \
                                                            ctypes.c_char_p
            self._uuid_ = self._hal.libhal_device_get_property_string(
                                self._ctx, udi, key, self._dbus_error)
        return self._uuid_

このPythonのコードは,ctypesというPythonの外部関数インターフェイス(FFI)用モジュールを使って,Cで実装されたライブラリ(libhal)の関数を呼び出しています.ただ,残念なことにこのコードをそのまま利用すると,セグメンテーションフォールトとなってしまいました.どうやらバグがあるようです.libhalのドキュメント(参考サイト[2],[3])をもとに上記コードを調べてみたところ,_uuidメソッド定義の最後の方にlibhal_device_get_property_stringを呼び出している部分で,DBusErrorオブジェクトを渡しているところがありますが,そこで必要なctypes.byrefが抜けているのが原因とわかりました.

最終的にはエラー処理のコードなどを追加して,以下のような形にしました.

import ctypes
from ctypes.util import find_library
from ctypes import Structure

class DBusRuntimeError(Exception): pass

class DBusError(Structure):
    _fields_ = [("name", ctypes.c_char_p),
                ("message", ctypes.c_char_p),
                ("dummy1", ctypes.c_int),
                ("dummy2", ctypes.c_int),
                ("dummy3", ctypes.c_int),
                ("dummy4", ctypes.c_int),
                ("dummy5", ctypes.c_int),
                ("padding1", ctypes.c_void_p),]


class HardwareUuid(object):

    def __init__(self, dbus_error=DBusError):
        self._hal = ctypes.cdll.LoadLibrary(find_library('hal'))
        self._dbus_error = dbus_error()
        self._hal.dbus_error_init(ctypes.byref(self._dbus_error))

        self._conn = self._hal.dbus_bus_get(ctypes.c_int(1),
                                            ctypes.byref(self._dbus_error))
        if self._hal.dbus_error_is_set(ctypes.byref(self._dbus_error)):
            emesg = "Unable to connect to DBus: %s" % self._dbus_error.message
            raise DBusRuntimeError(emesg)

        self._ctx = self._hal.libhal_ctx_new()
        if not self._hal.libhal_ctx_set_dbus_connection(self._ctx, self._conn):
            emesg = "Error: %s" % self._dbus_error.message
            raise DBusRuntimeError(emesg)

        if not self._hal.libhal_ctx_init(self._ctx, ctypes.byref(self._dbus_error)):
            emesg =  "Hal context initializing failure: %s" % self._dbus_error.message
            raise DBusRuntimeError(emesg)

        self._uuid_ = None

    def __call__(self):
        return self._uuid

    @property
    def _uuid(self):
        if not self._uuid_:
            udi = ctypes.c_char_p("/org/freedesktop/Hal/devices/computer")
            key = ctypes.c_char_p("system.hardware.uuid")
            self._hal.libhal_device_get_property_string.restype = ctypes.c_char_p
            self._uuid_ = self._hal.libhal_device_get_property_string(
                                self._ctx, udi, key, ctypes.byref(self._dbus_error))
            if self._hal.dbus_error_is_set(ctypes.byref(self._dbus_error)):
                emesg = "Error getting string property: %s" % self._dbus_error.message
                raise DBusRuntimeError(emesg)

        return self._uuid_

実行してみます。

>>> getuuid = HardwareUuid()
>>> getuuid()
'564D5934-8B4C-6465-D4B3-519D08DCC3C9'

めでたしめでたし.

■ 参考サイト

[1] http://stackoverflow.com/questions/2461141/get-a-unique-computer-id-in-python-on-windows-and-linux
[2] http://harmattan-dev.nokia.com/docs/platform-api-reference/showdoc.php?pkn=libhal&wb=daily-docs&url=Li94bWwvZGFpbHktZG9jcy9saWJoYWw%3D
[3] http://madison-project.wdfiles.com/local--files/tutorials/Hal_Tutorial.pdf