放射状

勉強のメモ。

コードリーディング:ping 四日目

これまでのあらすじ~ なんか部下の中の人が普通に上から順にソースを読んでいる。苦戦している。あと、「このpingのオプションが全部掲載されている日本語のページをぐぐっても見つかりません」とか言っている。こ、こらー! なんのためにコンパイル方法を教えたのか。
 *部下の中の人と放射氏はフィクションであり、実在の人物・団体とは一切関係ありません。そういう体裁をとっておくと記事が書きやすいのです。そういうことにしておいてください。

ソースコードの読み方(放射氏流)

勉強のための読み方なので、割と深く読み込んでいきます。さっと把握したい場合には不向きです。また、pingのように小さいプログラムだから通じる手段だと思います。
基本的に、

  • コマンドに一切オプションをつけない、デフォルトの挙動ができる最低限のところまでソースコードを削る。
  • 最低限のソースコードを読み込んで把握する
  • オプションを一つずつ戻していく

という感じです。
今回はそのソースコードの削り方を説明します。

何をどこまで削るか

centos上でpingをオプションを指定せずに実行したときとまったく同じ挙動を得るために必要なソースコード以外の部分を削ります。
↓たとえばこれが基準です。

[root@localhost ~]# ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=255 time=1.00 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=255 time=1.79 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=255 time=1.23 ms

削った後で以下のようにコンパイルして、こんな感じで同じように動けばOKということです。

[root@localhost iputils-s20121221]# gcc ping.c ping_common.c
[root@localhost iputils-s20121221]# ./a.out 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=255 time=1.49 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=255 time=1.91 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=255 time=1.90 ms
その1:#ifdefとか#ifndefなところを削る

プリプロセッサ、嫌いですね。読み辛いもんね。ないと困るんだけどね。古いソースコードのメンテナンスの仕事でこれの#ifndefの嵐を見て「*ね!!!!!!!!」とか思ったりしますよね! なのでまず最初にこれを削ります。

#ifdef USE_IDN
	setlocale(LC_ALL, "");
#endif

こういうの。IDNは国際ドメイン名のことですが、そんなもん使わなくてもipを直接指定すればpingコマンドは動作するので削ります。

#ifdef CAPABILITIES
static cap_value_t cap_raw = CAP_NET_RAW;
static cap_value_t cap_admin = CAP_NET_ADMIN;
#endif

ケイパビリティーとか、いつか今度勉強しようと思うので、今回はいいです。削ります。

#ifndef WITHOUT_IFADDRS

なにやってるかよくわかんねーけど削っても動くので削ります。

という感じで、最初の慣れないうちは一つ削るたびにコンパイルして動作を確認しながらやっていくといいと思います。
たまに削ったらコンパイル通らなくなった! というのもあるので↓ そういうときは元に戻しましょう。

#ifndef ICMP_FILTER
#define ICMP_FILTER	1
struct icmp_filter {
	//__u32 : 32bit integer type
	__u32	data;
};
#endif

ここまで削ったソースコードここにサンプルとしておいてきます。

その2:オプションに関係するソースコードを削る

オプションまで把握するなんて最初は難しいので削ります。

int
main(int argc, char **argv)

とあるので、「argv」とかで検索してそれっぽいところを削ります。また、ping_common.hで「options」といういかにもな変数がexternされているので、それも検索してそれっぽいところを削ります。
例えば、

while ((ch = getopt(argc, argv, COMMON_OPTSTR "bRT:")) != EOF) {

いかにもオプションの処理しかしてないので削ります。

if ((options&F_STRICTSOURCE) &&
    bind(icmp_sock, (struct sockaddr*)&source, sizeof(source)) == -1) {
	perror("bind");
	exit(2);
}

削ります。

if (device || (options&F_STRICTSOURCE))
	printf("from %s %s: ", inet_ntoa(source.sin_addr), device ?: "");

これはだめ。

#ifdefのときと同じように、最初は一回ずつコンパイルして動作確認するといいと思います。変なところを消すと改行が消えたりIPアドレスが表示されなくなったりします。
ここまで削ったものをここに置いておきます。

その3:これぐらいでよいのではないでしょうか

ここまで削ったらソースコードの分量はかなり減ったはずです。mainも読みやすくなったはずです。
次回はそこからの読み方について書きます。

「man ping」のオプションの部分ぐらいは日本語訳して書いておこうと思ったけれど

あと15分でぷよクエのガチャピン祭りボーナスタイムが終わってしまうので、次回にします。
英語読めないとオープンソース系は詰むので読めるようになってください。>部下の中の人 人のことが言えるほど自分も読めるわけではないですが。