2015年7月16日木曜日

httpd-2.4.16 Released

Apache HTTP Server 2.4.16 がリリースアナウンスされていた。
リリースアナウンスを見ると、


脆弱性の修正が4件あげられている。
例によって無理やり訳してみた。

修正された脆弱性


CVE-2015-3183

  • チャンクヘッダ構文解析の欠陥を修正した。
    HTTP_INフィルタにあったバッファへの読み込み、複製を行うコード、apr_brigade_flatten()を削除した。
    チャンクを単一の経路で複製なしで構文解析するようにした。
    受け入れ可能なチャンクサイズを2^63-1に限定した。
    そして、chunk-extの認定済み文字集合を厳密に適用した。
  • http://svn.apache.org/viewvc?view=revision&revision=1684515
    • どういう性格の脆弱性なのか、よく分からない。
      今後脆弱性情報のページなどに情報が補完されるのだろうか。
      現時点での印象としては、不正に細工したchunkedデータを使って
      サーバに過剰にメモリを消費させることができるとかそういったことを想像している。
    • (2015/7/21追記)
    • http://httpd.apache.org/security/vulnerabilities_24.html#2.4.16
      • HTTPリクエストsmuggling攻撃が可能だった。
        悪意のあるクライアントはサーバにリクエスト長を間違って解釈をさせることができた。
        もし中継にプロキシが利用されている場合に、これにより、キャッシュ汚染やクレデンシャルハイジャックが可能だった。

CVE-2015-3185

  • ap_some_auth_required(Apache httpd 2.4では利用されていない関数)を新しい ap_some_authn_required関数とforce_authnフックに置き換えた。
  • http://svn.apache.org/viewvc?view=revision&revision=1684525
    • これもどういう脆弱性なのかがよく分からない。
      認証を回避できるとか言ったことかもしれない。


CVE-2015-0253



CVE-2015-0228



これ以外の主な修正内容

  • SSLCipherSuiteとSSLProxyCipherSuiteのデフォルト設定を改善した。
  • ProxySCGIInternalRedirectは、アプリケーションによって扱われる代替のレスポンスヘッダを指定できる。
  • event MPMの改善
  • 各種のmod_proxy_*モジュールの改善。
  • ログ出力の形式に "%{UNIT}T" を追加。UNITに指定した時間単位(秒(s)、ミリ秒(ms)、マイクロ秒(us))でリクエスト処理時間を出力できる。





2015年3月9日月曜日

Headersモジュール: Header ディレクティブの処理対象レスポンスヘッダテーブル

ちょっと、調べたことをメモする。

参考: http://httpd.apache.org/docs/2.4/en/mod/mod_headers.html#header

Headerディレクティブは指定子の直後に、省略可能なパラメータとしてconditionを取ることになっている。
conditiononsuccessalwaysを指定できる。
省略した場合はonsuccessを指定したことになる。

これは、マニュアルに書かれている通り、レスポンスヘッダ情報を持つ内部テーブルを指定する。
内部テーブルは2つあってrequest_recの持つテーブルだ。


