お題:man.confのロードと解析結果のデータ構造を調査せよ
manのソースディレクトリについては前回、C言語系/「デーモン君のソース探検」読書メモ/13, man(1)とnroff(1) にて特定している。man.confのロードと解析については、
manconf.c manconf.h
の二つが担当している。
いきなりman.confに入る前に、前回の最後にも触れた"TAILQ_"で始まるマクロについて簡単に調べ、まとめておく。
"TAILQ_"で始まるマクロは双方向リストによるキュー処理をサポートし、"sys/queue.h" で定義されている。同ヘッダファイルにはこの他にも単方向リストや単方向リストによるキュー処理をサポートするマクロが定義されている。
"TAILQ_"マクロの使用例を以下に示す。
test.c:
#include <stdio.h> #include <stdlib.h> #include <sys/queue.h> typedef struct _entry { /* この中に双方向リスト用のポインタが展開される */ TAILQ_ENTRY(_entry) joint; int no; } ENTRY; /* 先頭専用の構造体 "_head" を定義する */ TAILQ_HEAD(_head, _entry); int main(int argc, char *argv[]) { /* 先頭要素 */ struct _head head; int i = 0; ENTRY *pentry; /* 先頭要素の初期化 */ TAILQ_INIT(&head); for (i = 1; i <= 10; i++) { pentry = (ENTRY*)malloc(sizeof(ENTRY)); /* エラー処理は省略 */ pentry->no = i; TAILQ_INSERT_TAIL(&head, pentry, joint); /* 末尾に追加 */ //TAILQ_INSERT_HEAD(&head, pentry, joint); /* 先頭に挿入 */ } /* 先頭から順に処理 */ TAILQ_FOREACH(pentry, &head, joint) { printf("no = %d\n", pentry->no); } printf("-------\n"); /* 末尾から逆順に処理 */ TAILQ_FOREACH_REVERSE(pentry, &head, _head, joint) { printf("no = %d\n", pentry->no); } return 0; }
コンパイル・実行結果:
$ cc -Wall test.c $ ./a.out no = 1 no = 2 ... no = 9 no = 10 ------- no = 10 no = 9 ... no = 2 no = 1
"TAILQ_"マクロの使い方を先に押さえておけば、manconf.{c|h}も読みやすくなる。
まずmanconf.hでの構造体宣言を見てみる。
typedef struct _tag { TAILQ_ENTRY(_tag) q; /* Queue of tags. */ TAILQ_HEAD(tqh, _entry) list; /* Queue of entries. */ char *s; /* Associated string. */ size_t len; /* Length of 's'. */ } TAG; typedef struct _entry { TAILQ_ENTRY(_entry) q; /* Queue of entries. */ char *s; /* Associated string. */ size_t len; /* Length of 's'. */ } ENTRY; TAILQ_HEAD(_head, _tag); extern struct _head head;
2段にネストしていて分かりづらい。まずTAGだけについて注目すると次のようになっている。
typedef struct _tag { TAILQ_ENTRY(_tag) q; /* Queue of tags. */ char *s; /* Associated string. */ size_t len; /* Length of 's'. */ } TAG; TAILQ_HEAD(_head, _tag); extern struct _head head;
前掲のサンプルコードと同様になっている。"TAG"というレコードは文字列ポインタとその長さで構成されており、先頭専用の構造体として"_head"がTAILQ_HEADマクロで生成され、先頭要素を外部公開シンボルとしてグローバルなhead変数で定義している。
続いてENTRYに注目してみる。
typedef struct _tag { TAILQ_HEAD(tqh, _entry) list; /* Queue of entries. */ } TAG; typedef struct _entry { TAILQ_ENTRY(_entry) q; /* Queue of entries. */ char *s; /* Associated string. */ size_t len; /* Length of 's'. */ } ENTRY;
ENTRY構造体はTAGと同様だが、先頭専用の構造体とその実体がTAGの内部に宣言されている。
これを一目で分かるようにした図が、manconf.cのconfig()関数のコメントに記されている。
struct _head head; /* * config -- * * Read the configuration file and build a doubly linked * list that looks like: * * tag1 <-> record <-> record <-> record * | * tag2 <-> record <-> record <-> record */ void config(fname) const char *fname; {
ENTRY(上記コメントでは"record")のリンクリストがTAGにぶら下がり、TAG自体もリンクリストに組み込まれる。
ここで "/etc/man.conf" を見てみると、下のように同じ"_XXXX"で始まる設定が複数行出現している。
_whatdb /usr/share/man/whatis.db ... _whatdb /usr/local/man/whatis.db _build .0.Z /usr/bin/zcat %s _build .0.gz /usr/bin/gunzip -c %s ... _crunch .Z /usr/bin/compress -c > %s _crunch .gz /usr/bin/gzip -c > %s
このことから、"_XXXX"に相当するのがTAG構造体で、同じTAGの各設定内容がENTRY構造体に格納されるのが容易に予想出来る。
manconf.cの各関数の詳細については、ここまでの予備知識があれば素直に読み解けるようになっているため、今回の読書メモでは割愛する。
TAG, ENTRYのリンク構造については「デーモン君のソース探検」にも分かりやすい図で説明されている。
結論としては、man.confの各行について、先頭カラムをTAGとし、後ろの内容をENTRYに格納し、TAGに複数のENTRYがぶら下がるリンクリストとして設定内容をロードしていることが判明した。
今回のお題については、ここまで。