先日Apacheの設定をしていてふと気になったのですがApacheではモジュールが独自のディレクティブを定義することができます。モジュールを組み込まないで使えるディレクティブもモジュールと扱われている(coreモジュール)ようですがここら辺のモジュールとディレクティブの関係を中心にApacheの設定ファイル読み込みを見ていきたいと思います。
なお、今回対象としたバージョンは2.2.6です。
main関数はserver/main.cにあります。眺めた感じ、設定ファイル読み込みで重要そうなのは次の3つのようです。
ap_setup_prelinked_modules関数はconfig.cに書かれています。ap_setup_prelinked_modules関数ではap_prelinked_modulesに格納されているモジュールをセットアップしています。ap_prelinked_modulesはいつの間に定義されたんだ?と思ったらconfigureするとmodules.cというファイルができ、modules.cに定義されるようです。"./configure --enable-mods-shared=all"とした場合、以下のようになりました。
module *ap_preloaded_modules[] = { &core_module, &mpm_prefork_module, &http_module, &so_module, NULL };
modules.cはbuild/build-modules-c.awkにモジュール一覧を流すことで生成されるわけですが、configure.inにはモジュール一覧が格納されているMODLISTを設定するコードがありません。ていうかモジュールのenable/disableオプションを作成するコードもありません。これは読まないわけにはいかないでしょう:-)
500行目ぐらいに以下のコードがありました。
esyscmd(./build/config-stubs .)
build/config-stubsがカレント以下のconfig*.m4を取得して、*の部分の数字でソートして、sincludeを出力することでカレント以下のconfig*.m4がconfigure.inに取り込まれています。各config*.m4にモジュールのenable/disableオプションを作成するコードを書くことで生成物であるconfigureにモジュールのenable/disableオプションが作られることになるようです。configure.inもモジュール的なんですね:-)
それでは個々のモジュールをセットアップしているap_add_module関数に進みましょう。いろいろ設定を行った後、ap_add_module_commands関数を呼び出しています。
ap_add_module_commands関数を見ると、どうやらこの関数でモジュールが提供するディレクティブを登録しているようです。
参考としてcoreモジュールを眺めてみましょう。その前にinclude/http_config.hに書かれているmodule構造体とcommand_rec構造体の定義です。
typedef struct module_struct module; struct module_struct { int version; int minor_version; int module_index; const char *name; void *dynamic_load_handle; struct module_struct *next; unsigned long magic; void (*rewrite_args) (process_rec *process); void *(*create_dir_config) (apr_pool_t *p, char *dir); void *(*merge_dir_config) (apr_pool_t *p, void *base_conf, void *new_conf); void *(*create_server_config) (apr_pool_t *p, server_rec *s); void *(*merge_server_config) (apr_pool_t *p, void *base_conf, void *new_conf); const command_rec *cmds; void (*register_hooks) (apr_pool_t *p); };
typedef struct command_struct command_rec; struct command_struct { const char *name; cmd_func func; void *cmd_data; int req_override; enum cmd_how args_how; const char *errmsg; };
というわけでcommand_rec配列は後ろから2番目です。次にserver/core.cに書かれているcoreモジュールの定義です。数が合いませんがSTANDARD20_MODULE_STUFFが展開されるとversionからrewrite_argsまでになるようです。
AP_DECLARE_DATA module core_module = { STANDARD20_MODULE_STUFF, create_core_dir_config, /* create per-directory config structure */ merge_core_dir_configs, /* merge per-directory config structures */ create_core_server_config, /* create per-server config structure */ merge_core_server_configs, /* merge per-server config structures */ core_cmds, /* command apr_table_t */ register_hooks /* register hooks */ };
さらにcore_cmdsの定義(の抜粋)です。読むとおもしろそうなものをリストアップしました。ポイントは以下の2点です。処理関数を見るのは実際に呼ばれるときにしましょう。
static const command_rec core_cmds[] = { AP_INIT_RAW_ARGS("<Directory", dirsection, NULL, RSRC_CONF, "Container for directives affecting resources located in the specified " "directories"), AP_INIT_TAKE1("<IfModule", start_ifmod, NULL, EXEC_ON_READ | OR_ALL, "Container for directives based on existance of specified modules"), AP_INIT_TAKE1("DocumentRoot", set_document_root, NULL, RSRC_CONF, "Root directory of the document tree"), AP_INIT_RAW_ARGS("Options", set_options, NULL, OR_OPTIONS, "Set a number of attributes for a given directory"),
ap_add_module関数に戻るとap_register_hooks関数を呼んでモジュールのregister_hooks関数を実行しています。coreモジュールのregister_hooks関数を見るといろいろなフック(フックとはいうもののメイン処理だと思われますが)が登録されています。これらのフックも実際に呼ばれるときに見ることにしましょう。
ap_read_config関数ではまずinit_server_config関数を呼んでいます。init_server_config関数の中で呼ばれているcreate_server_config関数とcreate_default_per_dir_config関数のそれぞれで、モジュールに定義されている関数を呼び出しています。
続いて、process_command_config関数がap_server_pre_read_configを引数にして呼ばれています。-Cにより設定ファイル読み込み前に、-cにより設定ファイル読み込み後にディレクティブを指定できるようですがまあ普通やらないと思いますので飛ばします。
次にap_process_resource_config関数が呼ばれています。apr_fnmatch_test関数は設定ファイル指定がワイルドカードかを判断する関数のようです。ワイルドカードではないので*1process_resource_config_nofnmatch関数に進みましょう。
process_resource_config_nofnmatch関数でも設定ファイル指定がディレクトリの場合はディレクトリ内の各ファイルを引数にして再帰呼び出しを行っています。単一のファイル指定なのでそちらには進みません。ap_pcfg_openfile関数にて設定ファイルを開いているのでserver/util.cに書かれているap_pcfg_openfile関数を見てみるとgetch等が設定されています。どうやらap_configfile_tで一文字読み込みとかを抽象化することでコマンドライン引数とファイルを同様に扱っているようです。
それでは読み込み処理のメインらしいap_build_config関数に進みます。ap_cfg_getline関数で一行ずつ読み込んで(前後の空白はこの時点で省かれているようです)ap_build_config_sub関数を呼んでいます。
ap_build_config_sub関数ではまずap_getword_conf関数を呼び出してディレクティブ名の取りだし・引数位置の取得を行っています。ap_getword_conf関数は汎用的な単語取りだし関数のようでクォーテーションで囲まれた文字列の処理とかも行っています。
次にap_find_command_in_modules関数を呼び出してディレクティブ名に対応するディレクティブ情報を取り出し、EXEC_ON_READならexecute_now関数を呼び出しています。上で挙げたディレクティブの中では<IfModule>が該当するので見てみましょう。execute_now関数は改めてディレクティブ情報を取り出し、invoke_cmd関数に渡しています。invoke_cmd関数ではディレクティブがとる引数の数に応じて引数を取得し呼び出しを行っています。ちなみに、invoke_cmd関数の第3引数void *mconfigですがap_directive **sub_treeが渡されています。
<IfModule>の処理を行っているcore.cのstart_ifmod関数を見てみましょう。引数で指定されているモジュールが読み込まれているか*2チェックし、読み込まれていればap_build_cont_config関数を呼んでいます。ap_build_cont_config関数はap_build_config_sub関数を呼ぶことでコンテナの終わりまでのサブツリーを構築しています。なお、読み込まれていない場合はap_soak_end_container関数を呼んで読み込み時点でコンテナの内容を破棄しているようです。
話を1回目のap_build_config_sub関数呼び出しにもどしましょう。ディレクティブがEXEC_ON_READじゃなかった場合、コンテナの場合と単一のディレクティブの場合で最後の引数が違いますがutil_cfgtree.cに書かれているap_add_node関数を呼び出してツリーを構築しています。
ap_process_config_tree関数の前にap_run_pre_config関数が呼ばれています。この関数どこで定義されているのかと思ったらconfig.cの先頭の方の
AP_IMPLEMENT_HOOK_RUN_ALL(int, pre_config, (apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp), (pconf, plog, ptemp), OK, DECLINED)
のようです。AP_IMPLEMENT_HOOK_RUN_ALLマクロはinclude/ap_config.hで定義されています。また、ap_add_module関数の最後で呼んでいたregister_hooks関数で使われていたフック登録関数もこのマクロで作られているようです。つまり、各モジュールが定義しているハンドラを読んでいるということなようです。
ap_process_config_tree関数は初期化を行った上で、処理をap_walk_config関数に移しています。で、さらに各ディレクティブについてap_walk_config_sub関数を呼び出しています。
ap_walk_config_sub関数ではap_set_config_vectors関数を呼び出してディレクトリとサーバの設定を作った上でinvoke_cmd関数を呼び出しています。先ほどの設定ファイル読み込み中と違い、第3引数にはこのディレクトリとサーバの設定の組が渡されています。
設定ファイルの解析が終わったので後はリクエストを受け付けるポートを立ち上げるだけ、と思いきやまた設定ファイル解析処理を行っています。多分、SIGHUPで設定ファイルを読み直すためだと思うのですが、ap_mpm_runの後でいいような気がします。
今回はApacheの設定ファイル読み込み処理を読んでみました。感想としては、
といったところです。それではみなさんもよいコードリーディングを。次回はリクエスト処理を読みます。