(httpd-2.4.9/include/httpd.h)

    781 struct request_rec {
    :
    911     /** MIME header environment for the response */
    912     apr_table_t *headers_out;
    913     /** MIME header environment for the response, printed even on errors and
    914      * persist across internal redirects */
    915     apr_table_t *err_headers_out;
    :

condition毎の処理対象のテーブルは以下の通りの対応になっている。

onsuccess headers_out
always err_headers_out

厄介なことに、"成功時"、"常時"、といった意味のconditionのパラメータになっているが、
必ずしも、その字義通りの条件で機能しない。
私の把握する限りでは、CGIスクリプトの返すレスポンスヘッダの多くは、err_headers_outテーブルにセットされる(Locationや、Content-Lengthなどheaders_outにセットされるものもある)。
mod_proxy_ajpから返されるレスポンスヘッダのほとんどは、逆にheaders_outにセットされる。

つまり mod_proxy_ajpで繋いだ先のTomcatが
X-RESPONSE-HEADER: this is test
というレスポンスを返した場合、このヘッダ情報は、headers_outに格納されることになる。

もし、
Header onsuccess append X-RESPONSE-HEADER "set at Apache"
と指定すると、最終的なレスポンスヘッダは、
X-RESPONSE-HEADER: this is test, set at Apache
となる。

これは、headers_outテーブルに対して存在するX-RESPONSE-HEADERヘッダ情報に対して処理が行われたためだ。

他方、同じヘッダをmod_cgid経由で実行されるCGIスクリプトが返した場合、
レスポンスのヘッダは、
X-RESPONSE-HEADER: this is test
X-RESPONSE-HEADER: set at Apache
と2行になってしまう。

CGIスクリプトから返されたヘッダは、err_headers_outテーブルに格納されるが、
一方、Headersディレクティブは、headers_outテーブルにappendが指示されている。
そのため、結局2つのテーブルに同じヘッダが作成されてしまうことになってしまう。
そして、最後にレスポンスを返す時、この2つのテーブルがマージされるためにこうなってしまっている。

もし逆に、
Header always append X-RESPONSE-HEADER "set at Apache"
とalwaysを指定した場合、
mod_proxy_ajp経由だと、次のようなヘッダになる。
X-RESPONSE-HEADER: set at Apache
X-RESPONSE-HEADER: this is test
mod_cgid経由だと、次のようなヘッダになる。
X-RESPONSE-HEADER: this is test, set at Apache

onsuccessの結果が分かれば、こちらは予想通りだろう。
これは、httpd-2.2系でも同じだ。


2015年2月2日月曜日

httpd-2.4.12 Release

httpd-2.4.12がリリースされた。
2.4.11はキャンセルされた。

次の脆弱性が修正されている。

  • CVE-2014-3583 mod_proxy_fcgi
    • レスポンスヘッダのサイズが8Kバイトを超えた場合に、バッファオーバリードによってクラッシュする可能性があった問題を修正
      • バックエンドからのデータの読み込みで、\0終端ではなくデータ長で終了判定をするように修正されている。そういうことでしょう。
    • http://svn.apache.org/viewvc?view=revision&revision=1641551
  • CVE-2014-3581 mod_cache
    • Content-Typeの値が空の場合のクラッシュを回避
      • 下記bugzillaによれば、レスポンスのContent-Typeが空文字列のコンテンツをキャッシュしようとするとsegmentation faultが発生するようだ。
      • そういったレスポンスを生成する環境があるらしいが、悪意のあるサーバが意図的に生成するケースも想定はできる。
    • https://issues.apache.org/bugzilla/show_bug.cgi?id=56924
  • CVE-2014-8109 mod_lua
    • LuaAuthzProviderが複数のRequireディレクティブで異なる引数で使用されている場合のRequire行の処理を修正
      • 下記bugzillaの指定例だけ引用する
        <Directory /var/www/example.com/webroot/dir/ >
          Require LuaAuth 2
        </Directory>
        <Directory /var/www/example.com/webroot/ >
          Require LuaAuth 1
        </Directory>


  • CVE-2013-5704 core
    • HTTPトレーラがリクエスト処理の遅いタイミングでHTTPヘッダを置き換えるため、リクエストヘッダを早いタイミングで検査したり、修正するようなモジュールの処理の無効化や、混乱を引き起こすことが可能。
      旧方式によるリストアを指示するMergeTrailersディレクティブを追加。
      • 例えば、mod_headersでRangeヘッダが大量に指定されている場合にヘッダを削除している場合でも、あとからRangeヘッダを復活させることができたりするようだ。
    • http://svn.apache.org/viewvc?view=revision&revision=1619884
      • httpd-2.2.29で修正済
    • http://martin.swende.se/blog/HTTPChunked.html


その他多数のバグ修正に、機能追加。
以下にannounceからのコピペだけ。

  • Proxy FGI and websockets improvements
  • Proxy capability via handler
  • Finer control over scoping of RewriteRules
  • Unix Domain Socket (UDS) support for mod_proxy backends.
  • Support for larger shared memory sizes for mod_socache_shmcb
  • mod_lua and mod_ssl enhancements
  • Support named groups and backreferences within the LocationMatch, DirectoryMatch, FilesMatch and ProxyMatch directives.


以上

2015年1月26日月曜日

input/output filter: ローカルファイルを返す場合の構成

最小限の機能によるリクエスト処理として、/index.htmlにアクセスした場合にリクエストとレスポンスがたどる入出力フィルタの構成をみる。

(1)フィルタ構成の確認

ここではgdbでリクエスト処理を追って確認した。
(他にいい方法があるだろうか)

入力フィルタの確認のため、ap_get_brigadeにbreakpointを設定する。

(gdb) break ap_get_brigade
Breakpoint 1 at 0x4320b0: file util_filter.c, line 552.

出力フィルタの確認のため、ap_pass_brigadeにもbreakpointを設定する。

(gdb) break ap_pass_brigade
Breakpoint 2 at 0x4320d0: file util_filter.c, line 567.

省力化のため、backtraceと、フィルタ情報を出力して処理を継続するマクロを用意しておいた。

(gdb) define pinfo
Type commands for definition of "pinfo".
End with a line saying just "end".
>bt
>print *next->frec
>conti
>end

そして、curlコマンドで/index.htmlへのリクエストを発行した。

(1.1) read_request_line

  • リクエスト行の読み込み処理
  • 入力フィルタ
    • CORE_INフィルタ(COREモジュール)
      • AP_MODE_GETLINEモード
      • APR_BLOCK_READ読込モード

Breakpoint 1, ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9ac00baa8, mode=AP_MODE_GETLINE, block=APR_BLOCK_READ, readbytes=0) at util_filter.c:552
552         if (next) {
(gdb) pinfo
#0  ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9ac00baa8, mode=AP_MODE_GETLINE, block=APR_BLOCK_READ, readbytes=0) at util_filter.c:552
#1  0x0000000000435ef2 in ap_rgetline_core (s=0x7fa9ac00a9e0, n=8192, read=0x7fa9ba00cd10, r=0x7fa9ac00a9b0, fold=0, bb=0x7fa9ac00baa8) at protocol.c:229
#2  0x0000000000436d1e in read_request_line (conn=0x7fa9b400dbe0) at protocol.c:590
#3  ap_read_request (conn=0x7fa9b400dbe0) at protocol.c:956
#4  0x000000000045cff9 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:135
#5  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#6  0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#7  0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#8  worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#9  0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#10 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$37 = {name = 0x1370420 "core_in", filter_func = {out_func = 0x446790 , in_func = 0x446790 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_NETWORK, debug = 0, proto_flags = 0}

(1.2) ap_get_mime_headers_core(1)

  • リクエストヘッダ行読み込み処理。
    • 1行ごとの処理なので、ヘッダ行の数だけ処理が行われる。
  • 入力フィルタ:
    • CORE_INフィルタ(COREモジュール)
      • AP_MODE_GETLINE
      • APR_BLOCK_READ

Breakpoint 1, ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9ac00baa8, mode=AP_MODE_GETLINE, block=APR_BLOCK_READ, readbytes=0) at util_filter.c:552
552         if (next) {
(gdb)
#0  ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9ac00baa8, mode=AP_MODE_GETLINE, block=APR_BLOCK_READ, readbytes=0) at util_filter.c:552
#1  0x0000000000435ef2 in ap_rgetline_core (s=0x7fa9ba00cc98, n=8192, read=0x7fa9ba00cc90, r=0x7fa9ac00a9b0, fold=0, bb=0x7fa9ac00baa8) at protocol.c:229
#2  0x00000000004362e7 in ap_get_mime_headers_core (r=0x7fa9ac00a9b0, bb=0x7fa9ac00baa8) at protocol.c:713
#3  0x000000000043738b in ap_read_request (conn=0x7fa9b400dbe0) at protocol.c:1002
#4  0x000000000045cff9 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:135
#5  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#6  0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#7  0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#8  worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#9  0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#10 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$38 = {name = 0x1370420 "core_in", filter_func = {out_func = 0x446790 , in_func = 0x446790 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_NETWORK, debug = 0, proto_flags = 0}

(1.3) ap_get_mime_headers_core(2)

  • リクエストヘッダ行読み込み処理。
    • 1行ごとの処理なので、ヘッダ行の数だけ処理が行われる。
  • 入力フィルタ:
    • CORE_INフィルタ(COREモジュール)
      • AP_MODE_GETLINE
      • APR_BLOCK_READ

Breakpoint 1, ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9ac00baa8, mode=AP_MODE_GETLINE, block=APR_BLOCK_READ, readbytes=0) at util_filter.c:552
552         if (next) {
(gdb)
#0  ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9ac00baa8, mode=AP_MODE_GETLINE, block=APR_BLOCK_READ, readbytes=0) at util_filter.c:552
#1  0x0000000000435ef2 in ap_rgetline_core (s=0x7fa9ba00cc98, n=8192, read=0x7fa9ba00cc90, r=0x7fa9ac00a9b0, fold=0, bb=0x7fa9ac00baa8) at protocol.c:229
#2  0x00000000004362e7 in ap_get_mime_headers_core (r=0x7fa9ac00a9b0, bb=0x7fa9ac00baa8) at protocol.c:713
#3  0x000000000043738b in ap_read_request (conn=0x7fa9b400dbe0) at protocol.c:1002
#4  0x000000000045cff9 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:135
#5  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#6  0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#7  0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#8  worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#9  0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#10 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$39 = {name = 0x1370420 "core_in", filter_func = {out_func = 0x446790 , in_func = 0x446790 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_NETWORK, debug = 0, proto_flags = 0}

(1.4) ap_get_mime_headers_core(3)


  • リクエストヘッダ行読み込み処理。
    • 1行ごとの処理なので、ヘッダ行の数だけ処理が行われる。
  • 入力フィルタ:
    • CORE_INフィルタ(COREモジュール)
      • AP_MODE_GETLINE
      • APR_BLOCK_READ


Breakpoint 1, ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9ac00baa8, mode=AP_MODE_GETLINE, block=APR_BLOCK_READ, readbytes=0) at util_filter.c:552
552         if (next) {
(gdb)
#0  ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9ac00baa8, mode=AP_MODE_GETLINE, block=APR_BLOCK_READ, readbytes=0) at util_filter.c:552
#1  0x0000000000435ef2 in ap_rgetline_core (s=0x7fa9ba00cc98, n=8192, read=0x7fa9ba00cc90, r=0x7fa9ac00a9b0, fold=0, bb=0x7fa9ac00baa8) at protocol.c:229
#2  0x00000000004362e7 in ap_get_mime_headers_core (r=0x7fa9ac00a9b0, bb=0x7fa9ac00baa8) at protocol.c:713
#3  0x000000000043738b in ap_read_request (conn=0x7fa9b400dbe0) at protocol.c:1002
#4  0x000000000045cff9 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:135
#5  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#6  0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#7  0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#8  worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#9  0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#10 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$40 = {name = 0x1370420 "core_in", filter_func = {out_func = 0x446790 , in_func = 0x446790 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_NETWORK, debug = 0, proto_flags = 0}

(1.5) ap_get_mime_headers_core(4)


  • リクエストヘッダ行読み込み処理。
    • 1行ごとの処理なので、ヘッダ行の数だけ処理が行われる。
    • 最後は空行になる。
  • 入力フィルタ:
    • CORE_INフィルタ(COREモジュール)
      • AP_MODE_GETLINE
      • APR_BLOCK_READ


Breakpoint 1, ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9ac00baa8, mode=AP_MODE_GETLINE, block=APR_BLOCK_READ, readbytes=0) at util_filter.c:552
552         if (next) {
(gdb)
#0  ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9ac00baa8, mode=AP_MODE_GETLINE, block=APR_BLOCK_READ, readbytes=0) at util_filter.c:552
#1  0x0000000000435ef2 in ap_rgetline_core (s=0x7fa9ba00cc98, n=8192, read=0x7fa9ba00cc90, r=0x7fa9ac00a9b0, fold=0, bb=0x7fa9ac00baa8) at protocol.c:229
#2  0x00000000004362e7 in ap_get_mime_headers_core (r=0x7fa9ac00a9b0, bb=0x7fa9ac00baa8) at protocol.c:713
#3  0x000000000043738b in ap_read_request (conn=0x7fa9b400dbe0) at protocol.c:1002
#4  0x000000000045cff9 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:135
#5  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#6  0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#7  0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#8  worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#9  0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#10 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$41 = {name = 0x1370420 "core_in", filter_func = {out_func = 0x446790 , in_func = 0x446790 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_NETWORK, debug = 0, proto_flags = 0}

(1.6) default_handler: ap_discard_request_body


  • リクエストボディの読み捨て
  • 入力フィルタ:
    • HTTP_INフィルタ(HTTPモジュール)
      • AP_MODE_READBYTES
      • APR_BLOCK_READ


Breakpoint 1, ap_get_brigade (next=0x7fa9ac00bc48, bb=0x7fa9ac00c378, mode=AP_MODE_READBYTES, block=APR_BLOCK_READ, readbytes=8192) at util_filter.c:552
552         if (next) {
(gdb)
#0  ap_get_brigade (next=0x7fa9ac00bc48, bb=0x7fa9ac00c378, mode=AP_MODE_READBYTES, block=APR_BLOCK_READ, readbytes=8192) at util_filter.c:552
#1  0x00000000004616e0 in ap_discard_request_body (r=0x7fa9ac00a9b0) at http_filters.c:1418
#2  0x000000000043d915 in default_handler (r=0x7fa9ac00a9b0) at core.c:4277
#3  0x000000000044a1b0 in ap_run_handler (r=0x7fa9ac00a9b0) at config.c:170
#4  0x000000000044e33e in ap_invoke_handler (r=0x7fa9ac00a9b0) at config.c:439
#5  0x000000000046099a in ap_process_async_request (r=0x7fa9ac00a9b0) at http_request.c:317
#6  0x000000000045d070 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:143
#7  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#8  0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#9  0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#10 worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#11 0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#12 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$42 = {name = 0x1396b98 "http_in", filter_func = {out_func = 0x463410 , in_func = 0x463410 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_PROTOCOL, debug = 0, proto_flags = 0}

(1.7) default_handler


  • 出力フィルタ:
    • byterange出力フィルタ(HTTPモジュール)


Breakpoint 2, ap_pass_brigade (next=0x7fa9ac00ba08, bb=0x7fa9ac00c598) at util_filter.c:567
567         if (next) {
(gdb)
#0  ap_pass_brigade (next=0x7fa9ac00ba08, bb=0x7fa9ac00c598) at util_filter.c:567
#1  0x000000000043de27 in default_handler (r=0x7fa9ac00a9b0) at core.c:4369
#2  0x000000000044a1b0 in ap_run_handler (r=0x7fa9ac00a9b0) at config.c:170
#3  0x000000000044e33e in ap_invoke_handler (r=0x7fa9ac00a9b0) at config.c:439
#4  0x000000000046099a in ap_process_async_request (r=0x7fa9ac00a9b0) at http_request.c:317
#5  0x000000000045d070 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:143
#6  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#7  0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#8  0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#9  worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#10 0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#11 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$43 = {name = 0x1397878 "byterange", filter_func = {out_func = 0x464c80 , in_func = 0x464c80 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_PROTOCOL, debug = 0, proto_flags = 0}

(1.8) default_handler: ap_byterange_filter


  • 出力フィルタ:
    • content_length出力フィルタ(COREモジュール)


Breakpoint 2, ap_pass_brigade (next=0x7fa9ac00ba30, bb=0x7fa9ac00c598) at util_filter.c:567
567         if (next) {
(gdb)
#0  ap_pass_brigade (next=0x7fa9ac00ba30, bb=0x7fa9ac00c598) at util_filter.c:567
#1  0x0000000000464dc2 in ap_byterange_filter (f=0x7fa9ac00ba08, bb=0x7fa9ac00c598) at byterange_filter.c:496
#2  0x000000000043de27 in default_handler (r=0x7fa9ac00a9b0) at core.c:4369
#3  0x000000000044a1b0 in ap_run_handler (r=0x7fa9ac00a9b0) at config.c:170
#4  0x000000000044e33e in ap_invoke_handler (r=0x7fa9ac00a9b0) at config.c:439
#5  0x000000000046099a in ap_process_async_request (r=0x7fa9ac00a9b0) at http_request.c:317
#6  0x000000000045d070 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:143
#7  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#8  0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#9  0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#10 worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#11 0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#12 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$44 = {name = 0x1370740 "content_length", filter_func = {out_func = 0x437df0 , in_func = 0x437df0 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_PROTOCOL, debug = 0, proto_flags = 0}

(1.9) default_handler: ap_content_length_filter


  • 出力フィルタ:
    • http_header出力フィルタ(HTTPモジュール)


Breakpoint 2, ap_pass_brigade (next=0x7fa9ac00ba58, bb=0x7fa9ac00c598) at util_filter.c:567
567         if (next) {
(gdb)
#0  ap_pass_brigade (next=0x7fa9ac00ba58, bb=0x7fa9ac00c598) at util_filter.c:567
#1  0x0000000000437f7a in ap_content_length_filter (f=0x7fa9ac00ba30, b=0x7fa9ac00c598) at protocol.c:1421
#2  0x0000000000464dc2 in ap_byterange_filter (f=0x7fa9ac00ba08, bb=0x7fa9ac00c598) at byterange_filter.c:496
#3  0x000000000043de27 in default_handler (r=0x7fa9ac00a9b0) at core.c:4369
#4  0x000000000044a1b0 in ap_run_handler (r=0x7fa9ac00a9b0) at config.c:170
#5  0x000000000044e33e in ap_invoke_handler (r=0x7fa9ac00a9b0) at config.c:439
#6  0x000000000046099a in ap_process_async_request (r=0x7fa9ac00a9b0) at http_request.c:317
#7  0x000000000045d070 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:143
#8  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#9  0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#10 0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#11 worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#12 0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#13 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$45 = {name = 0x1396e60 "http_header", filter_func = {out_func = 0x462450 , in_func = 0x462450 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_PROTOCOL, debug = 0, proto_flags = 0}

(1.10) default_handler: ap_http_header_filter


  • レスポンスヘッダ処理
  • 出力フィルタ:
    • http_outerror出力フィルタ(HTTPモジュール)


Breakpoint 2, ap_pass_brigade (next=0x7fa9ac00ba80, bb=0x7fa9ac00c690) at util_filter.c:567
567         if (next) {
(gdb)
#0  ap_pass_brigade (next=0x7fa9ac00ba80, bb=0x7fa9ac00c690) at util_filter.c:567
#1  0x000000000046270d in ap_http_header_filter (f=, b=) at http_filters.c:1356
#2  0x0000000000437f7a in ap_content_length_filter (f=0x7fa9ac00ba30, b=0x7fa9ac00c598) at protocol.c:1421
#3  0x0000000000464dc2 in ap_byterange_filter (f=0x7fa9ac00ba08, bb=0x7fa9ac00c598) at byterange_filter.c:496
#4  0x000000000043de27 in default_handler (r=0x7fa9ac00a9b0) at core.c:4369
#5  0x000000000044a1b0 in ap_run_handler (r=0x7fa9ac00a9b0) at config.c:170
#6  0x000000000044e33e in ap_invoke_handler (r=0x7fa9ac00a9b0) at config.c:439
#7  0x000000000046099a in ap_process_async_request (r=0x7fa9ac00a9b0) at http_request.c:317
#8  0x000000000045d070 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:143
#9  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#10 0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#11 0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#12 worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#13 0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#14 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$46 = {name = 0x1397550 "http_outerror", filter_func = {out_func = 0x460d10 , in_func = 0x460d10 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_PROTOCOL, debug = 0, proto_flags = 0}

(1.11) default_handler: ap_http_header_filter


  • レスポンスヘッダ処理
  • 出力フィルタ:
    • core出力フィルタ


Breakpoint 2, ap_pass_brigade (next=0x7fa9b400df48, bb=0x7fa9ac00c690) at util_filter.c:567
567         if (next) {
(gdb)
#0  ap_pass_brigade (next=0x7fa9b400df48, bb=0x7fa9ac00c690) at util_filter.c:567
#1  0x000000000046270d in ap_http_header_filter (f=, b=) at http_filters.c:1356
#2  0x0000000000437f7a in ap_content_length_filter (f=0x7fa9ac00ba30, b=0x7fa9ac00c598) at protocol.c:1421
#3  0x0000000000464dc2 in ap_byterange_filter (f=0x7fa9ac00ba08, bb=0x7fa9ac00c598) at byterange_filter.c:496
#4  0x000000000043de27 in default_handler (r=0x7fa9ac00a9b0) at core.c:4369
#5  0x000000000044a1b0 in ap_run_handler (r=0x7fa9ac00a9b0) at config.c:170
#6  0x000000000044e33e in ap_invoke_handler (r=0x7fa9ac00a9b0) at config.c:439
#7  0x000000000046099a in ap_process_async_request (r=0x7fa9ac00a9b0) at http_request.c:317
#8  0x000000000045d070 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:143
#9  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#10 0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#11 0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#12 worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#13 0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#14 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$47 = {name = 0x1370c78 "core", filter_func = {out_func = 0x445f90 , in_func = 0x445f90 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_NETWORK, debug = 0, proto_flags = 0}

(1.12) default_handler: ap_http_header_filter


  • レスポンスボディ処理
  • 出力フィルタ:
    • http_outerror出力フィルタ(HTTPモジュール)


Breakpoint 2, ap_pass_brigade (next=0x7fa9ac00ba80, bb=0x7fa9ac00c598) at util_filter.c:567
567         if (next) {
(gdb)
#0  ap_pass_brigade (next=0x7fa9ac00ba80, bb=0x7fa9ac00c598) at util_filter.c:567
#1  0x00000000004624f3 in ap_http_header_filter (f=, b=) at http_filters.c:1379
#2  0x0000000000437f7a in ap_content_length_filter (f=0x7fa9ac00ba30, b=0x7fa9ac00c598) at protocol.c:1421
#3  0x0000000000464dc2 in ap_byterange_filter (f=0x7fa9ac00ba08, bb=0x7fa9ac00c598) at byterange_filter.c:496
#4  0x000000000043de27 in default_handler (r=0x7fa9ac00a9b0) at core.c:4369
#5  0x000000000044a1b0 in ap_run_handler (r=0x7fa9ac00a9b0) at config.c:170
#6  0x000000000044e33e in ap_invoke_handler (r=0x7fa9ac00a9b0) at config.c:439
#7  0x000000000046099a in ap_process_async_request (r=0x7fa9ac00a9b0) at http_request.c:317
#8  0x000000000045d070 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:143
#9  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#10 0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#11 0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#12 worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#13 0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#14 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$48 = {name = 0x1397550 "http_outerror", filter_func = {out_func = 0x460d10 , in_func = 0x460d10 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_PROTOCOL, debug = 0, proto_flags = 0}

(1.13) default_handler: ap_http_header_filter


  • レスポンスボディ処理
  • 出力フィルタ:
    • core出力フィルタ(COREモジュール)


Breakpoint 2, ap_pass_brigade (next=0x7fa9b400df48, bb=0x7fa9ac00c598) at util_filter.c:567
567         if (next) {
(gdb)
#0  ap_pass_brigade (next=0x7fa9b400df48, bb=0x7fa9ac00c598) at util_filter.c:567
#1  0x00000000004624f3 in ap_http_header_filter (f=, b=) at http_filters.c:1379
#2  0x0000000000437f7a in ap_content_length_filter (f=0x7fa9ac00ba30, b=0x7fa9ac00c598) at protocol.c:1421
#3  0x0000000000464dc2 in ap_byterange_filter (f=0x7fa9ac00ba08, bb=0x7fa9ac00c598) at byterange_filter.c:496
#4  0x000000000043de27 in default_handler (r=0x7fa9ac00a9b0) at core.c:4369
#5  0x000000000044a1b0 in ap_run_handler (r=0x7fa9ac00a9b0) at config.c:170
#6  0x000000000044e33e in ap_invoke_handler (r=0x7fa9ac00a9b0) at config.c:439
#7  0x000000000046099a in ap_process_async_request (r=0x7fa9ac00a9b0) at http_request.c:317
#8  0x000000000045d070 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:143
#9  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#10 0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#11 0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#12 worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#13 0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#14 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$49 = {name = 0x1370c78 "core", filter_func = {out_func = 0x445f90 , in_func = 0x445f90 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_NETWORK, debug = 0, proto_flags = 0}

(1.14) ap_finalize_request_protocol


  • リクエストボディ読み捨て
  • 入力フィルタ:
    • http_inフィルタ
      • AP_MODE_READBYTES
      • APR_BLOCK_READ


Breakpoint 1, ap_get_brigade (next=0x7fa9ac00bc48, bb=0x7fa9ac00c838, mode=AP_MODE_READBYTES, block=APR_BLOCK_READ, readbytes=8192) at util_filter.c:552
552         if (next) {
(gdb)
#0  ap_get_brigade (next=0x7fa9ac00bc48, bb=0x7fa9ac00c838, mode=AP_MODE_READBYTES, block=APR_BLOCK_READ, readbytes=8192) at util_filter.c:552
#1  0x00000000004616e0 in ap_discard_request_body (r=0x7fa9ac00a9b0) at http_filters.c:1418
#2  0x0000000000435879 in ap_finalize_request_protocol (r=0x7fa9ac00a9b0) at protocol.c:1235
#3  0x0000000000460808 in ap_process_async_request (r=0x7fa9ac00a9b0) at http_request.c:346
#4  0x000000000045d070 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:143
#5  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#6  0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#7  0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#8  worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#9  0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#10 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$50 = {name = 0x1396b98 "http_in", filter_func = {out_func = 0x463410 , in_func = 0x463410 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_PROTOCOL, debug = 0, proto_flags = 0}

(1.15) ap_process_request_after_handler


  • 出力フィルタ:
    • coreフィルタ


Breakpoint 2, ap_pass_brigade (next=0x7fa9b400df48, bb=0x7fa9b400e0b0) at util_filter.c:567
567         if (next) {
(gdb)
#0  ap_pass_brigade (next=0x7fa9b400df48, bb=0x7fa9b400e0b0) at util_filter.c:567
#1  0x000000000045fa8d in ap_process_request_after_handler (r=0x7fa9ac00a9b0) at http_request.c:256
#2  0x000000000045d070 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:143
#3  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#4  0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#5  0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#6  worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#7  0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#8  0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$51 = {name = 0x1370c78 "core", filter_func = {out_func = 0x445f90 , in_func = 0x445f90 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_NETWORK, debug = 0, proto_flags = 0}

(1.16) ap_process_request_after_handler


  • 次リクエスト到着チェック
  • 入力フィルタ:
    • core_inフィルタ
      • AP_MODE_SPECULATIVEモード
      • APR_NONBLOCK_READ


Breakpoint 1, ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9b400e0f0, mode=AP_MODE_SPECULATIVE, block=APR_NONBLOCK_READ, readbytes=1) at util_filter.c:552
552         if (next) {
(gdb)
#0  ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9b400e0f0, mode=AP_MODE_SPECULATIVE, block=APR_NONBLOCK_READ, readbytes=1) at util_filter.c:552
#1  0x000000000045fad6 in check_pipeline (r=0x7fa9ac00a9b0) at http_request.c:225
#2  ap_process_request_after_handler (r=0x7fa9ac00a9b0) at http_request.c:265
#3  0x000000000045d070 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:143
#4  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#5  0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#6  0x00007fa9baa557e1 in process_socket (thd=0x13c5fe0, dummy=) at event.c:970
#7  worker_thread (thd=0x13c5fe0, dummy=) at event.c:1815
#8  0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#9  0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$52 = {name = 0x1370420 "core_in", filter_func = {out_func = 0x446790 , in_func = 0x446790 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_NETWORK, debug = 0, proto_flags = 0}

(1.17) read_request_line


  • リクエスト行読み込み処理
    • クライアントの終了を検知する
  • 入力フィルタ:
    • core_inフィルタ
      • AP_MODE_GETLINE
      • APR_BLOCK_READ


Breakpoint 1, ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9ac003a68, mode=AP_MODE_GETLINE, block=APR_BLOCK_READ, readbytes=0) at util_filter.c:552
552         if (next) {
(gdb)
#0  ap_get_brigade (next=0x7fa9b400df20, bb=0x7fa9ac003a68, mode=AP_MODE_GETLINE, block=APR_BLOCK_READ, readbytes=0) at util_filter.c:552
#1  0x0000000000435ef2 in ap_rgetline_core (s=0x7fa9ac0029a0, n=8192, read=0x7fa9b3ffed10, r=0x7fa9ac002970, fold=0, bb=0x7fa9ac003a68) at protocol.c:229
#2  0x0000000000436d1e in read_request_line (conn=0x7fa9b400dbe0) at protocol.c:590
#3  ap_read_request (conn=0x7fa9b400dbe0) at protocol.c:956
#4  0x000000000045cff9 in ap_process_http_async_connection (c=0x7fa9b400dbe0) at http_core.c:135
#5  ap_process_http_connection (c=0x7fa9b400dbe0) at http_core.c:228
#6  0x0000000000454b60 in ap_run_process_connection (c=0x7fa9b400dbe0) at connection.c:41
#7  0x00007fa9baa557e1 in process_socket (thd=0x13c6070, dummy=) at event.c:970
#8  worker_thread (thd=0x13c6070, dummy=) at event.c:1815
#9  0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#10 0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$53 = {name = 0x1370420 "core_in", filter_func = {out_func = 0x446790 , in_func = 0x446790 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_NETWORK, debug = 0, proto_flags = 0}

(1.18) ap_start_lingering_close


  • EOF後の処理
  • 出力フィルタ:
    • coreフィルタ


Breakpoint 2, ap_pass_brigade (next=0x7fa9b400df48, bb=0x7fa9b400e138) at util_filter.c:567
567         if (next) {
(gdb)
#0  ap_pass_brigade (next=0x7fa9b400df48, bb=0x7fa9b400e138) at util_filter.c:567
#1  0x0000000000454d38 in ap_start_lingering_close (c=0x7fa9b400dbe0) at connection.c:121
#2  0x00007fa9baa55541 in start_lingering_close_blocking (thd=0x13c6070, dummy=) at event.c:823
#3  process_socket (thd=0x13c6070, dummy=) at event.c:1025
#4  worker_thread (thd=0x13c6070, dummy=) at event.c:1815
#5  0x0000003f53e079d1 in start_thread () from /lib64/libpthread.so.0
#6  0x0000003f53ae8b6d in clone () from /lib64/libc.so.6

$54 = {name = 0x1370c78 "core", filter_func = {out_func = 0x445f90 , in_func = 0x445f90 }, filter_init_func = 0, next = 0x0, providers = 0x0, ftype = AP_FTYPE_NETWORK, debug = 0, proto_flags = 0}


(2)フィルタの追加処理の確認

もともとのCORE/CORE_INフィルタの設定箇所はすでに
リクエスト処理の流れ: process_socket に戻る で見たが、下記のpre_connectionフック関数で行われている。

(httpd-2.4.9/server/core.c)

   4637 static int core_pre_connection(conn_rec *c, void *csd)
   4638 {

   4676     ap_set_core_module_config(net->c->conn_config, csd);
   4677     ap_add_input_filter_handle(ap_core_input_filter_handle, net, NULL, net->c);


CORE入力フィルタの追加

   4678     ap_add_output_filter_handle(ap_core_output_filter_handle, net, NULL, net->c);

CORE出力フィルタの追加

   4679     return DONE;
   4680 }

そして、HTTPリクエストボディの処理に使われていたHTTP_IN入力フィルタは
HTTPリクエストヘッダ処理後に、ap_read_request()関数内で追加される。
リクエスト処理の流れ: ap_read_request  参照。

(httpd-2.4.9/server/protocol.c)

    895 request_rec *ap_read_request(conn_rec *conn)
    896 {
     :

   1098     /*
   1099      * Add the HTTP_IN filter here to ensure that ap_discard_request_body
   1100      * called by ap_die and by ap_send_error_response works correctly on
   1101      * status codes that do not cause the connection to be dropped and
   1102      * in situations where the connection should be kept alive.
   1103      */
   1104
   1105     ap_add_input_filter_handle(ap_http_input_filter_handle,
   1106                                NULL, r, r->connection);
   1107

次はHTTPコアモジュールが提供しているcreate_requestフック関数だ。
このフック関数を実行する処理 ap_run_create_request()は
リクエスト処理の流れ: ap_read_request で見た。
read_request_line()でリクエスト行を読み込む前に実行される。

(httpd-2.4.9/modules/http/http_core.c)

    235 static int http_create_request(request_rec *r)
    236 {
    237     if (!r->main && !r->prev) {
    238         ap_add_output_filter_handle(ap_byterange_filter_handle,
    239                                     NULL, r, r->connection);

BYTERANGE出力フィルタの追加

    240         ap_add_output_filter_handle(ap_content_length_filter_handle,
    241                                     NULL, r, r->connection);

CONTENT_LENGTH出力フィルタの追加

    242         ap_add_output_filter_handle(ap_http_header_filter_handle,
    243                                     NULL, r, r->connection);

HTTP_HEADER出力フィルタの追加

    244         ap_add_output_filter_handle(ap_http_outerror_filter_handle,
    245                                     NULL, r, r->connection);

HTTP_OUTERROR出力フィルタの追加

    246     }
    247
    248     return OK;
    249 }


(3)フィルタの階層構成図

まとめると、次のような入出力フィルタがセットされていたことになる。


今回はここまで

2015年1月19日月曜日

CORE出力フィルタ: ソケット送信処理(3) sendfile系

CORE出力フィルタ: ソケット送信処理(1) の続きで今回はsendfile()系の
(1) sendfile_nonblocking()
とそこから呼ばれるAPRライブラリ関数の
(2) apr_socket_sendfile()
を見る

(1)sendfile_nonblocking関数

FILEデータバケットをapr_socket_sendfile()を使って送信するための関数となる。
apr_socket_sendfile()は本関数で1度実行される。
実行前に、ソケットのタイムアウト設定を0にし、実行後に元の値に戻す処理が行われている。

apr_socket_sendfile()実行後、ファイル全体の送信ができたか、送信サイズをファイルサイズを比較し、一致した場合には、送信されたFILEバケットをbucket brigadeから取り外し、削除(destroy)する。
未送信データが残っていた場合には、FILEバケットを送信済みデータ部分と未送信データ部分に
bucket分割し(split)、送信済み分のFILEバケットを取り外して、削除(destroy)する。
送信済みデータサイズは引数 *cumulative_bytes_writtenに加えられる。

    834 static apr_status_t sendfile_nonblocking(apr_socket_t *s,
    835                                          apr_bucket *bucket,
    836                                          apr_size_t *cumulative_bytes_written,
    837                                          conn_rec *c)
    838 {
    839     apr_status_t rv = APR_SUCCESS;
    840     apr_bucket_file *file_bucket;
    841     apr_file_t *fd;
    842     apr_size_t file_length;
    843     apr_off_t file_offset;
    844     apr_size_t bytes_written = 0;
    845
    846     if (!APR_BUCKET_IS_FILE(bucket)) {

FILEデータバケットでなければ、エラーで返る。

    847         ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server, APLOGNO(00006)
    848                      "core_filter: sendfile_nonblocking: "
    849                      "this should never happen");
    850         return APR_EGENERAL;
    851     }
    852     file_bucket = (apr_bucket_file *)(bucket->data);

FILEデータバケットのタイプ依存情報)apr_bucket_file)を取得する

    853     fd = file_bucket->fd;

apr_bucket_fileの持つファイル情報(apt_file_t)を取得する

    854     file_length = bucket->length;
    855     file_offset = bucket->start;

ファイルの参照する領域(開始位置とサイズ)をbucket情報から取得する

    856
    857     if (bytes_written < file_length) {

ここで、apr_socket_sendfile()が実行される。
実行前にタイムアウトが0にされるのは非ブロック処理とするため。

    858         apr_size_t n = file_length - bytes_written;
    859         apr_status_t arv;
    860         apr_interval_time_t old_timeout;
    861
    862         arv = apr_socket_timeout_get(s, &old_timeout);

現在セットされているタイムアウトの設定値をold_timeoutに退避する。

    863         if (arv != APR_SUCCESS) {
    864             return arv;
    865         }
    866         arv = apr_socket_timeout_set(s, 0);

タイムアウトの設定を0にする。

    867         if (arv != APR_SUCCESS) {
    868             return arv;
    869         }
    870         rv = apr_socket_sendfile(s, fd, NULL, &file_offset, &n, 0);

apr_socket_sendfile()を実行する。

    871         if (rv == APR_SUCCESS) {

成功すると、書込み済みバイト数を格納するbytes_writtenにnを加え、
開始位置であるfile_offsetがnバイト進められる。
しかし、この値はこの関数内では使われないので、この変更は意味が分からない。


    872             bytes_written += n;
    873             file_offset += n;
    874         }
    875         arv = apr_socket_timeout_set(s, old_timeout);

タイムアウトの設定をこの処理実行前の値に戻す。

    876         if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) {
    877             rv = arv;
    878         }
    879     }
    880     if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) {

ap__logio_add_bytes_outはlogioモジュールが提供するオプション関数で、
ここでは、ログ書式%Oのための出力バイト数を集計している。

    881         ap__logio_add_bytes_out(c, bytes_written);
    882     }
    883     *cumulative_bytes_written += bytes_written;

呼び出し元に返却用に、出力済みバイト数を*cumulative_bytes_writtenに加える。

    884     if ((bytes_written < file_length) && (bytes_written > 0)) {

出力したバイト数が、送信する予定だったファイルサイズより小さい場合

    885         apr_bucket_split(bucket, bytes_written);

送信済みの範囲と未送信の範囲にFILEデータバケットを分割(split)する。
apr_file_t 情報と FILEデータバケット (2.2)split関数 参照。

    886         APR_BUCKET_REMOVE(bucket);

送信済みのFILEデータバケットをbucket brigadeから外す。

    887         apr_bucket_destroy(bucket);

送信済みのFILEデータバケットを破棄(destroy)する。
apr_file_t 情報と FILEデータバケット  (2.3)destroy関数参照。

    888     }
    889     else if (bytes_written == file_length) {

出力したバイト数と送信する予定だったファイルサイズが一致した場合

    890         APR_BUCKET_REMOVE(bucket);

送信済みのFILEデータバケットをbucket brigadeから外す。

    891         apr_bucket_destroy(bucket);

送信済みのFILEデータバケットを破棄(destroy)する。
apr_file_t 情報と FILEデータバケット (2.3)destroy関数参照。

    892     }
    893     return rv;
    894 }


(2)apr_socket_sendfile

ちなみに、64bit環境で確認している。
ヘッダ・フッターといった独自のデータ構造(apr_hdtr_t)を扱っており、
コード上、sendfile()の実行前後でこのデータが送信されているようだが、
sendfile_nonblocking()から呼ばれている場合には、この情報はNULL固定で、利用されていない。

(apr/network_io/unix/sendrecv.c)

    257 apr_status_t apr_socket_sendfile(apr_socket_t *sock, apr_file_t *file,
    258                                  apr_hdtr_t *hdtr, apr_off_t *offset,
    259                                  apr_size_t *len, apr_int32_t flags)
    260 {
    261     int rv, nbytes = 0, total_hdrbytes, i;
    262     apr_status_t arv;
    263
    264 #if APR_HAS_LARGE_FILES && defined(HAVE_SENDFILE64)
     :
    268 #elif APR_HAS_LARGE_FILES && SIZEOF_OFF_T == 4
     :

    279 #else
    280     off_t off = *offset;
    281
    282     /* Multiple reports have shown sendfile failing with EINVAL if
    283      * passed a >=2Gb count value on some 64-bit kernels.  It won't
    284      * noticably hurt performance to limit each call to <2Gb at a
    285      * time, so avoid that issue here: */

一部の64bitカーネルにおいて、 2GB以上の値を渡すと、sendfileが失敗するという報告が複数ある。 一度の呼び出しでの上限を2GB未満に制限しても性能に悪影響は少ないだろうと考えられるので、 ここでその問題を回避している

    286     if (sizeof(off_t) == 8 && *len > INT_MAX) {

INT_MAXは2GB-1(0x7fffffff)だ。 一度に送信するデータ長の上限をINT_MAXに制限している。

    287         *len = INT_MAX;
    288     }
    289 #endif
    290

ヘッダを送信する。
ファイル送信の前に送信するデータバケットが存在しているケースだ。

ただし、sendfile_nonblocking()から呼ばれている場合、hdtr==NULLだ。
2.4.9のソースをgrepする限り、他の箇所で呼ばれている気配はない。
2.2.27のソースをgrepすると、値が指定されているので、2.4で使わなくなったのか。

    291     if (!hdtr) {

この経路に入る。

    292         hdtr = &no_hdtr;


    252 /* Define a structure to pass in when we have a NULL header value */
    253 static apr_hdtr_t no_hdtr;


    293     }
    294
    295     if (hdtr->numheaders > 0) {

この経路には入らない。

    296         apr_size_t hdrbytes;
    297
    298         /* cork before writing headers */
    299         rv = apr_socket_opt_set(sock, APR_TCP_NOPUSH, 1);

TCP_CORKオプションをONにする。
オプションの説明を man tcpからコピペする。

セットされると、 partialフレームを送信しない。
このオプションが解除されると、キューイングされた partial
フレームが送られる。これは sendfile(2) を呼ぶ前にヘッダを前置したり、
スループットを最適化したい場合に便利である。現在の実装では、 TCP_CORK
で出力を抑えることができる時間の上限は 200 ミリ秒である。
この上限に達すると、キューイングされたデータは自動的に送信される。 Linux
2.5.71 以降においてのみ、このオプションを TCP_NODELAY
と同時に用いることができる。
移植性の必要なプログラムではこのオプションを用いるべきではない。

つまり、TCP送信が少し遅延するということだ。sendfileで送信されるデータと このヘッダデータが別個に送信されることを回避できる。

    300         if (rv != APR_SUCCESS) {
    301             return rv;
    302         }
    303
    304         /* Now write the headers */
    305         arv = apr_socket_sendv(sock, hdtr->headers, hdtr->numheaders,
    306                                &hdrbytes);
    307         if (arv != APR_SUCCESS) {
    308             *len = 0;
    309             return errno;
    310         }
    311         nbytes += hdrbytes;
    312
    313         /* If this was a partial write and we aren't doing timeouts,
    314          * return now with the partial byte count; this is a non-blocking
    315          * socket.
    316          */
    317         total_hdrbytes = 0;
    318         for (i = 0; i < hdtr->numheaders; i++) {
    319             total_hdrbytes += hdtr->headers[i].iov_len;
    320         }
    321         if (hdrbytes < total_hdrbytes) {

ヘッダの一部だけ送信して終了した場合には、 sendfileまで行わないで、returnする。

    322             *len = hdrbytes;
    323             return apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);

TCP_CORKをOFFにしている。 ヘッダがキューイングされていた場合には、これで送信される。

    324         }
    325     }
    326

sendfileを実行する ここの処理の流れは、sendfileをwritevに置き換えれば、 apr_socket_sendvと同様


    327     if (sock->options & APR_INCOMPLETE_WRITE) {
    328         sock->options &= ~APR_INCOMPLETE_WRITE;
    329         goto do_select;
    330     }
    331
    332     do {
    333         rv = sendfile(sock->socketdes,    /* socket */
    334                       file->filedes, /* open file descriptor of the file to be sent */
    335                       &off,    /* where in the file to start */
    336                       *len);   /* number of bytes to send */
    337     } while (rv == -1 && errno == EINTR);
    338
    339     while ((rv == -1) && (errno == EAGAIN || errno == EWOULDBLOCK)
    340                       && (sock->timeout > 0)) {
    341 do_select:
    342         arv = apr_wait_for_io_or_timeout(NULL, sock, 0);
    343         if (arv != APR_SUCCESS) {
    344             *len = 0;
    345             return arv;
    346         }
    347         else {
    348             do {
    349                 rv = sendfile(sock->socketdes,    /* socket */
    350                               file->filedes, /* open file descriptor of the file to be sent */
    351                               &off,    /* where in the file to start */
    352                               *len);    /* number of bytes to send */
    353             } while (rv == -1 && errno == EINTR);
    354         }
    355     }
    356
    357     if (rv == -1) {
    358         *len = nbytes;
    359         rv = errno;
    360         apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);
    361         return rv;
    362     }
    363
    364     nbytes += rv;
    365
    366     if (rv < *len) {
    367         *len = nbytes;
    368         arv = apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);
    369         if (rv > 0) {
    370
    371             /* If this was a partial write, return now with the
    372              * partial byte count;  this is a non-blocking socket.
    373              */
    374
    375             if (sock->timeout > 0) {
    376                 sock->options |= APR_INCOMPLETE_WRITE;
    377             }
    378             return arv;
    379         }
    380         else {
    381             /* If the file got smaller mid-request, eventually the offset
    382              * becomes equal to the new file size and the kernel returns 0.
    383              * Make this an error so the caller knows to log something and
    384              * exit.
    385              */
    386             return APR_EOF;
    387         }
    388     }
    389
    390     /* Now write the footers */

フッターを送信する、とある。 ファイル送信の後に送信するデータバケットが存在しているケースだ。 ただし、上で見たとおり、sendfile_nonblocking()から呼ばれたケースでは、hdtrは空なので、 ここの処理も行われない。

    391     if (hdtr->numtrailers > 0) {

sendfile_nonblocking()からでは、この経路には入らない。 処理内容は、ヘッダの時と同様だ。

    392         apr_size_t trbytes;
    393         arv = apr_socket_sendv(sock, hdtr->trailers, hdtr->numtrailers,
    394                                &trbytes);
    395         nbytes += trbytes;
    396         if (arv != APR_SUCCESS) {
    397             *len = nbytes;
    398             rv = errno;
    399             apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);
    400             return rv;
    401         }
    402     }
    403
    404     apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);
    405
    406     (*len) = nbytes;
    407     return rv < 0 ? errno : APR_SUCCESS;
    408 }

これで今回はおしまい。

2015年1月8日木曜日

CORE出力フィルタ: ソケット送信処理(2) writev系

CORE出力フィルタ: ソケット送信処理(1) の続きでwritev()系の
(1) writev_nonblocking()
とそこから呼ばれるAPRライブラリ関数の
(2) apr_socket_sendv()
を見る

(1)writev_nonblocking関数


    763 static apr_status_t writev_nonblocking(apr_socket_t *s,
    764                                        struct iovec *vec, apr_size_t nvec,
    765                                        apr_bucket_brigade *bb,
    766                                        apr_size_t *cumulative_bytes_written,
    767                                        conn_rec *c)

この関数呼び出し時に、
IOベクトル vec と IOベクトルと
bucket brigade bb内のデータバケットと対応付けられている。
対応付けられている IOベクトルとbucket brigadeの数は、 nvecで指定されている。

    768 {
    769     apr_status_t rv = APR_SUCCESS, arv;
    770     apr_size_t bytes_written = 0, bytes_to_write = 0;
    771     apr_size_t i, offset;
    772     apr_interval_time_t old_timeout;
    773
    774     arv = apr_socket_timeout_get(s, &old_timeout);

設定されているソケットタイムアウトの情報を取得する→old_timeout

    775     if (arv != APR_SUCCESS) {
    776         return arv;
    777     }
    778     arv = apr_socket_timeout_set(s, 0);

ソケットタイムアウトを0にする

    779     if (arv != APR_SUCCESS) {
    780         return arv;
    781     }
    782

bytes_to_write: 送信するデータサイズの計算。

    783     for (i = 0; i < nvec; i++) {
    784         bytes_to_write += vec[i].iov_len;
    785     }

offset: IOベクトルのインデックスをセットする。初期値が0。

    786     offset = 0;

bytes_written: 送信済データサイズ。初期値0。
以下のwhileループでは、
apr_socket_sendv()で送信処理を行い、
送信したデータバケットを削除する処理を
送信済みデータ(bytes_written)が送信データサイズ(bytes_to_write)になるまで繰り返している。

    787     while (bytes_written < bytes_to_write) {
    788         apr_size_t n = 0;
    789         rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n);
    790         if (n > 0) {

nバイトデータ送信実行時の経路

    791             bytes_written += n;

送信済みデータサイズをカウントアップする。

    792             for (i = offset; i < nvec; ) {

最初のvec[offset]は、789行目で送信されたIOベクトルの先頭。
処理の最初では、これはvec[0]。

    793                 apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
    794                 if (APR_BUCKET_IS_METADATA(bucket)) {

bucket brigadeのうち、メタデータバケットは無視してよい。
ここでbucket brigadeから切り離し、削除する。

    795                     APR_BUCKET_REMOVE(bucket);
    796                     apr_bucket_destroy(bucket);
    797                 }
    798                 else if (n >= vec[i].iov_len) {

IOベクトル vec の先頭要素のデータ長が、今回送信されたデータ長nと同じか小さい場合。
対応するbucketの持つデータはすべて送信済みなので、削除する。

    799                     APR_BUCKET_REMOVE(bucket);
    800                     apr_bucket_destroy(bucket);

次の送信済みデータを持つbucketおよび対応するIOベクトルを処理するため、offsetを+1する。

    801                     offset++;

残るデータ長を計算する。
このIOベクトル要素に格納されていたデータ長を送信済みデータ長から差し引く。
そして、792行目のforループ先頭に戻る。

    802                     n -= vec[i++].iov_len;
    803                 }
    804                 else {

IOベクトル vec の先頭要素のデータ長が、今回送信されたデータ長nより大きい場合。
この場合、処理対象のデータバケットに未送信のデータが残っている。

送信済みのデータ(nバイト)と未送信のデータとにバケットを切り離す。

    805                     apr_bucket_split(bucket, n);

送信済みのデータのバケット(元の変数bucketに残った方)を切り離し、削除する。

    806                     APR_BUCKET_REMOVE(bucket);
    807                     apr_bucket_destroy(bucket);

処理中のIOベクトルの長さと、データの先頭位置を調整する。

    808                     vec[i].iov_len -= n;
    809                     vec[i].iov_base = (char *) vec[i].iov_base + n;

これで、送信済みのnバイト分のデータバケットの削除が完了する。

    810                     break;
    811                 }
    812             }
    813         }
    814         if (rv != APR_SUCCESS) {
    815             break;
    816         }
    817     }
    818     if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) {
    819         ap__logio_add_bytes_out(c, bytes_written);

ap__logio_add_bytes_outはlogioモジュールが提供するオプション関数だ。
ログ書式%Oのための出力バイト数を集計する。

    820     }
    821     *cumulative_bytes_written += bytes_written;

送信済みデータサイズ(bytes_written)を引数の値に加算する。

    822
    823     arv = apr_socket_timeout_set(s, old_timeout);

ソケットタイムアウトを元に戻す(774行目に取得したold_timeout)。

    824     if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) {
    825         return arv;
    826     }
    827     else {
    828         return rv;
    829     }
    830 }


