システムコールを使って名前解決

ふと、C言語&システムコールの勉強をしたくなり、twitterで呟いてみたのですが、id:yuripopさんからreplyがありました。

c言語でプログラムをつくれるようになりたいなぁ。OSのシステムコールの扱いとか。
http://twitter.com/nattou_curry/statuses/968734682

@nattou_curry Cはほんとかじっただけで終わってしまいましたけど、自分で書いたシステムコール呼び出しが実際に動いてるのを見ると興奮したりします!
http://twitter.com/yuripop/statuses/968750897

@yuripop システムコールは実際にOS動かしてる感じしますもんね!(JavaAPIがうそぱちとは言わないけども)。勉強対象に入れようかと思い始めています。
http://twitter.com/nattou_curry/statuses/968768492

ということで、さっそくC言語でのシステムコールを勉強対象にしてみました。

環境

OS
Solaris10(SPARC)
コンパイラ
gcc (GCC) 3.4.3 (csl-sol210-3_4-branch+sol_rpath)

manでシステムコールを調べてみた。

googleで「solaris+システムコール」で調べたら、writeシステムコールが見つかりました。
まずは、writeのmanを調べてみました。

man -s 2 write

manでのセクション指定は-sなのかと思いつつ読んでいくけど、英語だし読むのが大変。

他にもいくつかのシステムコールのmanをみつつ、googleでも検索をしてみます。

Solarisのドキュメント発見。

[システムコール]わざわざ英語のmanを見なくても、公式な日本語ドキュメントがあった http://tinyurl.com/65jo2t
http://twitter.com/nattou_curry/statuses/968796801

そりゃありますよね、公式情報。
通信を試してみたくなったので、ソケットのところを調べてみます。

構成要素がとても細かい・・・とりあえずホスト名からIPアドレスへの名前解決でもやってみることに。
ちなみに、Javaではjava.net.Socketのコンストラクタにホスト名を渡せば勝手に名前解決してくれるので、あまり気にしない部分でした。

もう眠いので作成したソースを載せます(唐突)。

難しかったですよ・・・。
まず、データ型に不慣れなので、すぐ間違えます。
というか、理解しきってないので間違えているかどうかの判断も難しいです。


C言語では、代入や引数渡しで型が違えばコンパイルエラーになります。
でも、(特にポインタの)キャストをした時点でその恩恵はなくなってしまうようです。
そして、ソケットのシステムコールではポインタのキャストが多い感じです。
たとえば、汎用的なアドレスの型から、IPなどの特定のアドレスの型へのキャストなど。
Javaであれば、継承機構で同じことを表現しても、型チェックはきちんと働くところですが・・・。
C言語の難しさを認識させられました・・・慣れられるのでしょうか?


さて、以下がソースです。

#include 
#include 
#include 
#include 
#include 

int make_hints( struct addrinfo *hintsp );
int host_to_addr( const char* host, const char* port, struct addrinfo *hintsp, u_long* addr );
int addr_to_array( struct in_addr *addr, u_long* bak );

int main( int argc, char** argv ) {
        struct addrinfo hints;  // アドレスの選択基準
        u_long addr[4];         // アドレス
        char* host_def = "localhost";   // デフォルトホスト名
        char *port = "80";      // ポート番号

        // ホスト名
        char *host = ( argc > 1 ? argv[1]
                : host_def );

        // アドレスの選択基準を作成する。
        make_hints( &hints );

        // ホスト名からアドレスを取得する。
        host_to_addr( host, port, &hints, addr );

        // ホスト名とアドレスを表示する。
        printf( "%s -> %d.%d.%d.%d\n", host, addr[0], addr[1], addr[2], addr[3] );
}

/*
 * アドレスの選択基準を作成する。
 */
int make_hints( struct addrinfo *hintsp ) {
        memset( hintsp, 0, sizeof( struct addrinfo ) );
        hintsp->ai_family = AF_UNSPEC;
        hintsp->ai_socktype = SOCK_STREAM;

        return 1;
}

/*
 * ホスト名からアドレスを取得する。
 */
int host_to_addr( const char* host, const char* port, struct addrinfo *hintsp, u_long* back ) {
        struct addrinfo *aip;
        struct sockaddr_in *addr_inp;
        struct in_addr *in_addr;

        getaddrinfo( host, port, hintsp, &aip );
        in_addr = &( (struct sockaddr_in *) aip->ai_addr )->sin_addr;
        addr_to_array( in_addr, back );

        return 1;
}

/*
 * アドレスを配列にする。
 */
int addr_to_array( struct in_addr *addr, u_long* bak ) {
        u_long tmp;
        int i;

        tmp = addr->s_addr;
        for ( i = 3; tmp > 0; --i, tmp = tmp / 256 ) {
                bak[i] = tmp % 256;
        }

        return 1;
}

コンパイル時のオプション

Solaris10でソケットを使用するためには、gccに「-lsocket」オプションが必要なようです。

実行しました。

localhost -> 127.0.0.1
-bash-3.00$ nametoaddr localhost
localhost -> 127.0.0.1
-bash-3.00$ nametoaddr d.hatena.ne.jp
d.hatena.ne.jp -> 59.106.108.77

OKぽい!