(2)apr_socket_sendv関数

この関数は、writev_nonblocking()から使用されている。
その際には、ソケット情報のtimeout(sock->timeout)を0に変更して使用している。
pollでの書込み可否判定と、writeに戻り値のEAGAIN/EWOULDBLOCKの両方をチェックして、全体としては、timeoutの時間は処理内で書き込み可能になるのを待つようになっている。

(apr-1.5.1/network_io/unix/sendrecv.c)

    194 apr_status_t apr_socket_sendv(apr_socket_t * sock, const struct iovec *vec,
    195                               apr_int32_t nvec, apr_size_t *len)
    196 {
    197 #ifdef HAVE_WRITEV
    198     apr_ssize_t rv;
    199     apr_size_t requested_len = 0;
    200     apr_int32_t i;
    201

送信データ長を計算する

    202     for (i = 0; i < nvec; i++) {
    203         requested_len += vec[i].iov_len;
    204     }
    205

ソケット情報のoptionsをチェックしてAPR_INCOMPLETE_WRITEフラグが立っている場合には、
フラグを消してから、poll処理(219行目)にジャンプする。

    206     if (sock->options & APR_INCOMPLETE_WRITE) {

APR_INCOMPLETE_WRITEフラグは、前回のapr_socket_sendv()処理で、クライアントが書き込み
可能にならないままだったケース(timeout時間待っても書き込み可能にならず、送信データが
残ってしまった場合)にONになる。ここでも、最初に書込み可能かどうかをチェックしている。

なお、writev_nonblocking()から呼ばれている場合、ソケット情報のtimeoutは0にセットされているので、前回のapr_socket_sendv()も同じ条件なら、ここには入らない
(timeout>0である場合に使用されるフラグ)

    207         sock->options &= ~APR_INCOMPLETE_WRITE;
    208         goto do_select;
    209     }
    210

writev()を実行する。
この処理は、EINTRエラーの場合(シグナルで割りこまれた場合)は、処理を再実行する。

    211     do {
    212         rv = writev(sock->socketdes, vec, nvec);
    213     } while (rv == -1 && errno == EINTR);
    214

以下のwhile()ループは
 (1)rv==-1(writevがエラー)
かつ
 (2) エラーがEAGAINかEWOULDBLOCKの場合(書込可能でなかった場合)
かつ
 (3)ソケットのtimeout情報が >0 の場合
に処理を繰り返すことになっている。

つまり、timeout>0の場合に、writevが書込み可能でなかったら繰り返されている。
writev_nonblocking()から呼ばれている場合は、timeout=0なので、この条件は満たさない
timeout>0がセットされている場合にも、このループ内だけ見るとまず、219行目の
apr_wait_for_io_or_timeout()内でソケットが書き込み可能になるのを待つので、
errnoがEAGAIN/EWOULDBLOCKとなってループするケースはないと考えられる。

この条件が必要なのは、212行目のwritev()で書込み可能でなかった場合と考えられる。
その場合、timeut>0であれば、書込み可能になるまで219行目で待ち、
書込み可能になれば、226行目で書き込まれる。

    215     while ((rv == -1) && (errno == EAGAIN || errno == EWOULDBLOCK)
    216                       && (sock->timeout > 0)) {
    217         apr_status_t arv;
    218 do_select:

まず、ソケットが書き込み可能かどうかをチェックする。
poll関数で、sock->socketdesをチェックする。
この際timeoutにsock->timeoutをセットするが、上述の通り、apr_socket_sendv()から呼ばれている場合は、このtimeoutは0となっている(書込み可能でなければ直ちにrerurnする)。

    219         arv = apr_wait_for_io_or_timeout(NULL, sock, 0);
    220         if (arv != APR_SUCCESS) {

エラーのほか、
タイムアウトの場合、apr_wait_for_io_or_timeoutはAPR_TIMEUPを返すのでこの経路となる。
タイムアウトが0の場合は、書込み可能でなければ、直ちにこの経路となる。

    221             *len = 0;
    222             return arv;
    223         }
    224         else {

書込み可能だった場合に、writev()を実行する。
ここでも、シグナル割り込みは無視される(writev()を再実行する)。

    225             do {
    226                 rv = writev(sock->socketdes, vec, nvec);
    227             } while (rv == -1 && errno == EINTR);
    228         }
    229     }

writevエラーの場合、エラーコードを返す。

    230     if (rv == -1) {
    231         *len = 0;
    232         return errno;
    233     }

timeout>0が指定されており、実際の送信済データ長が、リクエストされた送信データ長を満たさなかった場合に、ソケット情報のoptionsにAPR_INCOMPLETE_WRITEフラグを立てている。


    234     if ((sock->timeout > 0) && (rv < requested_len)) {
    235         sock->options |= APR_INCOMPLETE_WRITE;
    236     }

送信したデータ長を *lenにセットし、成功でreturnする。

    237     (*len) = rv;
    238     return APR_SUCCESS;
    239 #else
    240     *len = vec[0].iov_len;
    241     return apr_socket_send(sock, vec[0].iov_base, len);
    242 #endif
    243 }

以上から、apr_socket_sendv()では、writev()を1回実行してreturnする。

2014年12月22日月曜日

apr_file_t 情報と FILEデータバケット

ここでは apr_bucketとapr_bucket_brigade で調べたデータバケットのひとつであるFILEデータバケットを見ていく。

(1)apr_bucket_file情報

FILEデータバケットを見てみる。
FILEデータバケットの格納データはapr_bucket_file情報だ。

apr_bucket_file情報は、以下の情報を持っていて、バケット情報のタイプ依存変数 data にセットされる。

(apr-util-1.5.4/include/apr_brigade.h)
    612 struct apr_bucket_file {
    613     /** Number of buckets using this memory */
    614     apr_bucket_refcount  refcount;

共有リソース用の参照回数の情報

    615     /** The file this bucket refers to */
    616     apr_file_t *fd;

FILEバケット情報の処理対象のapr_file_t情報がセットされる

    617     /** The pool into which any needed structures should
    618      *  be created while reading from this file bucket */
    619     apr_pool_t *readpool;
    620 #if APR_HAS_MMAP
    621     /** Whether this bucket should be memory-mapped if
    622      *  a caller tries to read from it */
    623     int can_mmap;

mmapが使用できるかどうかのフラグ。
centos環境ではAPR_HAS_MMAPは定義されているので、この値は1が初期値。

    624 #endif /* APR_HAS_MMAP */
    625 };

この構造体の先頭の変数、refcountは、この情報が複数のバケットで共有されるタイプのリソースであることを意味している。
この変数には次の構造体 apr_bucket_refcount を使ってアクセスされる。

(include/apr_buckets.h)

    524 typedef struct apr_bucket_refcount apr_bucket_refcount;

    531 struct apr_bucket_refcount {
    532     /** The number of references to this bucket */
    533     int          refcount;
    534 };

(2)FILEデータバケットタイプ情報

FILEデータバケットのバケットタイプ apr_bucket_type_t情報は次の通り。

(apr-util-1.5.4/buckets/apr_buckets_file.c)

    221 APU_DECLARE_DATA const apr_bucket_type_t apr_bucket_type_file = {
    222     "FILE", 5, APR_BUCKET_DATA,
    223     file_bucket_destroy,
    224     file_bucket_read,
    225     file_bucket_setaside,
    226     apr_bucket_shared_split,
    227     apr_bucket_shared_copy
    228 };

これを見ると、split関数とcopy関数に共有リソースの処理用関数がそのまま使われている。
また、file_bucket_destroy()からも、共有リソース破棄用の関数(apr_bucket_shared_destroy)が呼び出されている。

共有リソースというのは、参照するデータを複数のバケットで共有するための仕組みで、refcountはその名の通り、このリソースを参照しているバケット数を格納するようになっている。
初期値は1で、コピー(copy)や、分割(split)によって、その数はカウントアップされる。

(2.1)copy関数

下記はcopy関数だ。

apr-util-1.5.4/buckets/apr_buckets_refcount.c)

     33 APU_DECLARE_NONSTD(apr_status_t) apr_bucket_shared_copy(apr_bucket *a,
     34                                                         apr_bucket **b)
     35 {
     36     apr_bucket_refcount *r = a->data;
     37
     38     apr_bucket_simple_copy(a, b);

apr_bucket_simple_copy(a,b)は、 apr_bucket *bにapr_bucketの領域を確保し、値を*aからコピーする処理だ。

(apr-util-1.5.4/buckets/apr_buckets_simple.c)

     19 APU_DECLARE_NONSTD(apr_status_t) apr_bucket_simple_copy(apr_bucket *a,
     20                                                         apr_bucket **b)
     21 {
     22     *b = apr_bucket_alloc(sizeof(**b), a->list); /* XXX: check for failure? */
     23     **b = *a;
     24
     25     return APR_SUCCESS;
     26 }

この時、タイプ依存情報のdataのアドレスもコピーされるので、a,bは同じapr_bucket_file情報を参照するようになる。

     39     r->refcount++;

そして、データ依存関数のrefcountをカウントアップする。

     40
     41     return APR_SUCCESS;
     42 }


(2.2)split関数

続いて、split関数だ

(apr-util-1.5.4/buckets/apr_buckets_refcount.c)

     19 APU_DECLARE_NONSTD(apr_status_t) apr_bucket_shared_split(apr_bucket *a,
     20                                                          apr_size_t point)
     21 {
     22     apr_bucket_refcount *r = a->data;
     23     apr_status_t rv;
     24
     25     if ((rv = apr_bucket_simple_split(a, point)) != APR_SUCCESS) {

apr_bucket_simple_splitでは、内部でapr_bucket_simple_copy()を実行し、同じapr_bucketを複製する。
そして、バケット情報のstartとlengthを調整している。

(apr-util-1.5.4/buckets/apr_buckets_simple.c)

     28 APU_DECLARE_NONSTD(apr_status_t) apr_bucket_simple_split(apr_bucket *a,
     29                                                          apr_size_t point)
     30 {
     31     apr_bucket *b;
     32
     33     if (point > a->length) {

pointがバケットaのサイズより大きければ、エラーで返している。

     34         return APR_EINVAL;
     35     }
     36
     37     apr_bucket_simple_copy(a, &b);

バケットaの複製バケットbを作成する。

     38
     39     a->length  = point;

バケットaの方のlengthをpointにおきかえる。

     40     b->length -= point;

バケットbの方のlengthはpoint分、引き算する。

     41     b->start  += point;

バケットbの先頭を元のaの先頭から、point分、先に進める。

     42
     43     APR_BUCKET_INSERT_AFTER(a, b);

そして、バケットbをbucket brigadeの バケットaの後ろに挿入する。

     44
     45     return APR_SUCCESS;
     46 }

バケットaは、aの先頭から、pointまでのデータを含むようになり、
aから複製されたバケットbは、先頭がa+pointに、そして、もとの長さ(length)のpointから後ろ分を格納する。
新しいバケットbは、bucket brigadeのaの後ろに追加される。

     26         return rv;
     27     }
     28     r->refcount++;

apr_bucket_simple_split()でもタイプ異存情報dataは同じアドレスの情報を参照している。
そこでrefcountをカウントアップしている。

     29
     30     return APR_SUCCESS;
     31 }

(2.3)destroy関数

バケットを破棄(destroy)するときにマイナスされていく。refcount==0となると、このリソースを参照しているバケットがないことになる。
FILEデータタイプの場合、その場合に、実際のそのリソースを破棄する処理を行うようになっている。

(apr-util-1.5.4/buckets/apr_buckets_file.c)

    32 static void file_bucket_destroy(void *data)
     33 {
     34     apr_bucket_file *f = data;
     35
     36     if (apr_bucket_shared_destroy(f)) {
     37         /* no need to close the file here; it will get
     38          * done automatically when the pool gets cleaned up */
     39         apr_bucket_free(f);
     40     }
     41 }

この36行目 apr_bucket_shared_destroy(f)が、共有リソースのrefcountをマイナスする処理で、マイナスした結果が0だと真を返す。
refcountが0になると(返り値が真だと)、この関数では、apr_bucket_free()を実行するようになっている。

(2.4)read関数


read関数は、次のようになっている

(apr-util-1.5.4/buckets/apr_buckets_file.c)

     75 static apr_status_t file_bucket_read(apr_bucket *e, const char **str,
     76                                      apr_size_t *len, apr_read_type_e block)

FILEデータバケットを読み込み、データのアドレスをstrにセットする。
lenは、読み込みデータ長が格納される。
読み込み時のブロックモードをblockに指定する。

     77 {
     78     apr_bucket_file *a = e->data;
     79     apr_file_t *f = a->fd;
     80     apr_bucket *b = NULL;
     81     char *buf;
     82     apr_status_t rv;
     83     apr_size_t filelength = e->length;  /* bytes remaining in file past offset */
     84     apr_off_t fileoffset = e->start;

     86     apr_int32_t flags;

     88

     90     if (file_make_mmap(e, filelength, fileoffset, a->readpool)) {

file_make_mmapでは、FILEデータバケットをMMAPデータバケットに変換している。
FILEデータバケットの can_mmapが0の場合にはMMAPに変換できないが、
初期値では、ビルド環境依存になるが、mmapが利用可能(APR_HAS_MMAPが定義されている)なら、このフラグは1で初期化されている。

ちなみに、MMAPデータバケットには参照できるデータサイズの上限が決まっている。
APR_MMAP_LIMITバイト(MMAP_LIMITまたは、4*1024*1024(4MB))だ。
これを越えるファイルは珍しくないだろう。
この場合、FILEデータオブジェクトが、4MBまででsplitされ、先頭の4MBがMMAPデータバケットに変換される。

     91         return apr_bucket_read(e, str, len, block);

その後、MMAPデータバケットのread()関数を実行している。

     92     }

ここ以降は、MMAPデータバケットに変換できない場合の処理だ。

今回は割愛する。

    :
    154 }

(2.5)setaside関数

setaside関数は次のようになっている。

(apr-util-1.5.4/buckets/apr_buckets_file.c)

    201 static apr_status_t file_bucket_setaside(apr_bucket *data, apr_pool_t *reqpool)

バケット dataを メモリプールreqpoolに確保しなおす処理だ。
現在使用されるメモリプールが短命な場合に不都合が生じる(バケットが使用される予定があるのに破棄されてしまう)ようなケースで、より長命なプール上に退避するために使用する。

    202 {
    203     apr_bucket_file *a = data->data;
    204     apr_file_t *fd = NULL;
    205     apr_file_t *f = a->fd;
    206     apr_pool_t *curpool = apr_file_pool_get(f);

apr_foo_pool_get()はgrepしても見つからない。
APR_POOL_DECLARE_ACCESSOR(foo)マクロ
および
APR_POOL_IMPLEMENT_ACCESSOR(foo)マクロ
で定義されており、apr_foo_t構造体のpoolを返す。

    207
    208     if (apr_pool_is_ancestor(curpool, reqpool)) {

メモリプールは、aprライブラリが提供する機能で、親子(parent-child)関係と兄弟姉妹(sibling)関係を持っている。
apr_pool_create_ex()で引数にメモリプールが指定されると、生成されるプールの親にそのメモリプールがセットされ、親メモリプール側には子として新しいメモリプールがセットされる。
親メモリプールが複数の子メモリプールを持つ場合には、子メモリプールには兄弟関係を持つメモリプールのリストが接続される。
メモリプールが破棄される場合、そのメモリプールの子孫のプールもその兄弟も同時に破棄される。
reqpoolに確保しなおす要求の目的はより長命なプールにバケットを移すことであるから、reqpool自体が現在確保されているcurpoolの子孫であった場合には、確保しなおしても意味がないことになる。

apr_pool_is_ancestor()は、reqpoolのparentを辿って、curpoolが現れないかどうかをチェックする。
もし現れた場合(reqpoolがcurpoolの子孫の場合)、この関数では処理を行わない。
なお、reqpoolとcurpoolが同じプールである場合もこの関数は真を返す。

    209         return APR_SUCCESS;
    210     }
    211

以降は、reqpoolがcurpoolの子孫でない場合の処理だ。

    212     if (!apr_pool_is_ancestor(a->readpool, reqpool)) {

apr_bucket_file情報の内部に持っているメモリプール readpool も 親子関係をチェックする。

    213         a->readpool = reqpool;
    214     }
    215
    216     apr_file_setaside(&fd, f, reqpool);

apr_file_t情報をreqpoolに退避する。
aprライブラリ関数で定義されている。

    217     a->fd = fd;

apr_bucket_file情報のapr_file_t情報を fdを退避先のfdに更新して、退避処理が終わる。

    218     return APR_SUCCESS;
    219 }

(3)apr_file_t情報

apr_bucket_file情報のファイル情報は、apr_file_t情報が持っている。
これは次のような構造体だ。

(apr-1.5.1/include/arch/unix/apr_arch_file_io.h)

     93 struct apr_file_t {
     94     apr_pool_t *pool;
     95     int filedes;

ファイルディスクリプタがセットされる。
FILEデータバケットがcopy()やsplit()で新たなapr_file_t情報を生成しては、内部で次々にファイルをオープンすることになりかねない
共有リソース化することでそういった事象が回避できる。

     96     char *fname;

ファイル名

     97     apr_int32_t flags;

apr_file_open()で指定されたフラグが保持されている。
EnableSendfileが有効な場合には、APR_SENDFILE_ENABLEDフラグもONとなっている。

     98     int eof_hit;
     99     int is_pipe;
    100     apr_interval_time_t timeout;
    101     int buffered;

入出力をバッファモードで処理するかどうかの識別。
フラグにAPR_FOPEN_BUFFEREDか、APR_BUFFEREDを指定した場合に有効となる。

    102     enum {BLK_UNKNOWN, BLK_OFF, BLK_ON } blocking;
    103     int ungetchar;    /* Last char provided by an unget op. (-1 = no char)*/

    105     /* if there is a timeout set, then this pollset is used */
    106     apr_pollset_t *pollset;

    108     /* Stuff for buffered mode */
    109     char *buffer;
    110     apr_size_t bufpos;        /* Read/Write position in buffer */
    111     apr_size_t bufsize;       /* The size of the buffer */
    112     unsigned long dataRead;   /* amount of valid data read into buffer */
    113     int direction;            /* buffer being used for 0 = read, 1 = write */
    114     apr_off_t filePtr;        /* position in file of handle */

    116     struct apr_thread_mutex_t *thlock;

マルチスレッドでの排他制御用

    118 };

(4)apr_file_t情報のセットアップ : default_handler

FILEデータバケットは、apr_brigade_insert_file()で作成される。

(apr-util-1.5.4/buckets/apr_brigade.c)

    707 APU_DECLARE(apr_bucket *) apr_brigade_insert_file(apr_bucket_brigade *bb,
    708                                                   apr_file_t *f,
    709                                                   apr_off_t start,
    710                                                   apr_off_t length,
    711                                                   apr_pool_t *p)

この関数の引数には、apr_file_t情報が渡されている。

apr_bucket_brigade *bb 生成されたFILEデータバケットはこのbucket brigade bbの末尾に追加される。

apr_file_t *f 生成するFILEデータバケットに格納するapr_file_t情報

apr_off_t start 生成するFILEデータバケットは、対象のapr_file_t情報のatartバイト目からを参照する。

apr_off_t length 生成するFILEデータバケットは、対象のapr_file_t情報のstartバイト目から、lengthバイト分を参照する。

apr_pool_t *p 処理で使用するメモリプール

FILEデータバケットが内部で参照するデータ長には上限が設けられている。
1GBだ。

(apr-util-1.5.4/buckets/apr_brigade.c)

    704 /* A "safe" maximum bucket size, 1Gb */
    705 #define MAX_BUCKET_SIZE (0x40000000)

例えば、10GBのファイルの場合、1GB単位に分割されて、10個のFILEデータバケットが生成されている。
このとき、バケットの格納データ自体は同じ、apr_file_tで、バケット情報に持つstartとlengthが連続したデータを参照するように調整されることになる。
つまり、1つめのバケットはstart=0でlength=4GB、2つめのバケットは、startが4GBでlengthは同じく4GBと続き、10個のFILEバケットが生成される。

このapr_brigade_insert_file()関数の利用例を見てみる。
例えば、ハンドラフック関数であるdefault_handler()で使われている。

(httpd-2.4.9/server/core.c)

   4248 static int default_handler(request_rec *r)
   4249 {
    :
   4281     if (r->method_number == M_GET || r->method_number == M_POST) {

GET/POSTリクエストの処理の経路となる。

    :
   4330         if ((status = apr_file_open(&fd, r->filename, APR_READ | APR_BINARY

   4332                             | AP_SENDFILE_ENABLED(d->enable_sendfile)

   4334                                     , 0, r->pool)) != APR_SUCCESS) {

apr_file_open()で、r->filenameに指定されたファイルをオープンする。
この処理で生成されるのがapr_file_t情報で、fdに格納される。
メモリ領域は、r->poolを使用する。
この処理では、 (APR_READ|APR_BINARY) → (O_RDONLY|O_BINARY) に変換され、ファイルをオープンする際のフラグに使用される。
ファイル識別子は、apr_file_t情報のfiledes変数にセットされる。
また、open()では使用されないAP_SENDFILE_ENABLEDフラグを含め、フラグ情報は apr_file_t情報のflagsにセットされる。
初期値では、blockingはBLK_ON(ブロック有効)。
bufferedは、APR_FOPEN_BUFFEREDフラグの有無で判定されるが、ここでは指定されていないので、0。
bufferやbuffsizeはAPR_FOPEN_BUFFEREDが有効な場合に使用さえるが、ここでは使用されない。
その他の変数が初期化される。
そして、このapr_file_t情報のcleanup関数が登録される。
以下、apr_file_openのコードを抜粋引用しておく。

(apr-1.5.1/file_io/unix/open.c)

     90 APR_DECLARE(apr_status_t) apr_file_open(apr_file_t **new,
     91                                         const char *fname,
     92                                         apr_int32_t flag,
     93                                         apr_fileperms_t perm,
     94                                         apr_pool_t *pool)
     95 {
     96     apr_os_file_t fd;
     97     int oflags = 0;

     99     apr_thread_mutex_t *thlock;
    100     apr_status_t rv;

    102
    103     if ((flag & APR_FOPEN_READ) && (flag & APR_FOPEN_WRITE)) {
    104         oflags = O_RDWR;
    105     }
    106     else if (flag & APR_FOPEN_READ) {
    107         oflags = O_RDONLY;
    108     }
    109     else if (flag & APR_FOPEN_WRITE) {
    110         oflags = O_WRONLY;
    111     }
    112     else {
    113         return APR_EACCES;
    114     }
     :

    133     if (flag & APR_FOPEN_BINARY) {
    134         oflags |= O_BINARY;
    135     }
     :

    171
    172     if (perm == APR_OS_DEFAULT) {
    173         fd = open(fname, oflags, 0666);
    174     }
    175     else {
    176         fd = open(fname, oflags, apr_unix_perms2mode(perm));
    177     }
    178     if (fd < 0) {
    179        return errno;
    180     }
    181     if (!(flag & APR_FOPEN_NOCLEANUP)) {

    183         static int has_o_cloexec = 0;
    184         if (!has_o_cloexec)

    186         {
    187             int flags;
    188
    189             if ((flags = fcntl(fd, F_GETFD)) == -1) {
    190                 close(fd);
    191                 return errno;
    192             }
    193             if ((flags & FD_CLOEXEC) == 0) {
    194                 flags |= FD_CLOEXEC;
    195                 if (fcntl(fd, F_SETFD, flags) == -1) {
    196                     close(fd);
    197                     return errno;
    198                 }
    199             }

    201             else {
    202                 has_o_cloexec = 1;
    203             }

    205         }
    206     }
    207
    208     (*new) = (apr_file_t *)apr_pcalloc(pool, sizeof(apr_file_t));
    209     (*new)->pool = pool;
    210     (*new)->flags = flag;
    211     (*new)->filedes = fd;
    212
    213     (*new)->fname = apr_pstrdup(pool, fname);
    214
    215     (*new)->blocking = BLK_ON;
    216     (*new)->buffered = (flag & APR_FOPEN_BUFFERED) > 0;
    217
    218     if ((*new)->buffered) {
    219         (*new)->buffer = apr_palloc(pool, APR_FILE_DEFAULT_BUFSIZE);
    220         (*new)->bufsize = APR_FILE_DEFAULT_BUFSIZE;

    222         if ((*new)->flags & APR_FOPEN_XTHREAD) {
    223             (*new)->thlock = thlock;
    224         }

    226     }
    227     else {
    228         (*new)->buffer = NULL;
    229     }
    230
    231     (*new)->is_pipe = 0;
    232     (*new)->timeout = -1;
    233     (*new)->ungetchar = -1;
    234     (*new)->eof_hit = 0;
    235     (*new)->filePtr = 0;
    236     (*new)->bufpos = 0;
    237     (*new)->dataRead = 0;
    238     (*new)->direction = 0;

    240     /* Start out with no pollset.  apr_wait_for_io_or_timeout() will
    241      * initialize the pollset if needed.
    242      */
    243     (*new)->pollset = NULL;

    245     if (!(flag & APR_FOPEN_NOCLEANUP)) {
    246         apr_pool_cleanup_register((*new)->pool, (void *)(*new),
    247                                   apr_unix_file_cleanup,
    248                                   apr_unix_child_file_cleanup);
    249     }
    250     return APR_SUCCESS;
    251 }

この処理が失敗した場合がこのif文内の経路だ。
エラーログを出力して、HTTP_FORBIDDENが戻り値になっている。

   4335             ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00132)
   4336                           "file permissions deny server access: %s", r->filename);
   4337             return HTTP_FORBIDDEN;
   4338         }
    :

ファイルオープン後の処理に進む。

   4350         bb = apr_brigade_create(r->pool, c->bucket_alloc);

bucket brigade bbを作成する。
生成するのはrequestプール(r->pool)上になっている。

   4351
   4352         if ((errstatus = ap_meets_conditions(r)) != OK) {
   4353             apr_file_close(fd);
   4354             r->status = errstatus;
   4355         }
   4356         else {
   4357             e = apr_brigade_insert_file(bb, fd, 0, r->finfo.size, r->pool);

ここで、先に生成したapr_file_t情報 fdを指定して、FILEデータバケットを生成し、bucket brigade bbに追加する処理が行われる。
第3引数は対象ファイルのデータの先頭位置を指定している。0は先頭ということだ。
第4引数は、データサイズ。r->finfo.sizeには、ファイルサイズがセット済みだ。
この関数では、つまり、r->filenameで用意されたファイル全体をFILEデータバケットとして、bucket brigadeに追加している。

   4358
   4359 #if APR_HAS_MMAP
   4360             if (d->enable_mmap == ENABLE_MMAP_OFF) {
   4361                 (void)apr_bucket_file_enable_mmap(e, 0);
   4362             }
   4363 #endif
   4364         }
   4365
   4366         e = apr_bucket_eos_create(c->bucket_alloc);
   4367         APR_BRIGADE_INSERT_TAIL(bb, e);

EOSメタデータバケットを作成し、さらにbucket brigade bbに追加する。

   4368
   4369         status = ap_pass_brigade(r->output_filters, bb);

そして、この処理では、生成したbucket brigade bbを出力フィルタに引き渡している。
出力フィルタは、最終的にはCORE出力フィルタに引き渡され、AP_SENDFILE_ENABLED フラグが有効かどうかによって最終的な処理の方式は異なるが、このFILEデータバケットはソケットからクライアントにデータとして送信されることになる。

    :


今回はここまで